Stop Orders behaving strangely, what's missing??!!
-
Hi, I am using 'Order.Stop' in the following code for a simple strategy. But, the stops orders are not executing as expected after the first two trades. I am including the minimal reproducible code here first and then I'll describe the problem in detail.
import backtrader as bt import pandas as pd pd.core.common.is_list_like = pd.api.types.is_list_like import datetime as dt import matplotlib from tabulate import tabulate print (matplotlib.rcParams['backend']) ### main strategy: class maCross(bt.Strategy): ''' oneplot = Force all datas to plot on the same master. ''' params = ( ('sma1', 10), ('sma2', 50), ('newMinMax', 10), # period for new close's highs or lows ('oneplot', True), ('atrPeriod', 20), ('lPerc', 0.01), ('sPerc', 0.015), ('tPerc', 0.02), ('RiskPerc', 0.006) ) def __init__(self): self.o = dict() # orders per data (main, stop, limit, manual-close) self.holding = dict() # holding periods per data self.tradeHigh = dict() # high during the current trade self.tradeLow = dict() # low during the current trade self.tempHigh = dict() # temperory variable self.tempLow = dict() # temperory variable self.tradeATR = dict() self.longTrailStop = dict() self.shortTrailStop = dict() self.pos = dict() self.tradePrice = dict() self.atBarOpen = {} ''' Create an dictionary of indicators so that we can dynamically add the indicators to the strategy using a loop. This mean the strategy will work with any numner of data feeds. ''' self.inds = dict() for i, d in enumerate(self.datas): self.inds[d] = dict() self.inds[d]['sma1'] = bt.indicators.SimpleMovingAverage( d.close, period=self.params.sma1) self.inds[d]['sma2'] = bt.indicators.SimpleMovingAverage( d.close, period=self.params.sma2) self.inds[d]['upTrend'] = self.inds[d]['sma1'] > self.inds[d]['sma2'] self.inds[d]['downTrend'] = self.inds[d]['sma1'] < self.inds[d]['sma2'] self.inds[d]['newLow'] = bt.ind.Lowest(d.close, period = self.params.newMinMax) self.inds[d]['newHigh'] = bt.ind.Highest(d.close, period = self.params.newMinMax) self.inds[d]['ATR'] = bt.ind.ATR(d, period = self.params.atrPeriod) self.atBarOpen[d._name] = None if i > 0: #Check we are not on the first loop of data feed: if self.p.oneplot == True: d.plotinfo.plotmaster = self.datas[0] def next(self): for i, d in enumerate(self.datas): dt, dn = self.datetime.date(), d._name self.pos[d] = self.getposition(d).size if self.pos[d] == 0: # no existing positions: self.tradeHigh[d] = self.tempHigh[d] = 0 self.tradeLow[d] = self.tempLow[d] = 10000000 self.tradeATR[d] = self.inds[d]['ATR'][-1] qty = 1000000 * self.params.RiskPerc / self.tradeATR[d] qty = round (qty, 1) # if self.getdatabyname(dn).close[0] >= self.inds[d]['newHigh'][0]: if self.inds[d]['upTrend'][0] ==1 and d.close[0] >= self.inds[d]['newHigh'][0]: print("______________") print("{}: time to buy on this date: {}" .format(dn, dt) ) self.buy(data=d, size=qty) elif self.inds[d]['downTrend'][0] ==1 and d.close[0] <= self.inds[d]['newLow'][0]: # print(self.inds[d]['downTrend'][0]) print("______________") print("{}: time to sell on this date: {}" .format(dn, dt) ) self.sell(data=d, size=qty) else: # print("No trades today: {}" .format(dt)) notrade = 1 else: # already have a position: self.tradeLen = len(self) - self.atBarOpen[d._name] if self.tradeLen ==0 : self.tradePrice[d] = d.open[0] self.longTrailStop[d] = self.tradePrice[d] - self.tradeATR[d] self.shortTrailStop[d] = self.tradePrice[d] + self.tradeATR[d] print('\r\n $$$ New trade initiated for: {} on {} -- current positions: {},opened at: {}' .format (dn, dt, round(self.pos[d],2), round(self.tradePrice[d],3 ))) else: self.tradeHigh[d] = max(d.high[0], self.tradeHigh[d], self.tradePrice[d]) self.tradeLow[d] = min(self.tradeLow[d], d.low[0], self.tradePrice[d]) if d.close[0] >= self.longTrailStop[d] + self.tradeATR[d]: self.longTrailStop[d] = self.tradeHigh[d] - self.tradeATR[d] if d.close[0] <= self.shortTrailStop[d] - self.tradeATR[d]: self.shortTrailStop[d] = self.tradeLow[d] + self.tradeATR[d] print('\r\n ### {} {} current positions: {}, trade length: {}, ATR: {}, trade high: {}, trade low: {}, close: {}, longStop: {}, shortStop: {}' .format (dt, dn, round(self.pos[d],2), self.tradeLen, round(self.tradeATR[d],3), round(self.tradeHigh[d],2), round(self.tradeLow[d],2), round(d.close[0],3), round(self.longTrailStop[d], 3), round(self.shortTrailStop[d],3) )) if self.order is None: if (self.pos[d] > 0 and self.tradeLen >0 ): self.sell(data=d, size=self.pos[d], exectype = bt.Order.Stop, price = self.longTrailStop[d], valid = 0) if (self.pos[d] < 0 and self.tradeLen >0 ): self.buy(data=d, size=-self.pos[d], exectype = bt.Order.Stop, price = self.shortTrailStop[d], valid =0) def log(self, txt, dt=None): ''' Logging function fot this strategy''' dt = dt or self.datas[0].datetime.date(0) print('%s, %s' % (dt.isoformat(), txt)) def notify_order(self, order): dt = self.data.datetime.date() if order.status in [order.Submitted, order.Accepted]: print('\r\n {}, Status {}: Ref: {}, Size: {}, Price: {}'.format( dt, order.Status[order.status], order.ref, round(order.size, 2), 'NA' if not order.price else round(order.price,5) )) # Buy/Sell order submitted/accepted to/by broker - Nothing to do return # Check if an order has been completed # Attention: broker could reject order if not enough cash if order.status in [order.Completed]: if order.isbuy(): self.log(' BUY EXECUTED, %.2f' % order.executed.price ) self.log(' Just completed BUY order with ref id: %d' % order.ref) elif order.issell(): self.log(' SELL EXECUTED, %.2f' % order.executed.price) self.log(' Just completed SELL order with ref id: %d' % order.ref) self.bar_executed = len(self) # print (self.bar_executed) elif order.status in [order.Canceled, order.Margin, order.Rejected]: self.log('Order Canceled/Margin/Rejected') # Write down: no pending order self.order = None def notify_trade(self, trade): dt = self.data.datetime.date() self.atBarOpen[trade.data._name] = trade.baropen if trade.isopen: tradeLength = trade.barlen tradeDuration = len(trade.data) print ('\r\n +++Openning Trade Notification: {} opened on bar {} at price: {} trade duration: {} at bar no.: {}' .format(trade.data._name, self.atBarOpen[trade.data._name], round( trade.price,3), tradeLength, tradeDuration) ) if trade.isclosed: print('\r\n ---Closing Trade Notification: {} {} Opened at {}, Current Valuet {}: PnL Gross {}, Net {}' .format( dt, trade.data._name, round(trade.price,3), trade.value, round(trade.pnl,2), round(trade.pnlcomm,2))) #Create an instance of cerebro cerebro = bt.Cerebro() #Add our strategy cerebro.addstrategy(maCross, oneplot=False) start = dt.datetime(2017, 10, 1) end = dt.datetime(2018,2, 28) # chagne the path for your own data: datapath = 'F:\\Michael\\Python\\backtrader\\' skiprows = 0 header = 0 # data symbol used for this example: s = "IH_CFE" datafile = datapath + s + '.csv' dataframe = pd.read_csv(datafile, skiprows=skiprows, header=header, encoding = 'gbk', parse_dates=True, index_col=0) dataframe.columns = ( ' WINDCODE', 'open', 'high', 'low', 'close', 'volume') dataframe = dataframe.loc[start: end] dataframe = dataframe.fillna(method = 'backfill') data = bt.feeds.PandasData(dataname= dataframe) cerebro.adddata(data, name = s) # Give the data to cerebro #Variable for our starting cash startcash = 1000000 # Set our desired cash start cerebro.broker.setcash(startcash) cerebro.addobserver(bt.observers.BuySell) cerebro.addobserver(bt.observers.Trades) #add executed file: cerebro.addwriter(bt.WriterFile, csv=True, out = "trade_history.csv" ) # cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio') # cerebro.addanalyzer(trade_list, _name='trade_list') # Run over everything result = cerebro.run(stdstats=False, tradehistory=True)
Ticker I am using in this example is "IH_CFE". The output of the first trade re as follows. I included many print statements in an attempt to resovle the issue. The first two trades executed as as expected. The stop order is sent out on the second day of each trade with 'valid=0', so the order is only good for that day. Then the stop price is updated based on the day's data then send out again the next day. This works well as show in this trade output initiated on 2018-01-02:
______________ IH_CFE: time to buy on this date: 2018-01-02 2018-01-03, Status Submitted: Ref: 3, Size: 143.7, Price: NA 2018-01-03, Status Accepted: Ref: 3, Size: 143.7, Price: NA 2018-01-03, BUY EXECUTED, 2904.79 2018-01-03, Just completed BUY order with ref id: 3 +++Openning Trade Notification: IH_CFE opened on bar 62 at price: 2904.786 trade duration: 0 at bar no.: 62 $$$ New trade initiated for: IH_CFE on 2018-01-03 -- current positions: 143.7,opened at: 2904.786 ### 2018-01-03 IH_CFE current positions: 143.7, trade length: 0, ATR: 41.765, trade high: 0, trade low: 10000000, close: 2906.986, longStop: 2863.022, shortStop: 2946.551 ### 2018-01-04 IH_CFE current positions: 143.7, trade length: 1, ATR: 41.765, trade high: 2924.99, trade low: 2901.99, close: 2913.186, longStop: 2883.222, shortStop: 2946.551 2018-01-05, Status Submitted: Ref: 4, Size: -143.7, Price: 2883.22178 2018-01-05, Status Accepted: Ref: 4, Size: -143.7, Price: 2883.22178 ### 2018-01-05 IH_CFE current positions: 143.7, trade length: 2, ATR: 41.765, trade high: 2930.99, trade low: 2901.99, close: 2926.786, longStop: 2889.222, shortStop: 2946.551 2018-01-08, Status Submitted: Ref: 5, Size: -143.7, Price: 2889.22178 2018-01-08, Status Accepted: Ref: 5, Size: -143.7, Price: 2889.22178 ### 2018-01-08 IH_CFE current positions: 143.7, trade length: 3, ATR: 41.765, trade high: 2943.39, trade low: 2901.99, close: 2934.186, longStop: 2901.622, shortStop: 2946.551 2018-01-09, Status Submitted: Ref: 6, Size: -143.7, Price: 2901.62178 2018-01-09, Status Accepted: Ref: 6, Size: -143.7, Price: 2901.62178 ### 2018-01-09 IH_CFE current positions: 143.7, trade length: 4, ATR: 41.765, trade high: 2963.79, trade low: 2901.99, close: 2963.786, longStop: 2922.022, shortStop: 2946.551 2018-01-10, Status Submitted: Ref: 7, Size: -143.7, Price: 2922.02178 2018-01-10, Status Accepted: Ref: 7, Size: -143.7, Price: 2922.02178 ### 2018-01-10 IH_CFE current positions: 143.7, trade length: 5, ATR: 41.765, trade high: 2987.39, trade low: 2901.99, close: 2984.386, longStop: 2945.622, shortStop: 2946.551 2018-01-11, Status Submitted: Ref: 8, Size: -143.7, Price: 2945.62178 2018-01-11, Status Accepted: Ref: 8, Size: -143.7, Price: 2945.62178 ### 2018-01-11 IH_CFE current positions: 143.7, trade length: 6, ATR: 41.765, trade high: 2995.19, trade low: 2901.99, close: 2982.986, longStop: 2945.622, shortStop: 2946.551 2018-01-12, Status Submitted: Ref: 9, Size: -143.7, Price: 2945.62178 2018-01-12, Status Accepted: Ref: 9, Size: -143.7, Price: 2945.62178 ### 2018-01-12 IH_CFE current positions: 143.7, trade length: 7, ATR: 41.765, trade high: 3010.59, trade low: 2901.99, close: 3010.586, longStop: 2968.822, shortStop: 2946.551 2018-01-15, Status Submitted: Ref: 10, Size: -143.7, Price: 2968.82178 2018-01-15, Status Accepted: Ref: 10, Size: -143.7, Price: 2968.82178 ### 2018-01-15 IH_CFE current positions: 143.7, trade length: 8, ATR: 41.765, trade high: 3055.59, trade low: 2901.99, close: 3033.186, longStop: 3013.822, shortStop: 2946.551 2018-01-16, Status Submitted: Ref: 11, Size: -143.7, Price: 3013.82178 2018-01-16, Status Accepted: Ref: 11, Size: -143.7, Price: 3013.82178 ### 2018-01-16 IH_CFE current positions: 143.7, trade length: 9, ATR: 41.765, trade high: 3058.19, trade low: 2901.99, close: 3057.786, longStop: 3016.422, shortStop: 2946.551 2018-01-17, Status Submitted: Ref: 12, Size: -143.7, Price: 3016.42178 2018-01-17, Status Accepted: Ref: 12, Size: -143.7, Price: 3016.42178 ### 2018-01-17 IH_CFE current positions: 143.7, trade length: 10, ATR: 41.765, trade high: 3117.39, trade low: 2901.99, close: 3062.986, longStop: 3075.622, shortStop: 2946.551 2018-01-18, Status Submitted: Ref: 13, Size: -143.7, Price: 3075.62178 2018-01-18, Status Accepted: Ref: 13, Size: -143.7, Price: 3075.62178 2018-01-18, SELL EXECUTED, 3075.62 2018-01-18, Just completed SELL order with ref id: 13 ---Closing Trade Notification: 2018-01-18 IH_CFE Opened at 2904.786, Current Valuet 0.0: PnL Gross 24549.05, Net 24549.05
As we can see, each day the stop order is send out with a new Ref no. (a new order). And on 2018-01-18, the stop order is executed based on the stop price and the trade is closed. However , on the next trade, things started to behave strangely.
______________ IH_CFE: time to buy on this date: 2018-01-18 2018-01-19, Status Submitted: Ref: 14, Size: 152.9, Price: NA 2018-01-19, Status Accepted: Ref: 14, Size: 152.9, Price: NA 2018-01-19, BUY EXECUTED, 3124.98 2018-01-19, Just completed BUY order with ref id: 14 +++Openning Trade Notification: IH_CFE opened on bar 74 at price: 3124.982 trade duration: 0 at bar no.: 74 $$$ New trade initiated for: IH_CFE on 2018-01-19 -- current positions: 152.9,opened at: 3124.982 ### 2018-01-19 IH_CFE current positions: 152.9, trade length: 0, ATR: 39.232, trade high: 0, trade low: 10000000, close: 3109.982, longStop: 3085.75, shortStop: 3164.214 ### 2018-01-22 IH_CFE current positions: 152.9, trade length: 1, ATR: 39.232, trade high: 3129.58, trade low: 3099.18, close: 3121.782, longStop: 3085.75, shortStop: 3138.414 2018-01-23, Status Submitted: Ref: 15, Size: -152.9, Price: 3085.74991 2018-01-23, Status Accepted: Ref: 15, Size: -152.9, Price: 3085.74991 ### 2018-01-23 IH_CFE current positions: 152.9, trade length: 2, ATR: 39.232, trade high: 3172.18, trade low: 3099.18, close: 3171.182, longStop: 3132.95, shortStop: 3138.414 2018-01-24, Status Submitted: Ref: 16, Size: -152.9, Price: 3132.94991 2018-01-24, Status Accepted: Ref: 16, Size: -152.9, Price: 3132.94991 ### 2018-01-24 IH_CFE current positions: 152.9, trade length: 3, ATR: 39.232, trade high: 3189.18, trade low: 3099.18, close: 3164.182, longStop: 3132.95, shortStop: 3138.414 2018-01-25, Status Submitted: Ref: 17, Size: -152.9, Price: 3132.94991 2018-01-25, Status Accepted: Ref: 17, Size: -152.9, Price: 3132.94991 2018-01-25, SELL EXECUTED, 3132.95 2018-01-25, Just completed SELL order with ref id: 16 2018-01-25, SELL EXECUTED, 3132.95 2018-01-25, Just completed SELL order with ref id: 17 ---Closing Trade Notification: 2018-01-25 IH_CFE Opened at 3124.982, Current Valuet 0.0: PnL Gross 1218.32, Net 1218.32 +++Openning Trade Notification: IH_CFE opened on bar 78 at price: 3132.95 trade duration: 0 at bar no.: 78 $$$ New trade initiated for: IH_CFE on 2018-01-25 -- current positions: -152.9,opened at: 3164.382 ### 2018-01-25 IH_CFE current positions: -152.9, trade length: 0, ATR: 39.232, trade high: 3189.18, trade low: 3099.18, close: 3140.982, longStop: 3125.15, shortStop: 3203.614 ### 2018-01-26 IH_CFE current positions: -152.9, trade length: 1, ATR: 39.232, trade high: 3189.18, trade low: 3099.18, close: 3165.182, longStop: 3149.95, shortStop: 3203.614 2018-01-29, Status Submitted: Ref: 18, Size: 152.9, Price: 3203.61372 2018-01-29, Status Accepted: Ref: 18, Size: 152.9, Price: 3203.61372 ### 2018-01-29 IH_CFE current positions: -152.9, trade length: 2, ATR: 39.232, trade high: 3189.18, trade low: 3093.98, close: 3103.182, longStop: 3149.95, shortStop: 3133.214 2018-01-30, Status Submitted: Ref: 19, Size: 152.9, Price: 3133.21372 2018-01-30, Status Accepted: Ref: 19, Size: 152.9, Price: 3133.21372 2018-01-30, SELL EXECUTED, 3085.75 2018-01-30, Just completed SELL order with ref id: 15
Everything was hehaving correctly, until 2018-01-25, both stop order Ref 16 and Ref 17 got executed. But order Ref 16 was from 2018-01-24 and should have been cancelled by the end of the day like many other orders before or did I misunderstand something? This happened again on 2018-01-30 when order ref 15 executed which was sent out on 2018-01-23. What am I missing? Thanks for any help from another pair of eyes. Sorry for the long post, but it was necessary to show the problem.
I can include the data file if anyone is interested.. thanks.
Here is the sample of the input data for the results shown above:
2018/1/2 IH.CFE 2857.186363 2903.986363 2857.186363 2902.386363 9954 2018/1/3 IH.CFE 2904.786363 2935.186363 2898.386363 2906.986363 11252 2018/1/4 IH.CFE 2911.986363 2924.986363 2901.986363 2913.186363 9763 2018/1/5 IH.CFE 2913.186363 2930.986363 2908.186363 2926.786363 8995 2018/1/8 IH.CFE 2929.986363 2943.386363 2918.986363 2934.186363 9043 2018/1/9 IH.CFE 2934.186363 2963.786363 2933.186363 2963.786363 8449 2018/1/10 IH.CFE 2962.986363 2987.386363 2952.186363 2984.386363 10191 2018/1/11 IH.CFE 2979.586363 2995.186363 2971.186363 2982.986363 9443 2018/1/12 IH.CFE 2986.586363 3010.586363 2982.786363 3010.586363 9367 2018/1/15 IH.CFE 3019.186363 3055.586363 3009.186363 3033.186363 11325 2018/1/16 IH.CFE 3029.586363 3058.186363 3024.186363 3057.786363 10790 2018/1/17 IH.CFE 3060.986363 3117.386363 3048.986363 3062.986363 12257 2018/1/18 IH.CFE 3083.986363 3111.586363 3073.586363 3098.586363 8170 2018/1/19 IH.CFE 3124.981818 3131.581818 3093.181818 3109.981818 12271 2018/1/22 IH.CFE 3101.181818 3129.581818 3099.181818 3121.781818 11730 2018/1/23 IH.CFE 3132.981818 3172.181818 3132.981818 3171.181818 12746 2018/1/24 IH.CFE 3176.581818 3189.181818 3139.181818 3164.181818 14310 2018/1/25 IH.CFE 3164.381818 3164.581818 3116.181818 3140.981818 13495 2018/1/26 IH.CFE 3139.181818 3188.981818 3136.981818 3165.181818 12791 2018/1/29 IH.CFE 3175.781818 3187.781818 3093.981818 3103.181818 14934 2018/1/30 IH.CFE 3105.181818 3114.981818 3062.781818 3063.381818 12005 2018/1/31 IH.CFE 3055.781818 3103.981818 3051.581818 3101.181818 13673
thanks again!!!
-
After some further digging, it looks like that the stop orders were not expiring as expected. So, they were executed at much later than designed. My understanding of "valid" parameter is not exactly correct.... probably. Anyway, I explicitly specified the valid parameter to 1 day as such:
self.sell(data=d, size=self.pos[d], exectype = bt.Order.Stop, price = self.longTrailStop[d], valid = validTime)
where validTime is defined as:
validTime = self.data.datetime.date(0) + datetime.timedelta(days=self.params.valid)
After this change, the stop orderes are expiring as expected, see below:
### 2018-01-08 IH.CFE current positions: 143.66, trade length: 3, ATR: 41.765, trade high: 2943.39, trade low: 2901.99, close: 2934.186, longStop: 2901.622, shortStop: 2946.551 2018-01-09, Status Submitted: Ref: 275, Size: -143.66, Price: 2901.62178 2018-01-09, Status Accepted: Ref: 275, Size: -143.66, Price: 2901.62178 ### 2018-01-09 IH.CFE current positions: 143.66, trade length: 4, ATR: 41.765, trade high: 2963.79, trade low: 2901.99, close: 2963.786, longStop: 2922.022, shortStop: 2946.551 2018-01-10, Status Submitted: Ref: 276, Size: -143.66, Price: 2922.02178 2018-01-10, Status Accepted: Ref: 276, Size: -143.66, Price: 2922.02178 2018-01-10, Status Expired: Ref: 275, Size: -143.66, Price: 2901.62178 ### 2018-01-10 IH.CFE current positions: 143.66, trade length: 5, ATR: 41.765, trade high: 2987.39, trade low: 2901.99, close: 2984.386, longStop: 2945.622, shortStop: 2946.551 2018-01-11, Status Submitted: Ref: 277, Size: -143.66, Price: 2945.62178 2018-01-11, Status Accepted: Ref: 277, Size: -143.66, Price: 2945.62178 2018-01-11, Status Expired: Ref: 276, Size: -143.66, Price: 2922.02178
thanks all... !