Skip to content

Commit 99615e4

Browse files
committed
merge plot
1 parent 24f37d8 commit 99615e4

File tree

5 files changed

+153
-159
lines changed

5 files changed

+153
-159
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,10 @@ engine.set_filter( universe )
103103

104104
f1 = -(factors.MA(5)-factors.MA(10)-factors.MA(30))
105105
f2 = -factors.BBANDS(win=5)
106+
f2 = f2.filter(f2 < 0.5)
106107

107108
engine.add( f1.rank(mask=universe).zscore(), 'ma_cross' )
108-
engine.add( f2.filter(f2 > -0.5).rank(mask=universe).zscore(), 'bb' )
109+
engine.add( f2.rank(mask=universe).zscore(), 'bb' )
109110

110111
engine.to_cuda()
111112
%time factor_data = engine.full_run("2013-01-02", "2018-01-19", periods=(1,5,10,))

spectre/factors/engine.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from typing import Union, Iterable, Tuple
88
import warnings
99
from .factor import BaseFactor, DataFactor, FilterFactor, AdjustedDataFactor
10-
from .plotting import plot_quantile_returns, plot_cumulative_return
10+
from .plotting import plot_quantile_and_cumulative_returns
1111
from .dataloader import DataLoader
1212
from ..parallel import ParallelGroupBy
1313
import pandas as pd
@@ -367,7 +367,6 @@ def full_run(self, start, end, trade_at='close', periods=(1, 4, 9),
367367

368368
# plot
369369
if preview:
370-
plot_quantile_returns(mean_return)
371-
plot_cumulative_return(factor_data)
370+
plot_quantile_and_cumulative_returns(factor_data, mean_return)
372371

373372
return factor_data, mean_return

spectre/factors/factor.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -322,11 +322,17 @@ def compute(self, *inputs: Sequence[torch.Tensor]) -> torch.Tensor:
322322
| 0 | 0 | NaN | ... | 123.45 |
323323
| | ... | ... | ... | ... |
324324
| | N | xxx.xx | ... | 234.56 |
325+
If this table too big, it will split to multiple tables and call the callback
326+
function separately.
325327
* Groupby time(set `is_timegroup = True`):
326-
| time id | stock1 | ... | stockN |
327-
|----------|--------|-----|--------|
328-
| 0 | 100.00 | ... | 200.00 |
329-
If this table too big, it will split to multiple tables.
328+
set N = asset count, Max = Max asset count in all time
329+
| time id | price(t+0) | ... | price(t+N) | price(t+N+1) | ... | price(t+Max) |
330+
|----------|------------|-----|------------|--------------|-----|--------------|
331+
| 0 | 100.00 | ... | 200.00 | NaN | ... | Nan |
332+
The prices is all asset price in same tick time, this is useful for calculations
333+
such as rank, quantile.
334+
But the order of assets in each row (time) is random, so the column cannot be
335+
considered as a particular asset.
330336
* Custom:
331337
Use `series = self._revert_to_series(input)` you can get `pd.Series` data type, and
332338
manipulate by your own. Remember to call `return self._regroup(series)` when returning.

spectre/factors/plotting.py

+35-60
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
]
1515

1616

17-
def plot_quantile_returns(mean_ret):
17+
def plot_quantile_and_cumulative_returns(factor_data, mean_ret):
1818
"""
1919
install plotly extension first:
2020
https://plot.ly/python/getting-started/
@@ -31,84 +31,59 @@ def plot_quantile_returns(mean_ret):
3131
x = quantiles
3232
factors = mean_ret.columns.levels[0]
3333
periods = mean_ret.columns.levels[1]
34-
rows = math.ceil(len(factors) / 2)
34+
rows = math.ceil(len(factors))
3535
cols = 2
3636

3737
colors = dict(zip(periods, cycle(DEFAULT_COLORS)))
38-
styles = {
38+
quantile_styles = {
3939
period: {'name': period, 'legendgroup': period,
4040
'hovertemplate': '<b>Quantile</b>:%{x}<br>'
4141
'<b>Return</b>: %{y:.3f}%±%{error_y.array:.3f}%',
4242
'marker': {'color': colors[period]}}
4343
for period in periods
4444
}
45+
cumulative_styles = {
46+
period: {'name': period, 'mode': 'lines', 'legendgroup': period, 'showlegend': False,
47+
'hovertemplate': '<b>Date</b>:%{x}<br>'
48+
'<b>Return</b>: %{y:.3f}x',
49+
'marker': {'color': colors[period]}}
50+
for period in periods
51+
}
4552

4653
fig = go.Figure()
4754
fig = subplots.make_subplots(
4855
rows=rows, cols=2,
49-
subplot_titles=factors,
56+
vertical_spacing=0.03,
57+
horizontal_spacing=0.06,
58+
subplot_titles=['Quantile Return', 'Portfolio cumulative returns'],
5059
)
5160

5261
for i, factor in enumerate(factors):
53-
row = int(i / cols) + 1
54-
col = i % cols + 1
62+
row = i + 1
63+
weight_col = (factor, 'factor_weight')
64+
weighted = factor_data['Returns'].multiply(factor_data[weight_col], axis=0)
65+
factor_return = weighted.groupby(level='date').sum()
5566
for j, period in enumerate(periods):
5667
y = mean_ret.loc[:, (factor, period, 'mean')] * 100
5768
err_y = mean_ret.loc[:, (factor, period, 'sem')] * 100
5869
fig.add_trace(go.Bar(
5970
x=x, y=y, error_y=dict(type='data', array=err_y, thickness=0.2),
60-
**styles[period]
61-
), row=row, col=col)
62-
styles[period]['showlegend'] = False
63-
fig.update_xaxes(title_text="factor quantile", type="category", row=row, col=col)
64-
65-
fig.update_layout(height=400 * rows, barmode='group', bargap=0.5,
66-
title_text="Mean return by quantile")
67-
fig.show()
68-
69-
70-
def plot_cumulative_return(factor_data):
71-
import plotly.graph_objects as go
72-
import plotly.subplots as subplots
73-
74-
factors = list(factor_data.columns.levels[0])
75-
factors.remove('Demeaned')
76-
factors.remove('Returns')
77-
periods = factor_data['Returns'].columns
78-
79-
rows = math.ceil(len(factors) / 2)
80-
cols = 2
81-
82-
fig = go.Figure()
83-
fig = subplots.make_subplots(
84-
rows=rows, cols=2,
85-
subplot_titles=factors,
86-
)
87-
88-
colors = dict(zip(periods, cycle(DEFAULT_COLORS)))
89-
styles = {
90-
period: {'name': period, 'mode': 'lines', 'legendgroup': period,
91-
'marker': {'color': colors[period]}}
92-
for period in periods
93-
}
94-
95-
for i, factor in enumerate(factors):
96-
row = int(i / cols) + 1
97-
col = i % cols + 1
98-
weight_col = (factor, 'factor_weight')
99-
weighted = factor_data['Returns'].multiply(factor_data[weight_col], axis=0)
100-
factor_return = weighted.groupby(level='date').sum()
101-
for period in periods:
102-
cumret = factor_return[period].resample('b' + period).mean().dropna()
103-
cumret = (cumret + 1).cumprod()
104-
fig.add_trace(go.Scatter(x=cumret.index, y=cumret.values, **styles[period]),
105-
row=row, col=col)
106-
styles[period]['showlegend'] = False
107-
108-
fig.add_shape(go.layout.Shape(type="line", y0=1, y1=1,
109-
x0=cumret.index[0], x1=cumret.index[-1],
110-
line=dict(width=1)),
111-
row=row, col=col)
112-
113-
fig.update_layout(height=400 * rows, title_text="Portfolio cumulative return")
71+
**quantile_styles[period]
72+
), row=row, col=1)
73+
quantile_styles[period]['showlegend'] = False
74+
fig.update_xaxes(type="category", row=row, col=1)
75+
fig.update_yaxes(title_text=factor, row=row, col=1, ticksuffix='%')
76+
77+
cum_ret = factor_return[period].resample('b' + period).mean().dropna()
78+
cum_ret = (cum_ret + 1).cumprod()
79+
fig.add_trace(go.Scatter(
80+
x=cum_ret.index, y=cum_ret.values, **cumulative_styles[period]
81+
), row=row, col=2)
82+
83+
fig.add_shape(go.layout.Shape(
84+
type="line", line=dict(width=1),
85+
y0=1, y1=1, x0=cum_ret.index[0], x1=cum_ret.index[-1],
86+
), row=row, col=2)
87+
88+
fig.update_layout(height=300 * rows, barmode='group', bargap=0.5, margin={'t': 50})
11489
fig.show()

0 commit comments

Comments
 (0)