private HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(HasSufficientBuyingPowerForOrderParameters parameters, OrderTicket ticket, decimal?freeMarginToUse = null, decimal?initialMarginRequired = null) { // When order only reduces or closes a security position, capital is always sufficient if (parameters.Security.Holdings.Quantity * parameters.Order.Quantity < 0 && Math.Abs(parameters.Security.Holdings.Quantity) >= Math.Abs(parameters.Order.Quantity)) { return(parameters.Sufficient()); } var freeMargin = freeMarginToUse ?? GetMarginRemaining(parameters.Portfolio, parameters.Security, parameters.Order.Direction); var initialMarginRequiredForOrder = initialMarginRequired ?? GetInitialMarginRequiredForOrder( new InitialMarginRequiredForOrderParameters( parameters.Portfolio.CashBook, parameters.Security, parameters.Order )); // pro-rate the initial margin required for order based on how much has already been filled var percentUnfilled = (Math.Abs(parameters.Order.Quantity) - Math.Abs(ticket.QuantityFilled)) / Math.Abs(parameters.Order.Quantity); var initialMarginRequiredForRemainderOfOrder = percentUnfilled * initialMarginRequiredForOrder; if (Math.Abs(initialMarginRequiredForRemainderOfOrder) > freeMargin) { return(parameters.Insufficient(Invariant($"Id: {parameters.Order.Id}, ") + Invariant($"Initial Margin: {initialMarginRequiredForRemainderOfOrder.Normalize()}, ") + Invariant($"Free Margin: {freeMargin.Normalize()}") )); } return(parameters.Sufficient()); }
/// <summary> /// Check if there is sufficient buying power to execute this order. /// </summary> /// <param name="parameters">An object containing the portfolio, the security and the order</param> /// <returns>Returns buying power information for an order</returns> public virtual HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(HasSufficientBuyingPowerForOrderParameters parameters) { // short circuit the div 0 case if (parameters.Order.Quantity == 0) { return(parameters.Sufficient()); } var ticket = parameters.Portfolio.Transactions.GetOrderTicket(parameters.Order.Id); if (ticket == null) { return(parameters.Insufficient( $"Null order ticket for id: {parameters.Order.Id}" )); } if (parameters.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)parameters.Security; var underlying = option.Underlying; if (option.IsAutoExercised(underlying.Close) && underlying.IsTradable) { var quantity = option.GetExerciseQuantity(parameters.Order.Quantity); var newOrder = new LimitOrder { Id = parameters.Order.Id, Time = parameters.Order.Time, LimitPrice = option.StrikePrice, Symbol = underlying.Symbol, Quantity = quantity }; // we continue with this call for underlying var parametersForUnderlying = parameters.ForUnderlying(newOrder); var freeMargin = underlying.BuyingPowerModel.GetBuyingPower(parametersForUnderlying.Portfolio, parametersForUnderlying.Security, parametersForUnderlying.Order.Direction); // we add the margin used by the option itself freeMargin += GetMaintenanceMargin(MaintenanceMarginParameters.ForQuantityAtCurrentPrice(option, -parameters.Order.Quantity)); var initialMarginRequired = underlying.BuyingPowerModel.GetInitialMarginRequiredForOrder( new InitialMarginRequiredForOrderParameters(parameters.Portfolio.CashBook, underlying, newOrder)); return(HasSufficientBuyingPowerForOrder(parametersForUnderlying, ticket, freeMargin, initialMarginRequired)); } return(parameters.Sufficient()); } return(HasSufficientBuyingPowerForOrder(parameters, ticket)); }
/// <summary> /// Check if there is sufficient buying power to execute this order. /// </summary> /// <param name="parameters">An object containing the portfolio, the security and the order</param> /// <returns>Returns buying power information for an order</returns> public override HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(HasSufficientBuyingPowerForOrderParameters parameters) { var baseCurrency = parameters.Security as IBaseCurrencySymbol; if (baseCurrency == null) { return(parameters.Insufficient($"The '{parameters.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 (parameters.Order.Direction == OrderDirection.Buy) { // quantity available for buying in quote currency totalQuantity = parameters.Portfolio.CashBook[parameters.Security.QuoteCurrency.Symbol].Amount; orderQuantity = parameters.Order.AbsoluteQuantity * GetOrderPrice(parameters.Security, parameters.Order); } else { // quantity available for selling in base currency totalQuantity = parameters.Portfolio.CashBook[baseCurrency.BaseCurrencySymbol].Amount; orderQuantity = parameters.Order.AbsoluteQuantity; } // calculate reserved quantity for open orders (in quote or base currency depending on direction) var openOrdersReservedQuantity = GetOpenOrdersReservedQuantity(parameters.Portfolio, parameters.Security, parameters.Order); if (parameters.Order.Direction == OrderDirection.Sell) { // can sell available and non-reserved quantities if (orderQuantity <= totalQuantity - openOrdersReservedQuantity) { return(parameters.Sufficient()); } return(parameters.Insufficient(Invariant($"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."))); } if (parameters.Order.Type == OrderType.Market) { // include existing holdings (in quote currency) var holdingsValue = parameters.Portfolio.CashBook.Convert( parameters.Portfolio.CashBook[baseCurrency.BaseCurrencySymbol].Amount, baseCurrency.BaseCurrencySymbol, parameters.Security.QuoteCurrency.Symbol); // find a target value in account currency for buy market orders var targetValue = parameters.Portfolio.CashBook.ConvertToAccountCurrency(totalQuantity - openOrdersReservedQuantity + holdingsValue, parameters.Security.QuoteCurrency.Symbol); // convert the target into a percent in relation to TPV var targetPercent = parameters.Portfolio.TotalPortfolioValue == 0 ? 0 : targetValue / parameters.Portfolio.TotalPortfolioValue; // maximum quantity that can be bought (in quote currency) var maximumQuantity = GetMaximumOrderQuantityForTargetBuyingPower( new GetMaximumOrderQuantityForTargetBuyingPowerParameters(parameters.Portfolio, parameters.Security, targetPercent, 0)).Quantity *GetOrderPrice(parameters.Security, parameters.Order); if (orderQuantity <= Math.Abs(maximumQuantity)) { return(parameters.Sufficient()); } return(parameters.Insufficient(Invariant($"Your portfolio holds {totalQuantity.Normalize()} {parameters.Security.QuoteCurrency.Symbol}, {openOrdersReservedQuantity.Normalize()} {parameters.Security.QuoteCurrency.Symbol} of which are reserved for open orders, but your Buy order is for {parameters.Order.AbsoluteQuantity.Normalize()} {baseCurrency.BaseCurrencySymbol}. Your order requires a total value of {orderQuantity.Normalize()} {parameters.Security.QuoteCurrency.Symbol}, but only a total value of {Math.Abs(maximumQuantity).Normalize()} {parameters.Security.QuoteCurrency.Symbol} is available."))); } // for limit orders, add fees to the order cost var orderFee = 0m; if (parameters.Order.Type == OrderType.Limit) { var fee = parameters.Security.FeeModel.GetOrderFee( new OrderFeeParameters(parameters.Security, parameters.Order)).Value; orderFee = parameters.Portfolio.CashBook.Convert( fee.Amount, fee.Currency, parameters.Security.QuoteCurrency.Symbol); } if (orderQuantity <= totalQuantity - openOrdersReservedQuantity - orderFee) { return(parameters.Sufficient()); } return(parameters.Insufficient(Invariant($"Your portfolio holds {totalQuantity.Normalize()} {parameters.Security.QuoteCurrency.Symbol}, {openOrdersReservedQuantity.Normalize()} {parameters.Security.QuoteCurrency.Symbol} of which are reserved for open orders, but your Buy order is for {parameters.Order.AbsoluteQuantity.Normalize()} {baseCurrency.BaseCurrencySymbol}. Your order requires a total value of {orderQuantity.Normalize()} {parameters.Security.QuoteCurrency.Symbol}, but only a total value of {(totalQuantity - openOrdersReservedQuantity - orderFee).Normalize()} {parameters.Security.QuoteCurrency.Symbol} is available."))); }
/// <summary> /// Check if there is sufficient buying power to execute this order. /// </summary> /// <param name="parameters">An object containing the portfolio, the security and the order</param> /// <returns>Returns buying power information for an order</returns> public virtual HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(HasSufficientBuyingPowerForOrderParameters parameters) { // short circuit the div 0 case if (parameters.Order.Quantity == 0) { return(parameters.Sufficient()); } var ticket = parameters.Portfolio.Transactions.GetOrderTicket(parameters.Order.Id); if (ticket == null) { return(parameters.Insufficient( $"Null order ticket for id: {parameters.Order.Id}" )); } if (parameters.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)parameters.Security; var underlying = option.Underlying; if (option.IsAutoExercised(underlying.Close) && underlying.IsTradable) { var quantity = option.GetExerciseQuantity(parameters.Order.Quantity); var newOrder = new LimitOrder { Id = parameters.Order.Id, Time = parameters.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( parameters.ForUnderlying(newOrder) )); } return(parameters.Sufficient()); } // When order only reduces or closes a security position, capital is always sufficient if (parameters.Security.Holdings.Quantity * parameters.Order.Quantity < 0 && Math.Abs(parameters.Security.Holdings.Quantity) >= Math.Abs(parameters.Order.Quantity)) { return(parameters.Sufficient()); } var freeMargin = GetMarginRemaining(parameters.Portfolio, parameters.Security, parameters.Order.Direction); var initialMarginRequiredForOrder = GetInitialMarginRequiredForOrder( new InitialMarginRequiredForOrderParameters( parameters.Portfolio.CashBook, parameters.Security, parameters.Order )); // pro-rate the initial margin required for order based on how much has already been filled var percentUnfilled = (Math.Abs(parameters.Order.Quantity) - Math.Abs(ticket.QuantityFilled)) / Math.Abs(parameters.Order.Quantity); var initialMarginRequiredForRemainderOfOrder = percentUnfilled * initialMarginRequiredForOrder; if (Math.Abs(initialMarginRequiredForRemainderOfOrder) > freeMargin) { return(parameters.Insufficient(Invariant($"Id: {parameters.Order.Id}, ") + Invariant($"Initial Margin: {initialMarginRequiredForRemainderOfOrder.Normalize()}, ") + Invariant($"Free Margin: {freeMargin.Normalize()}") )); } return(parameters.Sufficient()); }