Skip to content

Commit f77786e

Browse files
committed
ENH: Auto close open trades on backtest finish
Fixes #183 Refs: #168
1 parent c21d7d9 commit f77786e

File tree

2 files changed

+35
-25
lines changed

2 files changed

+35
-25
lines changed

backtesting/backtesting.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,6 +1159,10 @@ def run(self, **kwargs) -> pd.Series:
11591159
# Next tick, a moment before bar close
11601160
strategy.next()
11611161
else:
1162+
# Close any remaining open trades so they produce some stats
1163+
for trade in broker.trades:
1164+
trade.close()
1165+
11621166
# Re-run broker one last time to handle orders placed in the last strategy
11631167
# iteration. Use the same OHLC values as in the last broker iteration.
11641168
if start < len(self._data):

backtesting/test/_test.py

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ def next(self, FIVE_DAYS=pd.Timedelta('3 days')):
214214
bt = Backtest(GOOG, Assertive)
215215
with self.assertWarns(UserWarning):
216216
stats = bt.run()
217-
self.assertEqual(stats['# Trades'], 144)
217+
self.assertEqual(stats['# Trades'], 145)
218218

219219
def test_broker_params(self):
220220
bt = Backtest(GOOG.iloc[:100], SmaCross,
@@ -248,47 +248,46 @@ def test_compute_drawdown(self):
248248

249249
def test_compute_stats(self):
250250
stats = Backtest(GOOG, SmaCross).run()
251-
# Pandas compares in 'almost equal' manner
252-
from pandas.testing import assert_series_equal
253-
assert_series_equal(
254-
stats.filter(regex='^[^_]').sort_index(),
255-
pd.Series({
251+
expected = pd.Series({
256252
# NOTE: These values are also used on the website!
257-
'# Trades': 65,
253+
'# Trades': 66,
258254
'Avg. Drawdown Duration': pd.Timedelta('41 days 00:00:00'),
259255
'Avg. Drawdown [%]': -5.925851581948801,
260256
'Avg. Trade Duration': pd.Timedelta('46 days 00:00:00'),
261-
'Avg. Trade [%]': 2.3537113951143773,
257+
'Avg. Trade [%]': 2.531715975158555,
262258
'Best Trade [%]': 53.59595229490424,
263259
'Buy & Hold Return [%]': 703.4582419772772,
264-
'Calmar Ratio': 0.4445179349739874,
260+
'Calmar Ratio': 0.4414380935608377,
265261
'Duration': pd.Timedelta('3116 days 00:00:00'),
266262
'End': pd.Timestamp('2013-03-01 00:00:00'),
267-
'Equity Final [$]': 51959.94999999997,
263+
'Equity Final [$]': 51422.98999999996,
268264
'Equity Peak [$]': 75787.44,
269-
'Expectancy [%]': 3.097629974370269,
270-
'Exposure Time [%]': 93.99441340782123,
265+
'Expectancy [%]': 3.2748078066748834,
266+
'Exposure Time [%]': 96.74115456238361,
271267
'Max. Drawdown Duration': pd.Timedelta('584 days 00:00:00'),
272268
'Max. Drawdown [%]': -47.98012705007589,
273269
'Max. Trade Duration': pd.Timedelta('183 days 00:00:00'),
274-
'Profit Factor': 2.0880175388920286,
275-
'Return (Ann.) [%]': 21.32802699608929,
276-
'Return [%]': 419.59949999999964,
277-
'Volatility (Ann.) [%]': 36.53825234483751,
278-
'SQN': 0.916892986080858,
279-
'Sharpe Ratio': 0.5837177650097084,
280-
'Sortino Ratio': 1.0923863161583591,
270+
'Profit Factor': 2.167945974262033,
271+
'Return (Ann.) [%]': 21.180255813792282,
272+
'Return [%]': 414.2298999999996,
273+
'Volatility (Ann.) [%]': 36.49390889140787,
274+
'SQN': 1.0766187356697705,
275+
'Sharpe Ratio': 0.5803778344714113,
276+
'Sortino Ratio': 1.0847880675854096,
281277
'Start': pd.Timestamp('2004-08-19 00:00:00'),
282-
'Win Rate [%]': 46.15384615384615,
278+
'Win Rate [%]': 46.96969696969697,
283279
'Worst Trade [%]': -18.39887353835481,
284-
}).sort_index()
285-
)
280+
})
281+
diff = {key: print(key) or value
282+
for key, value in stats.filter(regex='^[^_]').items()
283+
if value != expected[key]}
284+
self.assertDictEqual(diff, {})
286285

287286
self.assertSequenceEqual(
288287
sorted(stats['_equity_curve'].columns),
289288
sorted(['Equity', 'DrawdownPct', 'DrawdownDuration']))
290289

291-
self.assertEqual(len(stats['_trades']), 65)
290+
self.assertEqual(len(stats['_trades']), 66)
292291

293292
self.assertSequenceEqual(
294293
sorted(stats['_trades'].columns),
@@ -488,6 +487,13 @@ def coroutine(self):
488487
stats = self._Backtest(coroutine).run()
489488
self.assertListEqual(stats._trades.filter(like='Price').stack().tolist(), [112, 107])
490489

490+
def test_autoclose_trades_on_finish(self):
491+
def coroutine(self):
492+
yield self.buy()
493+
494+
stats = self._Backtest(coroutine).run()
495+
self.assertEqual(len(stats._trades), 1)
496+
491497

492498
class TestOptimize(TestCase):
493499
def test_optimize(self):
@@ -786,7 +792,7 @@ def init(self):
786792
self.data.Close < sma)
787793

788794
stats = Backtest(GOOG, S).run()
789-
self.assertEqual(stats['# Trades'], 1180)
795+
self.assertEqual(stats['# Trades'], 1182)
790796

791797
def test_TrailingStrategy(self):
792798
class S(TrailingStrategy):
@@ -802,7 +808,7 @@ def next(self):
802808
self.buy()
803809

804810
stats = Backtest(GOOG, S).run()
805-
self.assertEqual(stats['# Trades'], 50)
811+
self.assertEqual(stats['# Trades'], 51)
806812

807813

808814
class TestUtil(TestCase):

0 commit comments

Comments
 (0)