예제 #1
0
        /// <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);
        }