Why did my stop limit sell order sell at a price below my stop limit?

I set a stop limit order on ACY with an 11.50 stop price and 11.40 limit price. Once it was triggered, it sold for 11.27. What happened?

I have the same issue; I’d like to know why as well.

@jakeyoon I can maybe help. What was the order number? With that I can check the logs and help troubleshoot.

I noticed slow order fill speed (even market orders get filled slow)
and I noticed positions going well over or below the profit and stop limit.

There were multiple orders that closed way below stop limit, even saw -20% loss and upward of 20% gain when I have +3% take profit and -3% stop limit.
SKYT was one of the example that lost 10%
ORDER ID - eea5cea1-9096-4dc4-88f7-d47876d8ced3
It’s also hard to see past performance and past trades via the website.

Thanks so much Dan,

@jakeyoon The order for SKYT above executed exactly as one would expect. I’ll walk through the execution which may help.

So, the order started as a bracket order

Take Profit LIMIT SELL $33.17
Stop loss LIMIT SELL $29.98 STOP PRICE $30.29

The market order filled immediately when the order was submitted

9:31:32 MARKET BUY SKYT filled at $32.51

In paper trading, BUY orders fill at the current ask_price. In live trading there may be some price improvement but this is sort of a ‘worse case’ used to simulate paper orders. Let’s check the latest quote as of when the order was submitted at 9:31:32 to see if the order actually did fill at that current ask_price.

# Set times to a bit before and after all the order times to ensure we get all the data
start_time = pd.to_datetime('2021-06-21 13:31:00', utc=True)
end_time = pd.to_datetime('2021-06-21 13:45:00', utc=True)

symbol = 'SKYT'

# First get quotes to verify initial market order fill
quotes = api.get_quotes(symbol, start=start_time.isoformat(), end=end_time.isoformat()).df
quotes.index = quotes.index.tz_convert('America/New_York')
quotes.between_time('9:31:20', '9:31:38')

Here are the quotes. Notice the latest quote as of when the order was submitted was at 9:31:27. The ask_price was $32.51 which matches the fill price.

After the market order filled at 9:31:32, the take profit and stop loss legs were submitted. A stop loss order will trigger when a ‘valid’ trade occurs at or below the stop loss price. There are certain conditions which exclude an order from being considered ‘valid’. There is a list of those conditions in the next post. So now let’s check the trades to see when the stop triggered. We are looking for the first ‘valid’ trade after 9:31:32 with a price less than the stop price of $30.29.

# The market buy filled at the current ask_price of $32.51 as of 9:31:32
# Now let's check why/when the stop limit leg triggered
# That had a stop loss of $30.29
# Check the trades for that (ie stops are triggered from trades)
# First get quotes to verify initial market order fill

trades = api.get_trades(symbol, start=start_time.isoformat(), end=end_time.isoformat()).df
trades.index = trades.index.tz_convert('America/New_York')

trades.between_time('9:31:32', '9:45').query('price<=30.29')

Here are the trades. Notice the first ‘valid’ trade with a price below the stop price was at 9:42:15. There was also a trade at 9:42:08 but that was an excluded odd lot order (condition I). Therefore, the stop loss triggered at 9:42:15.

So, the stop loss triggered and a SELL limit order was submitted at 9:42:15.828. The limit price was $29.98 so it will fill at the bid_price if it’s above that limit price. Let’s check the latest quote as of when the order was triggered at 9:42:15.828 to see if/when it filled at that current bid_price.

Here are the quotes. Notice the latest quote as of when the order was submitted was at 9:42:15.828. The bid_price was $30.21 which is above the limit price so the order filled immediately at that price.

One last check is to see if the take profit limit price was ever reached before the stop was triggered at 9:42:15.828. This would have filled if the bid_price was ever above the take profit limit of $33.17. So doing a quick query, it looks like there are no quotes returned. Therefore the take profit never would have filled before the stop was triggered.

Hope that helps explain the steps as the order executed. It all looks correct. I included the python code for checking trades and quotes so one can check their own orders in a similar way.

Here is the list of trades which are excluded when looking for ‘valid’ trades to trigger a stop.

Feed Tape Condition Description
CTS AB B Average Price Trade
UTDF C W Average Price Trade
CTS/UTDF ABC 4 Derivatively Priced
CTS/UTDF ABC 7 Qualified Contingent Trade (“QCT”)
CTS/UTDF ABC 9 Corrected Consolidated Close (per listing market)
CTS/UTDF ABC G Bunched Sold Trade
CTS/UTDF ABC H Price Variation Trade
CTS/UTDF ABC I Odd Lot Trade
CTS/UTDF ABC M Market Center Official Close
CTS/UTDF ABC P Prior Reference Price
CTS/UTDF ABC Q Market Center Official Open
CTS/UTDF ABC U Extended Trading Hours (Sold Out of Sequence)
CTS/UTDF ABC V Contingent Trade
CTS/UTDF ABC Z Sold (out of Sequence)

I appreciate it
Thank you so much for your detailed reply.

Your answers are so helpful. I wonder if you could help me understand why my stop_limit orders aren’t working?
I placed buy side stop_limit order (id=‘7f821eba-3eed-42be-9982-0ef78152b7ba’)
at ‘2021-12-17T11:56:43.024530498Z’ with stop price of 47180/limit price of 47400 for BTCUSD when BTC trading at 47106, and rather than waiting for price to trigger at the stop_price the paper trade api filled me immediately (‘filled_at’: ‘2021-12-17T11:56:43.103829941Z’,)
at ‘filled_avg_price’: ‘47105.8’.
further testing by me yielded a stop_limit order that didn’t fill at all despite price moving strongly past the stop: ‘id’: ‘a1bcc2e5-272b-4827-8bc9-062d1734ebd5’, ‘created_at’: ‘2021-12-17T18:54:26.023348604Z’, ‘stop_price’: ‘46920’, ‘limit_price’: ‘47000’, and despite BTC moving from 46880 to 47115 in the next few minutes thte order didn’t fill.
My algorithm is designed to follow momentum, so I’d like to enter a long position contingent on price moving up above some threshold. On Interactive Brokers and Etrade they call these “conditional_order” so in Alpaca I view the stop_price as the conditional trigger. As my orders suggest, I’m trading crypto.