Multi asset bracket orders
-
Hello there,
I'm trying to backtest on multiple assets that have 'prediction' and 'signal' columns.
I'm having some troubles to keep track of orders as i try to use manual bracket orders (1 market buy, 1 limit take profit, 1 limit stop loss).class custom_data_loader(btfeeds.PandasData): lines = ("signal","prediction",) params = (("signal", -1),("prediction", -1)) datafields = btfeeds.PandasData.datafields + (["prediction", "signal",]) class custom_Sizer(bt.Sizer): def __init__(self): self.size_per_asset = { 'bnbusd': 0.05, 'vetusd': 0.05, 'iotusd': 0.05, 'xlmusd': 0.05, 'ontusd': 0.05, 'trxusd': 0.05, 'xrpusd': 0.05, 'adausd': 0.05, 'ltcusd': 0.05, 'eosusd': 0.05, 'ethusd': 0.10, 'btcusd': 0.20 } def _getsizing(self, comminfo, cash, data, isbuy): if isbuy == True: size = math.floor((cash * self.size_per_asset[data._name]) / data[0]) else: size = self.broker.getposition(d).size return size class custom_Strategy(bt.Strategy): def log(self, txt, dt=None, vlm=None): dt = dt or self.datas[0].datetime.datetime(0) print('%s, %s' % (dt.isoformat(), txt)) def __init__(self): self.inds = dict() self.o = dict() for i, d in enumerate(self.datas): self.o[d] = None self.inds[d] = dict() self.inds[d]['close'] = d.close self.inds[d]['prediction'] = d.prediction self.inds[d]['signal'] = d.signal 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): for i, d in enumerate(self.datas): if not self.getposition(d).size: #and not self.order[d]: if self.inds[d]['signal'][0]>0: print('%s : close: %.2f, prediction: %.2f, signal: %.2f'% (d._name, self.inds[d]['close'][0], self.inds[d]['prediction'][0], self.inds[d]['signal'][0])) self.log('%s: BUY MARKET CREATE, at %.2f' % (d._name, d.close[0])) o1 = self.buy(data=d, exectype=bt.Order.Market, transmit=False) print('%s : close: %.2f, prediction: %.2f, signal: %.2f'% (d._name, self.inds[d]['close'][0], self.inds[d]['prediction'][0], self.inds[d]['signal'][0])) self.log('%s: SELL STOP CREATE, at %.2f' % (d._name, d.close[0]*0.90)) o2 = self.buy(data=d, price=d.close[0]*0.90, exectype=bt.Order.Stop, transmit=False, parent=o1) print('%s : close: %.2f, prediction: %.2f, signal: %.2f'% (d._name, self.inds[d]['close'][0], self.inds[d]['prediction'][0], self.inds[d]['signal'][0])) self.log('%s: SELL LIMIT CREATE, at %.2f' % (d._name, d.prediction[0])) o3 = self.buy(data=d, price=d.prediction[0], exectype=bt.Order.Limit, transmit=True, parent=o1) self.o[d] = [o1, o2, o3] # Create cerebro entity cerebro = bt.Cerebro() # Add data #pairs = ['bnbusd','vetusd','iotusd','xlmusd','ontusd','trxusd','xrpusd','adausd','ltcusd','btcusd','eosusd','ethusd'] pairs = ['bnbusd','ethusd', 'ltcusd'] for pair in pairs: pathname = 'data/' + pair + '.csv' df = pd.read_csv(pathname, infer_datetime_format=True, parse_dates=['datetime'], dtype={ "open" : "float", "high" : "float", "low" : "float", "close" : "float", "volume": "float" , "prediction": "float" , "signal": "float" }, index_col=0) df.index = pd.to_datetime(df.datetime, format='%Y-%m-%dT%H:%M:%S.%fZ') df = df[['open', 'high', 'low', 'close', 'volume', 'prediction', 'signal']] bt_data = custom_data_loader(dataname=df) cerebro.adddata(bt_data, name=pair) # Add sizer cerebro.addsizer(custom_Sizer) # Add analyzer cerebro.addanalyzer(bt.analyzers.PyFolio) # Add a strategy cerebro.addstrategy(custom_Strategy3) # Add broker fees cerebro.broker.setcommission(commission=0.00075) # Set our desired cash start cerebro.broker.setcash(10000.0) # Print out the starting conditions print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue()) # Run over everything strat = cerebro.run() # Print out the final result print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
It seems i'm printing some stuff twice... but the main problem is that only the first market buy is trigger...
Starting Portfolio Value: 10000.00 bnbusd : close: 32.94, prediction: 33.10, signal: 1.00 2020-12-26T09:26:00, bnbusd: BUY MARKET CREATE, at 32.94 bnbusd : close: 32.94, prediction: 33.10, signal: 1.00 2020-12-26T09:26:00, bnbusd: SELL STOP CREATE, at 29.65 bnbusd : close: 32.94, prediction: 33.10, signal: 1.00 2020-12-26T09:26:00, bnbusd: SELL LIMIT CREATE, at 33.10 2020-12-26T09:27:00, BUY EXECUTED, Price: 32.94, Cost: 494.09, Comm 0.37 2020-12-26T09:28:00, BUY EXECUTED, Price: 32.92, Cost: 493.87, Comm 0.37 2020-12-26T09:28:00, Order Canceled/Margin/Rejected ethusd : close: 622.26, prediction: 624.22, signal: 1.00 2020-12-26T10:13:00, ethusd: BUY MARKET CREATE, at 622.26 ethusd : close: 622.26, prediction: 624.22, signal: 1.00 2020-12-26T10:13:00, ethusd: SELL STOP CREATE, at 560.03 ethusd : close: 622.26, prediction: 624.22, signal: 1.00 2020-12-26T10:13:00, ethusd: SELL LIMIT CREATE, at 624.22 2020-12-26T10:14:00, BUY EXECUTED, Price: 622.25, Cost: 622.25, Comm 0.47 2020-12-26T10:15:00, BUY EXECUTED, Price: 621.40, Cost: 621.40, Comm 0.47 2020-12-26T10:15:00, Order Canceled/Margin/Rejected ltcusd : close: 124.39, prediction: 124.98, signal: 1.00 2020-12-30T12:27:00, ltcusd: BUY MARKET CREATE, at 124.39 ltcusd : close: 124.39, prediction: 124.98, signal: 1.00 2020-12-30T12:27:00, ltcusd: SELL STOP CREATE, at 111.95 ltcusd : close: 124.39, prediction: 124.98, signal: 1.00 2020-12-30T12:27:00, ltcusd: SELL LIMIT CREATE, at 124.98 2020-12-30T12:28:00, BUY EXECUTED, Price: 124.38, Cost: 373.14, Comm 0.28 2020-12-30T12:29:00, BUY EXECUTED, Price: 124.23, Cost: 372.69, Comm 0.28 2020-12-30T12:29:00, Order Canceled/Margin/Rejected Final Portfolio Value: 11154.97
If someone could help me figure out what i'm doing wrong... I tried to adapt the script from the multi-exemple on the blog but with no success so far.
Thanks!!
-
Couple of things jump out. The first is that you are creating:
self.o[d] = [o1, o2, o3]
But you don't use it anywhere. When entering next and your loop of datas you would normally check to make sure this list of live orders doesn't exists for that specific data otherwise you run the risk of filling orders twice, if your position is not filled right away.
In this case you are using market orders so you are probably only filling once before your checking of positions stops new orders. However, you should check for any live orders in the above mentioned dictionary before allowing orders to be created. I can see you had some code commented out for this.
def next(self): for i, d in enumerate(self.datas): if not self.getposition(d).size and not self.order[d]:
I would put this condition back in and manage my list better after this.
Remove that list for the data once there are no live orders left. You can check this in the completed notify orders section:
if len([o for o in self.o[order.data] if o.status < 4]) == 0: self.o[order.data] = None
One other minor point, if you datas are loaded already with:
lines = ("signal","prediction",)
Then this code is not necessary.
self.inds[d] = dict() self.inds[d]['close'] = d.close self.inds[d]['prediction'] = d.prediction self.inds[d]['signal'] = d.signal
You can simply refer to your data lines directly:
def next(self): for d in self.datas: d.close[0] d.prediction[0] d.signal[0]
-
Thanks a lot for your answer!!
I made some changes following your advices but it seems I'm still missing something...
class custom_Strategy(bt.Strategy): def log(self, txt, dt=None): dt = dt or self.datas[0].datetime.datetime(0) print('%s, %s' % (dt.isoformat(), txt)) def __init__(self): self.inds = dict() self.o = dict() for i, d in enumerate(self.datas): self.o[d] = None 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( 'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' % (order.executed.price, order.executed.value, order.executed.comm)) else: # Sell self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' % (order.executed.price, order.executed.value, order.executed.comm)) elif order.status in [order.Canceled, order.Margin, order.Rejected]: self.log('Order Canceled/Margin/Rejected') if len([o for o in self.o[order.data] if o.status < 4]) == 0: self.o[order.data].clear() 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): if not self.getposition(d).size and not self.o[d]: if d.signal[0]>0: self.log('-----------------------------------------------') self.log('%s : close: %.2f, prediction: %.2f, signal: %.2f'% (d._name, d.close[0], d.prediction[0], d.signal[0])) self.log('%s: BUY MARKET CREATED, at %.2f' % (d._name, d.close[0])) o1 = self.buy(data=d, exectype=bt.Order.Market, transmit=False) self.log('%s: SELL STOP CREATED, at %.2f' % (d._name, d.close[0]*0.90)) o2 = self.buy(data=d, price=d.close[0]*0.90, exectype=bt.Order.Stop, transmit=False, parent=o1) self.log('%s: SELL LIMIT CREATED, at %.2f' % (d._name, d.prediction[0])) o3 = self.buy(data=d, price=d.prediction[0], exectype=bt.Order.Limit, transmit=True, parent=o1) self.o[d] = [o1, o2, o3] else: self.log(' %s ################## Orders already opened ##################' % d._name)
I used self.o[order.data].clear() instead of = None as I got 'NoneType is not iterable' error.
I'm not sure to understand the meaning of 'o.status < 4' because in the docs, status is Submitted, Accepted, etc... but not an int ?
I guess the main thing I don't understand is why orders aren't all submitted as I send them all at the same time...
Starting Portfolio Value: 10000.00 2020-12-26T09:26:00, ----------------------------------------------- 2020-12-26T09:26:00, bnbusd : close: 32.94, prediction: 33.10, signal: 1.00 2020-12-26T09:26:00, bnbusd: BUY MARKET CREATED, at 32.94 2020-12-26T09:26:00, bnbusd: SELL STOP CREATED, at 29.65 2020-12-26T09:26:00, bnbusd: SELL LIMIT CREATED, at 33.10 2020-12-26T09:27:00, BUY EXECUTED, Price: 32.94, Cost: 500.00, Comm 0.37 2020-12-26T09:27:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:28:00, BUY EXECUTED, Price: 32.92, Cost: 499.78, Comm 0.37 2020-12-26T09:28:00, Order Canceled/Margin/Rejected 2020-12-26T09:28:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:29:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:30:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:31:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:32:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:33:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:34:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:35:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:36:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:37:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:38:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:39:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:40:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:41:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:42:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:43:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:44:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:45:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:46:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:47:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:48:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:49:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:50:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:51:00, bnbusd ################## Orders already opened ################## 2020-12-26T09:59:00, bnbusd ################## Orders already opened ################## 2020-12-26T10:00:00, bnbusd ################## Orders already opened ################## 2020-12-26T10:01:00, bnbusd ################## Orders already opened ################## 2020-12-26T10:02:00, bnbusd ################## Orders already opened ################## 2020-12-26T10:03:00, bnbusd ################## Orders already opened ################## 2020-12-26T10:04:00, bnbusd ################## Orders already opened ################## 2020-12-26T10:05:00, bnbusd ################## Orders already opened ################## 2020-12-26T10:06:00, bnbusd ################## Orders already opened ################## 2020-12-26T10:07:00, bnbusd ################## Orders already opened ################## 2020-12-26T10:08:00, bnbusd ################## Orders already opened ################## 2020-12-26T10:09:00, bnbusd ################## Orders already opened ################## 2020-12-26T10:10:00, bnbusd ################## Orders already opened ################## 2020-12-26T10:11:00, bnbusd ################## Orders already opened ################## 2020-12-26T10:12:00, bnbusd ################## Orders already opened ################## 2020-12-26T10:13:00, bnbusd ################## Orders already opened ################## 2020-12-26T10:13:00, ----------------------------------------------- 2020-12-26T10:13:00, ethusd : close: 622.26, prediction: 624.22, signal: 1.00 2020-12-26T10:13:00, ethusd: BUY MARKET CREATED, at 622.26 2020-12-26T10:13:00, ethusd: SELL STOP CREATED, at 560.03 2020-12-26T10:13:00, ethusd: SELL LIMIT CREATED, at 624.22 2020-12-26T10:14:00, BUY EXECUTED, Price: 622.25, Cost: 899.93, Comm 0.67 2020-12-26T10:14:00, bnbusd ################## Orders already opened ################## 2020-12-26T10:14:00, ethusd ################## Orders already opened ################## 2020-12-26T10:15:00, BUY EXECUTED, Price: 621.40, Cost: 898.70, Comm 0.67 2020-12-26T10:15:00, Order Canceled/Margin/Rejected 2020-12-26T10:15:00, bnbusd ################## Orders already opened ################## 2020-12-26T10:15:00, ethusd ################## Orders already opened ################## 2020-12-26T10:16:00, bnbusd ################## Orders already opened ################## 2020-12-26T10:16:00, ethusd ################## Orders already opened ################## 2020-12-26T10:17:00, bnbusd ################## Orders already opened ################## 2020-12-26T10:17:00, ethusd ################## Orders already opened ################## 2020-12-26T10:18:00, bnbusd ################## Orders already opened ################## 2020-12-26T10:18:00, ethusd ################## Orders already opened ##################
and it continues like this til the end of backtest without doing anything...
Sorry for those noob questions... and thanks again for your help!
-
Hello,
I've been trying to make it works for days but can't figure out what i'm doing wrong...
If you can have a quick look @run-out I would really appreciate :-)Thanks
-
@toinou222 said in Multi asset bracket orders:
self.log('%s: SELL STOP CREATED, at %.2f' % (d._name, d.close[0]*0.90))
o2 = self.buy(data=d, price=d.close[0]*0.90, exectype=bt.Order.Stop, transmit=False, parent=o1)
self.log('%s: SELL LIMIT CREATED, at %.2f' % (d._name, d.prediction[0]))Your sell orders are actually buy orders.
-
HAHAHA so sorry I wasted your time on this... thanks a lot mate!!