chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,659 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using QuantConnect.Securities.Positions;
|
||||
using QuantConnect.Securities.Option.StrategyMatcher;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Orders;
|
||||
|
||||
namespace QuantConnect.Securities.Option
|
||||
{
|
||||
/// <summary>
|
||||
/// Option strategy buying power model
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Reference used https://www.interactivebrokers.com/en/index.php?f=26660
|
||||
/// </remarks>
|
||||
public class OptionStrategyPositionGroupBuyingPowerModel : PositionGroupBuyingPowerModel
|
||||
{
|
||||
private readonly OptionStrategy _optionStrategy;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance for a target option strategy
|
||||
/// </summary>
|
||||
/// <param name="optionStrategy">The option strategy to model</param>
|
||||
public OptionStrategyPositionGroupBuyingPowerModel(OptionStrategy optionStrategy)
|
||||
{
|
||||
_optionStrategy = optionStrategy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the margin currently allocated to the specified holding
|
||||
/// </summary>
|
||||
/// <param name="parameters">An object containing the security</param>
|
||||
/// <returns>The maintenance margin required for the </returns>
|
||||
public override MaintenanceMargin GetMaintenanceMargin(PositionGroupMaintenanceMarginParameters parameters)
|
||||
{
|
||||
if (_optionStrategy == null)
|
||||
{
|
||||
// we could be liquidating a position
|
||||
return new MaintenanceMargin(0);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.ProtectivePut.Name || _optionStrategy.Name == OptionStrategyDefinitions.ProtectiveCall.Name)
|
||||
{
|
||||
// Minimum (((10% * Call/Put Strike Price) + Call/Put Out of the Money Amount), Short Stock/Long Maintenance Requirement)
|
||||
var optionPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => position.Symbol.SecurityType.IsOption());
|
||||
var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
|
||||
var optionSecurity = (Option)parameters.Portfolio.Securities[optionPosition.Symbol];
|
||||
var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
|
||||
|
||||
var absOptionQuantity = Math.Abs(optionPosition.Quantity);
|
||||
var outOfTheMoneyAmount = optionSecurity.OutOfTheMoneyAmount(underlyingSecurity.Price) * optionSecurity.ContractUnitOfTrade * absOptionQuantity;
|
||||
|
||||
var underlyingMarginRequired = Math.Abs(underlyingSecurity.BuyingPowerModel.GetMaintenanceMargin(MaintenanceMarginParameters.ForQuantityAtCurrentPrice(
|
||||
underlyingSecurity, underlyingPosition.Quantity)));
|
||||
|
||||
var result = Math.Min(0.1m * optionSecurity.StrikePrice * optionSecurity.ContractUnitOfTrade * absOptionQuantity + outOfTheMoneyAmount, underlyingMarginRequired);
|
||||
var inAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
|
||||
|
||||
return new MaintenanceMargin(inAccountCurrency);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.CoveredCall.Name)
|
||||
{
|
||||
// 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))]
|
||||
var optionPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => position.Symbol.SecurityType.IsOption());
|
||||
var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
|
||||
var optionSecurity = (Option)parameters.Portfolio.Securities[optionPosition.Symbol];
|
||||
var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
|
||||
|
||||
var intrinsicValue = optionSecurity.GetIntrinsicValue(underlyingSecurity.Price);
|
||||
var inTheMoneyAmount = intrinsicValue * optionSecurity.ContractUnitOfTrade * Math.Abs(optionPosition.Quantity);
|
||||
|
||||
var underlyingValue = underlyingSecurity.Holdings.GetQuantityValue(underlyingPosition.Quantity).InAccountCurrency;
|
||||
var optionValue = optionSecurity.Holdings.GetQuantityValue(optionPosition.Quantity).InAccountCurrency;
|
||||
|
||||
// mark price, strike price
|
||||
var underlyingPriceToEvaluate = Math.Min(underlyingSecurity.Price, optionSecurity.ScaledStrikePrice);
|
||||
var underlyingHypotheticalValue = underlyingSecurity.Holdings.GetQuantityValue(underlyingPosition.Quantity, underlyingPriceToEvaluate).InAccountCurrency;
|
||||
|
||||
var hypotheticalMarginRequired = underlyingSecurity.BuyingPowerModel.GetMaintenanceMargin(
|
||||
new MaintenanceMarginParameters(underlyingSecurity, underlyingPosition.Quantity, 0, underlyingHypotheticalValue));
|
||||
var marginRequired = underlyingSecurity.BuyingPowerModel.GetMaintenanceMargin(
|
||||
new MaintenanceMarginParameters(underlyingSecurity, underlyingPosition.Quantity, 0, underlyingValue));
|
||||
|
||||
var secondOperand = Math.Min(underlyingValue, Math.Max(optionValue, marginRequired));
|
||||
var result = Math.Max(inTheMoneyAmount + hypotheticalMarginRequired, secondOperand);
|
||||
var inAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
|
||||
|
||||
return new MaintenanceMargin(inAccountCurrency);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.CoveredPut.Name)
|
||||
{
|
||||
// Initial Stock Margin Requirement + In the Money Amount
|
||||
var optionPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => position.Symbol.SecurityType.IsOption());
|
||||
var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
|
||||
var optionSecurity = (Option)parameters.Portfolio.Securities[optionPosition.Symbol];
|
||||
var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
|
||||
|
||||
var intrinsicValue = optionSecurity.GetIntrinsicValue(underlyingSecurity.Price);
|
||||
var inTheMoneyAmount = intrinsicValue * optionSecurity.ContractUnitOfTrade * Math.Abs(optionPosition.Quantity);
|
||||
|
||||
var initialMarginRequirement = underlyingSecurity.BuyingPowerModel.GetInitialMarginRequirement(underlyingSecurity, underlyingPosition.Quantity);
|
||||
|
||||
var result = Math.Abs(initialMarginRequirement) + inTheMoneyAmount;
|
||||
var inAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
|
||||
|
||||
return new MaintenanceMargin(inAccountCurrency);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.ProtectiveCollar.Name)
|
||||
{
|
||||
// Minimum (((10% * Put Strike Price) + Put Out of the Money Amount), (25% * Call Strike Price))
|
||||
var putPosition = parameters.PositionGroup.Positions.Single(position =>
|
||||
position.Symbol.SecurityType.IsOption() && position.Symbol.ID.OptionRight == OptionRight.Put);
|
||||
var callPosition = parameters.PositionGroup.Positions.Single(position =>
|
||||
position.Symbol.SecurityType.IsOption() && position.Symbol.ID.OptionRight == OptionRight.Call);
|
||||
var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
|
||||
var putSecurity = (Option)parameters.Portfolio.Securities[putPosition.Symbol];
|
||||
var callSecurity = (Option)parameters.Portfolio.Securities[callPosition.Symbol];
|
||||
var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
|
||||
|
||||
var putMarginRequirement = 0.1m * putSecurity.StrikePrice + putSecurity.OutOfTheMoneyAmount(underlyingSecurity.Price);
|
||||
var callMarginRequirement = 0.25m * callSecurity.StrikePrice;
|
||||
|
||||
// call and put has the exact same number of contracts
|
||||
var contractUnits = Math.Abs(putPosition.Quantity) * putSecurity.ContractUnitOfTrade;
|
||||
var result = Math.Min(putMarginRequirement, callMarginRequirement) * contractUnits;
|
||||
var inAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, underlyingSecurity.QuoteCurrency.Symbol);
|
||||
|
||||
return new MaintenanceMargin(inAccountCurrency);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.Conversion.Name)
|
||||
{
|
||||
return GetConversionMaintenanceMargin(parameters.PositionGroup, parameters.Portfolio, OptionRight.Call);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.ReverseConversion.Name)
|
||||
{
|
||||
return GetConversionMaintenanceMargin(parameters.PositionGroup, parameters.Portfolio, OptionRight.Put);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.NakedCall.Name
|
||||
|| _optionStrategy.Name == OptionStrategyDefinitions.NakedPut.Name)
|
||||
{
|
||||
var option = parameters.PositionGroup.Positions.Single();
|
||||
var security = (Option)parameters.Portfolio.Securities[option.Symbol];
|
||||
var margin = security.BuyingPowerModel.GetMaintenanceMargin(MaintenanceMarginParameters.ForQuantityAtCurrentPrice(security,
|
||||
option.Quantity));
|
||||
|
||||
return new MaintenanceMargin(margin);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.BearCallSpread.Name
|
||||
|| _optionStrategy.Name == OptionStrategyDefinitions.BullCallSpread.Name)
|
||||
{
|
||||
var result = GetLongCallShortCallStrikeDifferenceMargin(parameters.PositionGroup.Positions, parameters.Portfolio, parameters.PositionGroup.Quantity);
|
||||
return new MaintenanceMargin(result);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.CallCalendarSpread.Name
|
||||
|| _optionStrategy.Name == OptionStrategyDefinitions.PutCalendarSpread.Name)
|
||||
{
|
||||
return new MaintenanceMargin(0);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortCallCalendarSpread.Name
|
||||
|| _optionStrategy.Name == OptionStrategyDefinitions.ShortPutCalendarSpread.Name)
|
||||
{
|
||||
var shortCall = parameters.PositionGroup.Positions.Single(position => position.Quantity < 0);
|
||||
var shortCallSecurity = (Option)parameters.Portfolio.Securities[shortCall.Symbol];
|
||||
var result = shortCallSecurity.BuyingPowerModel.GetMaintenanceMargin(MaintenanceMarginParameters.ForQuantityAtCurrentPrice(
|
||||
shortCallSecurity, shortCall.Quantity));
|
||||
|
||||
return new MaintenanceMargin(result);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.BearPutSpread.Name
|
||||
|| _optionStrategy.Name == OptionStrategyDefinitions.BullPutSpread.Name)
|
||||
{
|
||||
var result = GetShortPutLongPutStrikeDifferenceMargin(parameters.PositionGroup.Positions, parameters.Portfolio, parameters.PositionGroup.Quantity);
|
||||
return new MaintenanceMargin(result);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.Straddle.Name || _optionStrategy.Name == OptionStrategyDefinitions.Strangle.Name)
|
||||
{
|
||||
// Margined as two long options: since there is not margin requirements for long options, we return 0
|
||||
return new MaintenanceMargin(0);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortStraddle.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortStrangle.Name)
|
||||
{
|
||||
var result = GetShortStraddleStrangleMargin(parameters.PositionGroup, parameters.Portfolio,
|
||||
(option, quantity) => Math.Abs(option.BuyingPowerModel.GetMaintenanceMargin(
|
||||
MaintenanceMarginParameters.ForQuantityAtCurrentPrice(option, quantity))));
|
||||
return new MaintenanceMargin(result);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.ButterflyCall.Name || _optionStrategy.Name == OptionStrategyDefinitions.ButterflyPut.Name)
|
||||
{
|
||||
return new MaintenanceMargin(0);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortButterflyPut.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortButterflyCall.Name)
|
||||
{
|
||||
var result = GetMiddleAndLowStrikeDifference(parameters.PositionGroup, parameters.Portfolio);
|
||||
return new MaintenanceMargin(result);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.IronCondor.Name || _optionStrategy.Name == OptionStrategyDefinitions.IronButterfly.Name ||
|
||||
_optionStrategy.Name == OptionStrategyDefinitions.ShortIronCondor.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortIronButterfly.Name)
|
||||
{
|
||||
var result = GetShortPutLongPutStrikeDifferenceMargin(parameters.PositionGroup.Positions, parameters.Portfolio, parameters.PositionGroup.Quantity);
|
||||
return new MaintenanceMargin(result);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.BoxSpread.Name)
|
||||
{
|
||||
return new MaintenanceMargin(0);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortBoxSpread.Name)
|
||||
{
|
||||
// MAX(1.02 x cost to close, Long Call Strike – Short Call Strike)
|
||||
var longCallPosition = parameters.PositionGroup.Positions.Single(
|
||||
position => position.Quantity > 0 && position.Symbol.ID.OptionRight == OptionRight.Call);
|
||||
var shortCallPosition = parameters.PositionGroup.Positions.Single(
|
||||
position => position.Quantity < 0 && position.Symbol.ID.OptionRight == OptionRight.Call);
|
||||
var longPutPosition = parameters.PositionGroup.Positions.Single(
|
||||
position => position.Quantity > 0 && position.Symbol.ID.OptionRight == OptionRight.Put);
|
||||
var shortPutPosition = parameters.PositionGroup.Positions.Single(
|
||||
position => position.Quantity < 0 && position.Symbol.ID.OptionRight == OptionRight.Put);
|
||||
var longCallSecurity = (Option)parameters.Portfolio.Securities[longCallPosition.Symbol];
|
||||
var shortCallSecurity = (Option)parameters.Portfolio.Securities[shortCallPosition.Symbol];
|
||||
var longPutSecurity = (Option)parameters.Portfolio.Securities[longPutPosition.Symbol];
|
||||
var shortPutSecurity = (Option)parameters.Portfolio.Securities[shortPutPosition.Symbol];
|
||||
|
||||
// commission cost: MAX($1, $0.65/contract * quantity) + bid/ask price
|
||||
var commissionFees = Math.Max(Math.Abs(longCallPosition.Quantity) * 0.65m, 1m) * 4m; // 4 contracts in total
|
||||
var orderCosts = shortCallSecurity.AskPrice - longCallSecurity.BidPrice + shortPutSecurity.AskPrice - longPutSecurity.BidPrice;
|
||||
var multiplier = Math.Abs(longCallPosition.Quantity) * longCallSecurity.ContractUnitOfTrade;
|
||||
var closeCost = commissionFees + orderCosts * multiplier;
|
||||
|
||||
var strikeDifference = longCallPosition.Symbol.ID.StrikePrice - shortCallPosition.Symbol.ID.StrikePrice;
|
||||
|
||||
var result = Math.Max(1.02m * closeCost, strikeDifference * multiplier);
|
||||
var inAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, longCallSecurity.QuoteCurrency.Symbol);
|
||||
|
||||
return new MaintenanceMargin(inAccountCurrency);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.JellyRoll.Name
|
||||
|| _optionStrategy.Name == OptionStrategyDefinitions.ShortJellyRoll.Name)
|
||||
{
|
||||
// long calendar spread part has no margin requirement due to same strike
|
||||
// only the short calendar spread's short option has margin requirement
|
||||
var furtherExpiry = parameters.PositionGroup.Positions.Max(position => position.Symbol.ID.Date);
|
||||
var shortCalendarSpreadShortLeg = parameters.PositionGroup.Positions.Single(position =>
|
||||
position.Quantity < 0 && position.Symbol.ID.Date == furtherExpiry);
|
||||
var shortCalendarSpreadShortLegSecurity = (Option)parameters.Portfolio.Securities[shortCalendarSpreadShortLeg.Symbol];
|
||||
var result = Math.Abs(shortCalendarSpreadShortLegSecurity.BuyingPowerModel.GetMaintenanceMargin(
|
||||
MaintenanceMarginParameters.ForQuantityAtCurrentPrice(shortCalendarSpreadShortLegSecurity, shortCalendarSpreadShortLeg.Quantity)));
|
||||
|
||||
return new MaintenanceMargin(result);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.BearCallLadder.Name)
|
||||
{
|
||||
return GetCallLadderMargin(parameters, true);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.BearPutLadder.Name)
|
||||
{
|
||||
return GetPutLadderMargin(parameters, false);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.BullCallLadder.Name)
|
||||
{
|
||||
return GetCallLadderMargin(parameters, false);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.BullPutLadder.Name)
|
||||
{
|
||||
return GetPutLadderMargin(parameters, true);
|
||||
}
|
||||
|
||||
throw new NotImplementedException($"Option strategy {_optionStrategy.Name} margin modeling has yet to be implemented");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The margin that must be held in order to increase the position by the provided quantity
|
||||
/// </summary>
|
||||
/// <param name="parameters">An object containing the security and quantity</param>
|
||||
public override InitialMargin GetInitialMarginRequirement(PositionGroupInitialMarginParameters parameters)
|
||||
{
|
||||
var result = 0m;
|
||||
|
||||
if (_optionStrategy == null)
|
||||
{
|
||||
result = 0;
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.ProtectivePut.Name || _optionStrategy.Name == OptionStrategyDefinitions.ProtectiveCall.Name)
|
||||
{
|
||||
// Initial Standard Stock Margin Requirement
|
||||
var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
|
||||
var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
|
||||
|
||||
result = Math.Abs(underlyingSecurity.BuyingPowerModel.GetInitialMarginRequirement(underlyingSecurity, underlyingPosition.Quantity));
|
||||
result = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, underlyingSecurity.QuoteCurrency.Symbol);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.CoveredCall.Name)
|
||||
{
|
||||
// Max(Call Value, Long Stock Initial Margin)
|
||||
var optionPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => position.Symbol.SecurityType.IsOption());
|
||||
var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
|
||||
var optionSecurity = (Option)parameters.Portfolio.Securities[optionPosition.Symbol];
|
||||
var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
|
||||
|
||||
var optionValue = Math.Abs(optionSecurity.Holdings.GetQuantityValue(optionPosition.Quantity).InAccountCurrency);
|
||||
|
||||
var marginRequired = underlyingSecurity.BuyingPowerModel.GetInitialMarginRequirement(underlyingSecurity, underlyingPosition.Quantity);
|
||||
|
||||
// IB charges more than expected, this formula was inferred based on actual requirements see 'CoveredCallInitialMarginRequirementsTestCases'
|
||||
result = optionValue * 0.8m + marginRequired;
|
||||
result = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.CoveredPut.Name)
|
||||
{
|
||||
// Initial Stock Margin Requirement + In the Money Amount
|
||||
result = GetMaintenanceMargin(new PositionGroupMaintenanceMarginParameters(parameters.Portfolio, parameters.PositionGroup));
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.ProtectiveCollar.Name || _optionStrategy.Name == OptionStrategyDefinitions.Conversion.Name)
|
||||
{
|
||||
result = GetCollarConversionInitialMargin(parameters.PositionGroup, parameters.Portfolio, OptionRight.Call);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.ReverseConversion.Name)
|
||||
{
|
||||
result = GetCollarConversionInitialMargin(parameters.PositionGroup, parameters.Portfolio, OptionRight.Put);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.NakedCall.Name
|
||||
|| _optionStrategy.Name == OptionStrategyDefinitions.NakedPut.Name)
|
||||
{
|
||||
var option = parameters.PositionGroup.Positions.Single();
|
||||
var security = (Option)parameters.Portfolio.Securities[option.Symbol];
|
||||
var margin = security.BuyingPowerModel.GetInitialMarginRequirement(new InitialMarginParameters(security, option.Quantity));
|
||||
var optionMargin = margin as OptionInitialMargin;
|
||||
|
||||
if (optionMargin != null)
|
||||
{
|
||||
return new OptionInitialMargin(Math.Abs(optionMargin.ValueWithoutPremium), optionMargin.Premium);
|
||||
}
|
||||
|
||||
return margin;
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.BearCallSpread.Name
|
||||
|| _optionStrategy.Name == OptionStrategyDefinitions.BullCallSpread.Name)
|
||||
{
|
||||
result = GetLongCallShortCallStrikeDifferenceMargin(parameters.PositionGroup.Positions, parameters.Portfolio, parameters.PositionGroup.Quantity);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.CallCalendarSpread.Name
|
||||
|| _optionStrategy.Name == OptionStrategyDefinitions.PutCalendarSpread.Name)
|
||||
{
|
||||
result = 0m;
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortCallCalendarSpread.Name
|
||||
|| _optionStrategy.Name == OptionStrategyDefinitions.ShortPutCalendarSpread.Name)
|
||||
{
|
||||
var shortOptionPosition = parameters.PositionGroup.Positions.Single(position => position.Quantity < 0);
|
||||
var shortOption = (Option)parameters.Portfolio.Securities[shortOptionPosition.Symbol];
|
||||
result = Math.Abs(shortOption.BuyingPowerModel.GetInitialMarginRequirement(shortOption, shortOptionPosition.Quantity));
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.BearPutSpread.Name
|
||||
|| _optionStrategy.Name == OptionStrategyDefinitions.BullPutSpread.Name)
|
||||
{
|
||||
result = GetShortPutLongPutStrikeDifferenceMargin(parameters.PositionGroup.Positions, parameters.Portfolio, parameters.PositionGroup.Quantity);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.Straddle.Name || _optionStrategy.Name == OptionStrategyDefinitions.Strangle.Name)
|
||||
{
|
||||
// Margined as two long options: since there is not margin requirements for long options, we return 0
|
||||
result = 0m;
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortStraddle.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortStrangle.Name)
|
||||
{
|
||||
result = GetShortStraddleStrangleMargin(parameters.PositionGroup, parameters.Portfolio,
|
||||
(option, quantity) => Math.Abs(option.BuyingPowerModel.GetInitialMarginRequirement(option, quantity)));
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.ButterflyCall.Name || _optionStrategy.Name == OptionStrategyDefinitions.ButterflyPut.Name)
|
||||
{
|
||||
result = 0m;
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortButterflyPut.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortButterflyCall.Name)
|
||||
{
|
||||
result = GetMiddleAndLowStrikeDifference(parameters.PositionGroup, parameters.Portfolio);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.IronCondor.Name || _optionStrategy.Name == OptionStrategyDefinitions.IronButterfly.Name ||
|
||||
_optionStrategy.Name == OptionStrategyDefinitions.ShortIronCondor.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortIronButterfly.Name)
|
||||
{
|
||||
result = GetShortPutLongPutStrikeDifferenceMargin(parameters.PositionGroup.Positions, parameters.Portfolio, parameters.PositionGroup.Quantity);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.BoxSpread.Name)
|
||||
{
|
||||
result = 0m;
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortBoxSpread.Name)
|
||||
{
|
||||
result = GetMaintenanceMargin(new PositionGroupMaintenanceMarginParameters(parameters.Portfolio, parameters.PositionGroup));
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.JellyRoll.Name
|
||||
|| _optionStrategy.Name == OptionStrategyDefinitions.ShortJellyRoll.Name)
|
||||
{
|
||||
result = GetMaintenanceMargin(new PositionGroupMaintenanceMarginParameters(parameters.Portfolio, parameters.PositionGroup));
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.BearCallLadder.Name || _optionStrategy.Name == OptionStrategyDefinitions.BearPutLadder.Name
|
||||
|| _optionStrategy.Name == OptionStrategyDefinitions.BullCallLadder.Name || _optionStrategy.Name == OptionStrategyDefinitions.BullPutLadder.Name)
|
||||
{
|
||||
result = GetMaintenanceMargin(new PositionGroupMaintenanceMarginParameters(parameters.Portfolio, parameters.PositionGroup));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException($"Option strategy {_optionStrategy.Name} margin modeling has yet to be implemented");
|
||||
}
|
||||
|
||||
// Add premium to initial margin only when it is positive (the user must pay the premium)
|
||||
var premium = 0m;
|
||||
foreach (var position in parameters.PositionGroup.Positions.Where(position => position.Symbol.SecurityType.IsOption()))
|
||||
{
|
||||
var option = (Option)parameters.Portfolio.Securities[position.Symbol];
|
||||
premium += option.Holdings.GetQuantityValue(position.Quantity).InAccountCurrency;
|
||||
}
|
||||
|
||||
return new OptionInitialMargin(result, premium);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total margin required to execute the specified order in units of the account currency including fees
|
||||
/// </summary>
|
||||
/// <param name="parameters">An object containing the portfolio, the security and the order</param>
|
||||
/// <returns>The total margin in terms of the currency quoted in the order</returns>
|
||||
public override InitialMargin GetInitialMarginRequiredForOrder(PositionGroupInitialMarginForOrderParameters parameters)
|
||||
{
|
||||
var security = parameters.Portfolio.Securities[parameters.Order.Symbol];
|
||||
var fees = security.FeeModel.GetOrderFee(new OrderFeeParameters(security, parameters.Order));
|
||||
var feesInAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(fees.Value);
|
||||
|
||||
var initialMarginRequired = GetInitialMarginRequirement(new PositionGroupInitialMarginParameters(parameters.Portfolio, parameters.PositionGroup));
|
||||
|
||||
var feesWithSign = Math.Sign(initialMarginRequired) * feesInAccountCurrency.Amount;
|
||||
|
||||
return new InitialMargin(feesWithSign + initialMarginRequired);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the initial margin required for the specified contemplated position group.
|
||||
/// Used by <see cref="QuantConnect.Securities.Positions.PositionGroupBuyingPowerModel.GetReservedBuyingPowerImpact"/> to get the contemplated groups margin.
|
||||
/// </summary>
|
||||
protected override decimal GetContemplatedGroupsInitialMargin(SecurityPortfolioManager portfolio, PositionGroupCollection contemplatedGroups,
|
||||
List<IPosition> ordersPositions)
|
||||
{
|
||||
var contemplatedMargin = 0m;
|
||||
foreach (var contemplatedGroup in contemplatedGroups)
|
||||
{
|
||||
// We use the initial margin requirement as the contemplated groups margin in order to ensure
|
||||
// the available buying power is enough to execute the order.
|
||||
var initialMargin = contemplatedGroup.BuyingPowerModel.GetInitialMarginRequirement(
|
||||
new PositionGroupInitialMarginParameters(portfolio, contemplatedGroup));
|
||||
var optionInitialMargin = initialMargin as OptionInitialMargin;
|
||||
contemplatedMargin += optionInitialMargin?.ValueWithoutPremium ?? initialMargin;
|
||||
}
|
||||
|
||||
// Now we need to add the premium paid for the order:
|
||||
// This should always return a single group since it is a single order/combo
|
||||
var ordersGroups = portfolio.Positions.ResolvePositionGroups(new PositionCollection(ordersPositions));
|
||||
foreach (var orderGroup in ordersGroups)
|
||||
{
|
||||
var initialMargin = orderGroup.BuyingPowerModel.GetInitialMarginRequirement(
|
||||
new PositionGroupInitialMarginParameters(portfolio, orderGroup));
|
||||
var optionInitialMargin = initialMargin as OptionInitialMargin;
|
||||
|
||||
if (optionInitialMargin != null)
|
||||
{
|
||||
// We need to add the premium paid for the order. We use the TotalValue-Value difference instead of Premium
|
||||
// to add it only when needed -- when it is debited from the account
|
||||
contemplatedMargin += optionInitialMargin.Value - optionInitialMargin.ValueWithoutPremium;
|
||||
}
|
||||
}
|
||||
|
||||
return contemplatedMargin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents the current object.
|
||||
/// </summary>
|
||||
/// <returns>A string that represents the current object.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return _optionStrategy.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Maximum (Short Put Strike - Long Put Strike, 0)
|
||||
/// </summary>
|
||||
private static decimal GetShortPutLongPutStrikeDifferenceMargin(IEnumerable<IPosition> positions, SecurityPortfolioManager portfolio, decimal quantity)
|
||||
{
|
||||
var longOption = positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Put && position.Quantity > 0);
|
||||
var shortOption = positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Put && position.Quantity < 0);
|
||||
var optionSecurity = (Option)portfolio.Securities[longOption.Symbol];
|
||||
|
||||
// Maximum (Short Put Strike - Long Put Strike, 0)
|
||||
var strikeDifference = shortOption.Symbol.ID.StrikePrice - longOption.Symbol.ID.StrikePrice;
|
||||
|
||||
var result = Math.Max(strikeDifference * optionSecurity.ContractUnitOfTrade * Math.Abs(quantity), 0);
|
||||
|
||||
// convert into account currency
|
||||
return portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Maximum (Strike Long Call - Strike Short Call, 0)
|
||||
/// </summary>
|
||||
private static decimal GetLongCallShortCallStrikeDifferenceMargin(IEnumerable<IPosition> positions, SecurityPortfolioManager portfolio, decimal quantity)
|
||||
{
|
||||
var longOption = positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Call && position.Quantity > 0);
|
||||
var shortOption = positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Call && position.Quantity < 0);
|
||||
var optionSecurity = (Option)portfolio.Securities[longOption.Symbol];
|
||||
|
||||
var strikeDifference = longOption.Symbol.ID.StrikePrice - shortOption.Symbol.ID.StrikePrice;
|
||||
|
||||
var result = Math.Max(strikeDifference * optionSecurity.ContractUnitOfTrade * Math.Abs(quantity), 0);
|
||||
|
||||
// convert into account currency
|
||||
return portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Maximum (Middle Strike - Lowest Strike, 0)
|
||||
/// </summary>
|
||||
private static decimal GetMiddleAndLowStrikeDifference(IPositionGroup positionGroup, SecurityPortfolioManager portfolio)
|
||||
{
|
||||
var options = positionGroup.Positions.OrderBy(position => position.Symbol.ID.StrikePrice).ToList();
|
||||
var lowestCallStrike = options[0].Symbol.ID.StrikePrice;
|
||||
var middleCallStrike = options[1].Symbol.ID.StrikePrice;
|
||||
var optionSecurity = (Option)portfolio.Securities[options[0].Symbol];
|
||||
|
||||
var strikeDifference = Math.Max((middleCallStrike - lowestCallStrike) * optionSecurity.ContractUnitOfTrade * Math.Abs(positionGroup.Quantity), 0);
|
||||
|
||||
// convert into account currency
|
||||
return portfolio.CashBook.ConvertToAccountCurrency(strikeDifference, optionSecurity.QuoteCurrency.Symbol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the margin for a short straddle or strangle.
|
||||
/// This is the same for both the initial margin requirement and the maintenance margin.
|
||||
/// </summary>
|
||||
private static decimal GetShortStraddleStrangleMargin(IPositionGroup positionGroup, SecurityPortfolioManager portfolio,
|
||||
Func<Option, decimal, decimal> getOptionMargin)
|
||||
{
|
||||
var callOption = positionGroup.Positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Call);
|
||||
var callSecurity = (Option)portfolio.Securities[callOption.Symbol];
|
||||
var callMargin = getOptionMargin(callSecurity, callOption.Quantity);
|
||||
|
||||
var putOption = positionGroup.Positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Put);
|
||||
var putSecurity = (Option)portfolio.Securities[putOption.Symbol];
|
||||
var putMargin = getOptionMargin(putSecurity, putOption.Quantity);
|
||||
|
||||
var result = 0m;
|
||||
|
||||
if (putMargin > callMargin)
|
||||
{
|
||||
result = putMargin + callSecurity.Price * callSecurity.ContractUnitOfTrade * Math.Abs(callOption.Quantity);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = callMargin + putSecurity.Price * putSecurity.ContractUnitOfTrade * Math.Abs(putOption.Quantity);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the maintenance margin for a conversion or reverse conversion.
|
||||
/// </summary>
|
||||
private static decimal GetConversionMaintenanceMargin(IPositionGroup positionGroup, SecurityPortfolioManager portfolio, OptionRight optionRight)
|
||||
{
|
||||
// 10% * Strike Price + Call/Put In the Money Amount
|
||||
var optionPosition = positionGroup.Positions.Single(position =>
|
||||
position.Symbol.SecurityType.IsOption() && position.Symbol.ID.OptionRight == optionRight);
|
||||
var underlyingPosition = positionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
|
||||
var optionSecurity = (Option)portfolio.Securities[optionPosition.Symbol];
|
||||
var underlyingSecurity = portfolio.Securities[underlyingPosition.Symbol];
|
||||
|
||||
var marginRequirement = 0.1m * optionSecurity.StrikePrice + optionSecurity.GetIntrinsicValue(underlyingSecurity.Price);
|
||||
var result = marginRequirement * Math.Abs(optionPosition.Quantity) * optionSecurity.ContractUnitOfTrade;
|
||||
var inAccountCurrency = portfolio.CashBook.ConvertToAccountCurrency(result, underlyingSecurity.QuoteCurrency.Symbol);
|
||||
|
||||
return new MaintenanceMargin(inAccountCurrency);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the initial margin requirement for a collar, conversion, or reverse conversion.
|
||||
/// </summary>
|
||||
private static decimal GetCollarConversionInitialMargin(IPositionGroup positionGroup, SecurityPortfolioManager portfolio, OptionRight optionRight)
|
||||
{
|
||||
// Initial Stock Margin Requirement + In the Money Call/Put Amount
|
||||
var optionPosition = positionGroup.Positions.Single(position =>
|
||||
position.Symbol.SecurityType.IsOption() && position.Symbol.ID.OptionRight == optionRight);
|
||||
var underlyingPosition = positionGroup.Positions.Single(position => !position.Symbol.SecurityType.IsOption());
|
||||
var optionSecurity = (Option)portfolio.Securities[optionPosition.Symbol];
|
||||
var underlyingSecurity = portfolio.Securities[underlyingPosition.Symbol];
|
||||
|
||||
var intrinsicValue = optionSecurity.GetIntrinsicValue(underlyingSecurity.Price);
|
||||
var inTheMoneyAmount = intrinsicValue * optionSecurity.ContractUnitOfTrade * Math.Abs(optionPosition.Quantity);
|
||||
|
||||
var initialMarginRequirement = underlyingSecurity.BuyingPowerModel.GetInitialMarginRequirement(underlyingSecurity, underlyingPosition.Quantity);
|
||||
|
||||
var result = Math.Abs(initialMarginRequirement) + inTheMoneyAmount;
|
||||
return portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the initial/maintenance margin requirement for a call ladder
|
||||
/// </summary>
|
||||
private static decimal GetCallLadderMargin(PositionGroupMaintenanceMarginParameters parameters, bool bearCallLadder)
|
||||
{
|
||||
var quantity = parameters.PositionGroup.Quantity;
|
||||
|
||||
if ((quantity >= 0 && bearCallLadder) || (quantity < 0 && !bearCallLadder))
|
||||
{
|
||||
// Bear Call Ladder = Bear Call Spread of 2 lower strike prices + Long Call with the highest strike price (margin: 0)
|
||||
var callSpread = parameters.PositionGroup.Positions.OrderBy(position => position.Symbol.ID.StrikePrice).Take(2).ToList();
|
||||
return GetLongCallShortCallStrikeDifferenceMargin(callSpread, parameters.Portfolio, Math.Abs(quantity));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bull Call Ladder = Bull Call Spread of 2 lower strike prices (margin: 0) + Short Call with the highest strike price
|
||||
var shortNakedCall = parameters.PositionGroup.Positions.OrderByDescending(position => position.Symbol.ID.StrikePrice).First();
|
||||
var security = (Option)parameters.Portfolio.Securities[shortNakedCall.Symbol];
|
||||
var margin = security.BuyingPowerModel.GetInitialMarginRequirement(new InitialMarginParameters(security, shortNakedCall.Quantity));
|
||||
return new MaintenanceMargin(Math.Abs(margin));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the initial/maintenance margin requirement for a put ladder
|
||||
/// </summary>
|
||||
private static decimal GetPutLadderMargin(PositionGroupMaintenanceMarginParameters parameters, bool bullPutLadder)
|
||||
{
|
||||
var quantity = parameters.PositionGroup.Quantity;
|
||||
|
||||
if ((quantity >= 0 && bullPutLadder) || (quantity < 0 && !bullPutLadder))
|
||||
{
|
||||
// Bull Put Ladder = Bull Put Spread of 2 higher strike prices + Long Put with the lowest strike price (margin: 0)
|
||||
var putSpread = parameters.PositionGroup.Positions.OrderByDescending(position => position.Symbol.ID.StrikePrice).Take(2).ToList();
|
||||
return GetShortPutLongPutStrikeDifferenceMargin(putSpread, parameters.Portfolio, Math.Abs(quantity));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bear Put Ladder = Bear Put Spread of 2 higher strike prices (margin: 0) + Short Put with the lowest strike price
|
||||
var shortNakedPut = parameters.PositionGroup.Positions.OrderBy(position => position.Symbol.ID.StrikePrice).First();
|
||||
var security = (Option)parameters.Portfolio.Securities[shortNakedPut.Symbol];
|
||||
var margin = security.BuyingPowerModel.GetInitialMarginRequirement(new InitialMarginParameters(security, shortNakedPut.Quantity));
|
||||
return new MaintenanceMargin(Math.Abs(margin));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user