Skip to content

Commit 457c814

Browse files
committed
ENH: Add the possibility to close trades at end of bt.run (kernc#273 & kernc#343)
1 parent 0ce24d8 commit 457c814

File tree

3 files changed

+50
-18
lines changed

3 files changed

+50
-18
lines changed

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ What's New
33

44
These were the major changes contributing to each release:
55

6-
### 0.x.x
6+
7+
### 0.3.2
8+
* new param on run to manage close, or not the trades at end (#273)
79

810

911
### 0.3.3
@@ -132,4 +134,4 @@ These were the major changes contributing to each release:
132134
### 0.1.0
133135
(2019-01-15)
134136

135-
* Initial release
137+
* Initial release

backtesting/backtesting.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,7 +1030,8 @@ def __init__(self,
10301030
margin: float = 1.,
10311031
trade_on_close=False,
10321032
hedging=False,
1033-
exclusive_orders=False
1033+
exclusive_orders=False,
1034+
close_all_at_end=False
10341035
):
10351036
"""
10361037
Initialize a backtest. Requires data and a strategy to test.
@@ -1075,6 +1076,9 @@ def __init__(self,
10751076
trade/position, making at most a single trade (long or short) in effect
10761077
at each time.
10771078
1079+
If `close_all_at_end` is `False`, the trade will not be close at end,
1080+
and will not apear in _Stats.
1081+
10781082
[FIFO]: https://www.investopedia.com/terms/n/nfa-compliance-rule-2-43b.asp
10791083
"""
10801084

@@ -1124,7 +1128,7 @@ def __init__(self,
11241128
warnings.warn('Data index is not datetime. Assuming simple periods, '
11251129
'but `pd.DateTimeIndex` is advised.',
11261130
stacklevel=2)
1127-
1131+
self._close_all_at_end = bool(close_all_at_end)
11281132
self._data: pd.DataFrame = data
11291133
self._broker = partial(
11301134
_Broker, cash=cash, commission=commission, margin=margin,
@@ -1218,14 +1222,15 @@ def run(self, **kwargs) -> pd.Series:
12181222
# Next tick, a moment before bar close
12191223
strategy.next()
12201224
else:
1221-
# Close any remaining open trades so they produce some stats
1222-
for trade in broker.trades:
1223-
trade.close()
1224-
1225-
# Re-run broker one last time to handle orders placed in the last strategy
1226-
# iteration. Use the same OHLC values as in the last broker iteration.
1227-
if start < len(self._data):
1228-
try_(broker.next, exception=_OutOfMoneyError)
1225+
if self._close_all_at_end is True:
1226+
# Close any remaining open trades so they produce some stats
1227+
for trade in broker.trades:
1228+
trade.close()
1229+
1230+
# Re-run broker one last time to handle orders placed in the last strategy
1231+
# iteration. Use the same OHLC values as in the last broker iteration.
1232+
if start < len(self._data):
1233+
try_(broker.next, exception=_OutOfMoneyError)
12291234

12301235
# Set data back to full length
12311236
# for future `indicator._opts['data'].index` calls to work

backtesting/test/_test.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ def next(self, _FEW_DAYS=pd.Timedelta('3 days')): # noqa: N803
218218
bt = Backtest(GOOG, Assertive)
219219
with self.assertWarns(UserWarning):
220220
stats = bt.run()
221-
self.assertEqual(stats['# Trades'], 145)
221+
self.assertEqual(stats['# Trades'], 144)
222222

223223
def test_broker_params(self):
224224
bt = Backtest(GOOG.iloc[:100], SmaCross,
@@ -251,7 +251,7 @@ def test_compute_drawdown(self):
251251
np.testing.assert_array_equal(peaks, pd.Series([7, 4], index=[3, 5]).reindex(dd.index))
252252

253253
def test_compute_stats(self):
254-
stats = Backtest(GOOG, SmaCross).run()
254+
stats = Backtest(GOOG, SmaCross, close_all_at_end=True).run()
255255
expected = pd.Series({
256256
# NOTE: These values are also used on the website!
257257
'# Trades': 66,
@@ -401,7 +401,32 @@ def next(self):
401401
elif len(self.data) == len(SHORT_DATA):
402402
self.position.close()
403403

404-
self.assertFalse(Backtest(SHORT_DATA, S).run()._trades.empty)
404+
self.assertTrue(Backtest(SHORT_DATA, S).run()._trades.empty)
405+
406+
def test_dont_close_orders_from_last_strategy_iteration(self):
407+
class S(Strategy):
408+
def init(self): pass
409+
410+
def next(self):
411+
if not self.position:
412+
self.buy()
413+
elif len(self.data) == len(SHORT_DATA):
414+
self.position.close()
415+
self.assertEqual(len(
416+
Backtest(SHORT_DATA, S, close_all_at_end=False).run()._strategy.closed_trades), 0)
417+
self.assertEqual(len(
418+
Backtest(SHORT_DATA, S, close_all_at_end=False).run()._strategy.trades), 1)
419+
420+
def test_dont_close_orders_trades_from_last_strategy_iteration(self):
421+
class S(Strategy):
422+
def init(self): pass
423+
424+
def next(self):
425+
if not self.position:
426+
self.buy()
427+
428+
self.assertEqual(len(
429+
Backtest(SHORT_DATA, S, close_all_at_end=False).run()._strategy.trades), 1)
405430

406431
def test_check_adjusted_price_when_placing_order(self):
407432
class S(Strategy):
@@ -503,7 +528,7 @@ def test_autoclose_trades_on_finish(self):
503528
def coroutine(self):
504529
yield self.buy()
505530

506-
stats = self._Backtest(coroutine).run()
531+
stats = self._Backtest(coroutine, close_all_at_end=True).run()
507532
self.assertEqual(len(stats._trades), 1)
508533

509534
def test_order_tag(self):
@@ -873,7 +898,7 @@ def init(self):
873898
self.data.Close < sma)
874899

875900
stats = Backtest(GOOG, S).run()
876-
self.assertIn(stats['# Trades'], (1181, 1182)) # varies on different archs?
901+
self.assertIn(stats['# Trades'], (1179, 1180)) # varies on different archs?
877902

878903
def test_TrailingStrategy(self):
879904
class S(TrailingStrategy):
@@ -889,7 +914,7 @@ def next(self):
889914
self.buy()
890915

891916
stats = Backtest(GOOG, S).run()
892-
self.assertEqual(stats['# Trades'], 57)
917+
self.assertEqual(stats['# Trades'], 56)
893918

894919

895920
class TestUtil(TestCase):

0 commit comments

Comments
 (0)