@backtrader Hi Admin,
Appreciate the prompt response. What do you mean by low variability of data?
Thanks!
@backtrader Hi Admin,
Appreciate the prompt response. What do you mean by low variability of data?
Thanks!
Okay, I have narrowed down the problem to:
self.inds[d]['ADX'] = bt.indicators.AverageDirectionalMovementIndex(d, period=9, plot=False)
self.inds[d]['PDI'] = bt.indicators.PlusDirectionalIndicator(d, period=9, plot=False)
self.inds[d]['MDI'] = bt.indicators.MinusDirectionalIndicator(d, period=9, plot=False)
I believe due to the internal calculation of the indicators, zero division error is triggered.
I read as many postings as possible in this forum but I can't find a solution to this problem. safediv
and safezero
parameters only available to RSI and stochastic hence, I cant solve this ADX zero division error.
Kindly appreciate your guidance to overcome this issue especially on live trading.
Thanks!
Hi all,
I have codes below that works fine in backtesting but cannot execute in live trade using TWS. it returned the zero error as follows:
File "C:\Users\User\Anaconda3\lib\site-packages\backtrader\linebuffer.py", line 744, in next
self[0] = self.operation(self.a[0], self.b[0])
ZeroDivisionError: float division by zero
However, i have set the safediv=True
on the RSI in it still doesn't work. This strategy is the combination of RSI, ADX and Bollinger Band.
appreciate if you can take a look at my code and let me know what I missed.
from __future__ import (absolute_import, division, print_function, unicode_literals)
import backtrader as bt
import datetime as dt
import pytz
datalist = ['XXXX', 'YYYY'] #Update data names
csh=100000 #Change this accordingly!!!
port_start_cash=100000 #Change this accordingly!!!
SL=0.025 #stop loss pct
TP=0.01 #take profit pct
prop=1/len(datalist) #proportion of portfolio
batch = 5 #rounding
entrytime = dt.time(9,30) # start trade time
exittime = dt.time(15,55) # ending trade time
stakesize=10
lwbnd=50 #RSI Lower bound
upbnd=55 #RSI Upper bound
commis=1 #commission
TPpct=TP*100 #TP in pctge
SLpct=SL*100 #SL in pctge
class MainSt(bt.Strategy): #Strategy set up
data_live = False #start with data not live
def notify_data(self, data, status, *args, **kwargs): #notify when data turn to live
print('*' * 5, '%s DATA NOTIF: %s' % (data._name,data._getstatusname(status)),
*args)
if status == data.LIVE:
self.data_live = True
def log(self, txt, dt=None, vlm=None): #self log of data
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): #pre defined indicators
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): #iterate the data name
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=30) #sma
self.inds[d]['rsi'] = bt.indicators.RelativeStrengthIndex(d.close, period=9, safediv=True, upperband=upbnd,lowerband=lwbnd, plotname= 'rsi:'+d._name) #rsi
self.inds[d]['ADX'] = bt.indicators.AverageDirectionalMovementIndex(d, period=9, plot=False)
self.inds[d]['PDI'] = bt.indicators.PlusDirectionalIndicator(d, period=9, plot=False)
self.inds[d]['MDI'] = bt.indicators.MinusDirectionalIndicator(d, period=9, plot=False)
self.inds[d]['SD'] = bt.indicators.StandardDeviation(d, period=30, safepow=True)
self.inds[d]['TopBB'] = self.inds[d]['sma']+2.5*self.inds[d]['SD']
self.inds[d]['BotBB'] = self.inds[d]['sma']-2.5*self.inds[d]['SD']
def notify_order(self, order): #notify when order happen
if order.status in [order.Submitted, order.Accepted]:
return
if order.status in [order.Completed]:
if order.isbuy(): #when buy order created in next
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 = order.executed.price
self.buycomm = order.executed.comm
self.buy_executed_price = order.executed.price
self.order = None
self.bar_executed = len(self)
else: #when sell order created in next
# 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.sell_executed_price = order.executed.price
self.last_size = order.executed.size
self.order = None
self.bar_executed = len(self)
self.bar_executed = len(self)
self.last_executed_price = order.executed.price
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
return
'''
self.log('%s Order Canceled/Margin/Rejected' % (order.data._name))
'''
# Write down: no pending order[order.data._name]
self.order = None
def notify_trade(self, trade): #notify trade info
if not trade.isclosed:
return
self.log('%s: OPERATION PROFIT, GROSS %.2f, NET %.2f' %
(trade.data._name, trade.pnl, trade.pnlcomm))
def next_open(self): #define the buy criterias here
for i, d in enumerate(self.datas):
pv = self.broker.getvalue() #get broker value
cashd = self.broker.getcash() #get broker cash
port_gaind = pv - port_start_cash #calculate total portfolio gain
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.data_live: # make sure live then only trade!!!
return
if not self.getposition(d).size: #make sure there is no position to start buy
if (port_gaind <= -csh*SL): #check if portfolio value less then portfolio stop loss, don't buy
return
if (port_gaind >= csh*TP): #check if portfolio value less then portfolio stop loss, don't buy
return
if (d.datetime.time(0) >= exittime): # Don't buy when already end of day
return
if ((self.inds[d]['rsi'][0] < lwbnd)and(1<(self.inds[d]['MDI'][0]/self.inds[d]['PDI'][0])<1.3)and(30<self.inds[d]['ADX'][0]<40)and(d.datetime.time(0) >= entrytime)): #buy if RSI lower than lower bound and entry time is as defined
print('%s pv: %.2f, cash:%.2f, rsi: %.2f, ADX: %.2f, PDI: %.2f, MDI: %.2f'% (d._name, pv, cashd, self.inds[d]['rsi'][0], self.inds[d]['ADX'][0], self.inds[d]['PDI'][0], self.inds[d]['MDI'][0]))
self.log('%s: BUY CREATE, %.2f, VLM BOUGHT: %.2f' % (d._name, d.close[0], shares))
self.order = self.buy(data=d, size = shares, coo=True, coc=False) #buy data d shares using maximum allocated size
def next(self): #define the sell criterias here
for i, d in enumerate(self.datas):
pv = self.broker.getvalue() #get broker value
cashd = self.broker.getcash() #get broker cash
port_gain = pv - port_start_cash #calculate total portfolio gain
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
'''
self.log('%s, Close: %.2f, pv: %.2f, cash: %.2f, rsi: %.2f, ADX: %.2f, PDI/MDI: %.2f, MDI/PDI: %.2f' % (d._name, d.close[0], pv, cashd, self.inds[d]['rsi'][0], self.inds[d]['ADX'][0], self.inds[d]['PDI'][0]/self.inds[d]['MDI'][0], self.inds[d]['MDI'][0]/self.inds[d]['PDI'][0]))
'''
if not self.data_live: # make sure live then only trade!!!
return
if self.getposition(d).size: #make sure there is position to start sell
if (d.close[0] >= (self.getposition(d).price*(1+.005))): #if curent close price >= the SU price, sell position
print('%s pv: %.2f, cash:%.2f, rsi: %.2f, ADX: %.2f, PDI: %.2f, MDI: %.2f'% (d._name, pv, cashd, self.inds[d]['rsi'][0], self.inds[d]['ADX'][0], self.inds[d]['PDI'][0], self.inds[d]['MDI'][0]))
self.log('%s: SELL CREATE (>%.2fpct), %.2f, VLM BOUGHT: %.2f' % (d._name, 0.5, d.close[0], self.getposition(d).size))
self.order = self.sell(data=d, size=abs(self.getposition(d).size), coo=False, coc=True)
self.order = self.buy(data=d, size = shares, coo=True, coc=False)
else:
if ((d.close[0]<=self.inds[d]['BotBB'][0])):
print('%s pv: %.2f, cash:%.2f, rsi: %.2f, ADX: %.2f, PDI: %.2f, MDI: %.2f'% (d._name, pv, cashd, self.inds[d]['rsi'][0], self.inds[d]['ADX'][0], self.inds[d]['PDI'][0], self.inds[d]['MDI'][0]))
self.log('%s: SELL CREATE (Price<BotBB), %.2f, VLM BOUGHT: %.2f' % (d._name, d.close[0], self.getposition(d).size))
self.order = self.sell(data=d, size = abs(self.getposition(d).size), coo=False, coc=True)
else:
if ((d.close[0]>=self.inds[d]['TopBB'][0])):
print('%s pv: %.2f, cash:%.2f, rsi: %.2f, ADX: %.2f, PDI: %.2f, MDI: %.2f'% (d._name, pv, cashd, self.inds[d]['rsi'][0], self.inds[d]['ADX'][0], self.inds[d]['PDI'][0], self.inds[d]['MDI'][0]))
self.log('%s: SELL CREATE (Price>TopBB), %.2f, VLM BOUGHT: %.2f' % (d._name, d.close[0], self.getposition(d).size))
self.order = self.sell(data=d, size = abs(self.getposition(d).size), coo=False, coc=True)
else:
if (d.datetime.time(0) >= exittime): #exit trade when end of day arrive
print('%s pv: %.2f, cash:%.2f, rsi: %.2f'% (d._name, pv, cashd, self.inds[d]['rsi'][0]))
self.log('%s: EOD STOP, %.2f, VLM BOUGHT: %.2f' % (d._name, d.close[0], self.getposition(d).size))
self.order = self.sell(data=d, size=abs(self.getposition(d).size), coo=False, coc=True)
else:
if (port_gain >= csh*TP): #exit trade when portfolio gain >= take profit
print('%s pv: %.2f, cash:%.2f, rsi: %.2f'% (d._name, pv, cashd, self.inds[d]['rsi'][0]))
self.log('%s: TAKE PROFIT VAL CREATE (>%.2fpct), %.2f, VLM BOUGHT: %.2f' % (d._name, TPpct, d.close[0], self.getposition(d).size))
self.order = self.sell(data=d, size=abs(self.getposition(d).size), coo=False, coc=True)
else:
if (port_gain <= -csh*SL): #exit trade for stop loss
print('%s pv: %.2f, cash:%.2f, rsi: %.2f'% (d._name, pv, cashd, self.inds[d]['rsi'][0]))
self.log('%s: STOPLOSS VAL CREATE (<%.2fpct), %.2f, VLM BOUGHT: %.2f' % (d._name, SLpct, d.close[0], self.getposition(d).size))
self.order = self.sell(data=d, size=abs(self.getposition(d).size), coo=False, coc=True)
def run(args=None): #run the engine
cerebro = bt.Cerebro(cheat_on_open=True) #set COO to true to buy on same len
store = bt.stores.IBStore(host='127.0.0.1', port=7496, reconnect=5) #live port is 7496 and paper port is 7497
cerebro.broker = store.getbroker() #connect to broker #must use this to send trade to TWS
for i in datalist: #iterate the data in datalist
data = store.getdata(dataname=i, timeframe=bt.TimeFrame.Ticks, tz = pytz.timezone('US/Eastern'))
cerebro.resampledata(data, timeframe=bt.TimeFrame.Seconds, compression=15) #resample all data to live trade
cerebro.addstrategy(MainSt) #add strategy here
cerebro.run() #run everything
# Plot the result
# Print out the starting conditions
if __name__ == '__main__': #execute run here
run()
Thank you in advance!
But that will simply divide the total portfolio amount with number of data feeds right? I was thinking of something like
self.broker.get_value(data=d)
for each asset because my take profit is based on the increase in value of data d
(ie. self.getposition(d).size*d.close[0] >= self.broker.get_cash(d)*(1+take_profit)
). However, we cannot simply assign data=d
to the get_value
, so I am quite lost now/ Really appreciate your help please.
Thank you.
Hi all,
I have a multiple data feed strategy and I want to assign the starting cash and value for each and every data in the strategy to be independent in order for my Stop loss/Take Profit strategy to work (Something like
data_d_cash = portfolio_cash * (1/len(datalist)
).
I need each data to have its own independent proportion for the stop loss/ take profit to work for each d
in the strategy below.
from __future__ import (absolute_import, division, print_function, unicode_literals)
import backtrader as bt
import datetime as dt
import pytz
import math
cn1='XXX'
cn2='YYY'
datalist = [cn1,cn2]
csh=100000
stdt=dt.datetime(2019,07,2,9,30)
enddt=dt.datetime(2019,07,2,16,00)
SL=0.01
TP=2*SL
SU=0.005
SUpct=SU*100
prop=1/len(datalist) #proportion of portfolio
batch=10 #rounding
entrytime = dt.time(9,45)
exittime = dt.time(15,55)
stakesize=10
lwbnd=40
upbnd=90
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):
if order.status in [order.Submitted, order.Accepted]:
return
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 = order.executed.price
self.buycomm = order.executed.comm
self.last_executed_price = order.executed.price
self.order = None
self.bar_executed = 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 = None
self.bar_executed = len(self)
self.bar_executed = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('%s Order Canceled/Margin/Rejected' % (order.data._name))
# Write down: no pending order[order.data._name]
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):
for i, d in enumerate(self.datas):
pv = self.broker.get_value(None)
pvd = self.broker.get_value()*prop
cshd = self.broker.get_cash()*prop
self.stakes = abs(rounddown(pv/d.close[0]))
target = csh * prop # Ideal total value of the position
price = d.close[0]
shares_ideal = pvd / 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:
if (pv >= csh*(1+TP)):
return
if (pv <= csh*(1-SL)):
return
if (d.datetime.time(-1) >= exittime):
return
if ((self.inds[d]['rsi'][0] <= lwbnd)and(d.datetime.time(0) >= entrytime)):
print('%s pv: %.2f, cash:%.2f, rsi: %.2f'% (d._name, pvd, cshd, self.inds[d]['rsi'][0]))
self.log('%s: BUY CREATE, %.2f, VLM BOUGHT: %.2f' % (d._name, d.close[0], shares))
self.order = self.buy(data=d, size=shares)
else:
if (self.inds[d]['rsi'][0] >= upbnd):
print('%s pv: %.2f, cash:%.2f, rsi: %.2f'% (d._name, pvd, cshd, self.inds[d]['rsi'][0]))
self.log('%s: SELL CREATE (RSI>Upbnd), %.2f, VLM BOUGHT: %.2f' % (d._name, d.close[0], self.getposition(d).size))
self.order = self.sell(data=d, size = abs(self.getposition(d).size))
else:
if (d.close[0] >= ((self.getposition(d).price*(1+SU)))):
print('%s pv: %.2f, cash:%.2f, rsi: %.2f'% (d._name, pvd, cshd, self.inds[d]['rsi'][0]))
self.log('%s: SELL CREATE (>%.2fpct), %.2f, VLM BOUGHT: %.2f' % (d._name, SUpct, d.close[0], self.getposition(d).size))
self.order = self.sell(data=d, size = abs(self.getposition(d).size))
else:
if (d.close[0] <= ((self.getposition(d).price*(1-SU)))):
print('%s pv: %.2f, cash:%.2f, rsi: %.2f'% (d._name, pvd, cshd, self.inds[d]['rsi'][0]))
self.log('%s: SELL CREATE (<%.2fpct), %.2f, VLM BOUGHT: %.2f' % (d._name, SUpct, d.close[0], self.getposition(d).size))
self.order = self.sell(data=d, size = abs(self.getposition(d).size))
'''
else:
if (pvd >= target*(1+TP)):
print('%s pv: %.2f, cash:%.2f, rsi: %.2f'% (d._name, pvd, cshd, self.inds[d]['rsi'][0]))
self.log('%s: TAKE PROFIT VAL CREATE (>%.2fpct), %.2f, VLM BOUGHT: %.2f' % (d._name, TPpct, d.close[0], self.getposition(d).size))
self.order = self.sell(data=d, size=abs(self.getposition(d).size))
else:
if (pvd <= target*(1-SL)):
print('%s pv: %.2f, cash:%.2f, rsi: %.2f'% (d._name, pvd, cshd, self.inds[d]['rsi'][0]))
self.log('%s: STOPLOSS VAL CREATE (<%.2fpct), %.2f, VLM BOUGHT: %.2f' % (d._name, SLpct, d.close[0], self.getposition(d).size))
self.order = self.sell(data=d, size=abs(self.getposition(d).size))
'''
else:
if (d.datetime.time(0) >= exittime):
print('%s pv: %.2f, cash:%.2f, rsi: %.2f'% (d._name, pvd, cshd, self.inds[d]['rsi'][0]))
self.log('%s: EOD STOP, %.2f, VLM BOUGHT: %.2f' % (d._name, d.close[0], self.getposition(d).size))
self.order = self.sell(data=d, size=abs(self.getposition(d).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
is_first = True
#Loop through the list adding to cerebro.
for i in datalist:
data = ibstore.getdata(dataname=i,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)
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)
# 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 Portfolio Cash: %.2f' % cerebro.broker.getcash())
print('Final PnL Value: %.2f' % (cerebro.broker.getvalue()-start_value))
if __name__ == '__main__':
runstrat()
Appreciate your help. Thanks!
@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!
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.
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.