/// <summary> /// Calculate the order quantity to achieve target-percent holdings. /// </summary> /// <param name="symbol">Security object we're asking for</param> /// <param name="target">Target percentag holdings, this is an unlevered value, so /// if you have 2x leverage and request 100% holdings, it will utilize half of the /// available margin</param> /// <returns>Order quantity to achieve this percentage</returns> public int CalculateOrderQuantity(Symbol symbol, decimal target) { var security = Securities[symbol]; var price = security.Price; // can't order it if we don't have data if (price == 0) { return(0); } // this is the value in dollars that we want our holdings to have var targetPortfolioValue = target * Portfolio.TotalPortfolioValue; var quantity = security.Holdings.Quantity; var currentHoldingsValue = price * quantity; // remove directionality, we'll work in the land of absolutes var targetOrderValue = Math.Abs(targetPortfolioValue - currentHoldingsValue); var direction = targetPortfolioValue > currentHoldingsValue ? OrderDirection.Buy : OrderDirection.Sell; // determine the unit price in terms of the account currency var unitPrice = new MarketOrder(symbol, 1, UtcTime).GetValue(security); // define lower and upper thresholds for the iteration var lowerThreshold = targetOrderValue - unitPrice / 2; var upperThreshold = targetOrderValue + unitPrice / 2; // continue iterating while we're still not within the specified thresholds var iterations = 0; var orderQuantity = 0; decimal orderValue = 0; while ((orderValue < lowerThreshold || orderValue > upperThreshold) && iterations < 10) { // find delta from where we are to where we want to be var delta = targetOrderValue - orderValue; // use delta value to compute a change in quantity required var deltaQuantity = (int)(delta / unitPrice); orderQuantity += deltaQuantity; // recompute order fees var order = new MarketOrder(security.Symbol, orderQuantity, UtcTime); var fee = security.FeeModel.GetOrderFee(security, order); orderValue = Math.Abs(order.GetValue(security)) + fee; // we need to add the fee in as well, even though it's not value, it's still a cost for the transaction // and we need to account for it to be sure we can make the trade produced by this method, imagine // set holdings 100% with 1x leverage, but a high fee structure, it quickly becomes necessary to include // otherwise the result of this function will be inactionable. iterations++; } // add directionality back in return((direction == OrderDirection.Sell ? -1 : 1) * orderQuantity); }
/// <summary> /// Calculate the order quantity to achieve target-percent holdings. /// </summary> /// <param name="symbol">Security object we're asking for</param> /// <param name="target">Target percentag holdings, this is an unlevered value, so /// if you have 2x leverage and request 100% holdings, it will utilize half of the /// available margin</param> /// <returns>Order quantity to achieve this percentage</returns> public int CalculateOrderQuantity(Symbol symbol, decimal target) { var security = Securities[symbol]; var price = security.Price; // can't order it if we don't have data if (price == 0) { return(0); } // if targeting zero, simply return the negative of the quantity if (target == 0) { return(-security.Holdings.Quantity); } // this is the value in dollars that we want our holdings to have var targetPortfolioValue = target * Portfolio.TotalPortfolioValue; var currentHoldingsValue = security.Holdings.HoldingsValue; // remove directionality, we'll work in the land of absolutes var targetOrderValue = Math.Abs(targetPortfolioValue - currentHoldingsValue); var direction = targetPortfolioValue > currentHoldingsValue ? OrderDirection.Buy : OrderDirection.Sell; // determine the unit price in terms of the account currency var unitPrice = new MarketOrder(symbol, 1, UtcTime).GetValue(security); if (unitPrice == 0) { return(0); } // calculate the total margin available var marginRemaining = Portfolio.GetMarginRemaining(symbol, direction); if (marginRemaining <= 0) { return(0); } // continue iterating while we do not have enough margin for the order decimal marginRequired; decimal orderValue; decimal orderFees; var feeToPriceRatio = 0; // compute the initial order quantity var orderQuantity = (int)(targetOrderValue / unitPrice); var iterations = 0; do { // decrease the order quantity if (iterations > 0) { // if fees are high relative to price, we reduce the order quantity faster if (feeToPriceRatio > 0) { orderQuantity -= feeToPriceRatio; } else { orderQuantity--; } } // generate the order var order = new MarketOrder(security.Symbol, orderQuantity, UtcTime); orderValue = order.GetValue(security); orderFees = security.FeeModel.GetOrderFee(security, order); feeToPriceRatio = (int)(orderFees / unitPrice); // calculate the margin required for the order marginRequired = security.MarginModel.GetInitialMarginRequiredForOrder(security, order); iterations++; } while (orderQuantity > 0 && (marginRequired > marginRemaining || orderValue + orderFees > targetOrderValue)); //Rounding off Order Quantity to the nearest multiple of Lot Size if (orderQuantity % Convert.ToInt32(security.SymbolProperties.LotSize) != 0) { orderQuantity = orderQuantity - (orderQuantity % Convert.ToInt32(security.SymbolProperties.LotSize)); } // add directionality back in return((direction == OrderDirection.Sell ? -1 : 1) * orderQuantity); }
/// <summary> /// Get the maximum market order quantity to obtain a position with a given value in account currency /// </summary> /// <param name="portfolio">The algorithm's portfolio</param> /// <param name="security">The security to be traded</param> /// <param name="targetPortfolioValue">The value in account currency that we want our holding to have</param> /// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns> public GetMaximumOrderQuantityForTargetValueResult GetMaximumOrderQuantityForTargetValue(SecurityPortfolioManager portfolio, Security security, decimal targetPortfolioValue) { // if targeting zero, simply return the negative of the quantity if (targetPortfolioValue == 0) { return(new GetMaximumOrderQuantityForTargetValueResult(-security.Holdings.Quantity, string.Empty, false)); } var currentHoldingsValue = security.Holdings.HoldingsValue; // remove directionality, we'll work in the land of absolutes var targetOrderValue = Math.Abs(targetPortfolioValue - currentHoldingsValue); var direction = targetPortfolioValue > currentHoldingsValue ? OrderDirection.Buy : OrderDirection.Sell; // determine the unit price in terms of the account currency var unitPrice = new MarketOrder(security.Symbol, 1, DateTime.UtcNow).GetValue(security); if (unitPrice == 0) { return(new GetMaximumOrderQuantityForTargetValueResult(0, $"The price of the {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.")); } // calculate the total margin available var marginRemaining = GetMarginRemaining(portfolio, security, direction); if (marginRemaining <= 0) { return(new GetMaximumOrderQuantityForTargetValueResult(0, $"The portfolio does not have enough margin available.")); } // continue iterating while we do not have enough margin for the order decimal marginRequired; decimal orderValue; decimal orderFees; var feeToPriceRatio = 0m; // compute the initial order quantity var orderQuantity = targetOrderValue / unitPrice; // rounding off Order Quantity to the nearest multiple of Lot Size orderQuantity -= orderQuantity % security.SymbolProperties.LotSize; if (orderQuantity == 0) { return(new GetMaximumOrderQuantityForTargetValueResult(0, $"The order quantity is less than the lot size of {security.SymbolProperties.LotSize} and has been rounded to zero.", false)); } do { // reduce order quantity by feeToPriceRatio, since it is faster than by lot size // if it becomes nonpositive, return zero orderQuantity -= feeToPriceRatio; if (orderQuantity <= 0) { return(new GetMaximumOrderQuantityForTargetValueResult(0, $"The portfolio does not hold enough cash including the order fees.")); } // generate the order var order = new MarketOrder(security.Symbol, orderQuantity, DateTime.UtcNow); orderValue = order.GetValue(security); orderFees = security.FeeModel.GetOrderFee(security, order); // find an incremental delta value for the next iteration step feeToPriceRatio = orderFees / unitPrice; feeToPriceRatio -= feeToPriceRatio % security.SymbolProperties.LotSize; if (feeToPriceRatio < security.SymbolProperties.LotSize) { feeToPriceRatio = security.SymbolProperties.LotSize; } // calculate the margin required for the order marginRequired = GetInitialMarginRequiredForOrder(security, order); } while (marginRequired > marginRemaining || orderValue + orderFees > targetOrderValue); // add directionality back in return(new GetMaximumOrderQuantityForTargetValueResult((direction == OrderDirection.Sell ? -1 : 1) * orderQuantity)); }
/// <summary> /// Get the maximum market order quantity to obtain a position with a given value in account currency /// </summary> /// <param name="portfolio">The algorithm's portfolio</param> /// <param name="security">The security to be traded</param> /// <param name="targetPortfolioValue">The value in account currency that we want our holding to have</param> /// <returns>Returns the maximum allowed market order quantity</returns> public decimal GetMaximumOrderQuantityForTargetValue(SecurityPortfolioManager portfolio, Security security, decimal targetPortfolioValue) { var baseCurrency = security as IBaseCurrencySymbol; if (baseCurrency == null) { return(0); } // convert base currency cash to account currency var baseCurrencyPosition = portfolio.CashBook.ConvertToAccountCurrency( portfolio.CashBook[baseCurrency.BaseCurrencySymbol].Amount, baseCurrency.BaseCurrencySymbol); // convert quote currency cash to account currency var quoteCurrencyPosition = portfolio.CashBook.ConvertToAccountCurrency( portfolio.CashBook[security.QuoteCurrency.Symbol].Amount, security.QuoteCurrency.Symbol); // determine the unit price in terms of the account currency var unitPrice = new MarketOrder(security.Symbol, 1, DateTime.UtcNow).GetValue(security); if (unitPrice == 0) { return(0); } // remove directionality, we'll work in the land of absolutes var targetOrderValue = Math.Abs(targetPortfolioValue - baseCurrencyPosition); var direction = targetPortfolioValue > baseCurrencyPosition ? OrderDirection.Buy : OrderDirection.Sell; // calculate the total cash available var cashRemaining = direction == OrderDirection.Buy ? quoteCurrencyPosition : baseCurrencyPosition; if (cashRemaining <= 0) { return(0); } // continue iterating while we do not have enough cash for the order decimal cashRequired; decimal orderValue; decimal orderFees; var feeToPriceRatio = 0m; // compute the initial order quantity var orderQuantity = targetOrderValue / unitPrice; // rounding off Order Quantity to the nearest multiple of Lot Size orderQuantity -= orderQuantity % security.SymbolProperties.LotSize; do { // reduce order quantity by feeToPriceRatio, since it is faster than by lot size // if it becomes nonpositive, return zero orderQuantity -= feeToPriceRatio; if (orderQuantity <= 0) { return(0); } // generate the order var order = new MarketOrder(security.Symbol, orderQuantity, DateTime.UtcNow); orderValue = order.GetValue(security); orderFees = security.FeeModel.GetOrderFee(security, order); // find an incremental delta value for the next iteration step feeToPriceRatio = orderFees / unitPrice; feeToPriceRatio -= feeToPriceRatio % security.SymbolProperties.LotSize; if (feeToPriceRatio < security.SymbolProperties.LotSize) { feeToPriceRatio = security.SymbolProperties.LotSize; } // calculate the cash required for the order cashRequired = orderValue; } while (cashRequired > cashRemaining || orderValue + orderFees > targetOrderValue); // add directionality back in return((direction == OrderDirection.Sell ? -1 : 1) * orderQuantity); }
public GetMaximumOrderQuantityResult GetMaximumOrderQuantityForTargetBuyingPower( GetMaximumOrderQuantityForTargetBuyingPowerParameters parameters ) { EnsureSecurityExists(parameters.Security); var expected = SecurityModel.GetMaximumOrderQuantityForTargetBuyingPower(parameters); if (reentry) { return(expected); } reentry = true; var security = parameters.Security; var positionGroup = Portfolio.Positions[new PositionGroupKey(PositionGroupModel, security)]; var actual = PositionGroupModel.GetMaximumLotsForTargetBuyingPower( new GetMaximumLotsForTargetBuyingPowerParameters( parameters.Portfolio, positionGroup, parameters.TargetBuyingPower, parameters.MinimumOrderMarginPortfolioPercentage, parameters.SilenceNonErrorReasons ) ); var lotSize = security.SymbolProperties.LotSize; Assert.AreEqual(expected.IsError, actual.IsError, $"{PositionGroupModel.GetType().Name}:{nameof(GetMaximumOrderQuantityForTargetBuyingPower)}: " + $"ExpectedQuantity: {expected.Quantity} ActualQuantity: {actual.NumberOfLots * lotSize} {Environment.NewLine}" + $"ExpectedReason: {expected.Reason}{Environment.NewLine}" + $"ActualReason: {actual.Reason}" ); // we're not comparing group quantities, which is the number of position lots, but rather the implied // position quantities resulting from having that many lots. var resizedPositionGroup = positionGroup.WithQuantity(actual.NumberOfLots); var position = resizedPositionGroup.GetPosition(security.Symbol); var bpmOrder = new MarketOrder(security.Symbol, expected.Quantity, parameters.Portfolio.Securities.UtcTime); var pgbpmOrder = new MarketOrder(security.Symbol, position.Quantity, parameters.Portfolio.Securities.UtcTime); var bpmOrderValue = bpmOrder.GetValue(security); var pgbpmOrderValue = pgbpmOrder.GetValue(security); var bpmOrderFees = security.FeeModel.GetOrderFee(new OrderFeeParameters(security, bpmOrder)).Value.Amount; var pgbpmOrderFees = security.FeeModel.GetOrderFee(new OrderFeeParameters(security, pgbpmOrder)).Value.Amount; var bpmMarginRequired = bpmOrderValue + bpmOrderFees; var pgbpmMarginRequired = pgbpmOrderValue + pgbpmOrderFees; Assert.AreEqual(expected.Quantity, position.Quantity, $"{PositionGroupModel.GetType().Name}:{nameof(GetMaximumOrderQuantityForTargetBuyingPower)}: " + $"ExpectedReason: {expected.Reason}{Environment.NewLine}" + $"ActualReason: {actual.Reason}" ); Assert.AreEqual(expected.Reason, actual.Reason, $"{PositionGroupModel.GetType().Name}:{nameof(GetMaximumOrderQuantityForTargetBuyingPower)}: " + $"ExpectedReason: {expected.Reason}{Environment.NewLine}" + $"ActualReason: {actual.Reason}" ); reentry = false; return(expected); }
/// <summary> /// Get the maximum market order quantity to obtain a position with a given value in account currency /// </summary> /// <param name="portfolio">The algorithm's portfolio</param> /// <param name="security">The security to be traded</param> /// <param name="targetPortfolioValue">The value in account currency that we want our holding to have</param> /// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns> public GetMaximumOrderQuantityForTargetValueResult GetMaximumOrderQuantityForTargetValue(SecurityPortfolioManager portfolio, Security security, decimal targetPortfolioValue) { // no shorting allowed if (targetPortfolioValue < 0) { return(new GetMaximumOrderQuantityForTargetValueResult(0, "The cash model does not allow shorting.")); } var baseCurrency = security as IBaseCurrencySymbol; if (baseCurrency == null) { return(new GetMaximumOrderQuantityForTargetValueResult(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 GetMaximumOrderQuantityForTargetValueResult(-portfolio.CashBook[baseCurrency.BaseCurrencySymbol].Amount)); } // convert base currency cash to account currency var baseCurrencyPosition = portfolio.CashBook.ConvertToAccountCurrency( portfolio.CashBook[baseCurrency.BaseCurrencySymbol].Amount, baseCurrency.BaseCurrencySymbol); // convert quote currency cash to account currency var quoteCurrencyPosition = portfolio.CashBook.ConvertToAccountCurrency( portfolio.CashBook[security.QuoteCurrency.Symbol].Amount, security.QuoteCurrency.Symbol); // determine the unit price in terms of the account currency var unitPrice = new MarketOrder(security.Symbol, 1, DateTime.UtcNow).GetValue(security); if (unitPrice == 0) { if (security.QuoteCurrency.ConversionRate == 0) { return(new GetMaximumOrderQuantityForTargetValueResult(0, $"The internal cash feed required for converting {security.QuoteCurrency.Symbol} to {CashBook.AccountCurrency} does not have any data yet (or market may be closed).")); } if (security.SymbolProperties.ContractMultiplier == 0) { return(new GetMaximumOrderQuantityForTargetValueResult(0, $"The contract multiplier for the {security.Symbol.Value} security is zero. The symbol properties database may be out of date.")); } // security.Price == 0 return(new GetMaximumOrderQuantityForTargetValueResult(0, $"The price of the {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.")); } // remove directionality, we'll work in the land of absolutes var targetOrderValue = Math.Abs(targetPortfolioValue - baseCurrencyPosition); var direction = targetPortfolioValue > baseCurrencyPosition ? OrderDirection.Buy : OrderDirection.Sell; // calculate the total cash available var cashRemaining = direction == OrderDirection.Buy ? quoteCurrencyPosition : baseCurrencyPosition; var currency = direction == OrderDirection.Buy ? security.QuoteCurrency.Symbol : baseCurrency.BaseCurrencySymbol; if (cashRemaining <= 0) { return(new GetMaximumOrderQuantityForTargetValueResult(0, $"The portfolio does not hold any {currency} for the order.")); } // continue iterating while we do not have enough cash for the order decimal cashRequired; decimal orderValue; decimal orderFees; var feeToPriceRatio = 0m; // compute the initial order quantity var orderQuantity = targetOrderValue / unitPrice; // rounding off Order Quantity to the nearest multiple of Lot Size orderQuantity -= orderQuantity % security.SymbolProperties.LotSize; if (orderQuantity == 0) { return(new GetMaximumOrderQuantityForTargetValueResult(0, $"The order quantity is less than the lot size of {security.SymbolProperties.LotSize} and has been rounded to zero.", false)); } do { // reduce order quantity by feeToPriceRatio, since it is faster than by lot size // if it becomes nonpositive, return zero orderQuantity -= feeToPriceRatio; if (orderQuantity <= 0) { return(new GetMaximumOrderQuantityForTargetValueResult(0, $"The portfolio does not hold enough {currency} including the order fees.")); } // generate the order var order = new MarketOrder(security.Symbol, orderQuantity, DateTime.UtcNow); orderValue = order.GetValue(security); orderFees = security.FeeModel.GetOrderFee(security, order); // find an incremental delta value for the next iteration step feeToPriceRatio = orderFees / unitPrice; feeToPriceRatio -= feeToPriceRatio % security.SymbolProperties.LotSize; if (feeToPriceRatio < security.SymbolProperties.LotSize) { feeToPriceRatio = security.SymbolProperties.LotSize; } // calculate the cash required for the order cashRequired = orderValue; } while (cashRequired > cashRemaining || orderValue + orderFees > targetOrderValue); // add directionality back in return(new GetMaximumOrderQuantityForTargetValueResult((direction == OrderDirection.Sell ? -1 : 1) * orderQuantity)); }