¿ªÔÆÌåÓý

ctrl + shift + ? for shortcuts
© 2025 ¿ªÔÆÌåÓý

Trailing Stop/Stop-Loss Combo


 

Hey guys,
?
I have created a Python program that connects to the TWS API and places bracket trades when certain market conditions are met. The program is working as intended with one exception. I would like to be able to place a combined Stop Loss and Trailing Stop Loss instead of just a Stop Loss to take advantage of this combo in an attempt to reduce losses.
?
An article on Investopedia refers to the strategy as being available on Active Trades and provides the following example which illustrates exactly what I am seeking...
?
"For example, you could set a stop-loss at 2% below the current stock price and a trailing stop at 2.5% below the current stock price. As share price increases, the trailing stop will surpass the fixed stop-loss, rendering it redundant or obsolete."
?
This is exactly the behaviour I would like for my bracket trades. As I was unable to find a way to do this in a single order (I thought there might be an option to add a Stop Loss to a Trailing Stop Loss or visa versa), I instead placed an additional child order. So I have the following:
?
Parent
> child: Take Profit
> child: Stop Loss
> child: Trailing Stop Loss
?
This is submitted and works perfectly, with one unfortunate exception. In the scenario when the Trailing Stop Loss moves to the exact price of the Stop Loss and remains there as the price movement reverses, if the Stop Loss/Trailing Stop Loss orders are triggered together, both orders are filled, so I end up with one of the orders closing out my position, and the other opening a new position for the same quantity.
?
Is there a way to do either of the following:
1) Include a Stop Loss within a Trailing Stop Loss order (or visa versa)
2) Cancel the Stop Loss order when the Trailing Stop Loss reaches or moves beyond the Stop Loss price
?
I very much appreciate any insights to this issue.
?
Regards,
Scott.
?


 

When you place a Bracket Order (a parent plus several child orders all for the same instrument), IBKR wraps the child orders into a group by setting the child orders' fields to a common and unique string. You can see that from the order objects you receive in openOrder callbacks.

OCA groups can have one of three and last time I checked IBKR selected type 3. That type does not protect against overfills.

Type 1 or 2 might work better for you since they should ensure that only one of your two stop orders actually fills ("with block"). But that may delay the order fills slight;y and could result in a less favorable price since IBKR won't actually submit (one of) the stop orders to the exchange until the trigger condition is actually met.

´³¨¹°ù²µ±ð²Ô

?

On Thu, Aug 15, 2024 at 08:03 PM, <scott_hopgood@...> wrote:

Hey guys,
?
I have created a Python program that connects to the TWS API and places bracket trades when certain market conditions are met. The program is working as intended with one exception. I would like to be able to place a combined Stop Loss and Trailing Stop Loss instead of just a Stop Loss to take advantage of this combo in an attempt to reduce losses.
?
An article on Investopedia refers to the strategy as being available on Active Trades and provides the following example which illustrates exactly what I am seeking...
?
"For example, you could set a stop-loss at 2% below the current stock price and a trailing stop at 2.5% below the current stock price. As share price increases, the trailing stop will surpass the fixed stop-loss, rendering it redundant or obsolete."
?
This is exactly the behaviour I would like for my bracket trades. As I was unable to find a way to do this in a single order (I thought there might be an option to add a Stop Loss to a Trailing Stop Loss or visa versa), I instead placed an additional child order. So I have the following:
?
Parent
> child: Take Profit
> child: Stop Loss
> child: Trailing Stop Loss
?
This is submitted and works perfectly, with one unfortunate exception. In the scenario when the Trailing Stop Loss moves to the exact price of the Stop Loss and remains there as the price movement reverses, if the Stop Loss/Trailing Stop Loss orders are triggered together, both orders are filled, so I end up with one of the orders closing out my position, and the other opening a new position for the same quantity.
?
Is there a way to do either of the following:
1) Include a Stop Loss within a Trailing Stop Loss order (or visa versa)
2) Cancel the Stop Loss order when the Trailing Stop Loss reaches or moves beyond the Stop Loss price
?
I very much appreciate any insights to this issue.
?
Regards,
Scott.
?


 

Thanks ´³¨¹°ù²µ±ð²Ô for your most helpful reply. Just the solution I was looking for.?
?
Cheers, Scott.


 

Just to follow up, I have the following code for placing the bracket order, and below that is the data returned from a call to openOrder.
?
Code to place order:
?
# -- CONTRACT INTO --
? ? mycontract = Contract()
? ? mycontract.symbol = "NVDA"
? ? mycontract.secType = "STK" ? ?
? ? mycontract.exchange = "SMART"
? ? mycontract.currency = "USD"
?
# -- PARENT --
? ? parent = Order()
? ? parent.orderId = orderId
? ? parent.action = "BUY"
? ? parent.orderType = "LMT"
? ? parent.lmtPrice = 122.90
? ? parent.totalQuantity = 10
? ? # TESTING OCA TYPE HERE
? ? parent.ocaType = 1
? ? parent.transmit = False
?
# -- PROFIT TAKER --
? ? profit_taker = Order()
? ? profit_taker.orderId = parent.orderId + 1
? ? profit_taker.parentId = parent.orderId
? ? profit_taker.action = "SELL"
? ? profit_taker.orderType = "LMT"
? ? profit_taker.lmtPrice = 123.90
? ? profit_taker.totalQuantity = 10 ??
? ? profit_taker.transmit = False
?
# -- STOP LOSS --
? ? stop_loss = Order()
? ? stop_loss.orderId = parent.orderId + 2
? ? stop_loss.parentId = parent.orderId
? ? stop_loss.action = "SELL"
? ? stop_loss.orderType = "STP"
? ? stop_loss.auxPrice = 122.50
? ? stop_loss.totalQuantity = 10 ??
? ? stop_loss.transmit = False
?
# -- TRAILING STOP LOSS --
? ? trailing_stop_loss = Order()
? ? trailing_stop_loss.orderId = parent.orderId + 3
? ? trailing_stop_loss.parentId = parent.orderId
? ? trailing_stop_loss.action = "SELL"
? ? trailing_stop_loss.orderType = "TRAIL"
? ? trailing_stop_loss.totalQuantity = 10
? ? trailing_stop_loss.trailingPercent = 0.2
? ? trailing_stop_loss.transmit = True
?
# -- SUBMIT ORDER --
? ? self.placeOrder(parent.orderId, mycontract, parent)
? ? self.placeOrder(profit_taker.orderId, mycontract, profit_taker)
? ? self.placeOrder(stop_loss.orderId, mycontract, stop_loss)
? ? self.placeOrder(trailing_stop_loss.orderId, mycontract, trailing_stop_loss)
?
?
Data returned from openOrder:
?
openOrder. orderId: 6454, contract: 4815747,NVDA,STK,,,0,?,,SMART,,USD,NVDA,NMS,False,,,,combo:, order: 6454,0,1138849244: LMT BUY 10@... DAY
orderId: 6454, status: PreSubmitted, filled: 0, remaining: 10, avgFillPrice: 0.0, permId: 1138849244, parentId: 0, lastFillPrice: 0.0, clientId: 0, whyHeld: , mktCapPrice: 0.0
?
openOrder. orderId: 6455, contract: 4815747,NVDA,STK,,,0,?,,SMART,,USD,NVDA,NMS,False,,,,combo:, order: 6455,0,1138849245: LMT SELL 10@... DAY
orderId: 6455, status: PreSubmitted, filled: 0, remaining: 10, avgFillPrice: 0.0, permId: 1138849245, parentId: 6454, lastFillPrice: 0.0, clientId: 0, whyHeld: child, mktCapPrice: 0.0
?
openOrder. orderId: 6456, contract: 4815747,NVDA,STK,,,0,?,,SMART,,USD,NVDA,NMS,False,,,,combo:, order: 6456,0,1138849246: STP SELL 10@0 DAY
orderId: 6456, status: PreSubmitted, filled: 0, remaining: 10, avgFillPrice: 0.0, permId: 1138849246, parentId: 6454, lastFillPrice: 0.0, clientId: 0, whyHeld: child,trigger, mktCapPrice: 0.0
?
openOrder. orderId: 6457, contract: 4815747,NVDA,STK,,,0,?,,SMART,,USD,NVDA,NMS,False,,,,combo:, order: 6457,0,1138849247: TRAIL SELL 10@0 DAY
orderId: 6457, status: PreSubmitted, filled: 0, remaining: 10, avgFillPrice: 0.0, permId: 1138849247, parentId: 6454, lastFillPrice: 0.0, clientId: 0, whyHeld: child,trigger, mktCapPrice: 0.0
?
I can see that the child orders are bound to the primary order by way of parentId, however I am not sure where the common string is that binds these orders by ocaGroup. Where in my Python code to submit the orders would I specify that I would like the ocaType to be '1', as I believe this would work best to ensure no overfills occur.
?
Regards, Scott.
?
?


 

In the openOrder callbacks, also print order object fields ocaGroup and ocaType for the order objects you receive from IBKR. You will see values only for the child orders and should verify that, indeed, IBKR uses OCA type 3.

The parent order is not part of the OCA group, so don't set the ocaGroup or ocaType for the parent order.

You have to experiment with this. It's a long time since I made a few experiments. There are a few approaches that come to mind:

  • TWS does provide order presets, some of which also impact orders placed via the API. I have not tried this, but it may be as simple as selecting "OCA Overfill Protection" for instrument types you are interested in. But keep in mind that these are the presets for all orders. My TWS instances all show by default options "Reduced in size" selected and "Overfill Protection" not selected.
  • Instead of having IBKR wrap the child orders into an OCA, just do it yourself before you place the orders. If I remember correctly, IBKR did honor the OCA group I had selected.? Come up with a unique group name string for this order, set ocaGroup in all child orders to that string, and set the ocaType field in all child orders to the one you want, and place the orders just like you do now.
  • When you receive the order objects from IBKR in the first openOrder call after you place the orders, for each child order, set the ocaType you want and send the changed order back to IBKR. There could, obviously, be race conditions in case? the parent order fills and stoop orders trigger while you send the changed orders.
Let us know what you find and what path you took.
´³¨¹°ù²µ±ð²Ô

?

?

?

?
?
On Thu, Aug 15, 2024 at 11:43 PM, <scott_hopgood@...> wrote:

Just to follow up, I have the following code for placing the bracket order, and below that is the data returned from a call to openOrder.
?
[xxx code deleted]
?
I can see that the child orders are bound to the primary order by way of parentId, however I am not sure where the common string is that binds these orders by ocaGroup. Where in my Python code to submit the orders would I specify that I would like the ocaType to be '1', as I believe this would work best to ensure no overfills occur.
?
Regards, Scott.
?
?


 

Hi ´³¨¹°ù²µ±ð²Ô,
?
Thank you indeed. I have added order.ocaGroup and order.ocaType to the print of the openOrder call and I can see that IBKR sets the ocaType to '3' by default. I also see that the ocaGroup string is added to each of the child topics automatically. As I am not sure if the presets in TWS are recognised by the API for this, I will go ahead and add ocaType = 1 to each of the child orders and I will run my program to see if this does the trick.?
?
I will report back any results I obtain.
?
Cheers,?
Scott.


 

there is also the option to use :
You can attach one-time adjustments to stop, stop limit, trailing stop and trailing stop limit orders. When you attach an adjusted order, you set a trigger price that triggers a modification of the original (or parent) order, instead of triggering order transmission.
?
the benefit of this solution is submitting less orders, just the take profit and the stop loss. once the trigger price is reached, the stop loss order is adjusted to the new settings. also, another benefit is that the order can sit directly at the exchange (if supported) and there should be no delay in execution (depending on oca type). basically, if overfill protection is enabled, ib submits only one order from the oca group to the exchange and monitors the situation whether a different order should be triggered, and if so, it is submitted to the exchange instead. but when using single take profit and single stop loss, there is probably no need for overfill protection as the chances for overfill are low.
?
(interestingly, i didn't find this feature described in the new documentation so i'm referring to the old documentation. still, in the in the new documentation the fields are described there.)
?


 

¿ªÔÆÌåÓý

Hi Scott,

My approach is exactly the same. Initially I had just a bracket, but then wanted to add a trail and a time-based close, so switched to an OCA style order.

The overfill protection mentioned by ´³¨¹°ù²µ±ð²Ô is something that I learned from him as well and it's very important. Because I actualy had this happen to me where I ended up in a reversed position instead of being flat.

In my (Java) code, I prefer to use the actual enum names instead of integer values, but that's just me ;-) On every closing order in the oca group, I call this method on the order objects:

.ocaType(OcaType.ReduceWithBlocking);

So I do not want to cancel any oca order on just a partial fill, the remainder position should still be protected. Something to consider maybe.

For reference, I looked it up, it was a year ago when I faced this question:

/g/twsapi/topic/100875638
/g/twsapi/topic/100660671

Greetings,
Raoul



On 16-08-2024 06:43, scott_hopgood@... wrote:

Just to follow up, I have the following code for placing the bracket order, and below that is the data returned from a call to openOrder.
?
Code to place order:
?
# -- CONTRACT INTO --
? ? mycontract = Contract()
? ? mycontract.symbol = "NVDA"
? ? mycontract.secType = "STK" ? ?
? ? mycontract.exchange = "SMART"
? ? mycontract.currency = "USD"
?
# -- PARENT --
? ? parent = Order()
? ? parent.orderId = orderId
? ? parent.action = "BUY"
? ? parent.orderType = "LMT"
? ? parent.lmtPrice = 122.90
? ? parent.totalQuantity = 10
? ? # TESTING OCA TYPE HERE
? ? parent.ocaType = 1
? ? parent.transmit = False
?
# -- PROFIT TAKER --
? ? profit_taker = Order()
? ? profit_taker.orderId = parent.orderId + 1
? ? profit_taker.parentId = parent.orderId
? ? profit_taker.action = "SELL"
? ? profit_taker.orderType = "LMT"
? ? profit_taker.lmtPrice = 123.90
? ? profit_taker.totalQuantity = 10 ??
? ? profit_taker.transmit = False
?
# -- STOP LOSS --
? ? stop_loss = Order()
? ? stop_loss.orderId = parent.orderId + 2
? ? stop_loss.parentId = parent.orderId
? ? stop_loss.action = "SELL"
? ? stop_loss.orderType = "STP"
? ? stop_loss.auxPrice = 122.50
? ? stop_loss.totalQuantity = 10 ??
? ? stop_loss.transmit = False
?
# -- TRAILING STOP LOSS --
? ? trailing_stop_loss = Order()
? ? trailing_stop_loss.orderId = parent.orderId + 3
? ? trailing_stop_loss.parentId = parent.orderId
? ? trailing_stop_loss.action = "SELL"
? ? trailing_stop_loss.orderType = "TRAIL"
? ? trailing_stop_loss.totalQuantity = 10
? ? trailing_stop_loss.trailingPercent = 0.2
? ? trailing_stop_loss.transmit = True
?
# -- SUBMIT ORDER --
? ? self.placeOrder(parent.orderId, mycontract, parent)
? ? self.placeOrder(profit_taker.orderId, mycontract, profit_taker)
? ? self.placeOrder(stop_loss.orderId, mycontract, stop_loss)
? ? self.placeOrder(trailing_stop_loss.orderId, mycontract, trailing_stop_loss)
?
?
Data returned from openOrder:
?
openOrder. orderId: 6454, contract: 4815747,NVDA,STK,,,0,?,,SMART,,USD,NVDA,NMS,False,,,,combo:, order: 6454,0,1138849244: LMT BUY 10@... DAY
orderId: 6454, status: PreSubmitted, filled: 0, remaining: 10, avgFillPrice: 0.0, permId: 1138849244, parentId: 0, lastFillPrice: 0.0, clientId: 0, whyHeld: , mktCapPrice: 0.0
?
openOrder. orderId: 6455, contract: 4815747,NVDA,STK,,,0,?,,SMART,,USD,NVDA,NMS,False,,,,combo:, order: 6455,0,1138849245: LMT SELL 10@... DAY
orderId: 6455, status: PreSubmitted, filled: 0, remaining: 10, avgFillPrice: 0.0, permId: 1138849245, parentId: 6454, lastFillPrice: 0.0, clientId: 0, whyHeld: child, mktCapPrice: 0.0
?
openOrder. orderId: 6456, contract: 4815747,NVDA,STK,,,0,?,,SMART,,USD,NVDA,NMS,False,,,,combo:, order: 6456,0,1138849246: STP SELL 10@0 DAY
orderId: 6456, status: PreSubmitted, filled: 0, remaining: 10, avgFillPrice: 0.0, permId: 1138849246, parentId: 6454, lastFillPrice: 0.0, clientId: 0, whyHeld: child,trigger, mktCapPrice: 0.0
?
openOrder. orderId: 6457, contract: 4815747,NVDA,STK,,,0,?,,SMART,,USD,NVDA,NMS,False,,,,combo:, order: 6457,0,1138849247: TRAIL SELL 10@0 DAY
orderId: 6457, status: PreSubmitted, filled: 0, remaining: 10, avgFillPrice: 0.0, permId: 1138849247, parentId: 6454, lastFillPrice: 0.0, clientId: 0, whyHeld: child,trigger, mktCapPrice: 0.0
?
I can see that the child orders are bound to the primary order by way of parentId, however I am not sure where the common string is that binds these orders by ocaGroup. Where in my Python code to submit the orders would I specify that I would like the ocaType to be '1', as I believe this would work best to ensure no overfills occur.
?
Regards, Scott.
?
?


 

After reading fordfrog's and Raoul's posts I started to think a little more about what you are trying to accomplish and not so much how you are going about it. You may actually be able to do what you want with a single Trailing Stop order, eliminate your issue entirely, and stay with OcaType 3.

Take a look at the order example, the and the Order class field trailStopPrice. You would:
  • not add the STOP LOSS order at all
  • you would set trailing_stop_loss.trailStopPrice = 122.50 instead of stop_loss.auxPrice = 122.50

The initial stop trigger price for Trailing Stop orders may be more aggressive and closer than what the trailingPercent or trailingAmount define. Once the price moves away and the gap widens, those values will be used to update the trigger price.

In fact, your client could monitor the progress of your position and nudge the trailStopPrice to more aggressive prices when warranted.

´³¨¹°ù²µ±ð²Ô

?

?
On Fri, Aug 16, 2024 at 01:21 AM, <scott_hopgood@...> wrote:

Hi ´³¨¹°ù²µ±ð²Ô,
?
Thank you indeed. I have added order.ocaGroup and order.ocaType to the print of the openOrder call and I can see that IBKR sets the ocaType to '3' by default. I also see that the ocaGroup string is added to each of the child topics automatically. As I am not sure if the presets in TWS are recognised by the API for this, I will go ahead and add ocaType = 1 to each of the child orders and I will run my program to see if this does the trick.?
?
I will report back any results I obtain.
?
Cheers,?
Scott.


 

Hi ´³¨¹°ù²µ±ð²Ô,
?
This is indeed a much cleaner alternative to submitting an additional order in the bracket and adjusting the default ocaType. Thank you so much for suggesting this alternate method. I will give this a go and see how it performs.
?
Best regards, Scott.


 

Hey Fordfrog,
?
Thank you so much for your input. I was aware of the one-time adjustments feature but I had not considered this as an alternative to my current method. I will certainly keep this in mind should the other option I have implemented on ´³¨¹°ù²µ±ð²Ô's advice not perform as intended.?
?
Cheers, Scott.


 

Hi ´³¨¹°ù²µ±ð²Ô,
?
I have run my program against a day session and it appears to be working as intended. I believe this is the cleanest solution to my issue regarding the overfill of orders due to my original Stop Loss/Trailing Stop Loss conflict and I thank you very much for your assistance with this.
?
Cheers, Scott.


 

Hi Raoul,
?
Thank you indeed for your input. I may actually have to consider using a separate OCA Group that I have my program submit separately after the initial order is filled as I am finding that my entries typically slip from the intended limit price. Orders typically fill at a better price as limit price is placed ahead of the market price to help ensure order is filled and entries are not missed. Due to this my brackets are more often than not offset incorrectly, and I am finding that my Stop Loss is placed too close to the actual entry price and is being hit more often. My intention would be to place the initial order, retrieve the accurate average entry price, and then place an OCA Group that will contain my Take Profit/Stop Loss orders which I can then accurately place relative to the average order price.
?
Cheers, Scott.