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_1489778622405_3barsdelay.png

    [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()
    

  • administrators

    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.


Log in to reply
 

Looks like your connection to Backtrader Community was lost, please wait while we try to reconnect.