/// <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) { // SecurityPositionGroupBuyingPowerModel models buying power the same as non-grouped, so we can simply sum up // the reserved buying power via the security's model. We should really only ever get a single position here, // but it's not incorrect to ask the model for what the reserved buying power would be using default modeling var buyingPower = 0m; foreach (var position in parameters.PositionGroup) { var security = parameters.Portfolio.Securities[position.Symbol]; var result = security.BuyingPowerModel.GetMaintenanceMargin( MaintenanceMarginParameters.ForQuantityAtCurrentPrice(security, position.Quantity) ); buyingPower += result; } return(buyingPower); }
/// <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.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); var optionValue = optionSecurity.Holdings.GetQuantityValue(optionPosition.Quantity); // mark price, strike price var underlyingPriceToEvaluate = Math.Min(optionSecurity.Price, optionSecurity.StrikePrice); var underlyingHypotheticalValue = underlyingSecurity.Holdings.GetQuantityValue(underlyingPosition.Quantity, underlyingPriceToEvaluate); 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.BearCallSpread.Name || _optionStrategy.Name == OptionStrategyDefinitions.BullCallSpread.Name || _optionStrategy.Name == OptionStrategyDefinitions.CallCalendarSpread.Name) { var result = GetLongCallShortCallStrikeDifferenceMargin(parameters.PositionGroup, parameters.Portfolio); return(new MaintenanceMargin(result)); } else if (_optionStrategy.Name == OptionStrategyDefinitions.BearPutSpread.Name || _optionStrategy.Name == OptionStrategyDefinitions.BullPutSpread.Name || _optionStrategy.Name == OptionStrategyDefinitions.PutCalendarSpread.Name) { var result = GetShortPutLongPutStrikeDifferenceMargin(parameters.PositionGroup, parameters.Portfolio); return(new MaintenanceMargin(result)); } else if (_optionStrategy.Name == OptionStrategyDefinitions.Straddle.Name || _optionStrategy.Name == OptionStrategyDefinitions.Strangle.Name) { // Margined as two long options. var callOption = parameters.PositionGroup.Positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Call); var callSecurity = (Option)parameters.Portfolio.Securities[callOption.Symbol]; var callMargin = callSecurity.BuyingPowerModel.GetMaintenanceMargin(MaintenanceMarginParameters.ForQuantityAtCurrentPrice( callSecurity, callOption.Quantity)); var putOption = parameters.PositionGroup.Positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Put); var putSecurity = (Option)parameters.Portfolio.Securities[putOption.Symbol]; var putMargin = putSecurity.BuyingPowerModel.GetMaintenanceMargin(MaintenanceMarginParameters.ForQuantityAtCurrentPrice( putSecurity, putOption.Quantity)); var result = callMargin.Value + putMargin.Value; 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) { var result = GetShortPutLongPutStrikeDifferenceMargin(parameters.PositionGroup, parameters.Portfolio); return(new MaintenanceMargin(result)); } throw new NotImplementedException($"Option strategy {_optionStrategy.Name} margin modeling has yet to be implemented"); }