How to add takeprofit / stoploss?
-
Hello,
In my broker when sending an order, I can set stop-loss and take-profit levels on it. Such that e.g. I can send a limit buy order, with stoploss and takeprofit levels and forget about it. It will either remain open, or close with profit/loss. Simulating this with backtrader appears to result in errors.
In order notification, if buy order is executed I execute two sell orders: one limit order with stop loss, and one stop order with take profit. This eventually fails and results in stategy executing both of the sell orders. Ideally if one of these sell orders is executed, i would like the other one to be instantly killed.
How should I code takeprofit/stoploss on my positions?
Should I extend the bbroker itself to support stoploss/takeprofit?
Regards,
Dimitri.
-
"Simulating this with backtrader appears to result in errors."
backtrader is only doing what you are telling it to do. That isn't an error.
In order notification, if buy order is executed I execute two sell orders: one limit order with stop loss, and one stop order with take profit. This eventually fails and results in strategy executing both of the sell orders. Ideally if one of these sell orders is executed, i would like the other one to be instantly killed.
It doesn't fail. It simply doesn't fulfill your own expectations (it is nowhere stated in the documentation that one order will be cancelled because the other is executed)
You are looking for
OCO
(Order Cancels Order) functionality and this is not implemented in backtraderHow should I code takeprofit/stoploss on my positions?
In the absence of
OCO
, a dictionary can do the trick, by adding two entries. In one the stop-loss order is the key and the take-profit order is the value. In the second entry the roles are reversed.Upon being notified of the execution of one order, inspection of the dictionary allows cancelling the pending order
Should I extend the bbroker itself to support stoploss/takeprofit?
The platform is open.
-
Hi Guys,
I used dictionary with orders to implement stoploss/takeprofit in a strategy following the advice above and it works but I am facing an issue in a specific scenario.
If both limit and stop orders execution prices are between high and low on the next bar, they both get executed and I am unable to cancel the other one.
# excerpt from notify_order ... elif order.status == order.Completed: if 'name' in order.info: self.broker.cancel(self.order_dict[order.ref]) else: if order.isbuy(): stop_loss = order.executed.price*(1.0 - (self.p.stoploss)/100.0) take_profit = order.executed.price*(1.0 + 3*(self.p.stoploss)/100.0) sl_ord = self.sell(exectype=bt.Order.Stop, price=stop_loss) sl_ord.addinfo(name="Stop") tkp_ord = self.sell(exectype=bt.Order.Limit, price=take_profit) tkp_ord.addinfo(name="Prof") self.order_dict[sl_ord.ref] = tkp_ord self.order_dict[tkp_ord.ref] = sl_ord ...
Here is an execution log.
Starting Portfolio Value: 10000.00 SignalPrice: 1155.12 Buy: 1155.17, Stop: 1154.01, Prof: 1158.64 Open: 1 2002-01-10 1155.17 1.00 0.00 TRADE,OPEN,2002-01-10,1155.17,0.0,0.0 Stop: 2 2002-01-10 1154.01 -1.00 0.00 TRADE PROFIT, GROSS -1.16, NET -1.16 CANCELL,Prof,3,2002-01-10,0.0,0,0.0 SignalPrice: 1132.83 Buy: 1132.94, Stop: 1131.81, Prof: 1136.34 Open: 4 2002-01-17 1132.94 1.00 0.00 TRADE,OPEN,2002-01-17,1132.94,0.0,0.0 Stop: 5 2002-01-17 1131.81 -1.00 0.00 TRADE PROFIT, GROSS -1.13, NET -1.13 CANCELL,Prof,6,2002-01-17,0.0,0,0.0 SignalPrice: 1119.31 Buy: 1120.13, Stop: 1119.01, Prof: 1123.49 Open: 7 2002-01-23 1120.13 1.00 0.00 TRADE,OPEN,2002-01-23,1120.13,0.0,0.0 Stop: 8 2002-01-23 1119.01 -1.00 0.00 Prof: 9 2002-01-23 1123.49 -1.00 0.00 TRADE PROFIT, GROSS -1.12, NET -1.12 TRADE,OPEN,2002-01-23,-1123.49039,0.0,0.0
And the corresponding data:
On first bar is getting the signal, on second bar is buy with market order and on third bar is executing both stop and limit orders.2002-01-22 16:00:00,1119.34,1119.42,1119.3,1119.31,0,1119.31 2002-01-23 09:00:00,1120.13,1123.39,1120.11,1120.55,0,1120.55 2002-01-23 10:00:00,1120.62,1124.16,1117.43,1124.16,0,1124.16
BR,
dpetrov -
That case needs real
OCO
emulation (as real as you can get in an emulation), because both orders meet the condition during the same bar evaluation in the broker. -
To correctly backtest such orders you need to use lower data time frame.
-
In my playground
profit_percents
calculates inSuper
of this strategy. Then strategy check:if 3 < profit_percents or profit_percents < -1: self.close() # sell/buy according to open order sign
Nice idea about dictionary in orders.
-
Good idea, but I think this apporach will have 1 bar delay.
According to the docs:
- Stop and Limit orders will execute as soon as the price touches the order price
- Close will execute using the close price of the next barwhen the next bar actually CLOSES
I am going to test it today and compare.
Otherwise I can use data with higher frequency as mentioned by @ab_trader.
Is this going to change the strategy decisions if it is based on technical indicators?BR,
dpetrov -
If you use higher frequency to simulate execution of stop loss and take profit orders, and use your regular timeframe for indicators, than your base line data will be higher frequency data and you will use data resampling to get new data set for indicator calculations.
Data resampling - https://www.backtrader.com/docu/data-resampling/data-resampling.html
Also I was thinking about another approach without going to lower timeframes - check execution of the stop loss first, and then check execution of the take profit. This will be conservative, and I believe some additional coding be required, it will not be simple order sending.
-
@dimitar-petrov said in How to add takeprofit / stoploss?:
excerpt from notify_order
...
elif order.status == order.Completed:
if 'name' in order.info:
self.broker.cancel(self.order_dict[order.ref])
else:
if order.isbuy():
stop_loss = order.executed.price*(1.0 - (self.p.stoploss)/100.0)
take_profit = order.executed.price*(1.0 + 3*(self.p.stoploss)/100.0)sl_ord = self.sell(exectype=bt.Order.Stop, price=stop_loss) sl_ord.addinfo(name="Stop") tkp_ord = self.sell(exectype=bt.Order.Limit, price=take_profit) tkp_ord.addinfo(name="Prof") self.order_dict[sl_ord.ref] = tkp_ord self.order_dict[tkp_ord.ref] = sl_ord
...
Can you please share the complete code
-
@Usct here you go,
from __future__ import (absolute_import, division, print_function, unicode_literals) import backtrader as bt import backtrader.indicators as btind class BBands(bt.Strategy): params = ( ('stoploss', 0.001), ('profit_mult', 2), ('prdata', False), ('prtrade', False), ('prorder', False), ) def __init__(self): # import ipdb; ipdb.set_trace() self.bbands = bt.talib.BBANDS(self.data, timeperiod=25, plotname='TA_BBANDS') self.order_dict = {} self.buy_sig = btind.CrossOver(self.data, self.bbands.line2) self.sell_sig = btind.CrossOver(self.bbands.line0, self.data) self.trades = 0 self.order = None def start(self): self.trades = 0 def next(self): if self.position: return else: if self.buy_sig > 0: # import ipdb; ipdb.set_trace() self.buy(exectype=bt.Order.Market) self.last_sig_price = self.data.close[0] self.trades += 1 elif self.sell_sig > 0: self.sell(exextype=bt.Order.Market) self.trades += 1 if self.p.prdata: print(','.join(str(x) for x in ['DATA', 'OPEN', self.data.datetime.date().isoformat(), self.data.close[0], self.buy_sig[0]])) def notify_order(self, order): # import ipdb; ipdb.set_trace() if order.status in [order.Margin, order.Rejected]: pass elif order.status == order.Cancelled: if self.p.prorder: print(','.join(map(str, [ 'CANCELL', order.info['OCO'], order.ref, self.data.num2date(order.executed.dt).date().isoformat(), order.executed.price, order.executed.size, order.executed.comm, ] ))) elif order.status == order.Completed: if 'name' in order.info: self.broker.cancel(self.order_dict[order.ref]) self.order = None if self.p.prorder: print("%s: %s %s %.2f %.2f %.2f" % (order.info['name'], order.ref, self.data.num2date(order.executed.dt).date().isoformat(), order.executed.price, order.executed.size, order.executed.comm)) else: if order.isbuy(): stop_loss = order.executed.price*(1.0 - (self.p.stoploss)) take_profit = order.executed.price*(1.0 + self.p.profit_mult*(self.p.stoploss)) sl_ord = self.sell(exectype=bt.Order.Stop, price=stop_loss) sl_ord.addinfo(name="Stop") tkp_ord = self.sell(exectype=bt.Order.Limit, price=take_profit) tkp_ord.addinfo(name="Prof") self.order_dict[sl_ord.ref] = tkp_ord self.order_dict[tkp_ord.ref] = sl_ord if self.p.prorder: print("SignalPrice: %.2f Buy: %.2f, Stop: %.2f, Prof: %.2f" % (self.last_sig_price, order.executed.price, stop_loss, take_profit)) elif order.issell(): stop_loss = order.executed.price*(1.0 + (self.p.stoploss)) take_profit = order.executed.price*(1.0 - 3*(self.p.stoploss)) sl_ord = self.buy(exectype=bt.Order.Stop, price=stop_loss) sl_ord.addinfo(name="Stop") tkp_ord = self.buy(exectype=bt.Order.Limit, price=take_profit) tkp_ord.addinfo(name="Prof") self.order_dict[sl_ord.ref] = tkp_ord self.order_dict[tkp_ord.ref] = sl_ord if self.p.prorder: print("Open: %s %s %.2f %.2f %.2f" % (order.ref, self.data.num2date(order.executed.dt).date().isoformat(), order.executed.price, order.executed.size, order.executed.comm)) def notify_trade(self, trade): # import ipdb; ipdb.set_trace() if self.p.prtrade: if trade.isclosed: print('TRADE PROFIT, GROSS %.2f, NET %.2f' % (trade.pnl, trade.pnlcomm)) elif trade.justopened: print(','.join(map(str, [ 'TRADE', 'OPEN', self.data.num2date(trade.dtopen).date().isoformat(), trade.value, trade.pnl, trade.commission, ] ))) def stop(self): print('(Stop Loss Pct: %2f, S/P Multiplier: %2f) Ending Value %.2f (Num Trades: %d)' % (self.params.stoploss, self.params.profit_mult, self.broker.getvalue(), self.trades))
-
Thanks a lot!
-
CAn this be achieved with bracket orders?
-
That's the point of bracket orders which exactly implement this concept. But beware: only Interactive Brokers has native support for it.
-
@backtrader So in the case of Oanda, i would have to implement the bracket orders manually like so?
(From your code:)
mainside = self.buy(price=13.50, exectype=bt.Order.Limit, transmit=False) lowside = self.sell(price=13.00, size=mainsize.size, exectype=bt.Order.Stop, transmit=False, parent=mainside) highside = self.sell(price=14.00, size=mainsize.size, exectype=bt.Order.Limit, transmit=True, parent=mainside)
-
No. It means (unless it is somewhere well hidden in the docs) that model is not supported with Oanda.
There may be something similar by directly specifying the prices, but it will have to be looked into.
-
You may play with this commit:
It uses the built-in
stoploss
andtakeprofit
price attributes of an order inOanda
to make a completeBracket
order simulation.-
You can use
buy_bracket
/sell_bracket
or the manual pattern from above (the former two would be recommended)The execution type for the
stop
-side andprofit
-side cannot be set. They are controlled by Oanda. Only the prices can be set.You can still set the execution type for the
main
-side (Market
,Limit
, ...)
Even if Oanda manages everything as a single order, inside backtrader will be 3 orders and cancellation of any order will cancel the others. There will be individual notifications for each of the orders.
The commit adds also
StopTrail
support forOanda
-
-
@backtrader Oh man, you're awsome. Thanks for adding that code so quickly.
-
@taewoo-kim could you please elaborate how does the transmit parameter work here? why its set to false to stop order but true to take profit?
What if both orders fill at the same candle? (Huge high-low) range