Margin-aware order sizing

Chapter 14 flagged this: never size an F&O order off available.cash alone. This chapter closes the loop with kite.order_margins() — the API that tells you the *actual* SPAN+exposure requirement for a specific order, before you place it.

def get_required_margin(kite, exchange, symbol, side, product, order_type, qty, price=None, trigger_price=None) -> float:
    result = kite.order_margins([{
        "exchange": exchange, "tradingsymbol": symbol, "transaction_type": side,
        "variety": "regular", "product": product, "order_type": order_type,
        "quantity": qty, "price": price, "trigger_price": trigger_price,
    }])
    return result[0]["total"]
def margin_aware_size(kite, exchange, symbol, side, product, order_type, lot_size, max_lots, price=None):
    available = kite.margins(segment="equity")["available"]["cash"]
    for lots in range(max_lots, 0, -1):
        qty = lots * lot_size
        required = get_required_margin(kite, exchange, symbol, side, product, order_type, qty, price)
        if required <= available * 0.9:   # 10% safety buffer, never size to the exact edge
            return qty
    return 0   # can't afford even one lot with current margin

Why a safety buffer, not sizing to exactly the available margin

Margin requirements can shift between your check and your actual order placement — exchange SPAN parameters update periodically (sometimes intraday during high volatility), and other positions' mark-to-market (chapter 14) continuously eats into available margin. Sizing to exactly 100% of available margin means a routine SPAN increase or unrealized loss elsewhere on the account can cause your very next order to be rejected for insufficient funds moments after you computed it as "affordable."

Combine with fixed-fractional risk sizing (chapter 71-72) — margin as a ceiling, not the primary sizing method

def final_position_size(kite, risk_based_qty: int, exchange, symbol, side, product, order_type, lot_size, price=None) -> int:
    margin_capped_qty = margin_aware_size(kite, exchange, symbol, side, product, order_type, lot_size, max_lots=risk_based_qty // lot_size, price=price)
    return min(risk_based_qty, margin_capped_qty)

Your risk-based sizing (how much you're *willing* to lose) and your margin-based sizing (how much you *can afford* to hold) are two independent constraints — take the smaller of the two, never let one silently override the other's intent. A strategy that's margin-capped below its intended risk-based size on a given trade is a signal your account is under-capitalized for the strategy's design, worth noticing rather than just quietly accepting a smaller position every time.

Next: 079 — From idea to testable hypothesis