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 using QuantConnect.Orders;
23 
25 {
26  /// <summary>
27  /// Option strategy buying power model
28  /// </summary>
29  /// <remarks>
30  /// Reference used https://www.interactivebrokers.com/en/index.php?f=26660
31  /// </remarks>
33  {
34  private readonly OptionStrategy _optionStrategy;
35 
36  /// <summary>
37  /// Creates a new instance for a target option strategy
38  /// </summary>
39  /// <param name="optionStrategy">The option strategy to model</param>
41  {
42  _optionStrategy = optionStrategy;
43  }
44 
45  /// <summary>
46  /// Gets the margin currently allocated to the specified holding
47  /// </summary>
48  /// <param name="parameters">An object containing the security</param>
49  /// <returns>The maintenance margin required for the </returns>
51  {
52  if (_optionStrategy == null)
53  {
54  // we could be liquidating a position
55  return new MaintenanceMargin(0);
56  }
57  else if (_optionStrategy.Name == OptionStrategyDefinitions.ProtectivePut.Name || _optionStrategy.Name == OptionStrategyDefinitions.ProtectiveCall.Name)
58  {
59  // Minimum (((10% * Call/Put Strike Price) + Call/Put Out of the Money Amount), Short Stock/Long Maintenance Requirement)
60  var optionPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => position.Symbol.SecurityType.IsOption());
61  var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
62  var optionSecurity = (Option)parameters.Portfolio.Securities[optionPosition.Symbol];
63  var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
64 
65  var absOptionQuantity = Math.Abs(optionPosition.Quantity);
66  var outOfTheMoneyAmount = optionSecurity.OutOfTheMoneyAmount(underlyingSecurity.Price) * optionSecurity.ContractUnitOfTrade * absOptionQuantity;
67 
68  var underlyingMarginRequired = Math.Abs(underlyingSecurity.BuyingPowerModel.GetMaintenanceMargin(MaintenanceMarginParameters.ForQuantityAtCurrentPrice(
69  underlyingSecurity, underlyingPosition.Quantity)));
70 
71  var result = Math.Min(0.1m * optionSecurity.StrikePrice * optionSecurity.ContractUnitOfTrade * absOptionQuantity + outOfTheMoneyAmount, underlyingMarginRequired);
72  var inAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
73 
74  return new MaintenanceMargin(inAccountCurrency);
75  }
76  else if (_optionStrategy.Name == OptionStrategyDefinitions.CoveredCall.Name)
77  {
78  // 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))]
79  var optionPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => position.Symbol.SecurityType.IsOption());
80  var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
81  var optionSecurity = (Option)parameters.Portfolio.Securities[optionPosition.Symbol];
82  var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
83 
84  var intrinsicValue = optionSecurity.GetIntrinsicValue(underlyingSecurity.Price);
85  var inTheMoneyAmount = intrinsicValue * optionSecurity.ContractUnitOfTrade * Math.Abs(optionPosition.Quantity);
86 
87  var underlyingValue = underlyingSecurity.Holdings.GetQuantityValue(underlyingPosition.Quantity).InAccountCurrency;
88  var optionValue = optionSecurity.Holdings.GetQuantityValue(optionPosition.Quantity).InAccountCurrency;
89 
90  // mark price, strike price
91  var underlyingPriceToEvaluate = Math.Min(underlyingSecurity.Price, optionSecurity.ScaledStrikePrice);
92  var underlyingHypotheticalValue = underlyingSecurity.Holdings.GetQuantityValue(underlyingPosition.Quantity, underlyingPriceToEvaluate).InAccountCurrency;
93 
94  var hypotheticalMarginRequired = underlyingSecurity.BuyingPowerModel.GetMaintenanceMargin(
95  new MaintenanceMarginParameters(underlyingSecurity, underlyingPosition.Quantity, 0, underlyingHypotheticalValue));
96  var marginRequired = underlyingSecurity.BuyingPowerModel.GetMaintenanceMargin(
97  new MaintenanceMarginParameters(underlyingSecurity, underlyingPosition.Quantity, 0, underlyingValue));
98 
99  var secondOperand = Math.Min(underlyingValue, Math.Max(optionValue, marginRequired));
100  var result = Math.Max(inTheMoneyAmount + hypotheticalMarginRequired, secondOperand);
101  var inAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
102 
103  return new MaintenanceMargin(inAccountCurrency);
104  }
105  else if (_optionStrategy.Name == OptionStrategyDefinitions.CoveredPut.Name)
106  {
107  // Initial Stock Margin Requirement + In the Money Amount
108  var optionPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => position.Symbol.SecurityType.IsOption());
109  var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
110  var optionSecurity = (Option)parameters.Portfolio.Securities[optionPosition.Symbol];
111  var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
112 
113  var intrinsicValue = optionSecurity.GetIntrinsicValue(underlyingSecurity.Price);
114  var inTheMoneyAmount = intrinsicValue * optionSecurity.ContractUnitOfTrade * Math.Abs(optionPosition.Quantity);
115 
116  var initialMarginRequirement = underlyingSecurity.BuyingPowerModel.GetInitialMarginRequirement(underlyingSecurity, underlyingPosition.Quantity);
117 
118  var result = Math.Abs(initialMarginRequirement) + inTheMoneyAmount;
119  var inAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
120 
121  return new MaintenanceMargin(inAccountCurrency);
122  }
123  else if (_optionStrategy.Name == OptionStrategyDefinitions.ProtectiveCollar.Name)
124  {
125  // Minimum (((10% * Put Strike Price) + Put Out of the Money Amount), (25% * Call Strike Price))
126  var putPosition = parameters.PositionGroup.Positions.Single(position =>
127  position.Symbol.SecurityType.IsOption() && position.Symbol.ID.OptionRight == OptionRight.Put);
128  var callPosition = parameters.PositionGroup.Positions.Single(position =>
129  position.Symbol.SecurityType.IsOption() && position.Symbol.ID.OptionRight == OptionRight.Call);
130  var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
131  var putSecurity = (Option)parameters.Portfolio.Securities[putPosition.Symbol];
132  var callSecurity = (Option)parameters.Portfolio.Securities[callPosition.Symbol];
133  var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
134 
135  var putMarginRequirement = 0.1m * putSecurity.StrikePrice + putSecurity.OutOfTheMoneyAmount(underlyingSecurity.Price);
136  var callMarginRequirement = 0.25m * callSecurity.StrikePrice;
137 
138  // call and put has the exact same number of contracts
139  var contractUnits = Math.Abs(putPosition.Quantity) * putSecurity.ContractUnitOfTrade;
140  var result = Math.Min(putMarginRequirement, callMarginRequirement) * contractUnits;
141  var inAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, underlyingSecurity.QuoteCurrency.Symbol);
142 
143  return new MaintenanceMargin(inAccountCurrency);
144  }
145  else if (_optionStrategy.Name == OptionStrategyDefinitions.Conversion.Name)
146  {
147  return GetConversionMaintenanceMargin(parameters.PositionGroup, parameters.Portfolio, OptionRight.Call);
148  }
149  else if (_optionStrategy.Name == OptionStrategyDefinitions.ReverseConversion.Name)
150  {
151  return GetConversionMaintenanceMargin(parameters.PositionGroup, parameters.Portfolio, OptionRight.Put);
152  }
153  else if (_optionStrategy.Name == OptionStrategyDefinitions.NakedCall.Name
154  || _optionStrategy.Name == OptionStrategyDefinitions.NakedPut.Name)
155  {
156  var option = parameters.PositionGroup.Positions.Single();
157  var security = (Option)parameters.Portfolio.Securities[option.Symbol];
158  var margin = security.BuyingPowerModel.GetMaintenanceMargin(MaintenanceMarginParameters.ForQuantityAtCurrentPrice(security,
159  option.Quantity));
160 
161  return new MaintenanceMargin(margin);
162  }
163  else if (_optionStrategy.Name == OptionStrategyDefinitions.BearCallSpread.Name
164  || _optionStrategy.Name == OptionStrategyDefinitions.BullCallSpread.Name)
165  {
166  var result = GetLongCallShortCallStrikeDifferenceMargin(parameters.PositionGroup.Positions, parameters.Portfolio, parameters.PositionGroup.Quantity);
167  return new MaintenanceMargin(result);
168  }
169  else if (_optionStrategy.Name == OptionStrategyDefinitions.CallCalendarSpread.Name
170  || _optionStrategy.Name == OptionStrategyDefinitions.PutCalendarSpread.Name)
171  {
172  return new MaintenanceMargin(0);
173  }
174  else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortCallCalendarSpread.Name
175  || _optionStrategy.Name == OptionStrategyDefinitions.ShortPutCalendarSpread.Name)
176  {
177  var shortCall = parameters.PositionGroup.Positions.Single(position => position.Quantity < 0);
178  var shortCallSecurity = (Option)parameters.Portfolio.Securities[shortCall.Symbol];
179  var result = shortCallSecurity.BuyingPowerModel.GetMaintenanceMargin(MaintenanceMarginParameters.ForQuantityAtCurrentPrice(
180  shortCallSecurity, shortCall.Quantity));
181 
182  return new MaintenanceMargin(result);
183  }
184  else if (_optionStrategy.Name == OptionStrategyDefinitions.BearPutSpread.Name
185  || _optionStrategy.Name == OptionStrategyDefinitions.BullPutSpread.Name)
186  {
187  var result = GetShortPutLongPutStrikeDifferenceMargin(parameters.PositionGroup.Positions, parameters.Portfolio, parameters.PositionGroup.Quantity);
188  return new MaintenanceMargin(result);
189  }
190  else if (_optionStrategy.Name == OptionStrategyDefinitions.Straddle.Name || _optionStrategy.Name == OptionStrategyDefinitions.Strangle.Name)
191  {
192  // Margined as two long options: since there is not margin requirements for long options, we return 0
193  return new MaintenanceMargin(0);
194  }
195  else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortStraddle.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortStrangle.Name)
196  {
197  var result = GetShortStraddleStrangleMargin(parameters.PositionGroup, parameters.Portfolio,
198  (option, quantity) => Math.Abs(option.BuyingPowerModel.GetMaintenanceMargin(
200  return new MaintenanceMargin(result);
201  }
202  else if (_optionStrategy.Name == OptionStrategyDefinitions.ButterflyCall.Name || _optionStrategy.Name == OptionStrategyDefinitions.ButterflyPut.Name)
203  {
204  return new MaintenanceMargin(0);
205  }
206  else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortButterflyPut.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortButterflyCall.Name)
207  {
208  var result = GetMiddleAndLowStrikeDifference(parameters.PositionGroup, parameters.Portfolio);
209  return new MaintenanceMargin(result);
210  }
211  else if (_optionStrategy.Name == OptionStrategyDefinitions.IronCondor.Name || _optionStrategy.Name == OptionStrategyDefinitions.IronButterfly.Name ||
212  _optionStrategy.Name == OptionStrategyDefinitions.ShortIronCondor.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortIronButterfly.Name)
213  {
214  var result = GetShortPutLongPutStrikeDifferenceMargin(parameters.PositionGroup.Positions, parameters.Portfolio, parameters.PositionGroup.Quantity);
215  return new MaintenanceMargin(result);
216  }
217  else if (_optionStrategy.Name == OptionStrategyDefinitions.BoxSpread.Name)
218  {
219  return new MaintenanceMargin(0);
220  }
221  else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortBoxSpread.Name)
222  {
223  // MAX(1.02 x cost to close, Long Call Strike – Short Call Strike)
224  var longCallPosition = parameters.PositionGroup.Positions.Single(
225  position => position.Quantity > 0 && position.Symbol.ID.OptionRight == OptionRight.Call);
226  var shortCallPosition = parameters.PositionGroup.Positions.Single(
227  position => position.Quantity < 0 && position.Symbol.ID.OptionRight == OptionRight.Call);
228  var longPutPosition = parameters.PositionGroup.Positions.Single(
229  position => position.Quantity > 0 && position.Symbol.ID.OptionRight == OptionRight.Put);
230  var shortPutPosition = parameters.PositionGroup.Positions.Single(
231  position => position.Quantity < 0 && position.Symbol.ID.OptionRight == OptionRight.Put);
232  var longCallSecurity = (Option)parameters.Portfolio.Securities[longCallPosition.Symbol];
233  var shortCallSecurity = (Option)parameters.Portfolio.Securities[shortCallPosition.Symbol];
234  var longPutSecurity = (Option)parameters.Portfolio.Securities[longPutPosition.Symbol];
235  var shortPutSecurity = (Option)parameters.Portfolio.Securities[shortPutPosition.Symbol];
236 
237  // commission cost: MAX($1, $0.65/contract * quantity) + bid/ask price
238  var commissionFees = Math.Max(Math.Abs(longCallPosition.Quantity) * 0.65m, 1m) * 4m; // 4 contracts in total
239  var orderCosts = shortCallSecurity.AskPrice - longCallSecurity.BidPrice + shortPutSecurity.AskPrice - longPutSecurity.BidPrice;
240  var multiplier = Math.Abs(longCallPosition.Quantity) * longCallSecurity.ContractUnitOfTrade;
241  var closeCost = commissionFees + orderCosts * multiplier;
242 
243  var strikeDifference = longCallPosition.Symbol.ID.StrikePrice - shortCallPosition.Symbol.ID.StrikePrice;
244 
245  var result = Math.Max(1.02m * closeCost, strikeDifference * multiplier);
246  var inAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, longCallSecurity.QuoteCurrency.Symbol);
247 
248  return new MaintenanceMargin(inAccountCurrency);
249  }
250  else if (_optionStrategy.Name == OptionStrategyDefinitions.JellyRoll.Name
251  || _optionStrategy.Name == OptionStrategyDefinitions.ShortJellyRoll.Name)
252  {
253  // long calendar spread part has no margin requirement due to same strike
254  // only the short calendar spread's short option has margin requirement
255  var furtherExpiry = parameters.PositionGroup.Positions.Max(position => position.Symbol.ID.Date);
256  var shortCalendarSpreadShortLeg = parameters.PositionGroup.Positions.Single(position =>
257  position.Quantity < 0 && position.Symbol.ID.Date == furtherExpiry);
258  var shortCalendarSpreadShortLegSecurity = (Option)parameters.Portfolio.Securities[shortCalendarSpreadShortLeg.Symbol];
259  var result = Math.Abs(shortCalendarSpreadShortLegSecurity.BuyingPowerModel.GetMaintenanceMargin(
260  MaintenanceMarginParameters.ForQuantityAtCurrentPrice(shortCalendarSpreadShortLegSecurity, shortCalendarSpreadShortLeg.Quantity)));
261 
262  return new MaintenanceMargin(result);
263  }
264  else if (_optionStrategy.Name == OptionStrategyDefinitions.BearCallLadder.Name)
265  {
266  return GetCallLadderMargin(parameters, true);
267  }
268  else if (_optionStrategy.Name == OptionStrategyDefinitions.BearPutLadder.Name)
269  {
270  return GetPutLadderMargin(parameters, false);
271  }
272  else if (_optionStrategy.Name == OptionStrategyDefinitions.BullCallLadder.Name)
273  {
274  return GetCallLadderMargin(parameters, false);
275  }
276  else if (_optionStrategy.Name == OptionStrategyDefinitions.BullPutLadder.Name)
277  {
278  return GetPutLadderMargin(parameters, true);
279  }
280 
281  throw new NotImplementedException($"Option strategy {_optionStrategy.Name} margin modeling has yet to be implemented");
282  }
283 
284  /// <summary>
285  /// The margin that must be held in order to increase the position by the provided quantity
286  /// </summary>
287  /// <param name="parameters">An object containing the security and quantity</param>
289  {
290  var result = 0m;
291 
292  if (_optionStrategy == null)
293  {
294  result = 0;
295  }
296  else if (_optionStrategy.Name == OptionStrategyDefinitions.ProtectivePut.Name || _optionStrategy.Name == OptionStrategyDefinitions.ProtectiveCall.Name)
297  {
298  // Initial Standard Stock Margin Requirement
299  var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
300  var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
301 
302  result = Math.Abs(underlyingSecurity.BuyingPowerModel.GetInitialMarginRequirement(underlyingSecurity, underlyingPosition.Quantity));
303  result = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, underlyingSecurity.QuoteCurrency.Symbol);
304  }
305  else if (_optionStrategy.Name == OptionStrategyDefinitions.CoveredCall.Name)
306  {
307  // Max(Call Value, Long Stock Initial Margin)
308  var optionPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => position.Symbol.SecurityType.IsOption());
309  var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
310  var optionSecurity = (Option)parameters.Portfolio.Securities[optionPosition.Symbol];
311  var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
312 
313  var optionValue = Math.Abs(optionSecurity.Holdings.GetQuantityValue(optionPosition.Quantity).InAccountCurrency);
314 
315  var marginRequired = underlyingSecurity.BuyingPowerModel.GetInitialMarginRequirement(underlyingSecurity, underlyingPosition.Quantity);
316 
317  // IB charges more than expected, this formula was inferred based on actual requirements see 'CoveredCallInitialMarginRequirementsTestCases'
318  result = optionValue * 0.8m + marginRequired;
319  result = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
320  }
321  else if (_optionStrategy.Name == OptionStrategyDefinitions.CoveredPut.Name)
322  {
323  // Initial Stock Margin Requirement + In the Money Amount
325  }
326  else if (_optionStrategy.Name == OptionStrategyDefinitions.ProtectiveCollar.Name || _optionStrategy.Name == OptionStrategyDefinitions.Conversion.Name)
327  {
328  result = GetCollarConversionInitialMargin(parameters.PositionGroup, parameters.Portfolio, OptionRight.Call);
329  }
330  else if (_optionStrategy.Name == OptionStrategyDefinitions.ReverseConversion.Name)
331  {
332  result = GetCollarConversionInitialMargin(parameters.PositionGroup, parameters.Portfolio, OptionRight.Put);
333  }
334  else if (_optionStrategy.Name == OptionStrategyDefinitions.NakedCall.Name
335  || _optionStrategy.Name == OptionStrategyDefinitions.NakedPut.Name)
336  {
337  var option = parameters.PositionGroup.Positions.Single();
338  var security = (Option)parameters.Portfolio.Securities[option.Symbol];
339  var margin = security.BuyingPowerModel.GetInitialMarginRequirement(new InitialMarginParameters(security, option.Quantity));
340  var optionMargin = margin as OptionInitialMargin;
341 
342  if (optionMargin != null)
343  {
344  return new OptionInitialMargin(Math.Abs(optionMargin.ValueWithoutPremium), optionMargin.Premium);
345  }
346 
347  return margin;
348  }
349  else if (_optionStrategy.Name == OptionStrategyDefinitions.BearCallSpread.Name
350  || _optionStrategy.Name == OptionStrategyDefinitions.BullCallSpread.Name)
351  {
352  result = GetLongCallShortCallStrikeDifferenceMargin(parameters.PositionGroup.Positions, parameters.Portfolio, parameters.PositionGroup.Quantity);
353  }
354  else if (_optionStrategy.Name == OptionStrategyDefinitions.CallCalendarSpread.Name
355  || _optionStrategy.Name == OptionStrategyDefinitions.PutCalendarSpread.Name)
356  {
357  result = 0m;
358  }
359  else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortCallCalendarSpread.Name
360  || _optionStrategy.Name == OptionStrategyDefinitions.ShortPutCalendarSpread.Name)
361  {
362  var shortOptionPosition = parameters.PositionGroup.Positions.Single(position => position.Quantity < 0);
363  var shortOption = (Option)parameters.Portfolio.Securities[shortOptionPosition.Symbol];
364  result = Math.Abs(shortOption.BuyingPowerModel.GetInitialMarginRequirement(shortOption, shortOptionPosition.Quantity));
365  }
366  else if (_optionStrategy.Name == OptionStrategyDefinitions.BearPutSpread.Name
367  || _optionStrategy.Name == OptionStrategyDefinitions.BullPutSpread.Name)
368  {
369  result = GetShortPutLongPutStrikeDifferenceMargin(parameters.PositionGroup.Positions, parameters.Portfolio, parameters.PositionGroup.Quantity);
370  }
371  else if (_optionStrategy.Name == OptionStrategyDefinitions.Straddle.Name || _optionStrategy.Name == OptionStrategyDefinitions.Strangle.Name)
372  {
373  // Margined as two long options: since there is not margin requirements for long options, we return 0
374  result = 0m;
375  }
376  else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortStraddle.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortStrangle.Name)
377  {
378  result = GetShortStraddleStrangleMargin(parameters.PositionGroup, parameters.Portfolio,
379  (option, quantity) => Math.Abs(option.BuyingPowerModel.GetInitialMarginRequirement(option, quantity)));
380  }
381  else if (_optionStrategy.Name == OptionStrategyDefinitions.ButterflyCall.Name || _optionStrategy.Name == OptionStrategyDefinitions.ButterflyPut.Name)
382  {
383  result = 0m;
384  }
385  else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortButterflyPut.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortButterflyCall.Name)
386  {
387  result = GetMiddleAndLowStrikeDifference(parameters.PositionGroup, parameters.Portfolio);
388  }
389  else if (_optionStrategy.Name == OptionStrategyDefinitions.IronCondor.Name || _optionStrategy.Name == OptionStrategyDefinitions.IronButterfly.Name ||
390  _optionStrategy.Name == OptionStrategyDefinitions.ShortIronCondor.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortIronButterfly.Name)
391  {
392  result = GetShortPutLongPutStrikeDifferenceMargin(parameters.PositionGroup.Positions, parameters.Portfolio, parameters.PositionGroup.Quantity);
393  }
394  else if (_optionStrategy.Name == OptionStrategyDefinitions.BoxSpread.Name)
395  {
396  result = 0m;
397  }
398  else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortBoxSpread.Name)
399  {
401  }
402  else if (_optionStrategy.Name == OptionStrategyDefinitions.JellyRoll.Name
403  || _optionStrategy.Name == OptionStrategyDefinitions.ShortJellyRoll.Name)
404  {
406  }
407  else if (_optionStrategy.Name == OptionStrategyDefinitions.BearCallLadder.Name || _optionStrategy.Name == OptionStrategyDefinitions.BearPutLadder.Name
408  || _optionStrategy.Name == OptionStrategyDefinitions.BullCallLadder.Name || _optionStrategy.Name == OptionStrategyDefinitions.BullPutLadder.Name)
409  {
411  }
412  else
413  {
414  throw new NotImplementedException($"Option strategy {_optionStrategy.Name} margin modeling has yet to be implemented");
415  }
416 
417  // Add premium to initial margin only when it is positive (the user must pay the premium)
418  var premium = 0m;
419  foreach (var position in parameters.PositionGroup.Positions.Where(position => position.Symbol.SecurityType.IsOption()))
420  {
421  var option = (Option)parameters.Portfolio.Securities[position.Symbol];
422  premium += option.Holdings.GetQuantityValue(position.Quantity).InAccountCurrency;
423  }
424 
425  return new OptionInitialMargin(result, premium);
426  }
427 
428  /// <summary>
429  /// Gets the total margin required to execute the specified order in units of the account currency including fees
430  /// </summary>
431  /// <param name="parameters">An object containing the portfolio, the security and the order</param>
432  /// <returns>The total margin in terms of the currency quoted in the order</returns>
434  {
435  var security = parameters.Portfolio.Securities[parameters.Order.Symbol];
436  var fees = security.FeeModel.GetOrderFee(new OrderFeeParameters(security, parameters.Order));
437  var feesInAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(fees.Value);
438 
439  var initialMarginRequired = GetInitialMarginRequirement(new PositionGroupInitialMarginParameters(parameters.Portfolio, parameters.PositionGroup));
440 
441  var feesWithSign = Math.Sign(initialMarginRequired) * feesInAccountCurrency.Amount;
442 
443  return new InitialMargin(feesWithSign + initialMarginRequired);
444  }
445 
446  /// <summary>
447  /// Gets the initial margin required for the specified contemplated position group.
448  /// Used by <see cref="QuantConnect.Securities.Positions.PositionGroupBuyingPowerModel.GetReservedBuyingPowerImpact"/> to get the contemplated groups margin.
449  /// </summary>
450  protected override decimal GetContemplatedGroupsInitialMargin(SecurityPortfolioManager portfolio, PositionGroupCollection contemplatedGroups,
451  List<IPosition> ordersPositions)
452  {
453  var contemplatedMargin = 0m;
454  foreach (var contemplatedGroup in contemplatedGroups)
455  {
456  // We use the initial margin requirement as the contemplated groups margin in order to ensure
457  // the available buying power is enough to execute the order.
458  var initialMargin = contemplatedGroup.BuyingPowerModel.GetInitialMarginRequirement(
459  new PositionGroupInitialMarginParameters(portfolio, contemplatedGroup));
460  var optionInitialMargin = initialMargin as OptionInitialMargin;
461  contemplatedMargin += optionInitialMargin?.ValueWithoutPremium ?? initialMargin;
462  }
463 
464  // Now we need to add the premium paid for the order:
465  // This should always return a single group since it is a single order/combo
466  var ordersGroups = portfolio.Positions.ResolvePositionGroups(new PositionCollection(ordersPositions));
467  foreach (var orderGroup in ordersGroups)
468  {
469  var initialMargin = orderGroup.BuyingPowerModel.GetInitialMarginRequirement(
470  new PositionGroupInitialMarginParameters(portfolio, orderGroup));
471  var optionInitialMargin = initialMargin as OptionInitialMargin;
472 
473  if (optionInitialMargin != null)
474  {
475  // We need to add the premium paid for the order. We use the TotalValue-Value difference instead of Premium
476  // to add it only when needed -- when it is debited from the account
477  contemplatedMargin += optionInitialMargin.Value - optionInitialMargin.ValueWithoutPremium;
478  }
479  }
480 
481  return contemplatedMargin;
482  }
483 
484  /// <summary>
485  /// Returns a string that represents the current object.
486  /// </summary>
487  /// <returns>A string that represents the current object.</returns>
488  public override string ToString()
489  {
490  return _optionStrategy.Name;
491  }
492 
493  /// <summary>
494  /// Returns the Maximum (Short Put Strike - Long Put Strike, 0)
495  /// </summary>
496  private static decimal GetShortPutLongPutStrikeDifferenceMargin(IEnumerable<IPosition> positions, SecurityPortfolioManager portfolio, decimal quantity)
497  {
498  var longOption = positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Put && position.Quantity > 0);
499  var shortOption = positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Put && position.Quantity < 0);
500  var optionSecurity = (Option)portfolio.Securities[longOption.Symbol];
501 
502  // Maximum (Short Put Strike - Long Put Strike, 0)
503  var strikeDifference = shortOption.Symbol.ID.StrikePrice - longOption.Symbol.ID.StrikePrice;
504 
505  var result = Math.Max(strikeDifference * optionSecurity.ContractUnitOfTrade * Math.Abs(quantity), 0);
506 
507  // convert into account currency
508  return portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
509  }
510 
511  /// <summary>
512  /// Returns the Maximum (Strike Long Call - Strike Short Call, 0)
513  /// </summary>
514  private static decimal GetLongCallShortCallStrikeDifferenceMargin(IEnumerable<IPosition> positions, SecurityPortfolioManager portfolio, decimal quantity)
515  {
516  var longOption = positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Call && position.Quantity > 0);
517  var shortOption = positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Call && position.Quantity < 0);
518  var optionSecurity = (Option)portfolio.Securities[longOption.Symbol];
519 
520  var strikeDifference = longOption.Symbol.ID.StrikePrice - shortOption.Symbol.ID.StrikePrice;
521 
522  var result = Math.Max(strikeDifference * optionSecurity.ContractUnitOfTrade * Math.Abs(quantity), 0);
523 
524  // convert into account currency
525  return portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
526  }
527 
528  /// <summary>
529  /// Returns the Maximum (Middle Strike - Lowest Strike, 0)
530  /// </summary>
531  private static decimal GetMiddleAndLowStrikeDifference(IPositionGroup positionGroup, SecurityPortfolioManager portfolio)
532  {
533  var options = positionGroup.Positions.OrderBy(position => position.Symbol.ID.StrikePrice).ToList();
534  var lowestCallStrike = options[0].Symbol.ID.StrikePrice;
535  var middleCallStrike = options[1].Symbol.ID.StrikePrice;
536  var optionSecurity = (Option)portfolio.Securities[options[0].Symbol];
537 
538  var strikeDifference = Math.Max((middleCallStrike - lowestCallStrike) * optionSecurity.ContractUnitOfTrade * Math.Abs(positionGroup.Quantity), 0);
539 
540  // convert into account currency
541  return portfolio.CashBook.ConvertToAccountCurrency(strikeDifference, optionSecurity.QuoteCurrency.Symbol);
542  }
543 
544  /// <summary>
545  /// Returns the margin for a short straddle or strangle.
546  /// This is the same for both the initial margin requirement and the maintenance margin.
547  /// </summary>
548  private static decimal GetShortStraddleStrangleMargin(IPositionGroup positionGroup, SecurityPortfolioManager portfolio,
549  Func<Option, decimal, decimal> getOptionMargin)
550  {
551  var callOption = positionGroup.Positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Call);
552  var callSecurity = (Option)portfolio.Securities[callOption.Symbol];
553  var callMargin = getOptionMargin(callSecurity, callOption.Quantity);
554 
555  var putOption = positionGroup.Positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Put);
556  var putSecurity = (Option)portfolio.Securities[putOption.Symbol];
557  var putMargin = getOptionMargin(putSecurity, putOption.Quantity);
558 
559  var result = 0m;
560 
561  if (putMargin > callMargin)
562  {
563  result = putMargin + callSecurity.Price * callSecurity.ContractUnitOfTrade * Math.Abs(callOption.Quantity);
564  }
565  else
566  {
567  result = callMargin + putSecurity.Price * putSecurity.ContractUnitOfTrade * Math.Abs(putOption.Quantity);
568  }
569 
570  return result;
571  }
572 
573  /// <summary>
574  /// Returns the maintenance margin for a conversion or reverse conversion.
575  /// </summary>
576  private static decimal GetConversionMaintenanceMargin(IPositionGroup positionGroup, SecurityPortfolioManager portfolio, OptionRight optionRight)
577  {
578  // 10% * Strike Price + Call/Put In the Money Amount
579  var optionPosition = positionGroup.Positions.Single(position =>
580  position.Symbol.SecurityType.IsOption() && position.Symbol.ID.OptionRight == optionRight);
581  var underlyingPosition = positionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
582  var optionSecurity = (Option)portfolio.Securities[optionPosition.Symbol];
583  var underlyingSecurity = portfolio.Securities[underlyingPosition.Symbol];
584 
585  var marginRequirement = 0.1m * optionSecurity.StrikePrice + optionSecurity.GetIntrinsicValue(underlyingSecurity.Price);
586  var result = marginRequirement * Math.Abs(optionPosition.Quantity) * optionSecurity.ContractUnitOfTrade;
587  var inAccountCurrency = portfolio.CashBook.ConvertToAccountCurrency(result, underlyingSecurity.QuoteCurrency.Symbol);
588 
589  return new MaintenanceMargin(inAccountCurrency);
590  }
591 
592  /// <summary>
593  /// Returns the initial margin requirement for a collar, conversion, or reverse conversion.
594  /// </summary>
595  private static decimal GetCollarConversionInitialMargin(IPositionGroup positionGroup, SecurityPortfolioManager portfolio, OptionRight optionRight)
596  {
597  // Initial Stock Margin Requirement + In the Money Call/Put Amount
598  var optionPosition = positionGroup.Positions.Single(position =>
599  position.Symbol.SecurityType.IsOption() && position.Symbol.ID.OptionRight == optionRight);
600  var underlyingPosition = positionGroup.Positions.Single(position => !position.Symbol.SecurityType.IsOption());
601  var optionSecurity = (Option)portfolio.Securities[optionPosition.Symbol];
602  var underlyingSecurity = portfolio.Securities[underlyingPosition.Symbol];
603 
604  var intrinsicValue = optionSecurity.GetIntrinsicValue(underlyingSecurity.Price);
605  var inTheMoneyAmount = intrinsicValue * optionSecurity.ContractUnitOfTrade * Math.Abs(optionPosition.Quantity);
606 
607  var initialMarginRequirement = underlyingSecurity.BuyingPowerModel.GetInitialMarginRequirement(underlyingSecurity, underlyingPosition.Quantity);
608 
609  var result = Math.Abs(initialMarginRequirement) + inTheMoneyAmount;
610  return portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
611  }
612 
613  /// <summary>
614  /// Returns the initial/maintenance margin requirement for a call ladder
615  /// </summary>
616  private static decimal GetCallLadderMargin(PositionGroupMaintenanceMarginParameters parameters, bool bearCallLadder)
617  {
618  var quantity = parameters.PositionGroup.Quantity;
619 
620  if ((quantity >= 0 && bearCallLadder) || (quantity < 0 && !bearCallLadder))
621  {
622  // Bear Call Ladder = Bear Call Spread of 2 lower strike prices + Long Call with the highest strike price (margin: 0)
623  var callSpread = parameters.PositionGroup.Positions.OrderBy(position => position.Symbol.ID.StrikePrice).Take(2).ToList();
624  return GetLongCallShortCallStrikeDifferenceMargin(callSpread, parameters.Portfolio, Math.Abs(quantity));
625  }
626  else
627  {
628  // Bull Call Ladder = Bull Call Spread of 2 lower strike prices (margin: 0) + Short Call with the highest strike price
629  var shortNakedCall = parameters.PositionGroup.Positions.OrderByDescending(position => position.Symbol.ID.StrikePrice).First();
630  var security = (Option)parameters.Portfolio.Securities[shortNakedCall.Symbol];
631  var margin = security.BuyingPowerModel.GetInitialMarginRequirement(new InitialMarginParameters(security, shortNakedCall.Quantity));
632  return new MaintenanceMargin(Math.Abs(margin));
633  }
634  }
635 
636  /// <summary>
637  /// Returns the initial/maintenance margin requirement for a put ladder
638  /// </summary>
639  private static decimal GetPutLadderMargin(PositionGroupMaintenanceMarginParameters parameters, bool bullPutLadder)
640  {
641  var quantity = parameters.PositionGroup.Quantity;
642 
643  if ((quantity >= 0 && bullPutLadder) || (quantity < 0 && !bullPutLadder))
644  {
645  // Bull Put Ladder = Bull Put Spread of 2 higher strike prices + Long Put with the lowest strike price (margin: 0)
646  var putSpread = parameters.PositionGroup.Positions.OrderByDescending(position => position.Symbol.ID.StrikePrice).Take(2).ToList();
647  return GetShortPutLongPutStrikeDifferenceMargin(putSpread, parameters.Portfolio, Math.Abs(quantity));
648  }
649  else
650  {
651  // Bear Put Ladder = Bear Put Spread of 2 higher strike prices (margin: 0) + Short Put with the lowest strike price
652  var shortNakedPut = parameters.PositionGroup.Positions.OrderBy(position => position.Symbol.ID.StrikePrice).First();
653  var security = (Option)parameters.Portfolio.Securities[shortNakedPut.Symbol];
654  var margin = security.BuyingPowerModel.GetInitialMarginRequirement(new InitialMarginParameters(security, shortNakedPut.Quantity));
655  return new MaintenanceMargin(Math.Abs(margin));
656  }
657  }
658  }
659 }