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

Multi Assets Multi Order Executions Problem



  • Hi,

    I'm trying to simply execute the multi assets order based on the same strategy. However, when I run my strategy, it somehow executed the order for each asset twice and I can't figure out what is the problem. Appreciate if you could take a look at my code below. Thanks!

    from __future__ import (absolute_import, division, print_function, unicode_literals)
    
    import backtrader as bt
    import datetime as dt
    import pytz
    import math
    
    cn1='XXXX'
    cn2='YYYY'
    csh=100000
    stdt=dt.datetime(2019,06,28,9,30)
    enddt=dt.datetime(2019,06,28,16,00)
    SL=0.01
    TP=2*SL
    SU=0.005
    SUpct=SU*100
    prop=1/4 #proportion of portfolio
    batch=1 #rounding
    entrytime = dt.time(9,45)
    exittime = dt.time(15,30)
    stakesize=10
    lwbnd=40
    upbnd=75
    commis=0.05
    TPpct=TP*100
    SLpct=SL*100
    
    def rounddown(x):
        return int(math.floor(x / 1)) * 1
    
    class IBCommission(bt.CommInfoBase):
    
        """A :class:`IBCommision` charges the way interactive brokers does.
        """
    
        params = (('stocklike', True), ('commtype', bt.CommInfoBase.COMM_FIXED),)
    
        def _getcommission(self, size, price, pseudoexec):
            return self.p.commission
    
    class PropSizer(bt.Sizer):
        """A position sizer that will buy as many stocks as necessary for a certain proportion of the portfolio
           to be committed to the position, while allowing stocks to be bought in batches (say, 100)"""
        params = {"prop": prop, "batch": batch}
     
        def _getsizing(self, comminfo, cash, data, isbuy):
            """Returns the proper sizing"""
            
            for i, d in enumerate(self.datas):
                if isbuy:    # Buying
                    target = csh * self.params.prop    # Ideal total value of the position
                    price = self.data.close[0]
                    shares_ideal = target / price    # How many shares are needed to get target
                    batches = int(shares_ideal / self.params.batch)    # How many batches is this trade?
                    shares = batches * self.params.batch    # The actual number of shares bought
         
                    if shares * price > cash:
                        return 0    # Not enough money for this trade
                    else:
                        return shares
         
                else:    # Selling
                    return self.broker.getposition(d).size    # Clear the position
                
    class MainSt(bt.Strategy):
        
    
        data_live = False
        def notify_data(self, data, status, *args, **kwargs):
            print('*' * 5, 'DATA NOTIF:', data._getstatusname(status),
              *args)
            if status == data.LIVE:
                self.data_live = True
                
        def log(self, txt, dt=None, vlm=None):
            dt = dt or self.datas[0].datetime.datetime(0)
            vlm = vlm or self.data.volume[0]
            print('%s) %s, %s' % (len(self), dt.isoformat(), txt))
    
    
        def __init__(self):
    
            self.order = {}
            self.buyprice = {}
            self.buycomm = {}
            self.bar_executed = {}
            self.inds = dict()
            self.o = dict()
            self.lendata = dict()
            
            for i, d in enumerate(self.datas):
                self.order[d] = None
                self.buyprice[d] = None
                self.buycomm[d] = None   
                self.bar_executed[d] = None
                self.inds[d] = dict()
                self.inds[d]['sma'] = bt.indicators.SimpleMovingAverage(d.close, period=54)
                self.inds[d]['rsi'] = bt.indicators.RelativeStrengthIndex(d.close, period=14,safediv=True, upperband=upbnd,lowerband=lwbnd)
    
        def notify_order(self, order):
            for i, d in enumerate(self.datas):
                
                if order.status in [order.Submitted, order.Accepted]:
                    return    
                self.last_executed_price = order.executed.price
                
                if order.status in [order.Completed]:
                    
                    if order.isbuy():
                            
                            self.log('%s: BUY EXECUTED, Price: %.2f, Cost: %.2f, Size: %.2f, Comm %.2f' %
                                (order.data._name,
                                 order.executed.price,
                                 order.executed.value,
                                 order.executed.size,
                                 order.executed.comm))
            
                            self.buyprice[d] = order.executed.price
                            self.buycomm[d] = order.executed.comm
                            self.last_executed_price = order.executed.price
                            self.order[d] = None
                            self.bar_executed[d] = len(self)
                    else:
    
                        # Sell
                        self.log('%s: SELL EXECUTED, Price: %.2f, Cost: %.2f, Size: %.2f, Comm %.2f' %
                                 (order.data._name,
                                  order.executed.price,
                                  order.executed.value,
                                  order.executed.size,
                                  order.executed.comm))
                        
                        self.last_executed_price = order.executed.price
                        self.order[d] = None           
                        self.bar_executed[d] = len(self)
        
                    self.bar_executed[d] = len(self)
            
                elif order.status in [order.Canceled, order.Margin, order.Rejected]:
                    self.log('%s Order Canceled/Margin/Rejected' % (order.executed._name))
        
                # Write down: no pending order[order.data._name]
                self.order[d] = None
    
    
        def notify_trade(self, trade):
            if not trade.isclosed:
                return
    
            self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
                     (trade.pnl, trade.pnlcomm))
    
        def next(self):
            for i, d in enumerate(self.datas):
                pv = self.broker.get_value() * prop
                self.stakes = abs(rounddown(pv/d.close[0]))
                target = csh * prop    # Ideal total value of the position
                price = d.close[0]
                shares_ideal = target / price    # How many shares are needed to get target
                batches = int(shares_ideal / batch)    # How many batches is this trade?
                shares = batches * batch         
    
                if not self.getposition(d).size and not self.order[d]:
                    
                    if (pv >= target*(1+TP)):
                        return
                    
                    if (pv <= target*(1-SL)):
                        return
        
                    if (self.data.datetime.time(0) >= exittime):
                        return
                    
                    if ((self.inds[d]['rsi'][0] <= lwbnd)and(pv > target*(1-SL))and(self.data.datetime.time(0) >= entrytime)):
                        print('%s pv: %.2f, rsi: %.2f'% (d._name, pv, self.inds[d]['rsi'][0]))
                        self.log('%s: BUY CREATE, %.2f, VLM BOUGHT: %.2f' % (d._name, d.close[0], shares))
                        self.order[d] = self.buy(data=d, size=shares)
                        
                else:
                    
                    if (self.inds[d]['rsi'][0] >= upbnd):
                        print('%s pv: %.2f, rsi: %.2f'% (d._name, pv, self.inds[d]['rsi'][0]))
                        self.log('%s: SELL CREATE (RSI>Upbnd), %.2f, VLM BOUGHT: %.2f' % (d._name, d.close[0], self.position.size))
                        self.order[d] = self.close(data=d, size=abs(self.position.size))       
                        
                    else:
                        if (pv >= target*(1+TP)):
                            print('%s pv: %.2f, rsi: %.2f'% (d._name, pv, self.inds[d]['rsi'][0]))
                            self.log('%s: TAKE PROFIT VAL CREATE (>%.2fpct), %.2f, VLM BOUGHT: %.2f' % (d._name, TPpct, d.close[0], self.position.size))
                            self.order[d] = self.sell(data=d, size=abs(self.position.size))
                        else:
                            if (pv <= target*(1-SL)):
                                print('%s pv: %.2f, rsi: %.2f'% (d._name, pv, self.inds[d]['rsi'][0]))
                                self.log('%s: STOPLOSS VAL CREATE (<%.2fpct), %.2f, VLM BOUGHT: %.2f' % (d._name, SLpct, d.close[0], self.position.size))
                                self.order[d] = self.sell(data=d, size=abs(self.position.size))
                                return
                            else:
                                if (self.data.datetime.time(0) >= exittime):
                                    print('%s pv: %.2f, rsi: %.2f'% (d._name, pv, self.inds[d]['rsi'][0]))
                                    self.log('%s: EOD STOP, %.2f, VLM BOUGHT: %.2f' % (d._name, d.close[0], self.position.size))
                                    self.order[d] = self.close(data=d, exectype=bt.Order.Stop, size=abs(self.position.size))  
    
    def runstrat():
    
        
        # Get a pandas dataframe
        cerebro = bt.Cerebro()
        ibstore = bt.stores.IBStore(port=7497, host='127.0.0.1', clientId=12345)
        #create our data list
        datalist = [(cn1,'XXXX'),(cn2,'YYYY')]
        is_first = True
        #Loop through the list adding to cerebro.
        for i in datalist:
            data = ibstore.getdata(dataname=i[0],fromdate=stdt, historical =True, useRTH=True, tz = pytz.timezone('US/Eastern'),
                                 todate=enddt, timeframe=bt.TimeFrame.Seconds, compression=15)
            if i in datalist:
                if is_first:
                    data_main_plot = data
                    is_first = False
                else:
                    data.plotinfo.plotmaster = data_main_plot
            else:
                data.plotinfo.plot = False
            cerebro.adddata(data, name=i[1])
            
        cerebro.broker.setcash(csh)    
        comminfo = IBCommission(commission=commis)
        cerebro.broker.addcommissioninfo(comminfo)
        cerebro.addwriter(bt.WriterFile, csv=True, rounding=2, out="C:\\Users\\User\\Desktop\\Backtest Library\\TestResults.csv")
        
        start_value = cerebro.broker.getvalue()
    
        cerebro.addstrategy(MainSt)
        cerebro.addsizer(PropSizer)
        # Run over everything
        cerebro.run()  
        
        # Plot the result
        cerebro.plot(volume=False)
        
        # Print out the starting conditions
        print(' ')
        print('--','Summary','--')
        print('Start capital: %.2f' % start_value)
        # Print out the final result
        print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
        print('Final PnL Value: %.2f' % (cerebro.broker.getvalue()-start_value))
    
    if __name__ == '__main__':
    
        runstrat()
        
    

    the problematic results:

    Server Version: 76
    TWS Time at connection:20190704 20:50:32 SGT
    XXXX pv: 25000.00, rsi: 39.81
    65) 2019-06-28T09:46:00, XXXX: BUY CREATE, 53.66, VLM BOUGHT: 465.00
    66) 2019-06-28T09:46:15, XXXX: BUY EXECUTED, Price: 53.66, Cost: 24951.90, Size: 465.00, Comm 0.05
    66) 2019-06-28T09:46:15, XXXX: BUY EXECUTED, Price: 53.66, Cost: 24951.90, Size: 465.00, Comm 0.05
    YYYY pv: 24998.83, rsi: 33.98
    108) 2019-06-28T09:56:45, YYYY: BUY CREATE, 31.86, VLM BOUGHT: 784.00
    109) 2019-06-28T09:57:00, YYYY: BUY EXECUTED, Price: 31.84, Cost: 24962.56, Size: 784.00, Comm 0.05
    109) 2019-06-28T09:57:00, YYYY: BUY EXECUTED, Price: 31.84, Cost: 24962.56, Size: 784.00, Comm 0.05
    YYYY pv: 25028.35, rsi: 75.65
    251) 2019-06-28T10:32:30, YYYY: SELL CREATE (RSI>Upbnd), 32.05, VLM BOUGHT: 465.00
    252) 2019-06-28T10:32:45, YYYY: SELL EXECUTED, Price: 32.05, Cost: 14805.60, Size: -465.00, Comm 0.05
    252) 2019-06-28T10:32:45, YYYY: SELL EXECUTED, Price: 32.05, Cost: 14805.60, Size: -465.00, Comm 0.05
    XXXX pv: 25025.31, rsi: 77.52
    290) 2019-06-28T10:42:15, XXXX: SELL CREATE (RSI>Upbnd), 53.62, VLM BOUGHT: 465.00
    291) 2019-06-28T10:42:30, XXXX: SELL EXECUTED, Price: 53.62, Cost: 24951.90, Size: -465.00, Comm 0.05
    291) 2019-06-28T10:42:30, XXXX: SELL EXECUTED, Price: 53.62, Cost: 24951.90, Size: -465.00, Comm 0.05
    291) 2019-06-28T10:42:30, OPERATION PROFIT, GROSS -18.60, NET -18.70
    

    P/S: I intentionally not using the bracket order on this and try to find a workaround for it.

    Thanks again.


  • administrators

    @jabbarabdullah said in Multi Assets Multi Order Executions Problem:

    it somehow executed the order for each asset twice

    NO

    @jabbarabdullah said in Multi Assets Multi Order Executions Problem:

        def notify_order(self, order):
            for i, d in enumerate(self.datas):
    

    @jabbarabdullah said in Multi Assets Multi Order Executions Problem:

    I'm trying to simply execute the multi assets

    The magician inside me can say: multi == 2



  • Hi @backtrader ,

    @backtrader said in Multi Assets Multi Order Executions Problem:

    @jabbarabdullah said in Multi Assets Multi Order Executions Problem:

    it somehow executed the order for each asset twice

    NO

    Appreciate if you could explain further on what do you mean by 'NO'.

    @jabbarabdullah said in Multi Assets Multi Order Executions Problem:

        def notify_order(self, order):
            for i, d in enumerate(self.datas):
    

    @jabbarabdullah said in Multi Assets Multi Order Executions Problem:

    I'm trying to simply execute the multi assets

    The magician inside me can say: multi == 2

    Apologies, but I don't really get that. So how do I remedy the situation then?

    Thanks.



  • You have two data feeds, you cycle thru them twice and print same info twice. Check if data name is same as order data name, then print.

    On the other side you don't need to go thru all data feeds. Just print order info.


  • administrators

    @jabbarabdullah said in Multi Assets Multi Order Executions Problem:

    Appreciate if you could explain further on what do you mean by 'NO'.

    You say the orders are getting executed twice and the answer is: NO

    @ab_trader said in Multi Assets Multi Order Executions Problem:

    You have two data feeds, you cycle thru them twice and print same info twice. Check if data name is same as order data name, then print.
    On the other side you don't need to go thru all data feeds. Just print order info.

    I quoted the code in notify_order where you do exactly what @ab_trader is telling you. You print the execution twice (which is not the same as two executions), because you loop through the datas in notify_order



  • @backtrader said in Multi Assets Multi Order Executions Problem:

    @jabbarabdullah said in Multi Assets Multi Order Executions Problem:

    Appreciate if you could explain further on what do you mean by 'NO'.

    You say the orders are getting executed twice and the answer is: NO

    @ab_trader said in Multi Assets Multi Order Executions Problem:

    You have two data feeds, you cycle thru them twice and print same info twice. Check if data name is same as order data name, then print.
    On the other side you don't need to go thru all data feeds. Just print order info.

    I quoted the code in notify_order where you do exactly what @ab_trader is telling you. You print the execution twice (which is not the same as two executions), because you loop through the datas in notify_order

    I see, got it. Removed the loop in notify_order and it worked. Thanks @backtrader and @ab_trader !

    Just one more thing. when I tried executing the code with different date which the take profit and stop loss in my code should work, the numbers went crazy and it did not really stop the trade as expected. It worked great for a single asset data feed code but when I change it to multi assets data feeds, it went haywire. What went wrong? The single data feed code for comparison as follows:

    
    from __future__ import (absolute_import, division, print_function, unicode_literals)
    
    import backtrader as bt
    import datetime as dt
    import pytz
    import math
    
    cn1='UUUU'
    cn2='VVVV'
    csh=100000
    stdt=dt.datetime(2019,06,28,9,30)
    enddt=dt.datetime(2019,06,28,16,00)
    SL=0.01
    TP=2*SL
    SU=0.005
    SUpct=SU*100
    prop=1 #proportion of portfolio
    batch=10 #rounding
    entrytime = dt.time(9,45)
    exittime = dt.time(15,55)
    stakesize=10
    lwbnd=40
    upbnd=85
    commis=0.05
    TPpct=TP*100
    SLpct=SL*100
    
    def rounddown(x):
        return int(math.floor(x / 1)) * 1
    
    class IBCommission(bt.CommInfoBase):
    
        """A :class:`IBCommision` charges the way interactive brokers does.
        """
    
        params = (('stocklike', True), ('commtype', bt.CommInfoBase.COMM_FIXED),)
    
        def _getcommission(self, size, price, pseudoexec):
            return self.p.commission
    
    class PropSizer(bt.Sizer):
        """A position sizer that will buy as many stocks as necessary for a certain proportion of the portfolio
           to be committed to the position, while allowing stocks to be bought in batches (say, 100)"""
        params = {"prop": prop, "batch": batch}
     
        def _getsizing(self, comminfo, cash, data, isbuy):
            """Returns the proper sizing"""
     
            if isbuy:    # Buying
                target = csh * self.params.prop    # Ideal total value of the position
                price = data.close[0]
                shares_ideal = target / price    # How many shares are needed to get target
                batches = int(shares_ideal / self.params.batch)    # How many batches is this trade?
                shares = batches * self.params.batch    # The actual number of shares bought
     
                if shares * price > cash:
                    return 0    # Not enough money for this trade
                else:
                    return shares
     
            else:    # Selling
                return self.broker.getposition(data).size    # Clear the position
            
    class TestStrategy(bt.Strategy):
        
    
        data_live = False
        def notify_data(self, data, status, *args, **kwargs):
            print('*' * 5, 'DATA NOTIF:', data._getstatusname(status),
              *args)
            if status == data.LIVE:
                self.data_live = True
                
        def log(self, txt, dt=None, vlm=None):
            dt = dt or self.datas[0].datetime.datetime(0)
            vlm = vlm or self.data.volume[0]
            print('%s, %s, %s, Volume, %s' % (len(self), dt.isoformat(), txt, vlm))
    
    
        def __init__(self):
            self.dataclose = self.datas[0].close
            self.order = None
            self.buyprice = None
            self.buycomm = None
                
            self.sma0 = bt.indicators.SimpleMovingAverage(self.datas[0], period=20)
            self.sma = bt.indicators.SimpleMovingAverage(self.datas[0], period=54)
            self.rsi = bt.indicators.RelativeStrengthIndex(period=14,safediv=True, upperband=upbnd,lowerband=lwbnd)
    
        def notify_order(self, order):
            if order.status in [order.Submitted, order.Accepted]:
                return    
            self.last_executed_price = order.executed.price
            
            if order.status in [order.Completed]:
                if order.isbuy():
                    self.log(
                        'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                        (order.executed.price,
                         order.executed.value,
                         order.executed.comm))
    
                    self.buyprice = order.executed.price
                    self.buycomm = order.executed.comm
                    self.last_executed_price = order.executed.price
                    
                    
                else:  # Sell
                    self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                             (order.executed.price,
                              order.executed.value,
                              order.executed.comm))
                    
                    self.last_executed_price = order.executed.price
                               
                    
    
                self.bar_executed = len(self)
    
            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):
            if not trade.isclosed:
                return
    
            self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
                     (trade.pnl, trade.pnlcomm))
    
        def next(self):
            pv = self.broker.get_value()
            self.stakes = abs(rounddown(pv/self.dataclose[0]))
            target = csh * prop    # Ideal total value of the position
            price = self.data.close[0]
            shares_ideal = target / price    # How many shares are needed to get target
            batches = int(shares_ideal / batch)    # How many batches is this trade?
            shares = batches * batch         
            if self.order:
                return
        
            if not self.position:
                
                if (pv >= csh*(1+TP)):
                    return
                
                if (pv <= csh*(1-SL)):
                    return
    
                if (self.data.datetime.time(0) >= exittime):
                    return
                
                if ((self.rsi[0] <= lwbnd)and(pv > csh*(1-SL))and(self.data.datetime.time(0) >= entrytime)):
                    print('rsi:', self.rsi[0])
                    print('pv: ', pv)
                    self.log('BUY CREATE, %.2f, VLM BOUGHT: %.2f' % (self.dataclose[0], shares))
                    self.order = self.buy() 
            else:
                if (self.dataclose[0] >= (self.last_executed_price*(1+SU))):
                    print('rsi:', self.rsi[0])
                    print('pv: ', pv)
                    self.log('SELL CREATE (>%.2fpct), %.2f, VLM BOUGHT: %.2f' % (SUpct, self.dataclose[0], self.position.size))
                    self.order = self.sell(exectype=bt.Order.Stop)
                else:
                    if (self.rsi[0] >= upbnd):
                        print('rsi:', self.rsi[0])
                        print('pv: ', pv)
                        self.log('SELL CREATE (RSI>Upbnd), %.2f, VLM BOUGHT: %.2f' % (self.dataclose[0], self.position.size))
                        self.order = self.close()                        
                    else:
                        if (pv >= csh*(1+TP)):
                            print('rsi:', self.rsi[0])
                            print('pv: ', pv)
                            self.log('TAKE PROFIT VAL CREATE (>%.2fpct), %.2f, VLM BOUGHT: %.2f' % (TPpct, self.dataclose[0], self.position.size))
                            self.order = self.sell(exectype=bt.Order.StopLimit, price=self.dataclose[0])
                        else:
                            if (pv <= csh*(1-SL)):
                                print('rsi:', self.rsi[0])
                                print('pv: ', pv)
                                self.log('STOPLOSS VAL CREATE (<%.2fpct), %.2f, VLM BOUGHT: %.2f' % (SLpct, self.dataclose[0], self.position.size))
                                self.order = self.sell(exectype=bt.Order.StopLimit, price=self.dataclose[0])
                                return
                            else:
                                if (self.data.datetime.time(0) >= exittime):
                                    print('rsi:', self.rsi[0])
                                    print('pv: ', pv)
                                    self.log('EOD STOP, %.2f, VLM BOUGHT: %.2f' % (self.dataclose[0], self.position.size))
                                    self.order = self.close(exectype=bt.Order.Stop)  
    
    def runstrat():
    
        
        # Get a pandas dataframe
        cerebro = bt.Cerebro()
        ibstore = bt.stores.IBStore(port=7497, host='127.0.0.1', clientId=12345)
        data0 = ibstore.getdata(dataname=cn1,fromdate=stdt, historical =True, useRTH=True, tz = pytz.timezone('US/Eastern'),
                             todate=enddt, timeframe=bt.TimeFrame.Seconds, compression=15)
        cerebro.adddata(data0, name=cn1)
    
        data1 = ibstore.getdata(dataname=cn2,fromdate=stdt, historical =True, useRTH=True, tz = pytz.timezone('US/Eastern'),
                             todate=enddt, timeframe=bt.TimeFrame.Seconds, compression=15)
        cerebro.adddata(data1, name=cn2)
        
        data1.plotinfo.plotmaster = data0
        
        cerebro.broker.setcash(csh)    
        comminfo = IBCommission(commission=commis)
        cerebro.broker.addcommissioninfo(comminfo)
        cerebro.addwriter(bt.WriterFile, csv=True, rounding=2, out="C:\\Users\\User\\Desktop\\Backtest Library\\TestResults.csv")
        
        start_value = cerebro.broker.getvalue()
    
        cerebro.addstrategy(TestStrategy)
        cerebro.addsizer(PropSizer)
        # Run over everything
        cerebro.run()  
        
        # Plot the result
        cerebro.plot(volume=False)
        
        # Print out the starting conditions
        print(' ')
        print('--',cn1,'--')
        print('Start capital: %.2f' % start_value)
        # Print out the final result
        print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
        print('Final PnL Value: %.2f' % (cerebro.broker.getvalue()-start_value))
    
    if __name__ == '__main__':
    
        runstrat()
        
    

    Appreciate your comment on this. Thanks!


Log in to reply
 

});