Skip to content

Commit efeddf2

Browse files
author
goblincomet
committed
TST: Make tests pass the new Order/Trade/Position API
1 parent c9be766 commit efeddf2

File tree

1 file changed

+67
-55
lines changed

1 file changed

+67
-55
lines changed

backtesting/test/_test.py

Lines changed: 67 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,10 @@ def init(self):
6060

6161
def next(self):
6262
if crossover(self.sma1, self.sma2):
63+
self.position.close()
6364
self.buy()
6465
elif crossover(self.sma2, self.sma1):
66+
self.position.close()
6567
self.sell()
6668

6769

@@ -154,47 +156,55 @@ def next(self, FIVE_DAYS=pd.Timedelta('3 days')):
154156
assert not np.isnan(self.sma[-1])
155157
assert self.data.index[-1]
156158

157-
self.orders.is_long
158-
self.orders.is_short
159-
self.orders.entry
160-
self.orders.sl
161-
self.orders.tp
162-
163159
self.position
164160
self.position.size
165161
self.position.pl
166162
self.position.pl_pct
167-
self.position.open_price
168-
self.position.open_time
169163
self.position.is_long
170164

171165
if crossover(self.sma, self.data.Close):
172-
self.orders.cancel()
173-
self.sell()
174-
assert not self.orders.is_long
175-
assert self.orders.is_short
176-
assert self.orders.entry
177-
assert not self.orders.sl
178-
assert not self.orders.tp
166+
self.orders.cancel() # cancels only non-contingent
179167
price = self.data.Close[-1]
180168
sl, tp = 1.05 * price, .9 * price
181-
self.sell(price, sl=sl, tp=tp)
182-
self.orders.set_entry(price)
183-
self.orders.set_sl(sl)
184-
self.orders.set_tp(tp)
185-
assert self.orders.entry == price
186-
assert self.orders.sl == sl
187-
assert self.orders.tp == tp
169+
170+
n_orders = len(self.orders)
171+
self.sell(size=.21, limit=price, stop=price, sl=sl, tp=tp)
172+
assert len(self.orders) == n_orders + 1
173+
174+
order = self.orders[-1]
175+
assert order.limit == price
176+
assert order.stop == price
177+
assert order.size == -.21
178+
assert order.sl == sl
179+
assert order.tp == tp
180+
assert not order.is_contingent
188181

189182
elif self.position:
190-
assert not self.orders.entry
191183
assert not self.position.is_long
192-
assert not not self.position.is_short
193-
assert self.position.open_price
184+
assert self.position.is_short
194185
assert self.position.pl
195186
assert self.position.pl_pct
196187
assert self.position.size < 0
197-
if self.data.index[-1] - self.position.open_time > FIVE_DAYS:
188+
189+
trade = self.trades[0]
190+
if self.data.index[-1] - self.data.index[trade.entry_bar] > FIVE_DAYS:
191+
assert not trade.is_long
192+
assert trade.is_short
193+
assert trade.size < 0
194+
assert trade.entry_bar > 0
195+
assert trade.exit_bar is None
196+
assert trade.entry_price > 0
197+
assert trade.exit_price is None
198+
assert trade.pl / 1
199+
assert trade.pl_pct / 1
200+
assert trade.value > 0
201+
assert trade.sl
202+
assert trade.tp
203+
# Close multiple times
204+
self.position.close(.5)
205+
self.position.close(.5)
206+
self.position.close(.5)
207+
self.position.close()
198208
self.position.close()
199209

200210
bt = Backtest(GOOG, Assertive)
@@ -241,36 +251,42 @@ def test_compute_stats(self):
241251
# NOTE: These values are also used on the website!
242252
'# Trades': 65,
243253
'Avg. Drawdown Duration': pd.Timedelta('41 days 00:00:00'),
244-
'Avg. Drawdown [%]': -6.087158560194047,
254+
'Avg. Drawdown [%]': -5.925851581948801,
245255
'Avg. Trade Duration': pd.Timedelta('46 days 00:00:00'),
246-
'Avg. Trade [%]': 3.0404430275631444,
247-
'Best Trade [%]': 54.05363186670138,
256+
'Avg. Trade [%]': 3.097629974370268,
257+
'Best Trade [%]': 53.59595229490424,
248258
'Buy & Hold Return [%]': 703.4582419772772,
249-
'Calmar Ratio': 0.0631443286380662,
259+
'Calmar Ratio': 0.06456068720154355,
250260
'Duration': pd.Timedelta('3116 days 00:00:00'),
251261
'End': pd.Timestamp('2013-03-01 00:00:00'),
252-
'Equity Final [$]': 52624.29346696951,
253-
'Equity Peak [$]': 76908.27001642012,
254-
'Expectancy [%]': 8.774692825628644,
255-
'Exposure Time [%]': 93.93453145057767,
262+
'Equity Final [$]': 51959.94999999997,
263+
'Equity Peak [$]': 75787.44,
264+
'Expectancy [%]': 8.791710931051735,
265+
'Exposure Time [%]': 93.99441340782123,
256266
'Max. Drawdown Duration': pd.Timedelta('584 days 00:00:00'),
257-
'Max. Drawdown [%]': -48.15069053929621,
267+
'Max. Drawdown [%]': -47.98012705007589,
258268
'Max. Trade Duration': pd.Timedelta('183 days 00:00:00'),
259-
'Profit Factor': 2.060450149412349,
260-
'Return [%]': 426.2429346696951,
261-
'SQN': 0.91553210127173,
262-
'Sharpe Ratio': 0.23169782960690408,
263-
'Sortino Ratio': 0.7096713270577958,
269+
'Profit Factor': 2.0880175388920286,
270+
'Return [%]': 419.59949999999964,
271+
'SQN': 0.916892986080858,
272+
'Sharpe Ratio': 0.2357610034211845,
273+
'Sortino Ratio': 0.7355072888872161,
264274
'Start': pd.Timestamp('2004-08-19 00:00:00'),
265275
'Win Rate [%]': 46.15384615384615,
266-
'Worst Trade [%]': -18.85561318387153,
276+
'Worst Trade [%]': -18.39887353835481,
267277
}).sort_index()
268278
)
269-
self.assertTrue(
270-
stats._trade_data.columns.equals(
271-
pd.Index(['Equity', 'Exit Entry', 'Exit Position',
272-
'Entry Price', 'Exit Price', 'P/L', 'Returns',
273-
'Drawdown', 'Drawdown Duration'])))
279+
280+
self.assertSequenceEqual(
281+
sorted(stats['_equity_curve'].columns),
282+
sorted(['Equity', 'DrawdownPct', 'DrawdownDuration']))
283+
284+
self.assertEqual(len(stats['_trades']), 65)
285+
286+
self.assertSequenceEqual(
287+
sorted(stats['_trades'].columns),
288+
sorted(['Size', 'EntryBar', 'ExitBar', 'EntryPrice', 'ExitPrice',
289+
'PnL', 'ReturnPct', 'EntryTime', 'ExitTime', 'Duration']))
274290

275291
def test_compute_stats_bordercase(self):
276292

@@ -308,7 +324,7 @@ def next(self):
308324
stats = Backtest(GOOG.iloc[:100], strategy).run()
309325

310326
self.assertFalse(np.isnan(stats['Equity Final [$]']))
311-
self.assertFalse(stats._trade_data['Equity'].isnull().any())
327+
self.assertFalse(stats['_equity_curve']['Equity'].isnull().any())
312328
self.assertEqual(stats['_strategy'].__class__, strategy)
313329

314330

@@ -323,8 +339,6 @@ def coroutine(self):
323339
assert self.position.size > 0
324340
assert self.position.pl
325341
assert self.position.pl_pct
326-
assert self.position.open_price > 0
327-
assert self.position.open_time
328342

329343
yield self.position.close()
330344

@@ -334,8 +348,6 @@ def coroutine(self):
334348
assert not self.position.size
335349
assert not self.position.pl
336350
assert not self.position.pl_pct
337-
assert not self.position.open_price
338-
assert not self.position.open_time
339351

340352
class S(Strategy):
341353
def init(self):
@@ -363,8 +375,8 @@ def test_optimize(self):
363375
self.assertIsInstance(res, pd.Series)
364376

365377
res2 = bt.optimize(**OPT_PARAMS, maximize=lambda s: s['SQN'])
366-
self.assertSequenceEqual(res.filter(regex='^[^_]').to_dict(),
367-
res2.filter(regex='^[^_]').to_dict())
378+
self.assertDictEqual(res.filter(regex='^[^_]').fillna(-1).to_dict(),
379+
res2.filter(regex='^[^_]').fillna(-1).to_dict())
368380

369381
res3, heatmap = bt.optimize(**OPT_PARAMS, return_heatmap=True,
370382
constraint=lambda d: d.slow > 2 * d.fast)
@@ -589,7 +601,7 @@ def init(self):
589601
self.data.Close < sma)
590602

591603
stats = Backtest(GOOG, S).run()
592-
self.assertGreater(stats['# Trades'], 1000)
604+
self.assertEqual(stats['# Trades'], 1180)
593605

594606
def test_TrailingStrategy(self):
595607
class S(TrailingStrategy):
@@ -605,7 +617,7 @@ def next(self):
605617
self.buy()
606618

607619
stats = Backtest(GOOG, S).run()
608-
self.assertGreater(stats['# Trades'], 6)
620+
self.assertEqual(stats['# Trades'], 50)
609621

610622

611623
class TestUtil(TestCase):

0 commit comments

Comments
 (0)