Protocol for sending multi legged option orders

Hi,

While sending multi legged option orders, using the method outlined here, I have the following question:

How exactly should we place the long and short positions of an option spread to ensure optimal execution? Should we always keep the long position of a spread first? If that is not the case, how exactly should we place our legs in our multi legged order?

The reason I am asking this is the following. After some time, my trading program fails with one of the following errors:

Error 1:

File “/root/miniconda3/lib/python3.12/site-packages/alpaca/common/rest.py”, line 207, in _one_request raise APIError(error, http_error) alpaca.common.exceptions.APIError: {“available”:“0”,“code”:40310000,“existing_qty”:“1”,“held_for_orders”:“1”,“message”:“insufficient qty available for order (requested: 1, available: 0)”,“related_orders”:[“503acd55-9a8c-477e-bb85-f10de42d80f3”],“symbol”:“”}ss

Error 2:

File “/root/miniconda3/lib/python3.12/site-packages/alpaca/common/rest.py”, line 207, in _one_request raise APIError(error, http_error) alpaca.common.exceptions.APIError: {“available”:“0”,“code”:40310000,“existing_qty”:“1”,“held_for_orders”:“1”,“message”:“insufficient qty available for order (requested: 1, available: 0)”,“related_orders”:[“503acd55-9a8c-477e-bb85-f10de42d80f3”],“symbol”:“”}

The reality is that I am not really trying to do any of these things(that is sell an uncovered option or sell beyond available quantity). Hence, I believe that this could be due to suboptimal placing of my multilegged order and wanted to explore more on the same.

I have another related question as well. If such an exception happens in the running of my program, how can I ignore the same and move forward rather than allowing the program to fail? I believe that would be useful to know as well.

Thanks

I did some debugging and found out some points, which could be useful to many, and am listing them below:

  1. This looks to be happening when we sent orders in quick succession

  2. From point#1, it looks to me that this is happening because Alpaca system is receiving the second order before it is able to properly assimilate the effects of the first order (pardon my usage of these terms, but I hope that the meaning is clear).

To get around this, I have started pausing the program for around 500ms after sending of an order, so that Alpaca system has it’s time to properly take into account the effects of the first order. This looks to be working fine as I have not seen this error since then.

This is what I could find. @Dan_Whitnable_Alpaca can comment further on this.

Thanks

I got the following reply from Alpaca and am pasting the same here:

Thank you for the added detail. A few things to understand about orders in general (and not just multi-leg orders). All orders are simply ‘requests’. Your algo must always wait for confirmation that and order filled, or was cancelled, or rejected etc. Orders are executed asynchronously both in the paper and certainly live trading. In live trading orders typically are sent to an external trading partner for execution. That partner handles the order and then sends a status update back to Alpaca. That update could take milliseconds, seconds or minutes (or more). The 200 response from the API call only means the order ‘request’ was received. You should always query the order status and not assume an order filled.

Pardon my ignorance, but how exactly do you query order status and find out whether it was filled or not? I tried going through the documentation, but it was not entirely clear from it.

Thanks

I tried the following piece of code, but that does not look to work too well:

    # place the order of the long straddle
    res = trade_client.submit_order(req)

    res1 = trade_client.get_order_by_id(res.client_order_id)
    print("TEST2", res1.status)

The idea is to check res1.status would get changed to filled. It really does not look too well as it is throwing the following error:

alpaca.common.exceptions.APIError: {“code”:40410000,“message”:“order not found for e289cda0-d3ea-4e60-a332-aacc5d5e173b”}

What exactly is the best way to check order status?

Thanks

I believe I have figured out a way to check order status, though I am not quite sure if it is the best way. The following code snippet explains it:

    # place the relevant multilegged order
    res = trade_client.submit_order(req)

    # Ensure that the order is filled before we move on with the next order.
    res1 = trade_client.get_order_by_id(res.id)
    while(res1.status != OrderStatus.FILLED):
        res1 = trade_client.get_order_by_id(res.id)

The code is rather self explanatory. We keep on querying order status and ensure that it is filled before we move with the next order, in order to make sure that we do not have order collisions.

@Dan_Whitnable_Alpaca , do you have any comments on this? Perhaps there could be a better way of handling this.

Thanks

Does anyone have any thoughts on this?

Thanks

Trade updates can be grabbed asynchronously, see Websocket Streaming

Thank you @TreeHugger for your reply.

Would you mind sharing a small code snippet on how this could be done? I went over the documentation, but could not understand completely as to how this was done.

I am sure this would be beneficial to many.

Thanks
Babinu

@sarahvignale You asked “how exactly do you query order status and find out whether it was filled or not?” The first thing is you don’t necessarily need to ensure an order is filled before placing another order. If one wants to order 10 stocks for example, submit those orders as fast as you want independent of the others. Alpaca keeps track of orders in real time and there will never be ‘collisions’.

As @TreeHugger mentioned, one’s algo can asynchronously connect to the trade update stream and watch for fills. Whether to choose that option get’s into a discussion about if you want an event driven and stateful algo, or what I like to call a ‘state driven’ approach. I’ll save that discussion for another time, but for most strategies a ‘state driven’ approach will be the easiest, most flexible, and the least error prone.

The most straightforward method to check order status is to fetch all orders, put them into a dataframe, and query the dataframe for open or closed orders. Something like this

# instantiate a trading client. set raw_data=True so account data is returned as values and not objects.
trading_client = TradingClient(ALPACA_API_KEY, ALPACA_API_SECRET_KEY, paper=True, raw_data=True)

# place an order or orders
trading_client.submit_order(order_data=my_order_request)

# get last 500 orders and convert to a dataframe for easy manipulation
orders = trading_client.get_orders(GetOrdersRequest(limit=500, status='all', nested=False, direction='desc'))
orders_df = pd.DataFrame(orders)

# get positions and convert to a dataframe for easy manipulation
positions = trading_client.get_all_positions()
positions_df = pd.DataFrame(positions)

# now do any logic based upon current state
# for example submit a take profit limit order to close any open positions

# select open orders
OPEN_ORDER_STATUSES = ['accepted','held','pending_new','new','partially_filled']
open_orders = orders_df.query('status in @OPEN_ORDER_STATUSES')

# select positions without any open orders
positions_without_orders = positions_df.query('symbol not in @open_orders.symbol')

TAKE_PROFIT_PCT = .01

# only create an order if there are no open orders
for position in positions_without_orders.itertuples():
  take_profit_price = round(float(position.avg_entry_price) * (1 + TAKE_PROFIT_PCT), 2)
  
  trading_client.submit_order(LimitOrderRequest(
      symbol=position.symbol,
      qty=abs(float(position.qty)),
      side='sell' if position.side=='long' else 'buy',
      time_in_force='day',
      limit_price=take_profit_price
  ))

For completeness here are the imports etc which need to be executed first

!pip install -q alpaca-py
import pandas as pd


ALPACA_API_KEY = 'xxxxx'
ALPACA_API_SECRET_KEY = 'xxxxx'

from alpaca.trading.client import TradingClient
from alpaca.trading.requests import GetOrdersRequest, LimitOrderRequest

One caveat is this only fetches the last 500 orders. If there is a chance one would ever need to check more than the last 500 orders then logic needs to be added to ‘page’ through the orders and fetch what is needed across multiple calls.

Thank you @Dan_Whitnable_Alpaca for the reply, and this clears a lot of stuff. I have pasted my replies inline below:

You asked “how exactly do you query order status and find out whether it was filled or not? ” The first thing is you don’t necessarily need to ensure an order is filled before placing another order.

I get it, but this would be useful when you want to create multiple orders on the same security. If you want to go long in one order and short in another one, it would be better to know the status of the first order before sending in the second. I have seen wash trade errors happening when this is not done correctly in paper trading.

The most straightforward method to check order status is to fetch all orders, put them into a dataframe, and query the dataframe for open or closed orders. Something like this

I get your approach and I am doing something similar by directly checking the status of the sent order before sending in the next one. To ensure that this is done correctly, I check for order status in a while loop and exit once I get the status as filled. Essentially I do the following:

        while(True):

             # Get details of the sent order.
             current_order = trade_client.get_order_by_id(res.id)
             
             # If the current status if FILLED or EXPIRED, exit the loop. Ensure that the
             # trade happened is set if we get filled.
              if (current_order.status == OrderStatus.FILLED):
                  trade_happened = True                    
                  break

              if (current_order.status == OrderStatus.EXPIRED):
                  break

While this code works, I am not sure if this is the ideal solution. I was curious about the approach mentioned by @TreeHugger(which is the ‘state driven’ approach) , but could not get a working solution on my own. Can we have a discussion/sample piece of code using the ‘state driven’ approach?