kernc / backtesting.py

:mag_right: :chart_with_upwards_trend: :snake: :moneybag: Backtest trading strategies in Python.
https://kernc.github.io/backtesting.py/
GNU Affero General Public License v3.0
5.04k stars 987 forks source link

Avoid opening position that gaps down with limit order #1127

Open PabloCanovas opened 3 months ago

PabloCanovas commented 3 months ago

I think it could be useful to have an option that control how we handle gaps down with limit orders. So this could be an enhancement.

Expected Behavior

Let's say I am trade_on_close and set a buy limit order with a TP and SL attached. The order gets recorded on the close but won't fill until next candle (if price decline enough to match my limit order). Then, the next day the stock gaps down past my buy limit and also past my stop loss, so the open will already be below my stop loss. In that scenario, it could be useful to have an option that removes that order before it gets triggered to avoid getting into the position at the open in an awkward situation (with SL > buy price)

Actual Behavior

Currently, in the above scenario, the stock is bought at the open and sold on the next day close (if trade_on_close) because the library doesn't allow to buy and sell on the same candle, (see https://github.com/kernc/backtesting.py/issues/119#issuecomment-2016788300), so it would be bought on the open on day 1 and sold at the close price on day 2. I think it would close on the day 2 open if trade_on_close = False, but haven't tested that). So you are in a stock for whole 2 days without your SL getting triggered, although it should have triggered on the very same moment you entered the position.

Suggestion

My suggestion would be to include something like

# if open <= SL (being long) we don't even open the position
if order.limit:    
    if (order.is_long and price <= order.sl) or (order.is_short and price >= order.sl):
        print("*** Open past SL  =>  Order Removed ***")
        self.orders.remove(order)
        continue

just before calling self._open_trade() under the _process_orders() method. This would simply remove the order before we even enter the position.

Another option would be to control this behaviour through an additional variable, as I reckon this proposed behaviour makes more sense if we are backtesting on the daily timeframe (as by the open we would know AH/premarket prices and we could act on that new information), but it is probably less relevant or even cheating if we are thinking on intraday trading (as this will lead to some mixed behaviour were we are now both trading_on_close and also kind of trade_on_open because we are using 'future' open info to place or remove the order.