/// <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) { // no shorting allowed if (targetPortfolioValue < 0) { return(0); } var baseCurrency = security as IBaseCurrencySymbol; if (baseCurrency == null) { return(0); } // if target value is zero, return amount of base currency available to sell if (targetPortfolioValue == 0) { return(-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) { 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); }
/// <summary> /// Get the maximum market order quantity to obtain a position with a given value in account currency. Will not take into account buying power. /// </summary> /// <param name="portfolio">The algorithm's portfolio</param> /// <param name="security">The security to be traded</param> /// <param name="target">Target percentage holdings</param> /// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns> public GetMaximumOrderQuantityForTargetValueResult GetMaximumOrderQuantityForTargetValue(SecurityPortfolioManager portfolio, Security security, decimal target) { var targetPortfolioValue = target * portfolio.TotalPortfolioValue; // 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 orderValue = 0; decimal orderFees = 0; // 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)); } var loopCount = 0; // Just in case... var lastOrderQuantity = 0m; do { // Each loop will reduce the order quantity based on the difference between orderValue and targetOrderValue if (orderValue > targetOrderValue) { var currentOrderValuePerUnit = orderValue / orderQuantity; var amountOfOrdersToRemove = (orderValue - targetOrderValue) / currentOrderValuePerUnit; if (amountOfOrdersToRemove < security.SymbolProperties.LotSize) { // we will always substract at leat 1 LotSize amountOfOrdersToRemove = security.SymbolProperties.LotSize; } orderQuantity -= amountOfOrdersToRemove; 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." + $"Target order value {targetOrderValue}. Order fees {orderFees}. Order quantity {orderQuantity}.")); } // generate the order var order = new MarketOrder(security.Symbol, orderQuantity, DateTime.UtcNow); orderFees = security.FeeModel.GetOrderFee(security, order); // The TPV, take out the fees(unscaled) => yields available value for trading(less fees) -- then scale that by the target // finally remove currentHoldingsValue to get targetOrderValue targetOrderValue = Math.Abs(target * (portfolio.TotalPortfolioValue - orderFees) - currentHoldingsValue); // 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 = targetOrderValue / unitPrice; orderQuantity -= orderQuantity % security.SymbolProperties.LotSize; } else { // Start safe check after first loop if (lastOrderQuantity == orderQuantity) { throw new Exception($"GetMaximumOrderQuantityForTargetValue failed to converge to target order value {targetOrderValue}. " + $"Current order value is {orderValue}. Order quantity {orderQuantity}. Lot size is " + $"{security.SymbolProperties.LotSize}. Order fees {orderFees}. Security symbol {security.Symbol}"); } lastOrderQuantity = orderQuantity; } orderValue = orderQuantity * unitPrice; loopCount++; // we always have to loop at least twice } while (loopCount < 2 || orderValue > targetOrderValue); // add directionality back in return(new GetMaximumOrderQuantityForTargetValueResult((direction == OrderDirection.Sell ? -1 : 1) * orderQuantity)); }
/// <summary> /// Gets the buying power available for a trade /// </summary> /// <param name="portfolio">The algorithm's portfolio</param> /// <param name="security">The security to be traded</param> /// <param name="direction">The direction of the trade</param> /// <returns>The buying power available for the trade</returns> public decimal GetBuyingPower(SecurityPortfolioManager portfolio, Security security, OrderDirection direction) { return(GetMarginRemaining(portfolio, security, direction)); }
/// <summary> /// Performs application of an OrderEvent to the portfolio /// </summary> /// <param name="portfolio">The algorithm's portfolio</param> /// <param name="security">The fill's security</param> /// <param name="fill">The order event fill object to be applied</param> public virtual void ProcessFill(SecurityPortfolioManager portfolio, Security security, OrderEvent fill) { var quoteCash = security.QuoteCurrency; //Get the required information from the vehicle this order will affect var isLong = security.Holdings.IsLong; var isShort = security.Holdings.IsShort; var closedPosition = false; //Make local decimals to avoid any rounding errors from int multiplication var quantityHoldings = (decimal)security.Holdings.Quantity; var absoluteHoldingsQuantity = security.Holdings.AbsoluteQuantity; var averageHoldingsPrice = security.Holdings.AveragePrice; try { // apply sales value to holdings in the account currency var saleValueInQuoteCurrency = fill.FillPrice * Convert.ToDecimal(fill.AbsoluteFillQuantity) * security.SymbolProperties.ContractMultiplier; var saleValue = saleValueInQuoteCurrency * quoteCash.ConversionRate; security.Holdings.AddNewSale(saleValue); // subtract transaction fees from the portfolio var feeInAccountCurrency = 0m; if (fill.OrderFee != OrderFee.Zero // this is for user friendliness because some // Security types default to use 0 USD ConstantFeeModel && fill.OrderFee.Value.Amount != 0) { var feeThisOrder = fill.OrderFee.Value; feeInAccountCurrency = portfolio.CashBook.ConvertToAccountCurrency(feeThisOrder).Amount; security.Holdings.AddNewFee(feeInAccountCurrency); fill.OrderFee.ApplyToPortfolio(portfolio, fill); } // apply the funds using the current settlement model // we dont adjust funds for futures and CFDs: it is zero upfront payment derivative (margin applies though) // We do however apply funds for futures options, since they affect our cash balance the moment they are purchased/sold. if (security.Type != SecurityType.Future && security.Type != SecurityType.Cfd) { security.SettlementModel.ApplyFunds(portfolio, security, fill.UtcTime, quoteCash.Symbol, -fill.FillQuantity * fill.FillPrice * security.SymbolProperties.ContractMultiplier); } if (security.Type == SecurityType.Forex || security.Type == SecurityType.Crypto) { // model forex fills as currency swaps var forex = (IBaseCurrencySymbol)security; security.SettlementModel.ApplyFunds(portfolio, security, fill.UtcTime, forex.BaseCurrencySymbol, fill.FillQuantity); } // did we close or open a position further? closedPosition = isLong && fill.Direction == OrderDirection.Sell || isShort && fill.Direction == OrderDirection.Buy; // calculate the last trade profit if (closedPosition) { // profit = (closed sale value - cost)*conversion to account currency // closed sale value = quantity closed * fill price BUYs are deemed negative cash flow // cost = quantity closed * average holdings price SELLS are deemed positive cash flow var absoluteQuantityClosed = Math.Min(fill.AbsoluteFillQuantity, absoluteHoldingsQuantity); var closedSaleValueInQuoteCurrency = Math.Sign(-fill.FillQuantity) * fill.FillPrice * absoluteQuantityClosed; var closedCost = Math.Sign(-fill.FillQuantity) * absoluteQuantityClosed * averageHoldingsPrice; var lastTradeProfit = (closedSaleValueInQuoteCurrency - closedCost) * security.SymbolProperties.ContractMultiplier; var lastTradeProfitInAccountCurrency = lastTradeProfit * security.QuoteCurrency.ConversionRate; // Reflect account cash adjustment for futures/CFD position if (security.Type == SecurityType.Future || security.Type == SecurityType.Cfd) { security.SettlementModel.ApplyFunds(portfolio, security, fill.UtcTime, quoteCash.Symbol, lastTradeProfit); } //Update Vehicle Profit Tracking: security.Holdings.AddNewProfit(lastTradeProfitInAccountCurrency); security.Holdings.SetLastTradeProfit(lastTradeProfitInAccountCurrency); portfolio.AddTransactionRecord(security.LocalTime.ConvertToUtc( security.Exchange.TimeZone), lastTradeProfitInAccountCurrency - 2 * feeInAccountCurrency); } //UPDATE HOLDINGS QUANTITY, AVG PRICE: //Currently NO holdings. The order is ALL our holdings. if (quantityHoldings == 0) { //First transaction just subtract order from cash and set our holdings: averageHoldingsPrice = fill.FillPrice; quantityHoldings = fill.FillQuantity; } else if (isLong) { //If we're currently LONG on the stock. switch (fill.Direction) { case OrderDirection.Buy: //Update the Holding Average Price: Total Value / Total Quantity: averageHoldingsPrice = ((averageHoldingsPrice * quantityHoldings) + (fill.FillQuantity * fill.FillPrice)) / (quantityHoldings + fill.FillQuantity); //Add the new quantity: quantityHoldings += fill.FillQuantity; break; case OrderDirection.Sell: quantityHoldings += fill.FillQuantity; //+ a short = a subtraction if (quantityHoldings < 0) { //If we've now passed through zero from selling stock: new avg price: averageHoldingsPrice = fill.FillPrice; } else if (quantityHoldings == 0) { averageHoldingsPrice = 0; } break; } } else if (isShort) { //We're currently SHORTING the stock: What is the new position now? switch (fill.Direction) { case OrderDirection.Buy: //Buying when we're shorting moves to close position: quantityHoldings += fill.FillQuantity; if (quantityHoldings > 0) { //If we were short but passed through zero, new average price is what we paid. The short position was closed. averageHoldingsPrice = fill.FillPrice; } else if (quantityHoldings == 0) { averageHoldingsPrice = 0; } break; case OrderDirection.Sell: //We are increasing a Short position: //E.g. -100 @ $5, adding -100 @ $10: Avg: $7.5 // dAvg = (-500 + -1000) / -200 = 7.5 averageHoldingsPrice = ((averageHoldingsPrice * quantityHoldings) + (fill.FillQuantity * fill.FillPrice)) / (quantityHoldings + fill.FillQuantity); quantityHoldings += fill.FillQuantity; break; } } } catch (Exception err) { Log.Error(err); } //Set the results back to the vehicle. security.Holdings.SetHoldings(averageHoldingsPrice, quantityHoldings); }
/// <summary> /// Check if there is sufficient buying power to execute this order. /// </summary> /// <param name="portfolio">The algorithm's portfolio</param> /// <param name="security">The security to be traded</param> /// <param name="order">The order to be checked</param> /// <returns>Returns buying power information for an order</returns> public HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(SecurityPortfolioManager portfolio, Security security, Order order) { // short circuit the div 0 case if (order.Quantity == 0) { return(new HasSufficientBuyingPowerForOrderResult(true)); } var ticket = portfolio.Transactions.GetOrderTicket(order.Id); if (ticket == null) { var reason = $"Null order ticket for id: {order.Id}"; Log.Error($"SecurityMarginModel.HasSufficientBuyingPowerForOrder(): {reason}"); return(new HasSufficientBuyingPowerForOrderResult(false, reason)); } if (order.Type == OrderType.OptionExercise) { // for option assignment and exercise orders we look into the requirements to process the underlying security transaction var option = (Option.Option)security; var underlying = option.Underlying; if (option.IsAutoExercised(underlying.Close)) { var quantity = option.GetExerciseQuantity(order.Quantity); var newOrder = new LimitOrder { Id = order.Id, Time = order.Time, LimitPrice = option.StrikePrice, Symbol = underlying.Symbol, Quantity = option.Symbol.ID.OptionRight == OptionRight.Call ? quantity : -quantity }; // we continue with this call for underlying return(underlying.BuyingPowerModel.HasSufficientBuyingPowerForOrder(portfolio, underlying, newOrder)); } return(new HasSufficientBuyingPowerForOrderResult(true)); } // When order only reduces or closes a security position, capital is always sufficient if (security.Holdings.Quantity * order.Quantity < 0 && Math.Abs(security.Holdings.Quantity) >= Math.Abs(order.Quantity)) { return(new HasSufficientBuyingPowerForOrderResult(true)); } var freeMargin = GetMarginRemaining(portfolio, security, order.Direction); var initialMarginRequiredForOrder = GetInitialMarginRequiredForOrder(security, order); // pro-rate the initial margin required for order based on how much has already been filled var percentUnfilled = (Math.Abs(order.Quantity) - Math.Abs(ticket.QuantityFilled)) / Math.Abs(order.Quantity); var initialMarginRequiredForRemainderOfOrder = percentUnfilled * initialMarginRequiredForOrder; if (Math.Abs(initialMarginRequiredForRemainderOfOrder) > freeMargin) { var reason = $"Id: {order.Id}, Initial Margin: {initialMarginRequiredForRemainderOfOrder.Normalize()}, Free Margin: {freeMargin.Normalize()}"; Log.Error($"SecurityMarginModel.HasSufficientBuyingPowerForOrder(): {reason}"); return(new HasSufficientBuyingPowerForOrderResult(false, reason)); } return(new HasSufficientBuyingPowerForOrderResult(true)); }
/// <summary> /// Check if there is sufficient buying power to execute this order. /// </summary> /// <param name="portfolio">The algorithm's portfolio</param> /// <param name="security">The security to be traded</param> /// <param name="order">The order to be checked</param> /// <returns>Returns buying power information for an order</returns> public HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(SecurityPortfolioManager portfolio, Security security, Order order) { var baseCurrency = security as IBaseCurrencySymbol; if (baseCurrency == null) { return(new HasSufficientBuyingPowerForOrderResult(false, $"The '{security.Symbol.Value}' security is not supported by this cash model. Currently only SecurityType.Crypto and SecurityType.Forex are supported.")); } decimal totalQuantity; decimal orderQuantity; if (order.Direction == OrderDirection.Buy) { // quantity available for buying in quote currency totalQuantity = portfolio.CashBook[security.QuoteCurrency.Symbol].Amount; orderQuantity = order.AbsoluteQuantity * GetOrderPrice(security, order); } else { // quantity available for selling in base currency totalQuantity = portfolio.CashBook[baseCurrency.BaseCurrencySymbol].Amount; orderQuantity = order.AbsoluteQuantity; } // calculate reserved quantity for open orders (in quote or base currency depending on direction) var openOrdersReservedQuantity = GetOpenOrdersReservedQuantity(portfolio, security, order); bool isSufficient; var reason = string.Empty; if (order.Direction == OrderDirection.Sell) { // can sell available and non-reserved quantities isSufficient = orderQuantity <= totalQuantity - openOrdersReservedQuantity; if (!isSufficient) { reason = $"Your portfolio holds {totalQuantity.Normalize()} {baseCurrency.BaseCurrencySymbol}, {openOrdersReservedQuantity.Normalize()} {baseCurrency.BaseCurrencySymbol} of which are reserved for open orders, but your Sell order is for {orderQuantity.Normalize()} {baseCurrency.BaseCurrencySymbol}. Cash Modeling trading does not permit short holdings so ensure you only sell what you have, including any additional open orders."; } return(new HasSufficientBuyingPowerForOrderResult(isSufficient, reason)); } if (order.Type == OrderType.Market) { // include existing holdings (in quote currency) var holdingsValue = portfolio.CashBook.Convert( portfolio.CashBook[baseCurrency.BaseCurrencySymbol].Amount, baseCurrency.BaseCurrencySymbol, security.QuoteCurrency.Symbol); // find a target value in account currency for buy market orders var targetValue = portfolio.CashBook.ConvertToAccountCurrency(totalQuantity - openOrdersReservedQuantity + holdingsValue, security.QuoteCurrency.Symbol); // maximum quantity that can be bought (in quote currency) var maximumQuantity = GetMaximumOrderQuantityForTargetValue(portfolio, security, targetValue).Quantity *GetOrderPrice(security, order); isSufficient = orderQuantity <= Math.Abs(maximumQuantity); if (!isSufficient) { reason = $"Your portfolio holds {totalQuantity.Normalize()} {security.QuoteCurrency.Symbol}, {openOrdersReservedQuantity.Normalize()} {security.QuoteCurrency.Symbol} of which are reserved for open orders, but your Buy order is for {order.AbsoluteQuantity.Normalize()} {baseCurrency.BaseCurrencySymbol}. Your order requires a total value of {orderQuantity.Normalize()} {security.QuoteCurrency.Symbol}, but only a total value of {(Math.Abs(maximumQuantity) + holdingsValue).Normalize()} {security.QuoteCurrency.Symbol} is available."; } return(new HasSufficientBuyingPowerForOrderResult(isSufficient, reason)); } // for limit orders, add fees to the order cost var orderFee = 0m; if (order.Type == OrderType.Limit) { orderFee = security.FeeModel.GetOrderFee(security, order); orderFee = portfolio.CashBook.Convert(orderFee, CashBook.AccountCurrency, security.QuoteCurrency.Symbol); } isSufficient = orderQuantity <= totalQuantity - openOrdersReservedQuantity - orderFee; if (!isSufficient) { reason = $"Your portfolio holds {totalQuantity.Normalize()} {security.QuoteCurrency.Symbol}, {openOrdersReservedQuantity.Normalize()} {security.QuoteCurrency.Symbol} of which are reserved for open orders, but your Buy order is for {order.AbsoluteQuantity.Normalize()} {baseCurrency.BaseCurrencySymbol}. Your order requires a total value of {orderQuantity.Normalize()} {security.QuoteCurrency.Symbol}, but only a total value of {(totalQuantity - openOrdersReservedQuantity - orderFee).Normalize()} {security.QuoteCurrency.Symbol} is available."; } return(new HasSufficientBuyingPowerForOrderResult(isSufficient, reason)); }
private static decimal GetOpenOrdersReservedQuantity(SecurityPortfolioManager portfolio, Security security, Order order) { var baseCurrency = security as IBaseCurrencySymbol; if (baseCurrency == null) { return(0); } // find the target currency for the requested direction and the securities potentially involved var targetCurrency = order.Direction == OrderDirection.Buy ? security.QuoteCurrency.Symbol : baseCurrency.BaseCurrencySymbol; var symbolDirectionPairs = new Dictionary <Symbol, OrderDirection>(); foreach (var portfolioSecurity in portfolio.Securities.Values) { var basePortfolioSecurity = portfolioSecurity as IBaseCurrencySymbol; if (basePortfolioSecurity == null) { continue; } if (basePortfolioSecurity.BaseCurrencySymbol == targetCurrency) { symbolDirectionPairs.Add(portfolioSecurity.Symbol, OrderDirection.Sell); } else if (portfolioSecurity.QuoteCurrency.Symbol == targetCurrency) { symbolDirectionPairs.Add(portfolioSecurity.Symbol, OrderDirection.Buy); } } // fetch open orders with matching symbol/side var openOrders = portfolio.Transactions.GetOpenOrders(x => { OrderDirection dir; return(symbolDirectionPairs.TryGetValue(x.Symbol, out dir) && // same direction of our order dir == x.Direction && // don't count our current order x.Id != order.Id && // only count working orders (x.Type == OrderType.Limit || x.Type == OrderType.StopMarket)); } ); // calculate reserved quantity for selected orders var openOrdersReservedQuantity = 0m; foreach (var openOrder in openOrders) { var orderSecurity = portfolio.Securities[openOrder.Symbol]; var orderBaseCurrency = orderSecurity as IBaseCurrencySymbol; if (orderBaseCurrency != null) { // convert order value to target currency var quantityInTargetCurrency = openOrder.AbsoluteQuantity; if (orderSecurity.QuoteCurrency.Symbol == targetCurrency) { quantityInTargetCurrency *= GetOrderPrice(security, openOrder); } openOrdersReservedQuantity += quantityInTargetCurrency; } } return(openOrdersReservedQuantity); }
/// <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) { 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)); }
/// <summary> /// Initializes a new instance of the <see cref="MarginCallModel"/> class /// </summary> /// <param name="portfolio">The portfolio object to receive margin calls</param> public MarginCallModel(SecurityPortfolioManager portfolio) { Portfolio = portfolio; }
/// <summary> /// Initializes a new instance of the <see cref="HasSufficientBuyingPowerForOrderParameters"/> class /// </summary> /// <param name="portfolio">The algorithm's portfolio</param> /// <param name="security">The security</param> /// <param name="order">The order</param> public HasSufficientBuyingPowerForOrderParameters(SecurityPortfolioManager portfolio, Security security, Order order) { Portfolio = portfolio; Security = security; Order = order; }