Fix margin call handling for single-lot option strategy position groups#9612
Merged
jhonabreul merged 1 commit intoJul 13, 2026
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes an unhandled
InvalidOperationException: Sequence contains no matching elementthat 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.GenerateMarginCallOrdersrequests a partial reduction,PositionGroupBuyingPowerModel.GetPositionGroupOrderQuantitysolves 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 isWithQuantity(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.GetInitialMarginRequirementandGetMaintenanceMarginnow 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 sameSingle(...)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.BuyingPowerModel/SecurityMarginModel/PatternDayTradingMarginModelscale margin with quantity/holdings value;OptionMarginModeltakes the long branch (zero) forQuantity >= 0;FutureMarginModelandCryptoFutureMarginModelalready have this exact explicit zero-quantity guard (which this PR follows as precedent);FuturesOptionsMarginModeldelegates to the guardedFutureMarginModel;ConstantBuyingPowerModelscales with quantity;CashBuyingPowerModelandNullBuyingPowerModelhave 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
DefaultMarginCallModelto 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?
GetsZeroMarginRequirementsForZeroQuantityPositionGroupasserts zero initial/maintenance margin for zero-quantity groups across all option strategy definitions covered by the existing margin test cases.FullyLiquidatesSingleLotGroupWhenMarginCallRequiresPartialReductionemulates the margin call model's delta-buying-power request on a 1-lot bull put spread and asserts full liquidation instead of a throw.SingleLotOptionStrategyMarginCallRegressionAlgorithmreproduces the original crash end-to-end (1-lot SPX bull put spread + equity drawdown triggering a margin call): it previously died with theInvalidOperationException; it now receives oneOnMarginCallwith two full-liquidation leg orders, all filled.OptionStrategyPositionGroupBuyingPowerModelTestsfixture passes.Types of changes
Checklist:
bug-<issue#>-<description>orfeature-<issue#>-<description>