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.
-
@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.
-
@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 innotify_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 innotify_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!