Multi-leg option orders
Kite Connect has no single "spread order" API call — a multi-leg options strategy (straddle, spread, iron condor) is placed as separate individual orders, one per leg, that your code must sequence and track together.
def place_bull_call_spread(kite, underlying_expiry_df, buy_strike, sell_strike, qty):
buy_leg_symbol = resolve_option_symbol(underlying_expiry_df, buy_strike, "CE")
sell_leg_symbol = resolve_option_symbol(underlying_expiry_df, sell_strike, "CE")
buy_order_id = kite.place_order(
variety="regular", exchange="NFO", tradingsymbol=buy_leg_symbol,
transaction_type="BUY", quantity=qty, product="NRML",
order_type="MARKET", validity="DAY", tag="spread-leg1",
)
sell_order_id = kite.place_order(
variety="regular", exchange="NFO", tradingsymbol=sell_leg_symbol,
transaction_type="SELL", quantity=qty, product="NRML",
order_type="MARKET", validity="DAY", tag="spread-leg2",
)
return buy_order_id, sell_order_id
The core risk: leg-in risk
Between placing leg 1 and leg 2, the market can move — you might get filled on the buy leg at a worse price, then the sell leg's price has also moved, changing your actual net debit/credit from what you modeled. For multi-leg strategies this "leg-in risk" is real and can meaningfully change the strategy's risk/reward versus your intended entry.
Mitigations
- Place both legs as close together in time as possible — minimize the window between them (don't do anything else between the two calls).
- Use limit orders with a combined net-price check rather than blind market orders on both legs, when the strategy's edge is sensitive to entry price.
- Verify both legs filled before considering the position "open" — if leg 1 fills and leg 2 is rejected (chapter 57), you now hold a naked, unintended position, not the spread you designed. Have explicit handling for this (typically: immediately close the filled leg rather than leaving it open unmanaged).
def verify_spread_or_unwind(kite, buy_order_id, sell_order_id, buy_symbol, sell_symbol, qty):
buy_status = poll_order_status(kite, buy_order_id)
sell_status = poll_order_status(kite, sell_order_id)
if buy_status["status"] == "COMPLETE" and sell_status["status"] != "COMPLETE":
logging.error("Leg 2 failed — unwinding leg 1 to avoid naked exposure")
kite.place_order(
variety="regular", exchange="NFO", tradingsymbol=buy_symbol,
transaction_type="SELL", quantity=qty, product="NRML",
order_type="MARKET", validity="DAY",
)