When to Use
Use this skill in multi-leg trading algorithms (e.g. Options Delta Hedging, Convertible Bond Arbitrage, ETF Basis Trading, Cross-Asset Pairs) where primary leg fills must be immediately hedged in a secondary asset. Failing to synchronize hedge executions introduces severe Legging Risk—where market movements during the execution gap create unhedged directional exposure and slippage. This module calculates hedge quantities from fill events (including partial fills), generates hedge orders upon primary fill events, measures dispatch and fill latency against a configurable SLA, and force-flags any hedge left incomplete past an unhedged-exposure timeout so the primary leg can be emergency-unwound.
When NOT to Use
- Single-leg strategies with no hedge offset — there is no legging risk to synchronize.
- Passive / slow rebalancing (e.g. daily portfolio rebalancing against closing prices) where sub-second synchronization adds complexity without benefit.
- Exchange-native combos: when the venue offers the spread as a single instrument, prefer it over synthesizing the legs yourself. US options exchanges auction complex orders "as a packaged order with one net price" (Cboe), which removes the leg-to-leg gap this engine exists to measure. Note the limit: complex orders can still leg into the single-leg book, and Cboe issues a standing regulatory circular on "Legging Complex and Other Multi Part Orders" warning of risks "inherent in legging multi-part orders related to timing of the executions and potential for price movement". Confirm per venue and order type that your combo actually executes as a package before disabling synchronization.
- Atomic cross-asset legs are impossible on most venues — this engine minimizes the unhedged window; it cannot eliminate it. Do not treat the SLA flags as a guarantee of zero exposure.
Prerequisites
- Primary leg fill notification payload (
strategy_id,symbol,fill_qty,fill_price,fill_timestamp_ms) delivered per fill event, not per order. - A stable, unique
fill_idper economic fill. It is the deduplication key: the engine deriveshedge_order_idfrom it and refuses to hedge the samefill_idtwice. If your gateway does not supply a durable execution id, derive one before calling — a counter that resets on reconnect will collide and block legitimate hedges. - Strategy parameters:
hedge_symbol,hedge_ratio,max_sync_delay_ms(default 100 ms),unhedged_timeout_ms(default 500 ms), and an emergency-unwind callback wired to the primary-leg cancel/flatten path. - Threading: public methods are lock-guarded, so the gateway thread delivering fills and the timer thread driving
enforce_unhedged_timeouts()may differ.unwind_callbackis invoked outside the lock, so it runs after the hedge has leftpending_hedges. - Hedge ratio semantics:
hedge_ratiois expressed in hedge units per one unit of primary quantity and MUST include the contract multiplier. For US equity options, one contract usually represents 100 shares of the underlying (OIC/OCC) — a 0.50-delta call therefore needshedge_ratio = 0.50 × 100 = 50.0shares per contract. Corporate-action-adjusted (non-standard) contracts can carry multipliers other than 100 — verify per instrument.
Workflow
- Fill Event Ingestion: Receive each primary leg execution fill ($Q_{primary}, P_{primary}, t_{fill}$). Validate
strategy_idand symbol before generating any order; reject non-finite or zero quantities, and reject a hedge quantity that rounds to zero rather than sending a zero-quantity order.- Decision point — is this fill new?
generate_hedge_orderdeduplicates onfill_idand raises if that fill was already hedged (live or finalized). A resent execution report is not a new fill: FIX puts duplicate detection on the application layer, because the session layer cannot distinguish aPossResendfrom a genuine fill. Do not catch this and re-dispatch — reconcile against the OMS to establish whether the venue already holds the hedge.
- Decision point — is this fill new?
- Hedge Quantity Calculation:
- $\text{Hedge Qty} = -1.0 \times Q_{primary} \times \text{Hedge Ratio}$ (ratio includes the contract multiplier — see Prerequisites).
- Synchronized Hedge Order Dispatch:
- Dispatch the market or aggressive limit order to the hedge venue, then record the dispatch timestamp via
mark_dispatched()so dispatch latency is measured separately from fill latency.
- Dispatch the market or aggressive limit order to the hedge venue, then record the dispatch timestamp via
- Incremental Fill Processing: Call
process_hedge_fill()for every hedge fill callback, including partials. Quantities accumulate; the order stays pending until the cumulative fill reaches the target, so residual exposure is always tracked. Reject wrong-side fills outright — they indicate position books disagreeing, not a retryable condition. - Latency & Synchronization Audit:
- Measure synchronization delay $\Delta t = t_{hedge_fill} - t_{fill}$.
- $\Delta t \le \text{max_sync_delay_ms}$ and fully filled →
SYNCHRONIZED_OK. - Completed but late ($\Delta t > \text{max_sync_delay_ms}$) →
SYNC_DELAY_BREACH: flag for audit and have the execution gateway aggressively reprice any remaining quote (repricing is the OMS's action; this engine supplies the flag). - Incomplete within the window →
PARTIALLY_FILLEDwith the residual quantity exposed.
- Legging Risk Exception Handling: Run
enforce_unhedged_timeouts(now_ms)on a periodic timer. Any hedge still incomplete afterunhedged_timeout_msfrom its primary fill is flaggedUNHEDGED_TIMEOUT_UNWINDand routed to the unwind callback to flatten the primary leg — do not wait for a late fill to arrive before unwinding, and never re-submit the hedge order on a timeout without first reconciling position state.- The same breach observed by a late fill callback instead of the timer routes to the same callback. Whichever observer sees it first changes only the timing, never whether the primary leg is unwound. This matters most for a late partial fill: it finalizes the hedge order, so the timer sweep will never revisit it, and the residual exposure would otherwise leave tracking entirely.
Full procedure: see
references/workflows.md. Standards reference: seereferences/standards.md. Printable pre-flight checklist: seeassets/checklist.md.
Common Pitfalls
- Un-Synchronized Legging: Routing primary and hedge legs independently without cross-leg atomic state tracking.
- Hedging a Redelivered Fill Twice: a gateway reconnect, a
PossResendexecution report, or an OMS replay re-delivers a fill you already hedged. Keying hedge state onfill_idand rejecting repeats is the control; regenerating "just in case" discards the accumulated fill state of the live hedge and puts a second hedge on the market. SEC Rule 15c3-5(c)(1)(ii) requires pre-trade controls that reject orders "that indicate duplicative orders" — this is that control on the hedge path. - Ignoring Partial Fills: Waiting for the primary order to fill 100% before routing any hedge leg — and, on the hedge side, closing out hedge state on the first partial fill callback, which silently drops the residual unhedged quantity from tracking. Both directions must be incremental.
- Static Delta Assumption: Using static option delta for hedging during fast market moves without updating real-time implied volatility / spot delta.
- Forgetting the Contract Multiplier: Using delta directly as
hedge_ratio(e.g. 0.50 instead of 50.0 for a standard 100-share equity option contract) under-hedges by 100×. Adjusted contracts can carry non-standard multipliers — verify per instrument. - No Timer for the No-Fill Case: Reacting only to fill callbacks means a hedge that never fills is never unwound. Timeout enforcement must be driven by a clock, not by the arrival of a (possibly late) fill.
- Retrying a Timed-Out Hedge Blindly: A timeout does not tell you whether the venue holds a live order. Re-submitting without cancel/reconcile can double the hedge; unwind or reconcile first.
- Two Timeout Paths That Disagree: if the late-fill path only flags a breach while the timer sweep acts on it, a hedge whose late fill lands just before the next tick is silently never unwound — and a late partial fill takes its residual exposure out of tracking with it. Both observers must route to one unwind path.
- Running the Sweep on Its Own Thread Without Synchronization: a timer thread sweeping while the gateway thread accumulates fills races on the same hedge records — lost quantity updates, or the same order finalized twice. Guard the state, and never hold that guard while calling out to an unwind handler you do not control.
Verification
- Instantiate
CrossAssetHedgeSynchronizer. Register an options delta hedge strategy (Primary=AAPL_250516_C200,Hedge=AAPL,hedge_ratio= 50.0 = 0.50 delta × 100-share multiplier,Max Delay= 100 ms). Ingest primary fill of +10 option contracts at $t = 1000\text{ ms}$. Verify synchronizer generates hedge order of -500 sharesAAPL. Submit hedge fill at $t = 1040\text{ ms}$ ($\Delta t = 40\text{ ms}$) and verifySYNCHRONIZED_OKstatus. - Verify partial-fill tracking: submit -200 shares at $t = 1030\text{ ms}$ (expect
PARTIALLY_FILLED, residual 300, order still pending), then -300 shares at $t = 1060\text{ ms}$ (expectSYNCHRONIZED_OK, residual 0). - Verify timeout enforcement: generate a hedge order, call
enforce_unhedged_timeouts(now_ms=2000)with no hedge fill submitted, and confirm aUNHEDGED_TIMEOUT_UNWINDstatus is returned and the unwind callback fired. - Verify duplicate-fill rejection: after hedging
FILL_X, callinggenerate_hedge_orderagain withfill_id = FILL_Xmust raise while the live hedge keeps its accumulatedfilled_hedge_qtyand dispatch timestamp; the same must raise after that hedge has completed. - Verify unified timeout routing: submit a partial hedge fill past
unhedged_timeout_msand confirm the unwind callback fires, the residual is reported, and the subsequent sweep finds nothing left pending. - Verify rejection paths: wrong-side fill, duplicate fill for a completed order,
strategy_id/symbol mismatch, a hedge quantity rounding to zero, and NaN/zero quantities must all raiseValueError. - Run
python -m unittest discover -s skills/cross-asset-hedge-execution-synchronization/scripts.