Skip to content

Fix margin call handling for single-lot option strategy position groups#9612

Merged
jhonabreul merged 1 commit into
QuantConnect:masterfrom
jhonabreul:bug-option-strategy-margin-call-zero-quantity-group
Jul 13, 2026
Merged

Fix margin call handling for single-lot option strategy position groups#9612
jhonabreul merged 1 commit into
QuantConnect:masterfrom
jhonabreul:bug-option-strategy-margin-call-zero-quantity-group

Conversation

@jhonabreul

Copy link
Copy Markdown
Collaborator

Description

Fixes an unhandled InvalidOperationException: Sequence contains no matching element that kills a backtest/live algorithm when a margin call requires a partial reduction of an option strategy position group the user holds 1 lot of.

Root cause. When DefaultMarginCallModel.GenerateMarginCallOrders requests a partial reduction, PositionGroupBuyingPowerModel.GetPositionGroupOrderQuantity solves for the order quantity by stepping the group quantity one unit at a time toward the target and asking the buying power model for the margin at each trial quantity. For a 1-lot group, the first trial is WithQuantity(0) — a legitimate probe meaning "what margin remains if the group is fully closed?", whose correct answer is zero. However, OptionStrategyPositionGroupBuyingPowerModel's strategy-specific margin helpers pattern-match on the legs (e.g. positions.Single(p => p.Quantity > 0)) and throw on a group whose legs all have zero quantity.

Fix. OptionStrategyPositionGroupBuyingPowerModel.GetInitialMarginRequirement and GetMaintenanceMargin now return zero margin for zero-quantity position groups, letting the quantity search correctly converge on full liquidation. Guarding once at the two public entry points covers the same Single(...) assumption in every strategy margin helper (call spreads, ladders, etc.).

Why the fix lives in the model and not the quantity-search loop. "Zero margin for an empty group" is the contract the rest of the engine already implements implicitly, and these are public API methods with multiple callers — guarding them fixes every path, not just this one. No other model needs the same change:

  • SecurityPositionGroupBuyingPowerModel (the only other position group model) sums per-position security margins, so a zero-quantity group naturally yields zero.
  • All security-level margin models already return zero at zero quantity: BuyingPowerModel/SecurityMarginModel/PatternDayTradingMarginModel scale margin with quantity/holdings value; OptionMarginModel takes the long branch (zero) for Quantity >= 0; FutureMarginModel and CryptoFutureMarginModel already have this exact explicit zero-quantity guard (which this PR follows as precedent); FuturesOptionsMarginModel delegates to the guarded FutureMarginModel; ConstantBuyingPowerModel scales with quantity; CashBuyingPowerModel and NullBuyingPowerModel have no margin requirements.

Related Issue

N/A — diagnosed from a cloud backtest failure. Reproducible with any two-leg strategy: hold 1 lot, drift into a margin call that targets a partial reduction.

Motivation and Context

Any algorithm holding a single lot of any option strategy (no buying power overrides needed) whose equity drops enough for DefaultMarginCallModel to request a partial reduction crashes with an unhandled runtime error instead of being liquidated. The margin call should fully liquidate the group, since a single lot cannot be partially reduced.

Requires Documentation Change

No.

How Has This Been Tested?

  • New unit test GetsZeroMarginRequirementsForZeroQuantityPositionGroup asserts zero initial/maintenance margin for zero-quantity groups across all option strategy definitions covered by the existing margin test cases.
  • New unit test FullyLiquidatesSingleLotGroupWhenMarginCallRequiresPartialReduction emulates the margin call model's delta-buying-power request on a 1-lot bull put spread and asserts full liquidation instead of a throw.
  • New regression algorithm SingleLotOptionStrategyMarginCallRegressionAlgorithm reproduces the original crash end-to-end (1-lot SPX bull put spread + equity drawdown triggering a margin call): it previously died with the InvalidOperationException; it now receives one OnMarginCall with two full-liquidation leg orders, all filled.
  • Existing OptionStrategyPositionGroupBuyingPowerModelTests fixture passes.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • Refactor (non-breaking change which improves implementation)
  • Performance (non-breaking change which improves performance. Please add associated performance test and results)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Non-functional change (xml comments/documentation/etc)

Checklist:

  • My code follows the code style of this project.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • My branch follows the naming convention bug-<issue#>-<description> or feature-<issue#>-<description>

A margin call requiring a partial reduction of an option strategy
position group probes a zero-quantity trial group while solving for the
order quantity. The option strategy margin helpers assume at least one
leg with non-zero quantity and threw InvalidOperationException,
crashing the algorithm. Return zero initial/maintenance margin for
zero-quantity groups so the margin call converges on full liquidation.
@jhonabreul jhonabreul marked this pull request as ready for review July 13, 2026 17:24

@Martin-Molinero Martin-Molinero left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@jhonabreul jhonabreul merged commit c22774e into QuantConnect:master Jul 13, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants