For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/

Order Notification



  • Hi everyone:

    I am running into an issue while doing the coding exercise with multi line data feed, appreciate your help.
    Below code for your reference, modified from 'QuickStart' example, aim to add multiple line data feed, add SMA on each line of data feed, strategy is SMA cros over (only allow to BUY open), apparently need SMA to check against its own line of data feed.

    Code can run through, but when I look into the log file, the NVDA BUY order triggered on 2/9/2000, fills only comes back on 2/22/2009 after a few days' of 'Orders pending...'. I know something wrong here as when I run this SMA cross strategy with only one NVDA data feed, fills will come back next day after the order triggered.
    I put order in dictionary structure, when I loop through data lines, order created as:

    self.order[i] = self.buy(i)
    

    where i is data object.

    Can you please give some advise as where was the issue?

    Thanks

    
    from __future__ import (absolute_import, division, print_function,
                            unicode_literals)
    
    import datetime  # For datetime objects
    import os.path  # To manage paths
    import sys  # To find out the script name (in argv[0])
    from inspect import getsourcefile
    from os.path import abspath
    
    
    import warnings
    warnings.simplefilter(action='ignore')
    
    # Import the backtrader platform
    import backtrader as bt
    import csv
    
    
    
    # Create a Stratey
    class TestStrategy(bt.Strategy):
        params = {'maperiod': 15,
                  }
    
               
        def __init__(self):
            # Keep a reference to the "close" line in the data[0] dataseries
            self.dataclose = self.datas[0].close
    
            # To keep track of pending orders and buy price/commission
            self.order = {}
    
    
            #name is to description this strategy
            # each log file name is set to strategyname + _log_+ time when running.csv         
            self.name  = 'TestStrategy'
            self.logfilename = self.name + '_log_' + '{:%Y%m%d%H%M%S}'.format(datetime.datetime.now()) + '.csv'
            
            
            # Add a MovingAverageSimple indicator
    #        loop through data series, adding SMA for each dataline
            self.indicators = {}
            for dd in self.datas:
                self.indicators[dd] = bt.indicators.SimpleMovingAverage(
                        dd, period=self.params.maperiod)
    
        def log(self, txt, dt=None):
            ''' Logging function fot this strategy'''
            #         log file will need record all necessary information for analysis     
            
            dt = dt or self.datas[0].datetime.date(0)
            txt.insert(0,'{:%Y-%m-%d %H:%M:%S}'.format(dt))
            logfilename= os.getcwd() + '\\log\\' + self.logfilename
    
            #    'a' parameter meanings append record at bottom of the file     
            with open(logfilename,'a', newline = '\n') as csv_file:
                csv_writer = csv.writer(csv_file)
                csv_writer.writerow(txt)
    #        print(txt)
        def notify_order(self, order):
            for i in self.datas:
                order = self.order.get(i)
                #order is order for the current line feed
                if order is None:
                    return            
                if order.status in [order.Submitted, order.Accepted]:
                    # 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(
                            ['Order',i._name, 'BUY EXECUTED', 
                             'Price', '{:.4f}'.format(order.executed.price),
                             'Cost', '{:.4f}'.format(order.executed.value),
                             'Comm', '{:.4f}'.format(order.executed.comm)])
        
                    else:  # Sell
                        self.log(
                            ['Order',i._name, 'SELL EXECUTED', 
                             'Price', '{:.4f}'.format(order.executed.price),
                             'Cost', '{:.4f}'.format(order.executed.value),
                             'Comm', '{:.4f}'.format(order.executed.comm)])
            
                elif order.status in [order.Canceled, order.Margin, order.Rejected]:
                     self.log(['Order','Order Canceled/Margin/Rejected'])
        
                # Write down: no pending order
                self.order[i] = None
    
        def notify_trade(self, trade):
            if not trade.isclosed:
                return
    
            self.log(['Trades',
                       'OPERATION PROFIT', '{:.4f}'.format(trade.pnl)
                      ])
    
        def next(self):
            # Simply log the open,High, low, closing price of the series from the reference
            for i in self.datas:
                self.log(['PriceRef', i._name,
                          'Close', '{:.4f}'.format(i.close[0]),
                          'Open','{:.4f}'.format(i.open[0]),
                          'High','{:.4f}'.format(i.high[0]),
                          'Low','{:.4f}'.format(i.low[0]),
                          'SMA','{:.4f}'.format(self.indicators[i].sma[0]),
                          'Position', '{}'.format(self.getposition(i).size)])
    
    #            print('{:%Y-%m-%d %H:%M:%S}'.format(bt.num2date(i.datetime[0])))
                
                # Check if an order is pending ... if yes, we cannot send a 2nd one
                if self.order.get(i) is not None:
                    self.log(['Comments',i._name, 'Orders Pending, wait...', self.order[i].status ])
                    return
        
                # Check if we are in the market
                if not self.getposition(i).size:
        
                    # Not yet ... we MIGHT BUY if ...
                    if i.close[0] > self.indicators[i].sma[0]:
        
                        # BUY, BUY, BUY!!! (with all possible default parameters)
                        self.log(['Order',i._name, 'BUY', '{:.4f}'.format(i.close[0])])
                        
                        # Keep track of the created order to avoid a 2nd order
                        self.order[i] = self.buy(i)
            
                else:
    
                    if i.close[0] < self.indicators[i].sma[0]:
                        # SELL, SELL, SELL!!! (with all possible default parameters)
                        self.log(['Order',i._name,'SELL', '{:.4f}'.format(i.close[0])])
        
                        # Keep track of the created order to avoid a 2nd order
                        self.order[i] = self.sell(i)
    
    if __name__ == '__main__':
        # Create a cerebro entity
        cerebro = bt.Cerebro()
    
        # Add a strategy
        cerebro.addstrategy(TestStrategy)
    
        datalist = {'orcl-1995-2014.txt':'orcl',
                    'nvda-1999-2014.txt':'nvda'}
    
    
        for k,v in datalist.items():
            modpath = os.path.dirname(os.path.abspath(k))
            datapath = os.path.join(modpath, k)
            # Create a Data Feed
            data = bt.feeds.YahooFinanceCSVData(
                dataname=datapath,
                # Do not pass values before this date
                fromdate=datetime.datetime(2000, 1, 1),
                # Do not pass values before this date
                todate=datetime.datetime(2010, 12, 31),
                # Do not pass values after this date
                reverse=False)
        
            # Add the Data Feed to Cerebro
            cerebro.adddata(data, name = v)
        
        
        
        # Set our desired cash start
        cerebro.broker.setcash(1000.0)
    
        # Add a FixedSize sizer according to the stake
        cerebro.addsizer(bt.sizers.FixedSize, stake=10)
    #    cerebro.addsizer(bt.sizers.PercentSizer, )
    
        # Set the commission
        cerebro.broker.setcommission(commission=0.0)
    
        # Print out the starting conditions
        print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
    
        cerebro.addanalyzer(bt.analyzers.BasicTradeStats, filter = 'all')
        cerebro.addanalyzer(bt.analyzers.BasicTradeStats, filter = 'long')
        cerebro.addanalyzer(bt.analyzers.BasicTradeStats, filter = 'short')
        cerebro.addanalyzer(bt.analyzers.AnnualReturn)
        cerebro.addanalyzer(bt.analyzers.SharpeRatio)
        cerebro.addanalyzer(bt.analyzers.DrawDown)
        cerebro.addanalyzer(bt.analyzers.TradeAnalyzer)
        
        # Run over everything
        strategies = cerebro.run()
        firstStrategy = strategies[0]
    
        # Print out the final result
        print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
    
    
    
    


  • This is part of the log file showing the delayed order notification.

    0_1521368517855_dd8067ec-b7a9-497b-9ca8-63f731684dec-image.png


  • administrators

    The code is very hard to follow but two things pop out as culprits:

    @anfei said in Order Notification:

    def notify_order(self, order):
        for i in self.datas:
            order = self.order.get(i)
    

    If you get notified a specific order: why do you check every possible order you may have somewhere else?

    @anfei said in Order Notification:

    def log(self, txt, dt=None):
        ''' Logging function fot this strategy'''
        #         log file will need record all necessary information for analysis     
        
        dt = dt or self.datas[0].datetime.date(0)
    

    Seeing some of the logs, you log with the datetime of the 1st data in the system.



  • Fixed. Issue is in the notify_order(), where:

    order = self.order.get(i)
                #order is order for the current line feed
                if order is None:
                    return            
    

    this part if wrong, order return as parameter which is the notification result of the 'engine', I can't reset it.
    Logic should be: check to find out it belong to which data feed line, print log, set it to None.



  • @backtrader
    thanks for point that out.
    Figured out, the order notification parth. Not sure why I did not see your reply when I posted it.

    And missed the log part, thanks again.