Problem with multiple limit orders as take profit
-
Hello
I'm trying to set up two TP's with sell-limit orders and one SL with a sell-stop order.
The problem I'm encountering is that the first trades seem to be correct, but later bt creates multiple sell-limit orders without the buy order.
What am I doing wrong?import backtrader as bt from datetime import datetime class SmaCross(bt.Strategy): # list of parameters which are configurable for the strategy params = dict( pfast=100, # period for the fast moving average pslow=50 # period for the slow moving average ) def __init__(self): self.sma0 = bt.ind.SMA(period=800) sma1 = bt.ind.SMA(period=self.p.pfast) # fast moving average sma2 = bt.ind.SMA(period=self.p.pslow) # slow moving average self.crossover = bt.ind.CrossOver(sma1, sma2) # crossover signal def log(self, txt, dt=None): #Logging function fot this strategy''' dt = dt or self.datas[0].datetime.date(0) print('%s, %s' % (dt.isoformat(), txt)) def notify_order(self, order): if order.status in [order.Submitted, order.Accepted]: # Buy/Sell order submitted/accepted to/by broker - Nothing to do return if order.status in [order.Completed]: if order.isbuy(): self.sell(exectype=bt.Order.Limit, size=1, price=self.data.close[0] * 1.03) self.sell(exectype=bt.Order.Limit, size=1, price=self.data.close[0] * 1.06) self.sell(exectype=bt.Order.Stop, size=2, price=self.data.close[0] * 0.98) self.log('BUY EXECUTED, %.2f' % order.executed.price) elif order.issell(): self.log('SELL EXECUTED, %.2f' % order.executed.price) def next(self): if not self.position: # not in the market if self.crossover > 0 and self.datas[0] < self.sma0: # if fast crosses slow to the upside self.buy_order = self.buy(exectype=bt.Order.Market, size=2) # enter long startcash = 10000 cerebro = bt.Cerebro() # create a "Cerebro" engine instance # Create a data feed data = bt.feeds.GenericCSVData( dataname='data/1inch_perp_28-02_09-03_10S.csv', fromdate=datetime(2021, 3, 7), todate=datetime(2021, 3, 21), timeframe=bt.TimeFrame.Seconds, headers=False, separator=";", volume=5, openinterest=-1, dtformat=('%d.%m.%Y %H:%M:%S'), ) cerebro.adddata(data) # Add the data feed cerebro.addstrategy(SmaCross) # Add the trading strategy cerebro.run() # run it all portvalue = cerebro.broker.getvalue() pnl = portvalue - startcash print('Final Portfolio Value: ${}'.format(portvalue)) print('P/L: ${}'.format(pnl)) cerebro.plot() # and plot it with a single command
2021-03-07, BUY EXECUTED, 38.52 2021-03-07, SELL EXECUTED, 39.67 2021-03-07, SELL EXECUTED, 40.82 2021-03-07, BUY EXECUTED, 40.28 2021-03-07, SELL EXECUTED, 39.52 2021-03-07, BUY EXECUTED, 39.05 2021-03-07, SELL EXECUTED, 38.29 2021-03-07, BUY EXECUTED, 37.97 2021-03-08, SELL EXECUTED, 39.13 2021-03-08, SELL EXECUTED, 40.24 2021-03-08, SELL EXECUTED, 40.27 2021-03-08, SELL EXECUTED, 41.41 2021-03-08, SELL EXECUTED, 41.53 2021-03-08, SELL EXECUTED, 42.74
-
@stanski
You need to split up your stop orders and attach them to the limit orders usingone cancels other
orOCO
. You can find more information here.I modified your code below.
import backtrader as bt import datetime class SmaCross(bt.Strategy): # list of parameters which are configurable for the strategy params = dict( pfast=100, # period for the fast moving average pslow=50, # period for the slow moving average ) def __init__(self): self.sma0 = bt.ind.SMA(period=800) sma1 = bt.ind.SMA(period=self.p.pfast) # fast moving average sma2 = bt.ind.SMA(period=self.p.pslow) # slow moving average self.crossover = bt.ind.CrossOver(sma1, sma2) # crossover signal 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 notify_order(self, order): type = "Buy" if order.isbuy() else "Sell" self.log( f"{order.data._name:<6} Order: {order.ref:3d}\tType: {type:<5}\tStatus" f" {order.getstatusname():<8} \t" f"Size: {order.created.size:9.4f} Create Price: {order.created.price:9.4f} " f"Position: {self.getposition(order.data).size}" ) if order.status in [order.Submitted, order.Accepted]: return if order.status in [order.Completed]: self.log( f"{order.data._name:<6} {('BUY' if order.isbuy() else 'SELL'):<5} " f"Price: {order.executed.price:6.2f} " f"Cost: {order.executed.value:6.2f} " f"Comm: {order.executed.comm:4.2f} " f"Size: {order.created.size:9.4f} " ) if order.isbuy(): lmt_1 = self.sell( exectype=bt.Order.Limit, size=1, price=self.data.close[0] * 1.03, ) self.sell( exectype=bt.Order.Stop, size=1, price=self.data.close[0] * 0.98, oco=lmt_1, ) lmt_2 = self.sell( exectype=bt.Order.Limit, size=1, price=self.data.close[0] * 1.06, ) self.sell( exectype=bt.Order.Stop, size=1, price=self.data.close[0] * 0.98, oco=lmt_2, ) def next(self): if not self.position: # not in the market if ( self.crossover > 0 and self.datas[0] < self.sma0 ): # if fast crosses slow to the upside self.buy_order = self.buy( exectype=bt.Order.Market, size=2 ) # enter long ...
It's always a good idea to have more logging when you are unsure of what's going on. Here's some logging for two sets of orders:
2020-01-06T12:21:00, dev Order: 1 Type: Buy Status Submitted Size: 2.0000 Create Price: 3220.7500 Position: 2 2020-01-06T12:21:00, dev Order: 1 Type: Buy Status Accepted Size: 2.0000 Create Price: 3220.7500 Position: 2 2020-01-06T12:21:00, dev Order: 1 Type: Buy Status Completed Size: 2.0000 Create Price: 3220.7500 Position: 2 2020-01-06T12:21:00, dev BUY Price: 3221.00 Cost: 6442.00 Comm: 0.00 Size: 2.0000 2020-01-06T12:22:00, dev Order: 2 Type: Sell Status Submitted Size: -1.0000 Create Price: 3317.3725 Position: 2 2020-01-06T12:22:00, dev Order: 3 Type: Sell Status Submitted Size: -1.0000 Create Price: 3156.3350 Position: 2 2020-01-06T12:22:00, dev Order: 4 Type: Sell Status Submitted Size: -1.0000 Create Price: 3413.9950 Position: 2 2020-01-06T12:22:00, dev Order: 5 Type: Sell Status Submitted Size: -1.0000 Create Price: 3156.3350 Position: 2 2020-01-06T12:22:00, dev Order: 2 Type: Sell Status Accepted Size: -1.0000 Create Price: 3317.3725 Position: 2 2020-01-06T12:22:00, dev Order: 3 Type: Sell Status Accepted Size: -1.0000 Create Price: 3156.3350 Position: 2 2020-01-06T12:22:00, dev Order: 4 Type: Sell Status Accepted Size: -1.0000 Create Price: 3413.9950 Position: 2 2020-01-06T12:22:00, dev Order: 5 Type: Sell Status Accepted Size: -1.0000 Create Price: 3156.3350 Position: 2 2020-01-17T15:55:00, dev Order: 2 Type: Sell Status Completed Size: -1.0000 Create Price: 3317.3725 Position: 1 2020-01-17T15:55:00, dev SELL Price: 3317.37 Cost: 3221.00 Comm: 0.00 Size: -1.0000 2020-01-17T15:55:00, dev Order: 3 Type: Sell Status Canceled Size: -1.0000 Create Price: 3156.3350 Position: 1 2020-02-25T13:02:00, dev Order: 5 Type: Sell Status Completed Size: -1.0000 Create Price: 3156.3350 Position: 0 2020-02-25T13:02:00, dev SELL Price: 3156.34 Cost: 3221.00 Comm: 0.00 Size: -1.0000 2020-02-25T13:02:00, dev Order: 4 Type: Sell Status Canceled Size: -1.0000 Create Price: 3413.9950 Position: 0 2020-02-25T15:52:00, dev Order: 6 Type: Buy Status Submitted Size: 2.0000 Create Price: 3113.0000 Position: 2 2020-02-25T15:52:00, dev Order: 6 Type: Buy Status Accepted Size: 2.0000 Create Price: 3113.0000 Position: 2 2020-02-25T15:52:00, dev Order: 6 Type: Buy Status Completed Size: 2.0000 Create Price: 3113.0000 Position: 2 2020-02-25T15:52:00, dev BUY Price: 3113.00 Cost: 6226.00 Comm: 0.00 Size: 2.0000 2020-02-25T15:53:00, dev Order: 7 Type: Sell Status Submitted Size: -1.0000 Create Price: 3207.6775 Position: 2 2020-02-25T15:53:00, dev Order: 8 Type: Sell Status Submitted Size: -1.0000 Create Price: 3051.9650 Position: 2 2020-02-25T15:53:00, dev Order: 9 Type: Sell Status Submitted Size: -1.0000 Create Price: 3301.1050 Position: 2 2020-02-25T15:53:00, dev Order: 10 Type: Sell Status Submitted Size: -1.0000 Create Price: 3051.9650 Position: 2 2020-02-25T15:53:00, dev Order: 7 Type: Sell Status Accepted Size: -1.0000 Create Price: 3207.6775 Position: 2 2020-02-25T15:53:00, dev Order: 8 Type: Sell Status Accepted Size: -1.0000 Create Price: 3051.9650 Position: 2 2020-02-25T15:53:00, dev Order: 9 Type: Sell Status Accepted Size: -1.0000 Create Price: 3301.1050 Position: 2 2020-02-25T15:53:00, dev Order: 10 Type: Sell Status Accepted Size: -1.0000 Create Price: 3051.9650 Position: 2 2020-02-27T09:31:00, dev Order: 8 Type: Sell Status Completed Size: -1.0000 Create Price: 3051.9650 Position: 0 2020-02-27T09:31:00, dev SELL Price: 3043.00 Cost: 3113.00 Comm: 0.00 Size: -1.0000 2020-02-27T09:31:00, dev Order: 7 Type: Sell Status Canceled Size: -1.0000 Create Price: 3207.6775 Position: 0 2020-02-27T09:31:00, dev Order: 10 Type: Sell Status Completed Size: -1.0000 Create Price: 3051.9650 Position: 0 2020-02-27T09:31:00, dev SELL Price: 3043.00 Cost: 3113.00 Comm: 0.00 Size: -1.0000 2020-02-27T09:31:00, dev Order: 9 Type: Sell Status Canceled Size: -1.0000 Create Price: 3301.1050 Position: 0
-
@run-out Your solution works very nice, but this won't work live, will it? At least it reads in the doc's that there's no live support with oco's, although I see reports that binance api can deal with oco orders. I'm intenting to trade live on a crypto exchange. Do you have experience with oco / live ?
-
@stanski Not really but I suspect you would need to manually track your orders and one is complete, send a cancel for the other.