From 65140ec360d8f07f4ec6922a952874922fa81583 Mon Sep 17 00:00:00 2001 From: bigmomma Date: Wed, 17 Jun 2026 10:49:24 -0400 Subject: [PATCH] Fix option target sizing with quote prices --- Common/Securities/Option/OptionMarginModel.cs | 18 ++++++- .../OptionMarginBuyingPowerModelTests.cs | 53 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/Common/Securities/Option/OptionMarginModel.cs b/Common/Securities/Option/OptionMarginModel.cs index 2da6504ca91b..d0ff461592b4 100644 --- a/Common/Securities/Option/OptionMarginModel.cs +++ b/Common/Securities/Option/OptionMarginModel.cs @@ -107,15 +107,31 @@ public override InitialMargin GetInitialMarginRequirement(InitialMarginParameter { var security = parameters.Security; var quantity = parameters.Quantity; + var price = GetInitialMarginPrice(security, quantity); var value = security.QuoteCurrency.ConversionRate * security.SymbolProperties.ContractMultiplier - * security.Price + * price * quantity; // Initial margin requirement for long options is only the premium that is paid upfront return new OptionInitialMargin(parameters.Quantity >= 0 ? 0 : value * GetMarginRequirement(security, quantity, value), value); } + private static decimal GetInitialMarginPrice(Security security, decimal quantity) + { + if (quantity > 0 && security.AskPrice != 0m) + { + return security.AskPrice; + } + + if (quantity < 0 && security.BidPrice != 0m) + { + return security.BidPrice; + } + + return security.Price; + } + /// /// The percentage of the holding's absolute cost that must be held in free cash in order to avoid a margin call /// diff --git a/Tests/Common/Securities/OptionMarginBuyingPowerModelTests.cs b/Tests/Common/Securities/OptionMarginBuyingPowerModelTests.cs index 19a77e4170de..043e8b45a11d 100644 --- a/Tests/Common/Securities/OptionMarginBuyingPowerModelTests.cs +++ b/Tests/Common/Securities/OptionMarginBuyingPowerModelTests.cs @@ -78,6 +78,59 @@ public void TestLongCallsPuts() Assert.AreEqual(0m, buyingPowerModel.GetMaintenanceMargin(optionCall)); } + [Test] + public void LongOptionInitialMarginUsesAskPriceIfAvailable() + { + const decimal bidPrice = 1.5m; + const decimal askPrice = 2.5m; + const decimal lastPrice = 2m; + const decimal underlyingPrice = 200m; + const decimal quantity = 10m; + + var equity = CreateEquity(); + equity.SetMarketPrice(new Tick { Value = underlyingPrice }); + + var optionCall = CreateOption(equity, OptionRight.Call, 192m); + optionCall.SetMarketPrice(new Tick + { + Value = lastPrice, + BidPrice = bidPrice, + AskPrice = askPrice, + TickType = TickType.Quote + }); + + var buyingPowerModel = new OptionMarginModel(); + var expectedPremium = askPrice * optionCall.SymbolProperties.ContractMultiplier * quantity; + + Assert.AreEqual(expectedPremium, buyingPowerModel.GetInitialMarginRequirement(optionCall, quantity)); + } + + [Test] + public void ShortOptionInitialMarginUsesBidPriceIfAvailable() + { + const decimal bidPrice = 1.5m; + const decimal askPrice = 2.5m; + const decimal lastPrice = 2m; + const decimal underlyingPrice = 200m; + const decimal quantity = -10m; + + var equity = CreateEquity(); + equity.SetMarketPrice(new Tick { Value = underlyingPrice }); + + var optionCall = CreateOption(equity, OptionRight.Call, 192m); + optionCall.SetMarketPrice(new Tick + { + Value = lastPrice, + BidPrice = bidPrice, + AskPrice = askPrice, + TickType = TickType.Quote + }); + + var buyingPowerModel = new OptionMarginModel(); + + Assert.AreEqual(-41500, (double)buyingPowerModel.GetInitialMarginRequirement(optionCall, quantity), delta: 0.01); + } + [Test] public void TestShortCallsITM() {