/// <summary> /// Get the maximum market order quantity to obtain a position with a given value in account currency /// </summary> /// <param name="model">The <see cref="IBuyingPowerModel"/></param> /// <param name="portfolio">The algorithm's portfolio</param> /// <param name="security">The security to be traded</param> /// <param name="target">The target percent holdings</param> /// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns> public static GetMaximumOrderQuantityResult GetMaximumOrderQuantityForTargetBuyingPower( this IBuyingPowerModel model, SecurityPortfolioManager portfolio, Security security, decimal target ) { var parameters = new GetMaximumOrderQuantityForTargetBuyingPowerParameters(portfolio, security, target); return(model.GetMaximumOrderQuantityForTargetBuyingPower(parameters)); }
/// <summary> /// Get the maximum market order quantity to obtain a position with a given buying power percentage. /// Will not take into account free buying power. /// </summary> /// <param name="parameters">An object containing the portfolio, the security and the target signed buying power percentage</param> /// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns> public override GetMaximumOrderQuantityResult GetMaximumOrderQuantityForTargetBuyingPower(GetMaximumOrderQuantityForTargetBuyingPowerParameters parameters) { var targetPortfolioValue = parameters.TargetBuyingPower * parameters.Portfolio.TotalPortfolioValue; // no shorting allowed if (targetPortfolioValue < 0) { return(new GetMaximumOrderQuantityResult(0, "The cash model does not allow shorting.")); } var baseCurrency = parameters.Security as IBaseCurrencySymbol; if (baseCurrency == null) { return(new GetMaximumOrderQuantityResult(0, "The security type must be SecurityType.Crypto or SecurityType.Forex.")); } // if target value is zero, return amount of base currency available to sell if (targetPortfolioValue == 0) { return(new GetMaximumOrderQuantityResult(-parameters.Portfolio.CashBook[baseCurrency.BaseCurrencySymbol].Amount)); } // convert base currency cash to account currency var baseCurrencyPosition = parameters.Portfolio.CashBook.ConvertToAccountCurrency( parameters.Portfolio.CashBook[baseCurrency.BaseCurrencySymbol].Amount, baseCurrency.BaseCurrencySymbol); // remove directionality, we'll work in the land of absolutes var targetOrderValue = Math.Abs(targetPortfolioValue - baseCurrencyPosition); var direction = targetPortfolioValue > baseCurrencyPosition ? OrderDirection.Buy : OrderDirection.Sell; // determine the unit price in terms of the account currency var unitPrice = direction == OrderDirection.Buy ? parameters.Security.AskPrice : parameters.Security.BidPrice; unitPrice *= parameters.Security.QuoteCurrency.ConversionRate * parameters.Security.SymbolProperties.ContractMultiplier; if (unitPrice == 0) { if (parameters.Security.QuoteCurrency.ConversionRate == 0) { return(new GetMaximumOrderQuantityResult(0, "The internal cash feed required for converting" + Invariant($" {parameters.Security.QuoteCurrency.Symbol} to {parameters.Portfolio.CashBook.AccountCurrency} does not") + " have any data yet (or market may be closed).")); } if (parameters.Security.SymbolProperties.ContractMultiplier == 0) { return(new GetMaximumOrderQuantityResult(0, $"The contract multiplier for the {parameters.Security.Symbol.Value} security is zero. The symbol properties database may be out of date.")); } // security.Price == 0 return(new GetMaximumOrderQuantityResult(0, parameters.Security.Symbol.GetZeroPriceMessage())); } // continue iterating while we do not have enough cash for the order decimal orderFees = 0; decimal currentOrderValue = 0; // compute the initial order quantity var orderQuantity = targetOrderValue / unitPrice; // rounding off Order Quantity to the nearest multiple of Lot Size orderQuantity -= orderQuantity % parameters.Security.SymbolProperties.LotSize; if (orderQuantity == 0) { string reason = null; if (!parameters.SilenceNonErrorReasons) { reason = Invariant( $"The order quantity is less than the lot size of {parameters.Security.SymbolProperties.LotSize} and has been rounded to zero." ); } return(new GetMaximumOrderQuantityResult(0, reason, false)); } // Just in case... var lastOrderQuantity = 0m; var utcTime = parameters.Security.LocalTime.ConvertToUtc(parameters.Security.Exchange.TimeZone); do { // Each loop will reduce the order quantity based on the difference between // (cashRequired + orderFees) and targetOrderValue if (currentOrderValue > targetOrderValue) { var currentOrderValuePerUnit = currentOrderValue / orderQuantity; var amountOfOrdersToRemove = (currentOrderValue - targetOrderValue) / currentOrderValuePerUnit; if (amountOfOrdersToRemove < parameters.Security.SymbolProperties.LotSize) { // we will always substract at leat 1 LotSize amountOfOrdersToRemove = parameters.Security.SymbolProperties.LotSize; } orderQuantity -= amountOfOrdersToRemove; } // rounding off Order Quantity to the nearest multiple of Lot Size orderQuantity -= orderQuantity % parameters.Security.SymbolProperties.LotSize; if (orderQuantity <= 0) { return(new GetMaximumOrderQuantityResult(0, Invariant($"The order quantity is less than the lot size of {parameters.Security.SymbolProperties.LotSize} and has been rounded to zero.") + Invariant($"Target order value {targetOrderValue}. Order fees {orderFees}. Order quantity {orderQuantity}.") )); } if (lastOrderQuantity == orderQuantity) { throw new ArgumentException( Invariant($"GetMaximumOrderQuantityForTargetValue failed to converge to target order value {targetOrderValue}. ") + Invariant($"Current order value is {currentOrderValue}. Order quantity {orderQuantity}. Lot size is ") + Invariant($"{parameters.Security.SymbolProperties.LotSize}. Order fees {orderFees}. Security symbol {parameters.Security.Symbol}") ); } lastOrderQuantity = orderQuantity; // generate the order var order = new MarketOrder(parameters.Security.Symbol, orderQuantity, utcTime); var orderValue = orderQuantity * unitPrice; var fees = parameters.Security.FeeModel.GetOrderFee( new OrderFeeParameters(parameters.Security, order)).Value; orderFees = parameters.Portfolio.CashBook.ConvertToAccountCurrency(fees).Amount; currentOrderValue = orderValue + orderFees; } while (currentOrderValue > targetOrderValue); // add directionality back in return(new GetMaximumOrderQuantityResult((direction == OrderDirection.Sell ? -1 : 1) * orderQuantity)); }
/// <summary> /// Get the maximum market order quantity to obtain a position with a given buying power percentage. /// Will not take into account free buying power. /// </summary> /// <param name="parameters">An object containing the portfolio, the security and the target signed buying power percentage</param> /// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns> /// <remarks>This implementation ensures that our resulting holdings is less than the target, but it does not necessarily /// maximize the holdings to meet the target. To do that we need a minimizing algorithm that reduces the difference between /// the target final margin value and the target holdings margin.</remarks> public virtual GetMaximumOrderQuantityResult GetMaximumOrderQuantityForTargetBuyingPower(GetMaximumOrderQuantityForTargetBuyingPowerParameters parameters) { // this is expensive so lets fetch it once var totalPortfolioValue = parameters.Portfolio.TotalPortfolioValue; // adjust target buying power to comply with required Free Buying Power Percent var signedTargetFinalMarginValue = parameters.TargetBuyingPower * (totalPortfolioValue - totalPortfolioValue * RequiredFreeBuyingPowerPercent); // if targeting zero, simply return the negative of the quantity if (signedTargetFinalMarginValue == 0) { return(new GetMaximumOrderQuantityResult(-parameters.Security.Holdings.Quantity, string.Empty, false)); } // we use initial margin requirement here to avoid the duplicate PortfolioTarget.Percent situation: // PortfolioTarget.Percent(1) -> fills -> PortfolioTarget.Percent(1) _could_ detect free buying power if we use Maintenance requirement here var signedCurrentUsedMargin = this.GetInitialMarginRequirement(parameters.Security, parameters.Security.Holdings.Quantity); // determine the unit price in terms of the account currency var utcTime = parameters.Security.LocalTime.ConvertToUtc(parameters.Security.Exchange.TimeZone); // determine the margin required for 1 unit var absUnitMargin = this.GetInitialMarginRequirement(parameters.Security, 1); if (absUnitMargin == 0) { return(new GetMaximumOrderQuantityResult(0, parameters.Security.Symbol.GetZeroPriceMessage())); } // Check that the change of margin is above our models minimum percentage change var absDifferenceOfMargin = Math.Abs(signedTargetFinalMarginValue - signedCurrentUsedMargin); if (!BuyingPowerModelExtensions.AboveMinimumOrderMarginPortfolioPercentage(parameters.Portfolio, parameters.MinimumOrderMarginPortfolioPercentage, absDifferenceOfMargin)) { string reason = null; if (!parameters.SilenceNonErrorReasons) { var minimumValue = totalPortfolioValue * parameters.MinimumOrderMarginPortfolioPercentage; reason = $"The target order margin {absDifferenceOfMargin} is less than the minimum {minimumValue}."; } return(new GetMaximumOrderQuantityResult(0, reason, false)); } // Use the following loop to converge on a value that places us under our target allocation when adjusted for fees var lastOrderQuantity = 0m; // For safety check decimal orderFees = 0m; decimal signedTargetHoldingsMargin; decimal orderQuantity; do { // Calculate our order quantity orderQuantity = GetAmountToOrder(parameters.Security, signedTargetFinalMarginValue, absUnitMargin, out signedTargetHoldingsMargin); if (orderQuantity == 0) { string reason = null; if (!parameters.SilenceNonErrorReasons) { reason = Invariant($"The order quantity is less than the lot size of {parameters.Security.SymbolProperties.LotSize} ") + Invariant($"and has been rounded to zero. Target order margin {signedTargetFinalMarginValue - signedCurrentUsedMargin}. "); } return(new GetMaximumOrderQuantityResult(0, reason, false)); } // generate the order var order = new MarketOrder(parameters.Security.Symbol, orderQuantity, utcTime); var fees = parameters.Security.FeeModel.GetOrderFee( new OrderFeeParameters(parameters.Security, order)).Value; orderFees = parameters.Portfolio.CashBook.ConvertToAccountCurrency(fees).Amount; // Update our target portfolio margin allocated when considering fees, then calculate the new FinalOrderMargin signedTargetFinalMarginValue = (totalPortfolioValue - orderFees - totalPortfolioValue * RequiredFreeBuyingPowerPercent) * parameters.TargetBuyingPower; // Start safe check after first loop, stops endless recursion if (lastOrderQuantity == orderQuantity) { var message = Invariant($"GetMaximumOrderQuantityForTargetBuyingPower failed to converge on the target margin: {signedTargetFinalMarginValue}; ") + Invariant($"the following information can be used to reproduce the issue. Total Portfolio Cash: {parameters.Portfolio.Cash}; Security : {parameters.Security.Symbol.ID}; ") + Invariant($"Price : {parameters.Security.Close}; Leverage: {parameters.Security.Leverage}; Order Fee: {orderFees}; Lot Size: {parameters.Security.SymbolProperties.LotSize}; ") + Invariant($"Current Holdings: {parameters.Security.Holdings.Quantity} @ {parameters.Security.Holdings.AveragePrice}; Target Percentage: %{parameters.TargetBuyingPower * 100};"); // Need to add underlying value to message to reproduce with options if (parameters.Security is Option.Option option && option.Underlying != null) { var underlying = option.Underlying; message += Invariant($" Underlying Security: {underlying.Symbol.ID}; Underlying Price: {underlying.Close}; Underlying Holdings: {underlying.Holdings.Quantity} @ {underlying.Holdings.AveragePrice};"); } throw new ArgumentException(message); } lastOrderQuantity = orderQuantity; } // Ensure that our target holdings margin will be less than or equal to our target allocated margin while (Math.Abs(signedTargetHoldingsMargin) > Math.Abs(signedTargetFinalMarginValue)); // add directionality back in return(new GetMaximumOrderQuantityResult(orderQuantity)); }
/// <summary> /// Get the maximum market order quantity to obtain a position with a given buying power percentage. /// Will not take into account free buying power. /// </summary> /// <param name="parameters">An object containing the portfolio, the security and the target signed buying power percentage</param> /// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns> public virtual GetMaximumOrderQuantityResult GetMaximumOrderQuantityForTargetBuyingPower(GetMaximumOrderQuantityForTargetBuyingPowerParameters parameters) { // this is expensive so lets fetch it once var totalPortfolioValue = parameters.Portfolio.TotalPortfolioValue; // adjust target buying power to comply with required Free Buying Power Percent var signedTargetFinalMarginValue = parameters.TargetBuyingPower * (totalPortfolioValue - totalPortfolioValue * RequiredFreeBuyingPowerPercent); // if targeting zero, simply return the negative of the quantity if (signedTargetFinalMarginValue == 0) { return(new GetMaximumOrderQuantityResult(-parameters.Security.Holdings.Quantity, string.Empty, false)); } // we use initial margin requirement here to avoid the duplicate PortfolioTarget.Percent situation: // PortfolioTarget.Percent(1) -> fills -> PortfolioTarget.Percent(1) _could_ detect free buying power if we use Maintenance requirement here var currentSignedUsedMargin = GetInitialMarginRequirement(parameters.Security, parameters.Security.Holdings.Quantity); // remove directionality, we'll work in the land of absolutes var absFinalOrderMargin = Math.Abs(signedTargetFinalMarginValue - currentSignedUsedMargin); var direction = signedTargetFinalMarginValue > currentSignedUsedMargin ? OrderDirection.Buy : OrderDirection.Sell; // determine the unit price in terms of the account currency var utcTime = parameters.Security.LocalTime.ConvertToUtc(parameters.Security.Exchange.TimeZone); // determine the margin required for 1 unit, positive since we are working with absolutes var absUnitMargin = GetInitialMarginRequirement(parameters.Security, 1); if (absUnitMargin == 0) { var reason = $"The price of the {parameters.Security.Symbol.Value} security is zero because it does not have any market " + "data yet. When the security price is set this security will be ready for trading."; return(new GetMaximumOrderQuantityResult(0, reason)); } var minimumValue = absUnitMargin * parameters.Security.SymbolProperties.LotSize; if (minimumValue > absFinalOrderMargin) { string reason = null; if (!parameters.SilenceNonErrorReasons) { reason = $"The target order margin {absFinalOrderMargin} is less than the minimum {minimumValue}."; } return(new GetMaximumOrderQuantityResult(0, reason, false)); } var increasingFinalMargin = Math.Abs(signedTargetFinalMarginValue) > Math.Abs(currentSignedUsedMargin); // calculate the total margin available, only check if we are going to increase our margin usage if (increasingFinalMargin && GetMarginRemaining(parameters.Portfolio, parameters.Security, direction) <= 0) { var reason = "The portfolio does not have enough margin available."; return(new GetMaximumOrderQuantityResult(0, reason)); } // continue iterating while we do not have enough margin for the order decimal orderMargin = 0; decimal orderFees = 0; // compute the initial order quantity var orderQuantity = absFinalOrderMargin / absUnitMargin; // rounding off Order Quantity to the nearest multiple of Lot Size orderQuantity -= orderQuantity % parameters.Security.SymbolProperties.LotSize; if (orderQuantity == 0) { string reason = null; if (!parameters.SilenceNonErrorReasons) { reason = $"The order quantity is less than the lot size of {parameters.Security.SymbolProperties.LotSize} " + "and has been rounded to zero."; } return(new GetMaximumOrderQuantityResult(0, reason, false)); } var loopCount = 0; // Just in case... var lastOrderQuantity = 0m; do { // Each loop will reduce the order quantity based on the difference between orderMargin and targetOrderMargin if (orderMargin > absFinalOrderMargin) { var currentOrderMarginPerUnit = orderMargin / orderQuantity; var amountOfOrdersToRemove = (orderMargin - absFinalOrderMargin) / currentOrderMarginPerUnit; if (amountOfOrdersToRemove < parameters.Security.SymbolProperties.LotSize) { // we will always subtract at least 1 LotSize amountOfOrdersToRemove = parameters.Security.SymbolProperties.LotSize; } orderQuantity -= amountOfOrdersToRemove; orderQuantity -= orderQuantity % parameters.Security.SymbolProperties.LotSize; } if (orderQuantity <= 0) { return(new GetMaximumOrderQuantityResult(0, Invariant($"The order quantity is less than the lot size of {parameters.Security.SymbolProperties.LotSize} ") + Invariant($"and has been rounded to zero.Target order margin {absFinalOrderMargin}. Order fees ") + Invariant($"{orderFees}. Order quantity {orderQuantity}. Margin unit {absUnitMargin}."), false )); } // generate the order var order = new MarketOrder(parameters.Security.Symbol, orderQuantity, utcTime); var fees = parameters.Security.FeeModel.GetOrderFee( new OrderFeeParameters(parameters.Security, order)).Value; orderFees = parameters.Portfolio.CashBook.ConvertToAccountCurrency(fees).Amount; // The TPV, take out the fees(unscaled) => yields available margin for trading(less fees) // then scale that by the target -- finally remove currentUsedMargin to get finalOrderMargin absFinalOrderMargin = Math.Abs( (totalPortfolioValue - orderFees - totalPortfolioValue * RequiredFreeBuyingPowerPercent) * parameters.TargetBuyingPower - currentSignedUsedMargin ); // After the first loop we need to recalculate order quantity since now we have fees included if (loopCount == 0) { // re compute the initial order quantity orderQuantity = absFinalOrderMargin / absUnitMargin; orderQuantity -= orderQuantity % parameters.Security.SymbolProperties.LotSize; } else { // Start safe check after first loop if (lastOrderQuantity == orderQuantity) { var message = "GetMaximumOrderQuantityForTargetBuyingPower failed to converge to target order margin " + Invariant($"{absFinalOrderMargin}. Current order margin is {orderMargin}. Order quantity {orderQuantity}. ") + Invariant($"Lot size is {parameters.Security.SymbolProperties.LotSize}. Order fees {orderFees}. Security symbol ") + $"{parameters.Security.Symbol}. Margin unit {absUnitMargin}."; throw new ArgumentException(message); } lastOrderQuantity = orderQuantity; } orderMargin = orderQuantity * absUnitMargin; loopCount++; // we always have to loop at least twice }while (loopCount < 2 || orderMargin > absFinalOrderMargin); // add directionality back in return(new GetMaximumOrderQuantityResult((direction == OrderDirection.Sell ? -1 : 1) * orderQuantity)); }
/// <summary> /// Get the maximum market order quantity to obtain a position with a given buying power percentage. /// Will not take into account free buying power. /// </summary> /// <param name="parameters">An object containing the portfolio, the security and the target signed buying power percentage</param> /// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns> /// <remarks>This implementation ensures that our resulting holdings is less than the target, but it does not necessarily /// maximize the holdings to meet the target. To do that we need a minimizing algorithm that reduces the difference between /// the target final margin value and the target holdings margin.</remarks> public virtual GetMaximumOrderQuantityResult GetMaximumOrderQuantityForTargetBuyingPower(GetMaximumOrderQuantityForTargetBuyingPowerParameters parameters) { // this is expensive so lets fetch it once var totalPortfolioValue = parameters.Portfolio.TotalPortfolioValue; // adjust target buying power to comply with required Free Buying Power Percent var signedTargetFinalMarginValue = parameters.TargetBuyingPower * (totalPortfolioValue - totalPortfolioValue * RequiredFreeBuyingPowerPercent); // if targeting zero, simply return the negative of the quantity if (signedTargetFinalMarginValue == 0) { return(new GetMaximumOrderQuantityResult(-parameters.Security.Holdings.Quantity, string.Empty, false)); } // we use initial margin requirement here to avoid the duplicate PortfolioTarget.Percent situation: // PortfolioTarget.Percent(1) -> fills -> PortfolioTarget.Percent(1) _could_ detect free buying power if we use Maintenance requirement here var currentSignedUsedMargin = this.GetInitialMarginRequirement(parameters.Security, parameters.Security.Holdings.Quantity); // remove directionality, we'll work in the land of absolutes var absFinalOrderMargin = Math.Abs(signedTargetFinalMarginValue - currentSignedUsedMargin); var direction = signedTargetFinalMarginValue > currentSignedUsedMargin ? OrderDirection.Buy : OrderDirection.Sell; // determine the unit price in terms of the account currency var utcTime = parameters.Security.LocalTime.ConvertToUtc(parameters.Security.Exchange.TimeZone); // determine the margin required for 1 unit, positive since we are working with absolutes var absUnitMargin = this.GetInitialMarginRequirement(parameters.Security, 1); if (absUnitMargin == 0) { return(new GetMaximumOrderQuantityResult(0, parameters.Security.Symbol.GetZeroPriceMessage())); } var minimumValue = absUnitMargin * parameters.Security.SymbolProperties.LotSize; if (minimumValue > absFinalOrderMargin) { string reason = null; if (!parameters.SilenceNonErrorReasons) { reason = $"The target order margin {absFinalOrderMargin} is less than the minimum {minimumValue}."; } return(new GetMaximumOrderQuantityResult(0, reason, false)); } // compute the initial order quantity var absOrderQuantity = Math.Abs(GetAmountToOrder(currentSignedUsedMargin, signedTargetFinalMarginValue, absUnitMargin, parameters.Security.SymbolProperties.LotSize)); if (absOrderQuantity == 0) { string reason = null; if (!parameters.SilenceNonErrorReasons) { reason = $"The order quantity is less than the lot size of {parameters.Security.SymbolProperties.LotSize} " + "and has been rounded to zero."; } return(new GetMaximumOrderQuantityResult(0, reason, false)); } // Use the following loop to converge on a value that places us under our target allocation when adjusted for fees var lastOrderQuantity = 0m; // For safety check var signedTargetHoldingsMargin = ((direction == OrderDirection.Sell ? -1 : 1) * absOrderQuantity + parameters.Security.Holdings.Quantity) * absUnitMargin; decimal orderFees = 0; do { // If our order target holdings is larger than our target margin allocated we need to recalculate our order size if (Math.Abs(signedTargetHoldingsMargin) > Math.Abs(signedTargetFinalMarginValue)) { absOrderQuantity = Math.Abs(GetAmountToOrder(currentSignedUsedMargin, signedTargetFinalMarginValue, absUnitMargin, parameters.Security.SymbolProperties.LotSize, absOrderQuantity * (direction == OrderDirection.Sell ? -1 : 1))); } if (absOrderQuantity <= 0) { var sign = direction == OrderDirection.Buy ? 1 : -1; return(new GetMaximumOrderQuantityResult(0, Invariant($"The order quantity is less than the lot size of {parameters.Security.SymbolProperties.LotSize} ") + Invariant($"and has been rounded to zero.Target order margin {absFinalOrderMargin * sign}. Order fees ") + Invariant($"{orderFees}. Order quantity {absOrderQuantity * sign}. Margin unit {absUnitMargin}."), false )); } // generate the order var order = new MarketOrder(parameters.Security.Symbol, absOrderQuantity, utcTime); var fees = parameters.Security.FeeModel.GetOrderFee( new OrderFeeParameters(parameters.Security, order)).Value; orderFees = parameters.Portfolio.CashBook.ConvertToAccountCurrency(fees).Amount; // Update our target portfolio margin allocated when considering fees, then calculate the new FinalOrderMargin signedTargetFinalMarginValue = (totalPortfolioValue - orderFees - totalPortfolioValue * RequiredFreeBuyingPowerPercent) * parameters.TargetBuyingPower; absFinalOrderMargin = Math.Abs(signedTargetFinalMarginValue - currentSignedUsedMargin); // Start safe check after first loop if (lastOrderQuantity == absOrderQuantity) { var sign = direction == OrderDirection.Buy ? 1 : -1; var message = Invariant($"GetMaximumOrderQuantityForTargetBuyingPower failed to converge on the target margin: {signedTargetFinalMarginValue}; ") + Invariant($"the following information can be used to reproduce the issue. Total Portfolio Cash: {parameters.Portfolio.Cash}; ") + Invariant($"Leverage: {parameters.Security.Leverage}; Order Fee: {orderFees}; Lot Size: {parameters.Security.SymbolProperties.LotSize}; ") + Invariant($"Per Unit Margin: {absUnitMargin}; Current Holdings: {parameters.Security.Holdings}; Target Percentage: %{parameters.TargetBuyingPower * 100}; ") + Invariant($"Current Order Target Margin: {absFinalOrderMargin * sign}; Current Order Margin: {absOrderQuantity * absUnitMargin * sign}"); throw new ArgumentException(message); } lastOrderQuantity = absOrderQuantity; // Update our target holdings margin signedTargetHoldingsMargin = ((direction == OrderDirection.Sell ? -1 : 1) * absOrderQuantity + parameters.Security.Holdings.Quantity) * absUnitMargin; } // Ensure that our target holdings margin will be less than or equal to our target allocated margin while (Math.Abs(signedTargetHoldingsMargin) > Math.Abs(signedTargetFinalMarginValue)); // add directionality back in return(new GetMaximumOrderQuantityResult((direction == OrderDirection.Sell ? -1 : 1) * absOrderQuantity)); }