@@ -85,14 +85,65 @@ def lightness(color, lightness=.94):
85
85
return color .to_rgb ()
86
86
87
87
88
+ _MAX_CANDLES = 10_000
89
+
90
+
91
+ def _maybe_resample_data (resample_rule , df , indicators , equity_data , trades ):
92
+ if not resample_rule or len (df ) < _MAX_CANDLES :
93
+ return df , indicators , equity_data , trades
94
+
95
+ if isinstance (resample_rule , str ):
96
+ freq = resample_rule
97
+ else :
98
+ from_index = dict (day = - 2 , hour = - 6 , minute = 1 , second = 0 , millisecond = 0 ,
99
+ microsecond = 0 , nanosecond = 0 )[df .index .resolution ]
100
+ FREQS = ('1T' , '5T' , '10T' , '15T' , '30T' , '1H' , '2H' , '4H' , '8H' , '1D' , '1W' , '1M' )
101
+ freq = next ((f for f in FREQS [from_index :]
102
+ if len (df .resample (f )) <= _MAX_CANDLES ), FREQS [- 1 ])
103
+ warnings .warn ("Data contains too many candlesticks to plot; downsampling to {!r}. "
104
+ "See `Backtest.plot(resample=...)`" .format (freq ))
105
+
106
+ from .lib import OHLCV_AGG , TRADES_AGG , _EQUITY_AGG
107
+ df = df .resample (freq , label = 'right' ).agg (OHLCV_AGG ).dropna ()
108
+
109
+ # XXX: copy(True) pandas bug https://github.com/pandas-dev/pandas/issues/31710
110
+ indicators = [_Indicator (i .s .copy (True ).resample (freq ).mean ().dropna ().reindex (df .index ),
111
+ ** dict (i ._opts ,
112
+ # HACK: override `data` for its index
113
+ data = pd .Series (np .nan , index = df .index )))
114
+ for i in indicators ]
115
+ assert not indicators or indicators [0 ].s .index .equals (df .index )
116
+
117
+ equity_data = equity_data .resample (freq , label = 'right' ).agg (_EQUITY_AGG ).dropna (how = 'all' )
118
+ assert equity_data .index .equals (df .index )
119
+
120
+ def _weighted_returns (s , trades = trades ):
121
+ df = trades .loc [s .index ]
122
+ return ((df ['Size' ].abs () * df ['ReturnPct' ]) / df ['Size' ].abs ().sum ()).sum ()
123
+
124
+ trades = trades .assign (count = 1 ).resample (freq , on = 'ExitTime' , label = 'right' ).agg (dict (
125
+ TRADES_AGG ,
126
+ ReturnPct = _weighted_returns ,
127
+ count = 'sum' ,
128
+ # XXX: Can this prettier?
129
+ EntryBar = (lambda s , trades = trades , index = df .index :
130
+ index .get_loc (trades .loc [s .index ]['EntryTime' ].mean (), method = 'nearest' )),
131
+ ExitBar = (lambda s , trades = trades , index = df .index :
132
+ index .get_loc (trades .loc [s .index ]['ExitTime' ].mean (), method = 'nearest' )),
133
+ )).dropna ()
134
+
135
+ return df , indicators , equity_data , trades
136
+
137
+
88
138
def plot (* , results : pd .Series ,
89
139
df : pd .DataFrame ,
90
140
indicators : List [_Indicator ],
91
141
filename = '' , plot_width = None ,
92
142
plot_equity = True , plot_pl = True ,
93
143
plot_volume = True , plot_drawdown = False ,
94
144
smooth_equity = False , relative_equity = True ,
95
- superimpose = True , show_legend = True , open_browser = True ):
145
+ superimpose = True , resample = True ,
146
+ show_legend = True , open_browser = True ):
96
147
"""
97
148
Like much of GUI code everywhere, this is a mess.
98
149
"""
@@ -111,15 +162,19 @@ def plot(*, results: pd.Series,
111
162
trades = results ['_trades' ]
112
163
113
164
plot_volume = plot_volume and not df .Volume .isnull ().all ()
114
- time_resolution = getattr (df .index , 'resolution' , None )
115
165
is_datetime_index = df .index .is_all_dates
116
166
117
167
from .lib import OHLCV_AGG
118
168
# ohlc df may contain many columns. We're only interested in, and pass on to Bokeh, these
119
169
df = df [list (OHLCV_AGG .keys ())].copy (deep = False )
170
+
171
+ # Limit data to max_candles
172
+ if is_datetime_index :
173
+ df , indicators , equity_data , trades = _maybe_resample_data (
174
+ resample , df , indicators , equity_data , trades )
175
+
120
176
df .index .name = None # Provides source name @index
121
177
df ['datetime' ] = df .index # Save original, maybe datetime index
122
-
123
178
df = df .reset_index (drop = True )
124
179
equity_data = equity_data .reset_index (drop = True )
125
180
index = df .index
@@ -222,6 +277,10 @@ def _plot_equity_section():
222
277
x1 , x2 = dd_end - 1 , dd_end
223
278
y , y1 , y2 = equity [dd_start ], equity [x1 ], equity [x2 ]
224
279
dd_end -= (1 - (y - y1 ) / (y2 - y1 )) * (dd_end - x1 ) # y = a x + b
280
+ # If _plot_resample_data() was applied,
281
+ # the agg'd equity might have "stretched" the calculation
282
+ # XXX: test this?
283
+ dd_end = min (dd_end , equity .index [- 1 ])
225
284
226
285
if smooth_equity :
227
286
interest_points = pd .Index ([
@@ -339,8 +398,9 @@ def _plot_volume_section():
339
398
340
399
def _plot_superimposed_ohlc ():
341
400
"""Superimposed, downsampled vbars"""
401
+ time_resolution = pd .DatetimeIndex (df ['datetime' ]).resolution
342
402
resample_rule = (superimpose if isinstance (superimpose , str ) else
343
- dict (day = 'W ' ,
403
+ dict (day = 'M ' ,
344
404
hour = 'D' ,
345
405
minute = 'H' ,
346
406
second = 'T' ,
0 commit comments