コード例 #1
0
        /// <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));
        }
コード例 #2
0
        /// <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));
        }
コード例 #3
0
ファイル: BuyingPowerModel.cs プロジェクト: Capnode/Algoloop
        /// <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));
        }
コード例 #4
0
        /// <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));
        }
コード例 #5
0
        /// <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));
        }