Help with Simple BBand Strategy
-
Hi,
I am trying to work on a simple Bband strategy, where when close up cross the bband bottom line, I buy; when close down cross the bband top line, I sell.
Here is my code:
class BBand_CrossOver(bt.Strategy): params = (('period', 20),) def __init__(self): self.bband = bt.indicators.BollingerBands(self.data0, period=self.params.period) self.buysig = bt.indicators.CrossOver(self.data0, self.bband.lines.bot) self.sellsig = bt.indicators.CrossOver(self.data0, self.bband.lines.top) def next(self): if self.position.size: if self.sellsig < 0: self.sell() elif self.buysig > 0: self.buy()
I tried symbol 'BABA' on it for 2016 data, code:
cerebro = bt.Cerebro() data = bt.feeds.YahooFinanceData(dataname='BABA', fromdate=datetime.datetime(2016, 1, 1), todate=bt.datetime.datetime(2016, 12, 31), timeframe=bt.TimeFrame.Days, compression=1) cerebro.adddata(data) cerebro.addstrategy(BBand_CrossOver, period=20) cerebro.broker.setcash(10000.0) cerebro.addsizer(bt.sizers.AllInSizer) cerebro.addwriter(bt.WriterFile, csv=True, out='test.csv') cerebro.run(optreturn=False) cerebro.plot()
Interestingly, it did execute some orders, but it did not trigger on all of them.
link: https://www.dropbox.com/s/ysrsbwzde4f00eh/BABA.png?dl=0
You can see it triggered the buy order around Feb 5th, and Sell order around March 20th, which are expected.
However, around June 28th, there should be a up-cross. but the buy order is not triggered.
Is there a good way to debug this?
Can I output the self.bband.lines and the signals in the writer as well?Thanks very much!
-
@xisisu said in Help with Simple BBand Strategy:
cerebro.broker.setcash(10000.0) cerebro.addsizer(bt.sizers.AllInSizer)
Your orders are getting for sure rejected due to the
AllIn
strategy. There is plenty of literature in the community itself.- Your strategy sees the signal and issues the order. The only available price for size calculation is the current
close
(to avoid the lookahead bias which was a question yesterday) - The next
open
price is greater (opening gap) than the close - Because the order is sent as a
size
(like in a normal broker), the currentsize * open
>size * close
and there is not enough money in your account to execute the operation
See (quick googling, I know there are more because I read them)
- https://community.backtrader.com/topic/644/order-submission-execution/
- https://community.backtrader.com/topic/370/unexpected-additional-orders-created-rejected/
Your alternatives:
-
cheat-on-open
, but you cannot use the sizer, because the sizer is not designed to cheat. You have to makesize
calculation yourself -
https://www.backtrader.com/blog/posts/2017-05-01-cheat-on-open/cheat-on-open.html
I would personally avoid cheating but ...
- Your strategy sees the signal and issues the order. The only available price for size calculation is the current
-
Thank you so much for the detailed reply!
I have modified my code with the cheat_on_open option, but it seems using the current open price in next_open(), not the next day open price.
class BBand_CrossOver(bt.Strategy): params = (('period', 20),) def log(self, txt, dt=None): dt = dt or self.datas[0].datetime.date(0) print('{}, {}'.format(dt.isoformat(), txt)) def __init__(self): self.dataclose = self.data0.close self.dataopen = self.data0.open self.bband = bt.indicators.BollingerBands(self.data0, period=self.params.period) self.buysig = bt.indicators.CrossOver(self.data0, self.bband.lines.bot) self.sellsig = bt.indicators.CrossOver(self.data0, self.bband.lines.top) def next_open(self): self.log('Close: %.2f, Open: %.2f' % (self.dataclose[0], self.dataopen[0])) if self.position.size: if self.sellsig < 0: self.log('SELL CREATE, size {}'.format(self.position.size)) self.sell(size=self.position.size) elif self.buysig > 0: size = int(self.broker.getcash() / self.data0.open) self.log('BUY CREATE, cash {}, size {}, open {}, close {}'.format(self.broker.getcash(), size, self.dataopen[0], self.dataclose[0])) self.buy(size=size) 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 # Check if an order has been completed # Attention: broker could reject order if not enougth cash if order.status in [order.Completed]: if order.isbuy(): self.log('BUY EXECUTED, price {}, cost {}, comm {}'.format(order.executed.price, order.executed.value, order.executed.comm)) elif order.issell(): self.log('SELL EXECUTED, price {}, cost {}, comm {}'.format(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') # Write down: no pending order self.order = None def notify_trade(self, trade): if not trade.isclosed: return self.log('OPERATION PROFIT, GROSS {}, NET {}'.format(trade.pnl, trade.pnlcomm))
And here is the main program:
cerebro = bt.Cerebro(cheat_on_open=True) data = bt.feeds.YahooFinanceData(dataname='BABA', fromdate=datetime.datetime(2016, 1, 1), todate=bt.datetime.datetime(2016, 12, 31), timeframe=bt.TimeFrame.Days, compression=1) cerebro.adddata(data) cerebro.addstrategy(BBand_CrossOver, period=20) cerebro.broker.setcash(10000.0) # cerebro.addsizer(bt.sizers.AllInSizer) # cerebro.broker.setcommission(commission=0.001) cerebro.addwriter(bt.WriterFile, csv=True, out='test.csv') print('start: {}'.format(cerebro.broker.getvalue())) cerebro.run(optreturn=False) print('finish: {}'.format(cerebro.broker.getvalue())) cerebro.plot()
To be more specific, in the output, I have:
2016-06-29, Close: 78.04, Open: 76.98 2016-06-29, BUY CREATE, cash 12071.300000000001, size 156, open 76.98, close 78.04 2016-06-29, Order Canceled/Margin/Rejected 2016-06-30, Close: 79.53, Open: 78.31
- if we use close price on 0629, size is 12071.3 / 78.04 = 154
- if we use open price on 0629, size is 12071.3 / 76.98 = 156
- if we use open price on 0630, size is 12071.3 / 78.31 = 154
Seems here it is using the open price on current day, not next day.
Any suggestions?
-
Another general question:
If we know that the AllInSizer have this price gap problem, and we cannot use it with the cheat-on-open option, what would be the correct use case for the AllInSizer?
Thanks.
-
Digging around, I saw there is also the option of Order.Partial, link: https://www.backtrader.com/docu/order.html
However, seems it is not triggered automatically.
is there a way to
- still use the AllInSizer
- if price gap happens, partially execute the order, and cancel the rest that cannot be filled?
Thanks.
-
@xisisu said in Help with Simple BBand Strategy:
However, seems it is not triggered automatically.
is there a way to- still use the AllInSizer
- if price gap happens, partially execute the order, and cancel the rest that cannot be filled?
I believe you misunderstand
Order.Partial
. It's not an option to execute partially. It's telling you that your order executed partially (probably because the volume at your price was not enough to fill your entire request)But your order CANNOT be partially executed because it's not even accepted.
@xisisu said in Help with Simple BBand Strategy:
- if we use close price on 0629, size is 12071.3 / 78.04 = 154
- if we use open price on 0629, size is 12071.3 / 76.98 = 156
- if we use open price on 0630, size is 12071.3 / 78.31 = 154
Let me formulate a theory:
- The close price on 2016-06-29 is larger than the
open
. - Your size has been calculated against the opening price
- And here comes the theory ... the broker is using the
close
price to check the validity. - And your order is rejected because it doesn't pass the submission criteria
(The theory would have to be validated by looking at the sources)
Deactivate order submission checking: https://www.backtrader.com/docu/broker.html
checksubmit
(there is a method to do it, find it in the doc)
-
Thanks for the detailed reply!
cerebro.broker.set_checksubmit(checksubmit=False)
Fixed the problem. Thank you so much! :)