How can I find why an order was rejected?

My algo checks Alpaca once a second to see if the currently active stop limit in Alpaca matches what it should be as defined in the algorithm and, if not, updates the order. Sometimes I get small runs of ‘rejected’ and I’m trying to figure out why. This screenshot is an example of what I’m talking about. All of these orders are otherwise identical, so I’m not sure why I got so many ‘rejected/canceled’ and the final one took. Thoughts?

Also, is there a way where I can just pop in to see why any particular order was rejected? I don’t mind popping into the UI to see why it was canceled or rejected, but I would like to be able to find out why it was canceled or rejected so I can update my process accordingly. Thanks!

I’ve preemptively added another 1 second pause after it places/updates a limit order just to give it a little breathing room to process the order before waiting another second before fetching the orders. That would be a total of 2 seconds between placing/updating an order and checking the current open orders to update again. Maybe that would help?

@kcducttaper I checked the logs, and the reason those orders are being rejected is Stop Price Already Triggered/Exceeds $ Threshold. For example, you had a stop price of $3.23, but the current price was $3.21. It would have triggered immediately. For sell orders, check the current bid price. That is the price your order will fill at. Ensure your stop loss is lower than that to avoid the order being rejected.

Gotcha. Is there a way I can see those logs on my end so I can troubleshoot more efficiently?

I also keep seeing some “rate limit exceeded” messages, but I’m having a really difficult time believing I’m burning up 10,000 calls/min (or 166 calls/second) with my current setup. Is that something you have visibility to on your side by chance?

@kcducttaper You asked if there was a way to see why an order was rejected.

There are three places in an order lifecycle where it can be rejected: 1) when an order is first created, 2) when the order is submitted to the execution venue (i.e., the execution venue rejects the order), and 3) once an order has already filled. Only in the first instance, when an order is created, is the reject reason exposed. There are internal discussions on exposing execution venue reject reason codes. In almost all cases, however, the reason is that the limit or stop prices were set too far from the current price, as was the case here. It is extremely rare for an order to be rejected once it is filled. The industry term for this is ‘busted’ where an order is effectively voided. This might happen if an order is inadvertently filled during a trading halt. This is a manual process, and you would be notified by email if this happens.

It’s good practice to check for errors when submitting orders. That will provide details if an order is rejected when it is created. Something like this if you are using the alpaca-py SDk.

try:
  my_order = trading_client.submit_order(MarketOrderRequest(
                              symbol='URG1',
                              qty=1,
                              side='buy',
                              time_in_force='day'))
  

except Exception as e:
  print(e)

This would print or log any error:

{"code":42210000,"message":"asset \"URG1\" not found"}

You also asked about the “rate limit exceeded” errors that can appear on the dashboard. Those errors are the result of the website placing too many calls and are not related to your algo(s). You can disregard those errors, but I understand they can be annoying. The UI team is aware of the issue and hopes to resolve it. Again, this is strictly a web dashboard issue, not related to anything you are doing.

I’ve already got all of my “phase 1” errors writing out to a file and have used those to debug a bit when generating an order. Unfortunately, those are clean if it fails in “phase 2”.

For “phase 2” rejections, I’ve gotten a couple rejection reasons from proper rounding to SP / Limit spread to placing a SL above the current price. I assumed placing a SL above the current price wouldn’t be a problem and it would just execute immediately at the current bid, but I guess that’s not accurate. I’m tweaking and tuning to try to ‘guestimate’ why else an order would be rejected in phase 2, but it’s just guess work at the moment. It sometimes feels more robust to simply ‘heck with the SL’ and have the algo track and only place buy/sell orders and handle the SL threshold on my end, but I’d really rather not do that and I am making good headway towards sending orders that shouldn’t be rejected. I’d still love to be able to retrieve and iterate directly from the phase 2 error itself! That way, I could bake in some intelligence to order re-submission - or at least log it to know what tree to start barking at.

Got it on the UI rate limit! I assumed it was tied to my algo via the API key attached to my account, but if it’s just UI issue, I’ll disregard it.

Appreciate the prompt and thorough replies!