half of my bracket order is being “held”, which completely undermines the strategy. Can anyone help me understand what is happening?
When executing a ‘bracket order’, the first order is immediately submitted to the market which will enter a position (either long or short). While that order is being filled, the other two legs of the bracket order will be ‘held’. Neither will be submitted until the first order is completely filled. Once the first order is filled, the take-profit order, which is a limit order, is submitted to the market. It becomes a ‘new’ order.
The remaining ‘stop loss’ order continues to be ‘held’. This is for two reasons. First, one cannot have ‘sell’ orders which total more than the available shares (what would happen if they both executed?). Moreover, a ‘stop loss’ order isn’t a real submit-able order. It’s really saying “if the price reaches a certain value then submit the order”. Until the market price reaches that value, it is ‘held’.
Now, if the take-profit limit order fills, then the stop-loss order is canceled. But, conversely, if the price reaches the stop loss value, then the take-profit order is canceled and the stop loss order is now submitted and the state is changed from 'held to ‘new’. Maybe that’s what isn’t obvious? In the background, the price is being monitored. If/when the price reaches the stop price then the stop-loss order will ‘un-held’ (and the limit order canceled).
Hope that helps?
Thanks for that reply. That is exactly what I thought was happening. Except that last week both the take-profit and stop-loss orders were listed as open orders for the same security, the exact thing you said can’t happen. If I’d never seen the situation where both sides were in the open orders I wouldn’t have been concerned about seeing the stop-limit in a “held” status. It was the change the freaked me out. It doesn’t really matter anymore b/c I switched up from a bracket order to a combination of limit plus oco. Thanks!
Yes, I’ve seen that situation too. Where both the take-profit and stop-loss orders are listed as ‘new’. It’s not often but does appear. That was from looking at the open orders list on the dashboard. I haven’t followed through and checked what the api says in those cases.
thanks for your insights. I am fairly puzzled by the behavior of bracket orders. Just today I had the issue where both of the stop & the limit in a bracket order were held. Both are showing up under closed orders. Does that imply that none of the orders will be triggered ? This would be concerning.
Further, how do I cancel held orders through code ? When I do an API request for all orders, held orders do not show up. So how do I actually cancel held orders, specifically in the case mentioned above ?
I really does not make any sense to me how they work and why I get different outcomes for the same order type. Does anyone have any more explanations what is happening here ? I am actually trading live…which is a bit concerning if your orders may or may not work :-).
@secl The behavior of bracket orders shouldn’t be puzzling. The first order is immediately submitted to Alpaca, and during market hours, immediately routed to an exchange. It will have a status of ‘new’. If it’s not during market hours it will have a status of ‘accepted’. In either case, until the initial order is filled, the other two legs of the bracket order will be ‘held’. Those orders are not triggered until the first order is completely filled. Having both those orders in a ‘held’ state is completely normal. It is a bit disconcerting that ‘held’ orders show up under the ‘closed’ section, but not to worry, they are truly being held (and not closed).
The key to thinking of bracket orders is that only one of the three orders is really active on an exchange at any given time. They all are in the broker system but only one (at most) is active on the exchange. If two were ever simultaneously active, they both could potentially execute. That would be bad. One would end up selling before buying or selling the same shares twice. Bad. Therefore, all an exchange ever ‘sees’ is one of the orders. The status of the orders is a reflection of which one is currently active on an exchange. Being ‘held’ simply means it hasn’t been sent to the exchange yet.
The best way to cancel a bracket order is to simply cancel the initial order. Canceling that order will automatically cancel all the associated legs too. No need to cancel each individually. Really all one needs to do is track the initial order. To cancel the orders, just cancel the initial order. To modify either of the two associated orders one can look up their order IDs using the ‘legs’ attribute of the initial order.
All orders should show up with an API request for ‘all’ orders. Something like this
import pandas as pd order_list = api.list_orders(status='all', limit=10) order_df = pd.DataFrame([order._raw for order in order_list])
Working with dataframes is much easier than list so you may find it beneficial to turn the list into a dataframe. Dataframes can easily be queried so finding all the ‘held’ orders could be done like this
held_orders = order_df.query('status == "held"')
It’s not directly indicated in the order list which orders are associated as a bracket order. There are some indications though. The ‘order_class’ will be ‘bracket’ for all three associated orders and they all will have the same ‘created_at’ time. Really the best way to track bracket orders though is by using the initial order id. Once one has the initial order ID all three orders can be canceled at once something like this
The problem that I’m seeing that is the most concerning, and what has made me stop using the bracket orders altogether, isn’t that they are in “held” per se, but that they aren’t triggering appropriately. The situation that seems to “destroy” the bracket order is described by the example below:
I buy a stock at 100 with a take_profit of 102, and stop_loss with a stop_price at 99.1 and a limit_price of 98.9. If the price goes below 99.1 but doesn’t sell for some reason it activates the stop_loss portion of the bracket order but seems to completely cancel the take_profit. I know this because I had a stock go below 99.1, not sell, then rebound back above 102. The take_profit price was completely gone. The stock spent significant time below the original stop_price and then considerable time above the take_profit limit, and it never sold. In the first case it was probably b/c there weren’t enough buyers and I have it set to all or nothing. But in the 2nd instance the take_profit order never moved back into the “open” status, wasn’t in the “held” status, and was just completely “closed”.
@keithegiles The bracket order behavior you are seeing is as expected (though as you noted maybe not what is desired). Once the initial order is filled, the ‘take profit’ limit order becomes active. The stop loss order remains held, but in the background, the stop loss price for that order is being monitored. If the market price ever drops below the stop loss price (even for a single trade), the limit order is canceled and the stop loss order becomes active as a ‘market’ order. The process is very stateful with at most exactly one order active at any given time. Once the stop loss is triggered there’s no going back. It’s an active market order until it’s filled or manually canceled.
My personal feeling is that a bracket order is most appropriate for long(er) term manual buy-and-hold trading. One submits this order as good-till-canceled and forgets it. The take profit price is set well above the cost basis and the stop loss is well below that. Typically +/- 5% or so. That range is outside the typical daily price swing and avoids the situation noted above. Bracket orders are a relic of manual trading (IMHO).
Algo trading affords the luxury of being able to submit an order and forget about it (similar to a bracket order) BUT also manage the order real time automatically. I personally always use OTO (One-Triggers-Other) orders with a pair of limit orders (and never bracket orders). The key is, once the initial limit order is filled, monitor the last trade or the current bid/ask price and adjust the limit price on the second order. Use the
replace_order method to adjust this price. At a minimum this avoids placing market orders (always bad IMHO) and ensures one gets at least the known limit price for a trade.