Skip to content

Commit 1e4abd9

Browse files
committed
TST: Make tests pass the new Order/Trade/Position API
1 parent 7e1ed29 commit 1e4abd9

File tree

1 file changed

+64
-57
lines changed

1 file changed

+64
-57
lines changed

backtesting/test/_test.py

+64-57
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

@@ -154,52 +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:
198-
self.position.close()
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+
self.position.close(.5)
199204

200205
bt = Backtest(GOOG, Assertive)
201206
stats = bt.run()
202-
self.assertEqual(stats['# Trades'], 144)
207+
self.assertEqual(stats['# Trades'], 420)
203208

204209
def test_broker_params(self):
205210
bt = Backtest(GOOG.iloc[:100], SmaCross,
@@ -240,36 +245,42 @@ def test_compute_stats(self):
240245
pd.Series({
241246
# NOTE: These values are also used on the website!
242247
'# Trades': 65,
243-
'Avg. Drawdown Duration': pd.Timedelta('41 days 00:00:00'),
244-
'Avg. Drawdown [%]': -6.087158560194047,
248+
'Avg. Drawdown Duration': pd.Timedelta('40 days 00:00:00'),
249+
'Avg. Drawdown [%]': -5.752030209842444,
245250
'Avg. Trade Duration': pd.Timedelta('46 days 00:00:00'),
246-
'Avg. Trade [%]': 3.0404430275631444,
247-
'Best Trade [%]': 54.05363186670138,
251+
'Avg. Trade [%]': 3.097629974370268,
252+
'Best Trade [%]': 53.59595229490424,
248253
'Buy & Hold Return [%]': 703.4582419772772,
249-
'Calmar Ratio': 0.0631443286380662,
254+
'Calmar Ratio': 0.06715981043291917,
250255
'Duration': pd.Timedelta('3116 days 00:00:00'),
251256
'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,
257+
'Equity Final [$]': 49072.06999999996,
258+
'Equity Peak [$]': 70073.97999999997,
259+
'Expectancy [%]': 8.791710931051735,
260+
'Exposure Time [%]': 93.99441340782123,
256261
'Max. Drawdown Duration': pd.Timedelta('584 days 00:00:00'),
257-
'Max. Drawdown [%]': -48.15069053929621,
262+
'Max. Drawdown [%]': -46.123268579863776,
258263
'Max. Trade Duration': pd.Timedelta('183 days 00:00:00'),
259-
'Return [%]': 426.2429346696951,
260-
'SQN': 0.91553210127173,
261-
'Sharpe Ratio': 0.23169782960690408,
262-
'Sortino Ratio': 0.7096713270577958,
264+
'Return [%]': 390.7206999999997,
265+
'SQN': 0.9565100140529863,
266+
'Sharpe Ratio': 0.2357610034211845,
267+
'Sortino Ratio': 0.7355072888872161,
263268
'Start': pd.Timestamp('2004-08-19 00:00:00'),
264269
'Win Rate [%]': 46.15384615384615,
265-
'Worst Trade [%]': -18.85561318387153,
270+
'Worst Trade [%]': -18.39887353835481,
266271
}).sort_index()
267272
)
268-
self.assertTrue(
269-
stats._trade_data.columns.equals(
270-
pd.Index(['Equity', 'Exit Entry', 'Exit Position',
271-
'Entry Price', 'Exit Price', 'P/L', 'Returns',
272-
'Drawdown', 'Drawdown Duration'])))
273+
274+
self.assertSequenceEqual(
275+
sorted(stats['_equity_curve'].columns),
276+
sorted(['Equity', 'DrawdownPct', 'DrawdownDuration']))
277+
278+
self.assertEqual(len(stats['_trades']), 65)
279+
280+
self.assertSequenceEqual(
281+
sorted(stats['_trades'].columns),
282+
sorted(['Size', 'EntryBar', 'ExitBar', 'EntryPrice', 'ExitPrice',
283+
'PnL', 'ReturnPct', 'EntryTime', 'ExitTime', 'Duration']))
273284

274285
def test_compute_stats_bordercase(self):
275286

@@ -307,7 +318,7 @@ def next(self):
307318
stats = Backtest(GOOG.iloc[:100], strategy).run()
308319

309320
self.assertFalse(np.isnan(stats['Equity Final [$]']))
310-
self.assertFalse(stats._trade_data['Equity'].isnull().any())
321+
self.assertFalse(stats['_equity_curve']['Equity'].isnull().any())
311322
self.assertEqual(stats['_strategy'].__class__, strategy)
312323

313324

@@ -322,8 +333,6 @@ def coroutine(self):
322333
assert self.position.size > 0
323334
assert self.position.pl
324335
assert self.position.pl_pct
325-
assert self.position.open_price > 0
326-
assert self.position.open_time
327336

328337
yield self.position.close()
329338

@@ -333,8 +342,6 @@ def coroutine(self):
333342
assert not self.position.size
334343
assert not self.position.pl
335344
assert not self.position.pl_pct
336-
assert not self.position.open_price
337-
assert not self.position.open_time
338345

339346
class S(Strategy):
340347
def init(self):
@@ -362,8 +369,8 @@ def test_optimize(self):
362369
self.assertIsInstance(res, pd.Series)
363370

364371
res2 = bt.optimize(**OPT_PARAMS, maximize=lambda s: s['SQN'])
365-
self.assertSequenceEqual(res.filter(regex='^[^_]').to_dict(),
366-
res2.filter(regex='^[^_]').to_dict())
372+
self.assertDictEqual(res.filter(regex='^[^_]').fillna(-1).to_dict(),
373+
res2.filter(regex='^[^_]').fillna(-1).to_dict())
367374

368375
res3, heatmap = bt.optimize(**OPT_PARAMS, return_heatmap=True,
369376
constraint=lambda d: d.slow > 2 * d.fast)
@@ -553,7 +560,7 @@ def init(self):
553560
self.data.Close < sma)
554561

555562
stats = Backtest(GOOG, S).run()
556-
self.assertGreater(stats['# Trades'], 1000)
563+
self.assertEqual(stats['# Trades'], 2273)
557564

558565
def test_TrailingStrategy(self):
559566
class S(TrailingStrategy):
@@ -569,7 +576,7 @@ def next(self):
569576
self.buy()
570577

571578
stats = Backtest(GOOG, S).run()
572-
self.assertGreater(stats['# Trades'], 6)
579+
self.assertEqual(stats['# Trades'], 50)
573580

574581

575582
class TestUtil(TestCase):

0 commit comments

Comments
 (0)