Skip to content

Commit 31cff13

Browse files
committed
BUG: Fix SL always executes before TP when hit in the same bar, as documented
Fixes #1241
1 parent e1a86ef commit 31cff13

File tree

2 files changed

+22
-9
lines changed

2 files changed

+22
-9
lines changed

backtesting/backtesting.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -801,11 +801,8 @@ def new_order(self,
801801
f"TP ({tp}) < LIMIT ({limit or stop or adjusted_price}) < SL ({sl})")
802802

803803
order = Order(self, size, limit, stop, sl, tp, trade, tag)
804-
# Put the new order in the order queue,
805-
# inserting SL/TP/trade-closing orders in-front
806-
if trade:
807-
self.orders.insert(0, order)
808-
else:
804+
805+
if not trade:
809806
# If exclusive orders (each new order auto-closes previous orders/position),
810807
# cancel all non-contingent orders and close all open trades beforehand
811808
if self._exclusive_orders:
@@ -815,7 +812,8 @@ def new_order(self,
815812
for t in self.trades:
816813
t.close()
817814

818-
self.orders.append(order)
815+
# Put the new order in the order queue, Ensure SL orders are processed first
816+
self.orders.insert(0 if trade and stop else len(self.orders), order)
819817

820818
return order
821819

@@ -1067,9 +1065,6 @@ def _open_trade(self, price: float, size: int,
10671065
# Apply broker commission at trade open
10681066
self._cash -= self._commission(size, price)
10691067
# Create SL/TP (bracket) orders.
1070-
# Make sure SL order is created first so it gets adversarially processed before TP order
1071-
# in case of an ambiguous tie (both hit within a single bar).
1072-
# Note, sl/tp orders are inserted at the front of the list, thus order reversed.
10731068
if tp:
10741069
trade.tp = tp
10751070
if sl:

backtesting/test/_test.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,3 +1108,21 @@ def test_trades_dates_match_prices(self):
11081108
trades = bt.run()._trades
11091109
self.assertEqual(EURUSD.Close[trades['ExitTime']].tolist(),
11101110
trades['ExitPrice'].tolist())
1111+
1112+
def test_sl_always_before_tp(self):
1113+
class S(Strategy):
1114+
def init(self): pass
1115+
1116+
def next(self):
1117+
i = len(self.data.index)
1118+
if i == 4:
1119+
self.buy()
1120+
if i == 5:
1121+
t = self.trades[0]
1122+
t.sl = 105
1123+
t.tp = 107.9
1124+
1125+
trades = Backtest(SHORT_DATA, S).run()._trades
1126+
(bt := Backtest(SHORT_DATA, S)).run()
1127+
bt.plot()
1128+
self.assertEqual(trades['ExitPrice'].iloc[0], 104.95)

0 commit comments

Comments
 (0)