@@ -59,8 +59,10 @@ def init(self):
59
59
60
60
def next (self ):
61
61
if crossover (self .sma1 , self .sma2 ):
62
+ self .position .close ()
62
63
self .buy ()
63
64
elif crossover (self .sma2 , self .sma1 ):
65
+ self .position .close ()
64
66
self .sell ()
65
67
66
68
@@ -153,48 +155,51 @@ def next(self, FIVE_DAYS=pd.Timedelta('3 days')):
153
155
assert not np .isnan (self .sma [- 1 ])
154
156
assert self .data .index [- 1 ]
155
157
156
- self .orders .is_long
157
- self .orders .is_short
158
- self .orders .entry
159
- self .orders .sl
160
- self .orders .tp
161
-
162
158
self .position
163
159
self .position .size
164
160
self .position .pl
165
161
self .position .pl_pct
166
- self .position .open_price
167
- self .position .open_time
168
162
self .position .is_long
169
163
170
164
if crossover (self .sma , self .data .Close ):
171
- self .orders .cancel ()
172
- self .sell ()
173
- assert not self .orders .is_long
174
- assert self .orders .is_short
175
- assert self .orders .entry
176
- assert not self .orders .sl
177
- assert not self .orders .tp
165
+ self .orders .cancel () # cancels only non-contingent
178
166
price = self .data .Close [- 1 ]
179
167
sl , tp = 1.05 * price , .9 * price
180
- self .sell (price , sl = sl , tp = tp )
181
- self .orders .set_entry (price )
182
- self .orders .set_sl (sl )
183
- self .orders .set_tp (tp )
184
- assert self .orders .entry == price
185
- assert self .orders .sl == sl
186
- assert self .orders .tp == tp
168
+
169
+ n_orders = len (self .orders )
170
+ self .sell (size = .21 , limit = price , stop = price , sl = sl , tp = tp )
171
+ assert len (self .orders ) == n_orders + 1
172
+
173
+ order = self .orders [- 1 ]
174
+ assert order .limit == price
175
+ assert order .stop == price
176
+ assert order .size == - .21
177
+ assert order .sl == sl
178
+ assert order .tp == tp
179
+ assert not order .is_contingent
187
180
188
181
elif self .position :
189
- assert not self .orders .entry
190
182
assert not self .position .is_long
191
- assert not not self .position .is_short
192
- assert self .position .open_price
183
+ assert self .position .is_short
193
184
assert self .position .pl
194
185
assert self .position .pl_pct
195
186
assert self .position .size < 0
196
- if self .data .index [- 1 ] - self .position .open_time > FIVE_DAYS :
197
- self .position .close ()
187
+
188
+ trade = self .trades [0 ]
189
+ if self .data .index [- 1 ] - self .data .index [trade .entry_bar ] > FIVE_DAYS :
190
+ assert not trade .is_long
191
+ assert trade .is_short
192
+ assert trade .size < 0
193
+ assert trade .entry_bar > 0
194
+ assert trade .exit_bar is None
195
+ assert trade .entry_price > 0
196
+ assert trade .exit_price is None
197
+ assert trade .pl / 1
198
+ assert trade .pl_pct / 1
199
+ assert trade .value > 0
200
+ assert trade .sl
201
+ assert trade .tp
202
+ self .position .close (.5 )
198
203
199
204
bt = Backtest (GOOG , Assertive )
200
205
stats = bt .run ()
@@ -239,36 +244,42 @@ def test_compute_stats(self):
239
244
pd .Series ({
240
245
# NOTE: These values are also used on the website!
241
246
'# Trades' : 65 ,
242
- 'Avg. Drawdown Duration' : pd .Timedelta ('41 days 00:00:00' ),
243
- 'Avg. Drawdown [%]' : - 6.087158560194047 ,
247
+ 'Avg. Drawdown Duration' : pd .Timedelta ('40 days 00:00:00' ),
248
+ 'Avg. Drawdown [%]' : - 5.752030209842444 ,
244
249
'Avg. Trade Duration' : pd .Timedelta ('46 days 00:00:00' ),
245
- 'Avg. Trade [%]' : 3.0404430275631444 ,
246
- 'Best Trade [%]' : 54.05363186670138 ,
250
+ 'Avg. Trade [%]' : 3.097629974370268 ,
251
+ 'Best Trade [%]' : 53.59595229490424 ,
247
252
'Buy & Hold Return [%]' : 703.4582419772772 ,
248
- 'Calmar Ratio' : 0.0631443286380662 ,
253
+ 'Calmar Ratio' : 0.06715981043291917 ,
249
254
'Duration' : pd .Timedelta ('3116 days 00:00:00' ),
250
255
'End' : pd .Timestamp ('2013-03-01 00:00:00' ),
251
- 'Equity Final [$]' : 52624.29346696951 ,
252
- 'Equity Peak [$]' : 76908.27001642012 ,
253
- 'Expectancy [%]' : 8.774692825628644 ,
254
- 'Exposure Time [%]' : 93.93453145057767 ,
256
+ 'Equity Final [$]' : 49072.06999999996 ,
257
+ 'Equity Peak [$]' : 70073.97999999997 ,
258
+ 'Expectancy [%]' : 8.791710931051735 ,
259
+ 'Exposure Time [%]' : 93.99441340782123 ,
255
260
'Max. Drawdown Duration' : pd .Timedelta ('584 days 00:00:00' ),
256
- 'Max. Drawdown [%]' : - 48.15069053929621 ,
261
+ 'Max. Drawdown [%]' : - 46.123268579863776 ,
257
262
'Max. Trade Duration' : pd .Timedelta ('183 days 00:00:00' ),
258
- 'Return [%]' : 426.2429346696951 ,
259
- 'SQN' : 0.91553210127173 ,
260
- 'Sharpe Ratio' : 0.23169782960690408 ,
261
- 'Sortino Ratio' : 0.7096713270577958 ,
263
+ 'Return [%]' : 390.7206999999997 ,
264
+ 'SQN' : 0.9565100140529863 ,
265
+ 'Sharpe Ratio' : 0.2357610034211845 ,
266
+ 'Sortino Ratio' : 0.7355072888872161 ,
262
267
'Start' : pd .Timestamp ('2004-08-19 00:00:00' ),
263
268
'Win Rate [%]' : 46.15384615384615 ,
264
- 'Worst Trade [%]' : - 18.85561318387153 ,
269
+ 'Worst Trade [%]' : - 18.39887353835481 ,
265
270
}).sort_index ()
266
271
)
267
- self .assertTrue (
268
- stats ._trade_data .columns .equals (
269
- pd .Index (['Equity' , 'Exit Entry' , 'Exit Position' ,
270
- 'Entry Price' , 'Exit Price' , 'P/L' , 'Returns' ,
271
- 'Drawdown' , 'Drawdown Duration' ])))
272
+
273
+ self .assertSequenceEqual (
274
+ sorted (stats ['_equity_curve' ].columns ),
275
+ sorted (['Equity' , 'DrawdownPct' , 'DrawdownDuration' ]))
276
+
277
+ self .assertEqual (len (stats ['_trades' ]), 65 )
278
+
279
+ self .assertSequenceEqual (
280
+ sorted (stats ['_trades' ].columns ),
281
+ sorted (['Size' , 'EntryBar' , 'ExitBar' , 'EntryPrice' , 'ExitPrice' ,
282
+ 'PnL' , 'ReturnPct' , 'EntryTime' , 'ExitTime' , 'Duration' ]))
272
283
273
284
def test_compute_stats_bordercase (self ):
274
285
@@ -306,7 +317,7 @@ def next(self):
306
317
stats = Backtest (GOOG .iloc [:100 ], strategy ).run ()
307
318
308
319
self .assertFalse (np .isnan (stats ['Equity Final [$]' ]))
309
- self .assertFalse (stats . _trade_data ['Equity' ].isnull ().any ())
320
+ self .assertFalse (stats [ '_equity_curve' ] ['Equity' ].isnull ().any ())
310
321
self .assertEqual (stats ['_strategy' ].__class__ , strategy )
311
322
312
323
@@ -321,8 +332,6 @@ def coroutine(self):
321
332
assert self .position .size > 0
322
333
assert self .position .pl
323
334
assert self .position .pl_pct
324
- assert self .position .open_price > 0
325
- assert self .position .open_time
326
335
327
336
yield self .position .close ()
328
337
@@ -332,8 +341,6 @@ def coroutine(self):
332
341
assert not self .position .size
333
342
assert not self .position .pl
334
343
assert not self .position .pl_pct
335
- assert not self .position .open_price
336
- assert not self .position .open_time
337
344
338
345
class S (Strategy ):
339
346
def init (self ):
@@ -361,8 +368,8 @@ def test_optimize(self):
361
368
self .assertIsInstance (res , pd .Series )
362
369
363
370
res2 = bt .optimize (** OPT_PARAMS , maximize = lambda s : s ['SQN' ])
364
- self .assertSequenceEqual (res .filter (regex = '^[^_]' ).to_dict (),
365
- res2 .filter (regex = '^[^_]' ).to_dict ())
371
+ self .assertDictEqual (res .filter (regex = '^[^_]' ). fillna ( - 1 ).to_dict (),
372
+ res2 .filter (regex = '^[^_]' ). fillna ( - 1 ).to_dict ())
366
373
367
374
res3 , heatmap = bt .optimize (** OPT_PARAMS , return_heatmap = True ,
368
375
constraint = lambda d : d .slow > 2 * d .fast )
@@ -558,7 +565,7 @@ def init(self):
558
565
self .data .Close < sma )
559
566
560
567
stats = Backtest (GOOG , S ).run ()
561
- self .assertGreater (stats ['# Trades' ], 1000 )
568
+ self .assertEqual (stats ['# Trades' ], 1224 )
562
569
563
570
def test_TrailingStrategy (self ):
564
571
class S (TrailingStrategy ):
@@ -574,7 +581,7 @@ def next(self):
574
581
self .buy ()
575
582
576
583
stats = Backtest (GOOG , S ).run ()
577
- self .assertGreater (stats ['# Trades' ], 6 )
584
+ self .assertEqual (stats ['# Trades' ], 50 )
578
585
579
586
580
587
class TestUtil (TestCase ):
0 commit comments