3 bars delay in the backtest
-
hello, Can anyone explain why there is 3 bars delays between the signal and the execution of the orders ( enter and exit) ?.
I can't figure why there is the delay.
I have added the code used to demonstrate the delay.
[0_1489778684927_analyzer-annualreturn.py](Uploading 100%)
-
here the code in text :
#!/usr/bin/env python # -*- coding: utf-8; py-indent-offset:4 -*- ############################################################################### # # Copyright (C) 2015, 2016 Daniel Rodriguez # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # ############################################################################### from __future__ import (absolute_import, division, print_function, unicode_literals) import argparse import datetime # The above could be sent to an independent module import backtrader as bt import backtrader.feeds as btfeeds import backtrader.indicators as btind from backtrader.analyzers import (SQN, AnnualReturn, TimeReturn, SharpeRatio, TradeAnalyzer) class LongShortStrategy(bt.Strategy): '''This strategy buys/sells upong the close price crossing upwards/downwards a Simple Moving Average. It can be a long-only strategy by setting the param "onlylong" to True ''' params = dict( period=15, stake=1, printout=False, onlylong=False, csvcross=False, ) def start(self): pass def stop(self): pass def log(self, txt, dt=None): if self.p.printout: dt = dt or self.data.datetime[0] dt = bt.num2date(dt) print('%s, %s' % (dt.isoformat(), txt)) def __init__(self): # To control operation entries self.orderid = None # Create SMA on 2nd data sma = btind.MovAv.SMA(self.data, period=self.p.period) sma2 = btind.MovAv.SMA(self.data, period=self.p.period * 2) # Create a CrossOver Signal from close an moving average self.signal = btind.CrossOver(self.data.close, sma) self.signal2 = btind.CrossOver(sma, sma2) self.signal2.csv = self.p.csvcross def next(self): if self.orderid: return # if an order is active, no new orders are allowed if self.signal2 > 0.0: # cross upwards if self.position: self.log('CLOSE SHORT , %.2f' % self.data.close[0]) self.close() self.log('BUY CREATE , %.2f' % self.data.close[0]) self.buy(size=self.p.stake) elif self.signal2 < 0.0: if self.position: self.log('CLOSE LONG , %.2f' % self.data.close[0]) self.close() if not self.p.onlylong: self.log('SELL CREATE , %.2f' % self.data.close[0]) self.sell(size=self.p.stake) def notify_order(self, order): if order.status in [bt.Order.Submitted, bt.Order.Accepted]: return # Await further notifications if order.status == order.Completed: if order.isbuy(): buytxt = 'BUY COMPLETE, %.2f' % order.executed.price self.log(buytxt, order.executed.dt) else: selltxt = 'SELL COMPLETE, %.2f' % order.executed.price self.log(selltxt, order.executed.dt) elif order.status in [order.Expired, order.Canceled, order.Margin]: self.log('%s ,' % order.Status[order.status]) pass # Simply log # Allow new orders self.orderid = None def notify_trade(self, trade): if trade.isclosed: self.log('TRADE PROFIT, GROSS %.2f, NET %.2f' % (trade.pnl, trade.pnlcomm)) elif trade.justopened: self.log('TRADE OPENED, SIZE %2d' % trade.size) def runstrategy(): args = parse_args() # Create a cerebro cerebro = bt.Cerebro() # Get the dates from the args fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d') todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d') # Create the 1st data data = btfeeds.BacktraderCSVData( dataname=args.data, fromdate=fromdate, todate=todate) # Add the 1st data to cerebro cerebro.adddata(data) # Add the strategy cerebro.addstrategy(LongShortStrategy, period=args.period, onlylong=args.onlylong, csvcross=args.csvcross, stake=args.stake) # Add the commission - only stocks like a for each operation cerebro.broker.setcash(args.cash) # Add the commission - only stocks like a for each operation cerebro.broker.setcommission(commission=args.comm, mult=args.mult, margin=args.margin) tframes = dict( days=bt.TimeFrame.Days, weeks=bt.TimeFrame.Weeks, months=bt.TimeFrame.Months, years=bt.TimeFrame.Years) # Add the Analyzers cerebro.addanalyzer(SQN) if args.legacyannual: cerebro.addanalyzer(AnnualReturn) cerebro.addanalyzer(SharpeRatio, legacyannual=True) else: cerebro.addanalyzer(TimeReturn, timeframe=tframes[args.tframe]) cerebro.addanalyzer(SharpeRatio, timeframe=tframes[args.tframe]) cerebro.addanalyzer(TradeAnalyzer) cerebro.addwriter(bt.WriterFile, csv=args.writercsv, rounding=4) # And run it cerebro.run() # Plot if requested if args.plot: cerebro.plot(numfigs=args.numfigs, volume=False, zdown=False) def parse_args(): parser = argparse.ArgumentParser(description='TimeReturn') parser.add_argument('--data', '-d', default='../../datas/2005-2006-day-001.txt', help='data to add to the system') parser.add_argument('--fromdate', '-f', default='2005-01-01', help='Starting date in YYYY-MM-DD format') parser.add_argument('--todate', '-t', default='2006-12-31', help='Starting date in YYYY-MM-DD format') parser.add_argument('--period', default=15, type=int, help='Period to apply to the Simple Moving Average') parser.add_argument('--onlylong', '-ol', action='store_true', help='Do only long operations') parser.add_argument('--writercsv', '-wcsv', action='store_true', help='Tell the writer to produce a csv stream') parser.add_argument('--csvcross', action='store_true', help='Output the CrossOver signals to CSV') group = parser.add_mutually_exclusive_group() group.add_argument('--tframe', default='years', required=False, choices=['days', 'weeks', 'months', 'years'], help='TimeFrame for the returns/Sharpe calculations') group.add_argument('--legacyannual', action='store_true', help='Use legacy annual return analyzer') parser.add_argument('--cash', default=100000, type=int, help='Starting Cash') parser.add_argument('--comm', default=2, type=float, help='Commission for operation') parser.add_argument('--mult', default=10, type=int, help='Multiplier for futures') parser.add_argument('--margin', default=2000.0, type=float, help='Margin for each future') parser.add_argument('--stake', default=1, type=int, help='Stake to apply in each operation') parser.add_argument('--plot', '-p', default=True, action='store_true', help='Plot the read data') parser.add_argument('--numfigs', '-n', default=1, help='Plot using numfigs figures') return parser.parse_args() if __name__ == '__main__': runstrategy()
-
Your 1st circle in the chart is wrong. Even if you want to believe there is a cross, that's only the lines of the moving averages, which need to be drawn across bars.
The 1st
CrossOver
below the data chart shows clearly when the crossing down is taking place, which is exactly one bar before the execution takes place.The execution happens on the next bar with the 1st incoming price (the
open
price), because when you are evaluating a bar that bar is closed and execution can only happen with incoming prices.If you want to cheat yourself, or you think you'll be acting in real-time before the close with a very good approximation of having had a cross, activate the
cheat-on-close
mode.See:
P.S.
If you format the code (see the banner at the top, it takes just 3 ticks) it will be a lot easier for others to read. -
thank you very much for your answer.
next time I'll use the function snippet. -
Hello @backtrader and guys,
Quick question on the same topic but with indicators. When all conditions are met (which is the case, see 4 circled indicators in picture), buy order is sent but why my trade is plotmarked after 3 bars ?
In the code, the 4 indicators are consolidated in strategy init like this :
long_condition = bt.And(cond1,cond2,cond3,cond4)
Then in strategy next :
if long_condition>0: buy
Can this create more delay on top of incoming price with the next bar which is expected behavior (no cheat-on-close desired) ?
Thanks for your clarifications
-
The
buy
should happen on the next bar. Obviously the condition and execution are taken out of a much larger code base, but when theAnd
isTrue
the order should go in. -
Notice in the chart that 3 indicators produce the value
1.0
constantly and a 4th indicator starts later. Once the 4 produce1.0
, the order is issued and executed on the next bar as expected. -
Hello @backtrader ,
Thanks for your answer. I have printed out the steps (see below) and notice that 3 bars are required to have order passing following steps : submitted, accepted, completed. Then trade is plotmarked as Buy.
LONG ENTRY 2018-02-05 01:40:00 0.01782714 2018-02-05 01:41:00 - Order Submitted 2018-02-05 01:41:00 - Self.orderid = Ref: 41 OrdType: 0 OrdType: Buy Status: 2 Status: Accepted Size: 1 Price: 0.01782714 Price Limit: None TrailAmount: None TrailPercent: None ExecType: 2 ExecType: Limit CommInfo: None End of Session: 736730.9999999999 Info: AutoOrderedDict() Broker: None Alive: True 2018-02-05 01:41:00 - Order Accepted 2018-02-05 01:41:00 - Self.orderid = Ref: 41 OrdType: 0 OrdType: Buy Status: 2 Status: Accepted Size: 1 Price: 0.01782714 Price Limit: None TrailAmount: None TrailPercent: None ExecType: 2 ExecType: Limit CommInfo: None End of Session: 736730.9999999999 Info: AutoOrderedDict() Broker: None Alive: True {} - NEXT is returned 2018-02-05 01:41:00 {} - NEXT is returned 2018-02-05 01:42:00 {} - NEXT is returned 2018-02-05 01:43:00 2018-02-05 01:44:00 - BUY COMPLETE, price = 0.01782714 2018-02-05 01:44:00 - Self.orderid = Ref: 41 OrdType: 0 OrdType: Buy Status: 4 Status: Completed Size: 1 Price: 0.01782714 Price Limit: None TrailAmount: None TrailPercent: None ExecType: 2 ExecType: Limit CommInfo: <backtrader.comminfo.CommInfoBase object at 0x7fe0e81d2780> End of Session: 736730.9999999999 Info: AutoOrderedDict() Broker: None Alive: False
-
@remroc said in 3 bars delay in the backtest:
ExecType: Limit
A
Limit
order is not guaranteed to be executed right after posted. The limit price has to be matched. -
@backtrader , damn you are right, this is due to limit order instead of market. Thanks a lot for that one !