Skip to content

Commit fcf45bb

Browse files
committed
TST: Make tests pass the new Order/Trade/Position API
1 parent 93ed1ec commit fcf45bb

File tree

1 file changed

+63
-56
lines changed

1 file changed

+63
-56
lines changed

backtesting/test/_test.py

+63-56
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,10 @@ def init(self):
5959

6060
def next(self):
6161
if crossover(self.sma1, self.sma2):
62+
self.position.close()
6263
self.buy()
6364
elif crossover(self.sma2, self.sma1):
65+
self.position.close()
6466
self.sell()
6567

6668

@@ -153,48 +155,51 @@ def next(self, FIVE_DAYS=pd.Timedelta('3 days')):
153155
assert not np.isnan(self.sma[-1])
154156
assert self.data.index[-1]
155157

156-
self.orders.is_long
157-
self.orders.is_short
158-
self.orders.entry
159-
self.orders.sl
160-
self.orders.tp
161-
162158
self.position
163159
self.position.size
164160
self.position.pl
165161
self.position.pl_pct
166-
self.position.open_price
167-
self.position.open_time
168162
self.position.is_long
169163

170164
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
178166
price = self.data.Close[-1]
179167
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
187180

188181
elif self.position:
189-
assert not self.orders.entry
190182
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
193184
assert self.position.pl
194185
assert self.position.pl_pct
195186
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)
198203

199204
bt = Backtest(GOOG, Assertive)
200205
stats = bt.run()
@@ -239,36 +244,42 @@ def test_compute_stats(self):
239244
pd.Series({
240245
# NOTE: These values are also used on the website!
241246
'# 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,
244249
'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,
247252
'Buy & Hold Return [%]': 703.4582419772772,
248-
'Calmar Ratio': 0.0631443286380662,
253+
'Calmar Ratio': 0.06715981043291917,
249254
'Duration': pd.Timedelta('3116 days 00:00:00'),
250255
'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,
255260
'Max. Drawdown Duration': pd.Timedelta('584 days 00:00:00'),
256-
'Max. Drawdown [%]': -48.15069053929621,
261+
'Max. Drawdown [%]': -46.123268579863776,
257262
'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,
262267
'Start': pd.Timestamp('2004-08-19 00:00:00'),
263268
'Win Rate [%]': 46.15384615384615,
264-
'Worst Trade [%]': -18.85561318387153,
269+
'Worst Trade [%]': -18.39887353835481,
265270
}).sort_index()
266271
)
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']))
272283

273284
def test_compute_stats_bordercase(self):
274285

@@ -306,7 +317,7 @@ def next(self):
306317
stats = Backtest(GOOG.iloc[:100], strategy).run()
307318

308319
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())
310321
self.assertEqual(stats['_strategy'].__class__, strategy)
311322

312323

@@ -321,8 +332,6 @@ def coroutine(self):
321332
assert self.position.size > 0
322333
assert self.position.pl
323334
assert self.position.pl_pct
324-
assert self.position.open_price > 0
325-
assert self.position.open_time
326335

327336
yield self.position.close()
328337

@@ -332,8 +341,6 @@ def coroutine(self):
332341
assert not self.position.size
333342
assert not self.position.pl
334343
assert not self.position.pl_pct
335-
assert not self.position.open_price
336-
assert not self.position.open_time
337344

338345
class S(Strategy):
339346
def init(self):
@@ -361,8 +368,8 @@ def test_optimize(self):
361368
self.assertIsInstance(res, pd.Series)
362369

363370
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())
366373

367374
res3, heatmap = bt.optimize(**OPT_PARAMS, return_heatmap=True,
368375
constraint=lambda d: d.slow > 2 * d.fast)
@@ -558,7 +565,7 @@ def init(self):
558565
self.data.Close < sma)
559566

560567
stats = Backtest(GOOG, S).run()
561-
self.assertGreater(stats['# Trades'], 1000)
568+
self.assertEqual(stats['# Trades'], 1224)
562569

563570
def test_TrailingStrategy(self):
564571
class S(TrailingStrategy):
@@ -574,7 +581,7 @@ def next(self):
574581
self.buy()
575582

576583
stats = Backtest(GOOG, S).run()
577-
self.assertGreater(stats['# Trades'], 6)
584+
self.assertEqual(stats['# Trades'], 50)
578585

579586

580587
class TestUtil(TestCase):

0 commit comments

Comments
 (0)