Multi Assets on IB Not Responding
-
Hello,
I'm having trouble executing one strategy on two live data feeds on Interactive Brokers. It seems like the connection is being made, but the strategy doesn't seem to be making any trades. When I reduce the data feeds to one data feed, the program works.
The first set of code is used to make the connection to interactive brokers. The second section of code is the strategy. Finally, the third is the output from python. I can see that the connection is being made, but again, no trades are being made. Any help would be highly appreciated.
Python code to add data feeds and connect to IB
import sys, argparse, os from datetime import datetime import backtrader as bt import backtrader.stores.ibstore as ibstore from strategy.long_copy import EmaCross_long def run(): cerebro = bt.Cerebro(stdstats=False) ibstore = bt.stores.IBStore(host='127.0.0.1', port=4002, clientId=0) stockkwargs = dict( timeframe=bt.TimeFrame.Seconds, rtbar=True, # use RealTime 5 seconds bars historical = False, qcheck=100, # timeout in seconds (float) to check for events latethrough=False) # tradename=None, # use a different asset as order target # fromdate = datetime.today()) contracts = ['NQ-202103-GLOBEX-USD','RTY-202103-GLOBEX-USD'] # c_name = ['NQ','RTY'] for d,c in zip(contracts,c_name): data = ibstore.getdata(dataname=d,**stockkwargs) cerebro.resampledata(data, name=c, timeframe=bt.TimeFrame.Seconds, compression=5) cerebro.broker = ibstore.getbroker() cerebro.addstrategy(EmaCross_long) cerebro.run() if __name__ == '__main__': run()
Python code for bt.strategy
import pandas as pd import backtrader as bt from datetime import datetime, timedelta import time import math import sqlalchemy as db class EmaCross_long(bt.Strategy): params = {('order_percentage', .95)} def log(self, txt, dt=None): ''' Logging function fot this strategy''' dt = dt or self.datas[0].datetime.datetime(0) print('%s, %s' % (dt.isoformat(), txt)) def __init__(self): self.orderid = list() self.inds = dict() for i,d in enumerate(self.datas): self.inds[d] = dict() self.inds[d]['dataopen'] = d.open self.inds[d]['dataclose'] = d.close self.inds[d]['datahigh'] = d.high self.inds[d]['datalow'] = d.low # Define average true range self.inds[d]['atr'] = bt.indicators.ATR(d, period =14) self.inds[d]['ema_s'] = bt.indicators.EMA(d,period=6,plotname='ema_s (6)') self.inds[d]['ema_l'] = bt.indicators.EMA(d, period=17, plotname='ema_l (17)') # Define self order self.order = None self.trail_stop = None self.cross_down = None self.sell_type = 'atr_cross_sell' def notify_store(self, msg, *args, **kwargs): print(f'message: {msg}') error = getattr(msg,'errorCode',False) if error: print(error) def notify_order(self, order): # https://www.backtrader.com/docu/order-creation-execution/order-creation-execution/ # Check the order status. print('{}: Order ref: {} / Type {} / Status {}'.format( self.data.datetime.datetime(0), order.ref, 'Buy' * order.isbuy() or 'Sell', order.getstatusname())) # If it is submitted/accepted, leave the fucntion if order.status in [order.Submitted, order.Accepted]: #self.log(f"Order submitted/accepted but not complete") return if order.status in [order.Completed]: if order.isbuy(): # Keep track of executred price and stop loss self.log(f"Buy Executed at {order.executed.price}") elif order.issell(): # Keep track of sell price self.log(f"Sell Executed at {order.executed.price} and order {order}") #print(f'position: \n {self.position} and order: \n {order}') self.order = None self.trail_stop = None self.cross_down = None if order.status in [order.Canceled, order.Margin, order.Rejected]: self.log(f"Order Rejected: {order.Rejected}") return if order.status == order.Canceled: self.log(f'Order Canceled {order.Canceled}') return def next(self): if self.order: # An order is pending ... nothing can be done return # if there is no current postion for i, d in enumerate(self.datas): if self.getposition(d).size <= 0: try: self.cancel(self.cross_down) except: pass if (self.inds[d].dataclose[0] > self.inds[d].ema_s[0] and self.inds[d].ema_s[0] > self.inds[d].ema_l[0] and (self.inds[d].ema_s[-1] < self.ema_l[-1] or self.inds[d].ema_s[-2] < self.inds[d].ema_l[-2])): # amount to invest #amount_to_invest = self.params.order_percentage * self.broker.cash #self.size = math.floor(amount_to_invest/self.dataclose) self.size = 1 self.log('BUY CREATE, %.2f' % self.dataclose[0]) self.order = self.buy(size = self.size, data = d) #elif self.getposition(d) > 0: # if (self.trail_stop is None and self.cross_down is None and self.sell_type in ['atr_sell','atr_cross_sell']): # # cancel any prior cross_down # try: # self.cancel(self.cross_down) # except: # pass # # sell on trailing stop # self.trail_stop = self.close(data = d, # exectype=bt.Order.StopTrail, # trailamount = 3 * self.atr[0], # size = self.getposition(d), # ref = '999') # #valid = datetime.now() + timedelta(hours = 12)) elif (self.getposition(d) > 0 and self.ema_s[0] < self.ema_l[0] and (self.inds[d].ema_s[-1] > self.inds[d].ema_l[-1] or self.inds[d].ema_s[-2] > self.inds[d].ema_l[-2])): # cancel trailing stop self.cancel(self.trail_stop) # sell on the cros down self.cross_down = self.close(data = d)
IB is connecting, but bt isn't performing any trades.
Server Version: 76 TWS Time at connection:20210113 11:47:50 EST message: <error id=-1, errorCode=2104, errorMsg=Market data farm connection is OK:usfuture.nj> 2104 message: <error id=-1, errorCode=2104, errorMsg=Market data farm connection is OK:usfarm.nj> 2104 message: <error id=-1, errorCode=2104, errorMsg=Market data farm connection is OK:usfuture> 2104 message: <error id=-1, errorCode=2104, errorMsg=Market data farm connection is OK:cashfarm> 2104 message: <error id=-1, errorCode=2104, errorMsg=Market data farm connection is OK:usfarm> 2104 message: <error id=-1, errorCode=2106, errorMsg=HMDS data farm connection is OK:euhmds> 2106 message: <error id=-1, errorCode=2106, errorMsg=HMDS data farm connection is OK:fundfarm> 2106 message: <error id=-1, errorCode=2106, errorMsg=HMDS data farm connection is OK:ushmds> 2106 message: <error id=-1, errorCode=2158, errorMsg=Sec-def data farm connection is OK:secdefil> 2158
-
I realized some of the code within the bt.strategy script wasn't correctly calling the datas feed within next(). I updated the script, but I'm still having the respective issues above. Below is the updated script for bt.strategy.
Python code for bt.strategy
import backtrader as bt from datetime import datetime, timedelta import time import math class EmaCross_long(bt.Strategy): params = {('order_percentage', .95)} def log(self, txt, dt=None): ''' Logging function fot this strategy''' dt = dt or self.datas[0].datetime.datetime(0) print('%s, %s' % (dt.isoformat(), txt)) def __init__(self): self.orderid = list() self.inds = dict() for i, d in enumerate(self.datas): self.inds[d] = dict() self.inds[d]['dataopen'] = d.open self.inds[d]['dataclose'] = d.close self.inds[d]['datahigh'] = d.high self.inds[d]['datalow'] = d.low # Define average true range self.inds[d]['atr'] = bt.indicators.ATR(d, period =14) self.inds[d]['ema_s'] = bt.indicators.EMA(d,period=6,plotname='ema_s (6)') self.inds[d]['ema_l'] = bt.indicators.EMA(d, period=17, plotname='ema_l (17)') # Define self order self.order = None self.trail_stop = None self.cross_down = None self.sell_type = 'atr_cross_sell' def notify_store(self, msg, *args, **kwargs): print(f'message: {msg}') error = getattr(msg,'errorCode',False) if error: print(error) def notify_order(self, order): # https://www.backtrader.com/docu/order-creation-execution/order-creation-execution/ # Check the order status. print('{}: Order ref: {} / Type {} / Status {}'.format( self.data.datetime.datetime(0), order.ref, 'Buy' * order.isbuy() or 'Sell', order.getstatusname())) # If it is submitted/accepted, leave the fucntion if order.status in [order.Submitted, order.Accepted]: #self.log(f"Order submitted/accepted but not complete") return if order.status in [order.Completed]: if order.isbuy(): # Keep track of executred price and stop loss self.log(f"Buy Executed at {order.executed.price}") elif order.issell(): # Keep track of sell price self.log(f"Sell Executed at {order.executed.price} and order {order}") #print(f'position: \n {self.position} and order: \n {order}') self.order = None self.trail_stop = None self.cross_down = None if order.status in [order.Canceled, order.Margin, order.Rejected]: self.log(f"Order Rejected: {order.Rejected}") return if order.status == order.Canceled: self.log(f'Order Canceled {order.Canceled}') return def next(self): if self.order: # An order is pending ... nothing can be done return # if there is no current postion for i, d in enumerate(self.datas): if self.getposition(d).size <= 0: if (self.inds[d].dataclose[0] > self.inds[d].ema_s[0] and self.inds[d].ema_s[0] > self.inds[d].ema_l[0] and (self.inds[d].ema_s[-1] < self.inds[d].ema_l[-1] or self.inds[d].ema_s[-2] < self.inds[d].ema_l[-2])): # amount to invest #amount_to_invest = self.params.order_percentage * self.broker.cash #self.size = math.floor(amount_to_invest/self.dataclose) self.size = 1 self.log('BUY CREATE, %.2f' % self.dataclose[0]) self.order = self.buy(size = self.size, data = d) #elif self.getposition(d) > 0: # if (self.trail_stop is None and self.cross_down is None and self.sell_type in ['atr_sell','atr_cross_sell']): # # cancel any prior cross_down # try: # self.cancel(self.cross_down) # except: # pass # # sell on trailing stop # self.trail_stop = self.close(data = d, # exectype=bt.Order.StopTrail, # trailamount = 3 * self.atr[0], # size = self.getposition(d), # ref = '999') # #valid = datetime.now() + timedelta(hours = 12)) elif (self.getposition(d) > 0 and self.inds[d].ema_s[0] < self.inds[d].ema_l[0] and (self.inds[d].ema_s[-1] > self.inds[d].ema_l[-1] or self.inds[d].ema_s[-2] > self.inds[d].ema_l[-2])): # sell on the cros down self.cross_down = self.close(data = d)
-
@Adham-Suliman said in Multi Assets on IB Not Responding:
cerebro.resampledata(data, name=c, timeframe=bt.TimeFrame.Seconds, compression=5)
Why do you need to resample the 5 sec bars to the 5 sec bars ? I'm also using the RT bars - and it works pretty reliably without resampling.
So instead of resampling I'd recommend to just specify the compression = 5 do the data arguments.
-
You're correct. After reading the documentation in more detail, there is no need to resample the data. I am now using cerebro.adddata(). I'm still getting the same output as before where a connection is made, but bt isn't performing any trades. I've cleaned up my main file, and have written it below.
Python code to add data feeds and connect to IB
# import modules import sys, argparse, os from datetime import datetime import backtrader as bt import backtrader.stores.ibstore as ibstore from strategy.long_copy import EmaCross_long def run(): cerebro = bt.Cerebro(stdstats=False) ibstore = bt.stores.IBStore(host='127.0.0.1', port=4002, clientId=0) # Adding two data feeds contracts = ['NQ-202103-GLOBEX-USD','RTY-202103-GLOBEX-USD'] # c_name = ['NQ','RTY'] for d,c in zip(contracts,c_name): data = ibstore.getdata(dataname=d, timeframe=bt.TimeFrame.Seconds, compression=5, rtbar=True, # use RealTime 5 seconds bars historical = False, qcheck=100, # timeout in seconds (float) to check for events latethrough=False) cerebro.adddata(data, name=c) cerebro.broker = ibstore.getbroker() cerebro.addstrategy(EmaCross_long) cerebro.run() if __name__ == '__main__': run()
Are there any places you would suggest setting breakpoints within the bt.strategy script that would help us figure out why bt isn't placing any trades?
Your contributions to the community are highly appreciated @vladisld. I've benefited immensely from your commentary on this forum, and I can't thank you enough!
-
If it could be one thing that seems strange to me (without running your code yet) it would be a quite large value used for
qcheck
.Setting this value too high may hurt in multi-data scenario: let's say one symbol (call it 'A') produces no new bars for some reason and the other one ('B') is very active. So the code using the
qcheck==100
will wait for more than a minute until new bar will be received for symbol 'A', while not reacting on multiple bars already available for symbol 'B'.I would suggest to lower this timeout to 0.5
-
For anyone looking to use this in the future, in the next method, I was trying to access a dictionary using
inds[d].field[0]
. The proper syntax to access a dictionary isinds[d]['field'][0]
. I also lowered my qcheck to .05 as @vladisld suggested. If someone has a link as to how to handle multiple orders with multiple data feeds, I would highly appreciate it if you posted the link below.Thanks