Lean  $LEAN_TAG$
OptionStrategyPositionGroupBuyingPowerModel.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.Linq;
21 using System.Collections.Generic;
22 
24 {
25  /// <summary>
26  /// Option strategy buying power model
27  /// </summary>
28  /// <remarks>
29  /// Reference used https://www.interactivebrokers.com/en/index.php?f=26660
30  /// </remarks>
32  {
33  private readonly OptionStrategy _optionStrategy;
34 
35  /// <summary>
36  /// Creates a new instance for a target option strategy
37  /// </summary>
38  /// <param name="optionStrategy">The option strategy to model</param>
40  {
41  _optionStrategy = optionStrategy;
42  }
43 
44  /// <summary>
45  /// Gets the margin currently allocated to the specified holding
46  /// </summary>
47  /// <param name="parameters">An object containing the security</param>
48  /// <returns>The maintenance margin required for the </returns>
50  {
51  if (_optionStrategy == null)
52  {
53  // we could be liquidating a position
54  return new MaintenanceMargin(0);
55  }
56  else if (_optionStrategy.Name == OptionStrategyDefinitions.ProtectivePut.Name || _optionStrategy.Name == OptionStrategyDefinitions.ProtectiveCall.Name)
57  {
58  // Minimum (((10% * Call/Put Strike Price) + Call/Put Out of the Money Amount), Short Stock/Long Maintenance Requirement)
59  var optionPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => position.Symbol.SecurityType.IsOption());
60  var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
61  var optionSecurity = (Option)parameters.Portfolio.Securities[optionPosition.Symbol];
62  var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
63 
64  var absOptionQuantity = Math.Abs(optionPosition.Quantity);
65  var outOfTheMoneyAmount = optionSecurity.OutOfTheMoneyAmount(underlyingSecurity.Price) * optionSecurity.ContractUnitOfTrade * absOptionQuantity;
66 
67  var underlyingMarginRequired = Math.Abs(underlyingSecurity.BuyingPowerModel.GetMaintenanceMargin(MaintenanceMarginParameters.ForQuantityAtCurrentPrice(
68  underlyingSecurity, underlyingPosition.Quantity)));
69 
70  var result = Math.Min(0.1m * optionSecurity.StrikePrice * optionSecurity.ContractUnitOfTrade * absOptionQuantity + outOfTheMoneyAmount, underlyingMarginRequired);
71  var inAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
72 
73  return new MaintenanceMargin(inAccountCurrency);
74  }
75  else if(_optionStrategy.Name == OptionStrategyDefinitions.CoveredCall.Name)
76  {
77  // MAX[In-the-money amount + Margin(long stock evaluated at min(mark price, strike(short call))), min(stock value, max(call value, long stock margin))]
78  var optionPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => position.Symbol.SecurityType.IsOption());
79  var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
80  var optionSecurity = (Option)parameters.Portfolio.Securities[optionPosition.Symbol];
81  var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
82 
83  var intrinsicValue = optionSecurity.GetIntrinsicValue(underlyingSecurity.Price);
84  var inTheMoneyAmount = intrinsicValue * optionSecurity.ContractUnitOfTrade * Math.Abs(optionPosition.Quantity);
85 
86  var underlyingValue = underlyingSecurity.Holdings.GetQuantityValue(underlyingPosition.Quantity).InAccountCurrency;
87  var optionValue = optionSecurity.Holdings.GetQuantityValue(optionPosition.Quantity).InAccountCurrency;
88 
89  // mark price, strike price
90  var underlyingPriceToEvaluate = Math.Min(underlyingSecurity.Price, optionSecurity.ScaledStrikePrice);
91  var underlyingHypotheticalValue = underlyingSecurity.Holdings.GetQuantityValue(underlyingPosition.Quantity, underlyingPriceToEvaluate).InAccountCurrency;
92 
93  var hypotheticalMarginRequired = underlyingSecurity.BuyingPowerModel.GetMaintenanceMargin(
94  new MaintenanceMarginParameters(underlyingSecurity, underlyingPosition.Quantity, 0, underlyingHypotheticalValue));
95  var marginRequired = underlyingSecurity.BuyingPowerModel.GetMaintenanceMargin(
96  new MaintenanceMarginParameters(underlyingSecurity, underlyingPosition.Quantity, 0, underlyingValue));
97 
98  var secondOperand = Math.Min(underlyingValue, Math.Max(optionValue, marginRequired));
99  var result = Math.Max(inTheMoneyAmount + hypotheticalMarginRequired, secondOperand);
100  var inAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
101 
102  return new MaintenanceMargin(inAccountCurrency);
103  }
104  else if (_optionStrategy.Name == OptionStrategyDefinitions.CoveredPut.Name)
105  {
106  // Initial Stock Margin Requirement + In the Money Amount
107  var optionPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => position.Symbol.SecurityType.IsOption());
108  var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
109  var optionSecurity = (Option)parameters.Portfolio.Securities[optionPosition.Symbol];
110  var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
111 
112  var intrinsicValue = optionSecurity.GetIntrinsicValue(underlyingSecurity.Price);
113  var inTheMoneyAmount = intrinsicValue * optionSecurity.ContractUnitOfTrade * Math.Abs(optionPosition.Quantity);
114 
115  var initialMarginRequirement = underlyingSecurity.BuyingPowerModel.GetInitialMarginRequirement(underlyingSecurity, underlyingPosition.Quantity);
116 
117  var result = Math.Abs(initialMarginRequirement) + inTheMoneyAmount;
118  var inAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
119 
120  return new MaintenanceMargin(inAccountCurrency);
121  }
122  else if (_optionStrategy.Name == OptionStrategyDefinitions.NakedCall.Name
123  || _optionStrategy.Name == OptionStrategyDefinitions.NakedPut.Name)
124  {
125  var option = parameters.PositionGroup.Positions.Single();
126  var security = (Option)parameters.Portfolio.Securities[option.Symbol];
127  var margin = security.BuyingPowerModel.GetMaintenanceMargin(MaintenanceMarginParameters.ForQuantityAtCurrentPrice(security,
128  option.Quantity));
129 
130  return new MaintenanceMargin(margin);
131  }
132  else if (_optionStrategy.Name == OptionStrategyDefinitions.BearCallSpread.Name
133  || _optionStrategy.Name == OptionStrategyDefinitions.BullCallSpread.Name)
134  {
135  var result = GetLongCallShortCallStrikeDifferenceMargin(parameters.PositionGroup, parameters.Portfolio);
136  return new MaintenanceMargin(result);
137  }
138  else if (_optionStrategy.Name == OptionStrategyDefinitions.CallCalendarSpread.Name
139  || _optionStrategy.Name == OptionStrategyDefinitions.PutCalendarSpread.Name)
140  {
141  return new MaintenanceMargin(0);
142  }
143  else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortCallCalendarSpread.Name
144  || _optionStrategy.Name == OptionStrategyDefinitions.ShortPutCalendarSpread.Name)
145  {
146  var shortCall = parameters.PositionGroup.Positions.Single(position => position.Quantity < 0);
147  var shortCallSecurity = (Option)parameters.Portfolio.Securities[shortCall.Symbol];
148  var result = shortCallSecurity.BuyingPowerModel.GetMaintenanceMargin(MaintenanceMarginParameters.ForQuantityAtCurrentPrice(
149  shortCallSecurity, shortCall.Quantity));
150 
151  return new MaintenanceMargin(result);
152  }
153  else if (_optionStrategy.Name == OptionStrategyDefinitions.BearPutSpread.Name
154  || _optionStrategy.Name == OptionStrategyDefinitions.BullPutSpread.Name)
155  {
156  var result = GetShortPutLongPutStrikeDifferenceMargin(parameters.PositionGroup, parameters.Portfolio);
157  return new MaintenanceMargin(result);
158  }
159  else if (_optionStrategy.Name == OptionStrategyDefinitions.Straddle.Name || _optionStrategy.Name == OptionStrategyDefinitions.Strangle.Name)
160  {
161  // Margined as two long options: since there is not margin requirements for long options, we return 0
162  return new MaintenanceMargin(0);
163  }
164  else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortStraddle.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortStrangle.Name)
165  {
166  var result = GetShortStraddleStrangleMargin(parameters.PositionGroup, parameters.Portfolio,
167  (option, quantity) => Math.Abs(option.BuyingPowerModel.GetMaintenanceMargin(
169  return new MaintenanceMargin(result);
170  }
171  else if (_optionStrategy.Name == OptionStrategyDefinitions.ButterflyCall.Name || _optionStrategy.Name == OptionStrategyDefinitions.ButterflyPut.Name)
172  {
173  return new MaintenanceMargin(0);
174  }
175  else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortButterflyPut.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortButterflyCall.Name)
176  {
177  var result = GetMiddleAndLowStrikeDifference(parameters.PositionGroup, parameters.Portfolio);
178  return new MaintenanceMargin(result);
179  }
180  else if (_optionStrategy.Name == OptionStrategyDefinitions.IronCondor.Name)
181  {
182  var result = GetShortPutLongPutStrikeDifferenceMargin(parameters.PositionGroup, parameters.Portfolio);
183  return new MaintenanceMargin(result);
184  }
185 
186  throw new NotImplementedException($"Option strategy {_optionStrategy.Name} margin modeling has yet to be implemented");
187  }
188 
189  /// <summary>
190  /// The margin that must be held in order to increase the position by the provided quantity
191  /// </summary>
192  /// <param name="parameters">An object containing the security and quantity</param>
194  {
195  var result = 0m;
196 
197  if (_optionStrategy == null)
198  {
199  result = 0;
200  }
201  else if (_optionStrategy.Name == OptionStrategyDefinitions.ProtectivePut.Name || _optionStrategy.Name == OptionStrategyDefinitions.ProtectiveCall.Name)
202  {
203  // Initial Standard Stock Margin Requirement
204  var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
205  var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
206 
207  result = Math.Abs(underlyingSecurity.BuyingPowerModel.GetInitialMarginRequirement(underlyingSecurity, underlyingPosition.Quantity));
208  result = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, underlyingSecurity.QuoteCurrency.Symbol);
209  }
210  else if(_optionStrategy.Name == OptionStrategyDefinitions.CoveredCall.Name)
211  {
212  // Max(Call Value, Long Stock Initial Margin)
213  var optionPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => position.Symbol.SecurityType.IsOption());
214  var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
215  var optionSecurity = (Option)parameters.Portfolio.Securities[optionPosition.Symbol];
216  var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
217 
218  var optionValue = Math.Abs(optionSecurity.Holdings.GetQuantityValue(optionPosition.Quantity).InAccountCurrency);
219 
220  var marginRequired = underlyingSecurity.BuyingPowerModel.GetInitialMarginRequirement(underlyingSecurity, underlyingPosition.Quantity);
221 
222  // IB charges more than expected, this formula was inferred based on actual requirements see 'CoveredCallInitialMarginRequirementsTestCases'
223  result = optionValue * 0.8m + marginRequired;
224  result = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
225  }
226  else if (_optionStrategy.Name == OptionStrategyDefinitions.CoveredPut.Name)
227  {
228  // Initial Stock Margin Requirement + In the Money Amount
230  }
231  else if (_optionStrategy.Name == OptionStrategyDefinitions.NakedCall.Name
232  || _optionStrategy.Name == OptionStrategyDefinitions.NakedPut.Name)
233  {
234  var option = parameters.PositionGroup.Positions.Single();
235  var security = (Option)parameters.Portfolio.Securities[option.Symbol];
236  var margin = security.BuyingPowerModel.GetInitialMarginRequirement(new InitialMarginParameters(security, option.Quantity));
237  var optionMargin = margin as OptionInitialMargin;
238 
239  if (optionMargin != null)
240  {
241  return new OptionInitialMargin(Math.Abs(optionMargin.ValueWithoutPremium), optionMargin.Premium);
242  }
243 
244  return margin;
245  }
246  else if (_optionStrategy.Name == OptionStrategyDefinitions.BearCallSpread.Name
247  || _optionStrategy.Name == OptionStrategyDefinitions.BullCallSpread.Name)
248  {
249  result = GetLongCallShortCallStrikeDifferenceMargin(parameters.PositionGroup, parameters.Portfolio);
250  }
251  else if (_optionStrategy.Name == OptionStrategyDefinitions.CallCalendarSpread.Name
252  || _optionStrategy.Name == OptionStrategyDefinitions.PutCalendarSpread.Name)
253  {
254  result = 0m;
255  }
256  else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortCallCalendarSpread.Name
257  || _optionStrategy.Name == OptionStrategyDefinitions.ShortPutCalendarSpread.Name)
258  {
259  var shortOptionPosition = parameters.PositionGroup.Positions.Single(position => position.Quantity < 0);
260  var shortOption = (Option)parameters.Portfolio.Securities[shortOptionPosition.Symbol];
261  result = Math.Abs(shortOption.BuyingPowerModel.GetInitialMarginRequirement(shortOption, shortOptionPosition.Quantity));
262  }
263  else if (_optionStrategy.Name == OptionStrategyDefinitions.BearPutSpread.Name
264  || _optionStrategy.Name == OptionStrategyDefinitions.BullPutSpread.Name)
265  {
266  result = GetShortPutLongPutStrikeDifferenceMargin(parameters.PositionGroup, parameters.Portfolio);
267  }
268  else if (_optionStrategy.Name == OptionStrategyDefinitions.Straddle.Name || _optionStrategy.Name == OptionStrategyDefinitions.Strangle.Name)
269  {
270  // Margined as two long options: since there is not margin requirements for long options, we return 0
271  result = 0m;
272  }
273  else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortStraddle.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortStrangle.Name)
274  {
275  result = GetShortStraddleStrangleMargin(parameters.PositionGroup, parameters.Portfolio,
276  (option, quantity) => Math.Abs(option.BuyingPowerModel.GetInitialMarginRequirement(option, quantity)));
277  }
278  else if (_optionStrategy.Name == OptionStrategyDefinitions.ButterflyCall.Name || _optionStrategy.Name == OptionStrategyDefinitions.ButterflyPut.Name)
279  {
280  result = 0m;
281  }
282  else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortButterflyPut.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortButterflyCall.Name)
283  {
284  result = GetMiddleAndLowStrikeDifference(parameters.PositionGroup, parameters.Portfolio);
285  }
286  else if (_optionStrategy.Name == OptionStrategyDefinitions.IronCondor.Name)
287  {
288  result = GetShortPutLongPutStrikeDifferenceMargin(parameters.PositionGroup, parameters.Portfolio);
289  }
290  else
291  {
292  throw new NotImplementedException($"Option strategy {_optionStrategy.Name} margin modeling has yet to be implemented");
293  }
294 
295  // Add premium to initial margin only when it is positive (the user must pay the premium)
296  var premium = 0m;
297  foreach (var position in parameters.PositionGroup.Positions.Where(position => position.Symbol.SecurityType.IsOption()))
298  {
299  var option = (Option)parameters.Portfolio.Securities[position.Symbol];
300  premium += option.Holdings.GetQuantityValue(position.Quantity).InAccountCurrency;
301  }
302 
303  return new OptionInitialMargin(result, premium);
304  }
305 
306  /// <summary>
307  /// Gets the total margin required to execute the specified order in units of the account currency including fees
308  /// </summary>
309  /// <param name="parameters">An object containing the portfolio, the security and the order</param>
310  /// <returns>The total margin in terms of the currency quoted in the order</returns>
312  {
313  var security = parameters.Portfolio.Securities[parameters.Order.Symbol];
314  var fees = security.FeeModel.GetOrderFee(new OrderFeeParameters(security, parameters.Order));
315  var feesInAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(fees.Value);
316 
317  var initialMarginRequired = GetInitialMarginRequirement(new PositionGroupInitialMarginParameters(parameters.Portfolio, parameters.PositionGroup));
318 
319  var feesWithSign = Math.Sign(initialMarginRequired) * feesInAccountCurrency.Amount;
320 
321  return new InitialMargin(feesWithSign + initialMarginRequired);
322  }
323 
324  /// <summary>
325  /// Gets the initial margin required for the specified contemplated position group.
326  /// Used by <see cref="GetReservedBuyingPowerImpact"/> to get the contemplated groups margin.
327  /// </summary>
328  protected override decimal GetContemplatedGroupsInitialMargin(SecurityPortfolioManager portfolio, PositionGroupCollection contemplatedGroups,
329  List<IPosition> ordersPositions)
330  {
331  var contemplatedMargin = 0m;
332  foreach (var contemplatedGroup in contemplatedGroups)
333  {
334  // We use the initial margin requirement as the contemplated groups margin in order to ensure
335  // the available buying power is enough to execute the order.
336  var initialMargin = contemplatedGroup.BuyingPowerModel.GetInitialMarginRequirement(
337  new PositionGroupInitialMarginParameters(portfolio, contemplatedGroup));
338  var optionInitialMargin = initialMargin as OptionInitialMargin;
339  contemplatedMargin += optionInitialMargin?.ValueWithoutPremium ?? initialMargin;
340  }
341 
342  // Now we need to add the premium paid for the order:
343  // This should always return a single group since it is a single order/combo
344  var ordersGroups = portfolio.Positions.ResolvePositionGroups(new PositionCollection(ordersPositions));
345  foreach (var orderGroup in ordersGroups)
346  {
347  var initialMargin = orderGroup.BuyingPowerModel.GetInitialMarginRequirement(
348  new PositionGroupInitialMarginParameters(portfolio, orderGroup));
349  var optionInitialMargin = initialMargin as OptionInitialMargin;
350 
351  if (optionInitialMargin != null)
352  {
353  // We need to add the premium paid for the order. We use the TotalValue-Value difference instead of Premium
354  // to add it only when needed -- when it is debited from the account
355  contemplatedMargin += optionInitialMargin.Value - optionInitialMargin.ValueWithoutPremium;
356  }
357  }
358 
359  return contemplatedMargin;
360  }
361 
362  /// <summary>
363  /// Returns a string that represents the current object.
364  /// </summary>
365  /// <returns>A string that represents the current object.</returns>
366  public override string ToString()
367  {
368  return _optionStrategy.Name;
369  }
370 
371  /// <summary>
372  /// Returns the Maximum (Short Put Strike - Long Put Strike, 0)
373  /// </summary>
374  private static decimal GetShortPutLongPutStrikeDifferenceMargin(IPositionGroup positionGroup, SecurityPortfolioManager portfolio)
375  {
376  var longOption = positionGroup.Positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Put && position.Quantity > 0);
377  var shortOption = positionGroup.Positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Put && position.Quantity < 0);
378  var optionSecurity = (Option)portfolio.Securities[longOption.Symbol];
379 
380  // Maximum (Short Put Strike - Long Put Strike, 0)
381  var strikeDifference = shortOption.Symbol.ID.StrikePrice - longOption.Symbol.ID.StrikePrice;
382 
383  var result = Math.Max(strikeDifference * optionSecurity.ContractUnitOfTrade * Math.Abs(positionGroup.Quantity), 0);
384 
385  // convert into account currency
386  return portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
387  }
388 
389  /// <summary>
390  /// Returns the Maximum (Strike Long Call - Strike Short Call, 0)
391  /// </summary>
392  private static decimal GetLongCallShortCallStrikeDifferenceMargin(IPositionGroup positionGroup, SecurityPortfolioManager portfolio)
393  {
394  var longOption = positionGroup.Positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Call && position.Quantity > 0);
395  var shortOption = positionGroup.Positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Call && position.Quantity < 0);
396  var optionSecurity = (Option)portfolio.Securities[longOption.Symbol];
397 
398  var strikeDifference = longOption.Symbol.ID.StrikePrice - shortOption.Symbol.ID.StrikePrice;
399 
400  var result = Math.Max(strikeDifference * optionSecurity.ContractUnitOfTrade * Math.Abs(positionGroup.Quantity), 0);
401 
402  // convert into account currency
403  return portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
404  }
405 
406  /// <summary>
407  /// Returns the Maximum (Middle Strike - Lowest Strike, 0)
408  /// </summary>
409  private static decimal GetMiddleAndLowStrikeDifference(IPositionGroup positionGroup, SecurityPortfolioManager portfolio)
410  {
411  var options = positionGroup.Positions.OrderBy(position => position.Symbol.ID.StrikePrice).ToList();
412  var lowestCallStrike = options[0].Symbol.ID.StrikePrice;
413  var middleCallStrike = options[1].Symbol.ID.StrikePrice;
414  var optionSecurity = (Option)portfolio.Securities[options[0].Symbol];
415 
416  var strikeDifference = Math.Max((middleCallStrike - lowestCallStrike) * optionSecurity.ContractUnitOfTrade * Math.Abs(positionGroup.Quantity), 0);
417 
418  // convert into account currency
419  return portfolio.CashBook.ConvertToAccountCurrency(strikeDifference, optionSecurity.QuoteCurrency.Symbol);
420  }
421 
422  /// <summary>
423  /// Returns the margin for a short straddle or strangle.
424  /// This is the same for both the initial margin requirement and the maintenance margin.
425  /// </summary>
426  private static decimal GetShortStraddleStrangleMargin(IPositionGroup positionGroup, SecurityPortfolioManager portfolio,
427  Func<Option, decimal, decimal> getOptionMargin)
428  {
429  var callOption = positionGroup.Positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Call);
430  var callSecurity = (Option)portfolio.Securities[callOption.Symbol];
431  var callMargin = getOptionMargin(callSecurity, callOption.Quantity);
432 
433  var putOption = positionGroup.Positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Put);
434  var putSecurity = (Option)portfolio.Securities[putOption.Symbol];
435  var putMargin = getOptionMargin(putSecurity, putOption.Quantity);
436 
437  var result = 0m;
438 
439  if (putMargin > callMargin)
440  {
441  result = putMargin + callSecurity.Price * callSecurity.ContractUnitOfTrade * Math.Abs(callOption.Quantity);
442  }
443  else
444  {
445  result = callMargin + putSecurity.Price * putSecurity.ContractUnitOfTrade * Math.Abs(putOption.Quantity);
446  }
447 
448  return result;
449  }
450  }
451 }