/// <summary> /// Returns true if the brokerage could accept this order. This takes into account /// order type, security type, and order size limits. /// </summary> /// <remarks> /// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit /// </remarks> /// <param name="security"></param> /// <param name="order">The order to be processed</param> /// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param> /// <returns>True if the brokerage could process the order, false otherwise</returns> public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message) { message = null; // validate security type if (security.Type != SecurityType.Equity) { message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported", Invariant($"The {nameof(AlpacaBrokerageModel)} does not support {security.Type} security type.") ); return(false); } // validate order type if (order.Type != OrderType.Limit && order.Type != OrderType.Market && order.Type != OrderType.StopMarket && order.Type != OrderType.StopLimit && order.Type != OrderType.MarketOnOpen) { message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported", Invariant($"The {nameof(AlpacaBrokerageModel)} does not support {order.Type} order type.") ); return(false); } // validate time in force if (order.TimeInForce != TimeInForce.GoodTilCanceled && order.TimeInForce != TimeInForce.Day) { message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported", $"The {nameof(AlpacaBrokerageModel)} does not support {order.TimeInForce.GetType().Name} time in force." ); return(false); } var openOrders = _orderProvider.GetOpenOrders(x => x.Symbol == order.Symbol && x.Id != order.Id); if (security.Holdings.IsLong) { var openSellQuantity = openOrders.Where(x => x.Direction == OrderDirection.Sell).Sum(x => x.Quantity); var availableSellQuantity = -security.Holdings.Quantity - openSellQuantity; // cannot reverse position from long to short (open sell orders are taken into account) if (order.Direction == OrderDirection.Sell && order.Quantity < availableSellQuantity) { message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported", (openSellQuantity == 0 ? $"The {nameof(AlpacaBrokerageModel)} does not support reversing the position (from long to short) with a single order" : $"The {nameof(AlpacaBrokerageModel)} does not support submitting orders which could potentially reverse the position (from long to short)") + $" [position:{security.Holdings.Quantity}, order quantity:{order.Quantity}, " + $"open sell orders quantity:{openSellQuantity}, available sell quantity:{availableSellQuantity}]." ); return(false); } } else if (security.Holdings.IsShort) { var openBuyQuantity = openOrders.Where(x => x.Direction == OrderDirection.Buy).Sum(x => x.Quantity); var availableBuyQuantity = -security.Holdings.Quantity - openBuyQuantity; // cannot reverse position from short to long (open buy orders are taken into account) if (order.Direction == OrderDirection.Buy && order.Quantity > availableBuyQuantity) { message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported", (openBuyQuantity == 0 ? $"The {nameof(AlpacaBrokerageModel)} does not support reversing the position (from short to long) with a single order" : $"The {nameof(AlpacaBrokerageModel)} does not support submitting orders which could potentially reverse the position (from short to long)") + $" [position:{security.Holdings.Quantity}, order quantity:{order.Quantity}, " + $"open buy orders quantity:{openBuyQuantity}, available buy quantity:{availableBuyQuantity}]." ); return(false); } } else if (security.Holdings.Quantity == 0) { // cannot open a short sell while a long buy order is open if (order.Direction == OrderDirection.Sell && openOrders.Any(x => x.Direction == OrderDirection.Buy)) { var openBuyQuantity = openOrders.Where(x => x.Direction == OrderDirection.Buy).Sum(x => x.Quantity); message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported", $"The {nameof(AlpacaBrokerageModel)} does not support submitting sell orders with open buy orders" + $" [position:{security.Holdings.Quantity}, order quantity:{order.Quantity}, " + $"open buy orders quantity:{openBuyQuantity}]." ); return(false); } // cannot open a long buy while a short sell order is open if (order.Direction == OrderDirection.Buy && openOrders.Any(x => x.Direction == OrderDirection.Sell)) { var openSellQuantity = openOrders.Where(x => x.Direction == OrderDirection.Sell).Sum(x => x.Quantity); message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported", $"The {nameof(AlpacaBrokerageModel)} does not support submitting buy orders with open sell orders" + $" [position:{security.Holdings.Quantity}, order quantity:{order.Quantity}, " + $"open sell orders quantity:{openSellQuantity}]." ); return(false); } } return(true); }