예제 #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">The security of the order</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;

            var securityType = order.SecurityType;
            if (securityType != SecurityType.Equity)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                    "This model only supports equities."
                    );
                
                return false;
            }

            if (order.Type == OrderType.MarketOnOpen || order.Type == OrderType.MarketOnClose)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                    "Tradier brokerage only supports Market orders. MarketOnOpen and MarketOnClose orders not supported."
                    );

                return false;
            }

            if (!CanExecuteOrder(security, order))
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "ExtendedMarket",
                    "Tradier does not support extended market hours trading.  Your order will be processed at market open."
                    );
            }

            // tradier order limits
            return true;
        }
예제 #2
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.Forex && security.Type != SecurityType.Cfd)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                    "This model 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)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                    "This model does not support " + order.Type + " order type."
                    );

                return false;
            }

            return true;
        }
예제 #3
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.Forex && security.Type != SecurityType.Cfd)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                    "This model 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)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                    "This model does not support " + order.Type + " order type."
                    );

                return false;
            }

            // validate order quantity
            if (order.Quantity % 1000 != 0)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                    "The order quantity must be a multiple of 1000."
                    );

                return false;
            }

            // validate stop/limit orders= prices
            var limit = order as LimitOrder;
            if (limit != null)
            {
                return IsValidOrderPrices(security, OrderType.Limit, limit.Direction, security.Price, limit.LimitPrice, ref message);
            }

            var stopMarket = order as StopMarketOrder;
            if (stopMarket != null)
            {
                return IsValidOrderPrices(security, OrderType.StopMarket, stopMarket.Direction, stopMarket.StopPrice, security.Price, ref message);
            }

            var stopLimit = order as StopLimitOrder;
            if (stopLimit != null)
            {
                return IsValidOrderPrices(security, OrderType.StopLimit, stopLimit.Direction, stopLimit.StopPrice, stopLimit.LimitPrice, ref message);
            }

            return true;
        }
        /// <summary>
        /// Returns true if the brokerage would allow updating the order as specified by the request
        /// </summary>
        /// <param name="security">The security of the order</param>
        /// <param name="order">The order to be updated</param>
        /// <param name="request">The requested update to be made to the order</param>
        /// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
        /// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
        public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
        {
            message = null;

            if (order.SecurityType == SecurityType.Forex && request.Quantity != null)
            {
                return IsForexWithinOrderSizeLimits(order.Symbol.Value, request.Quantity.Value, out message);
            }

            return true;
        }
        /// <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.Forex && security.Type != SecurityType.Cfd)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                    "This model 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)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                    "This model does not support " + order.Type + " order type."
                    );

                return false;
            }

            // validate order quantity
            if (order.Quantity % 1000 != 0)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                    "The order quantity must be a multiple of 1000."
                    );

                return false;
            }

            // validate order price
            var invalidPrice = order.Type == OrderType.Limit && order.Direction == OrderDirection.Buy && ((LimitOrder)order).LimitPrice > security.Price ||
                               order.Type == OrderType.Limit && order.Direction == OrderDirection.Sell && ((LimitOrder)order).LimitPrice < security.Price ||
                               order.Type == OrderType.StopMarket && order.Direction == OrderDirection.Buy && ((StopMarketOrder)order).StopPrice < security.Price ||
                               order.Type == OrderType.StopMarket && order.Direction == OrderDirection.Sell && ((StopMarketOrder)order).StopPrice > security.Price;
            if (invalidPrice)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                    "Limit Buy orders and Stop Sell orders must be below market, Limit Sell orders and Stop Buy orders must be above market."
                    );
            }

            return true;
        }
예제 #6
0
        /// <summary>
        /// Returns true if the brokerage would allow updating the order as specified by the request
        /// </summary>
        /// <param name="security">The security of the order</param>
        /// <param name="order">The order to be updated</param>
        /// <param name="request">The requested update to be made to the order</param>
        /// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
        /// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
        public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
        {
            message = null;

            // Tradier doesn't allow updating order quantities
            if (request.Quantity != null && request.Quantity != order.Quantity)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "UpdateRejected",
                    "Traider does not support updating order quantities."
                    );

                return false;
            }

            return true;
        }
        /// <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">The security being ordered</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;

            //https://www.interactivebrokers.com/en/?f=%2Fen%2Ftrading%2FforexOrderSize.php
            switch (order.SecurityType)
            {
                case SecurityType.Base:
                    return false;
                case SecurityType.Equity:
                    return true; // could not find order limits on equities
                case SecurityType.Option:
                    return true;
                case SecurityType.Commodity:
                    return true;
                case SecurityType.Forex:
                    return IsForexWithinOrderSizeLimits(order.Symbol.Value, order.Quantity, out message);
                case SecurityType.Future:
                    return true;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
        /// <summary>
        /// Handles the message
        /// </summary>
        /// <param name="message">The message to be handled</param>
        public void Handle(BrokerageMessageEvent message)
        {
            // based on message type dispatch to result handler
            switch (message.Type)
            {
                case BrokerageMessageType.Information:
                    _algorithm.Debug("Brokerage Info: " + message.Message);
                    break;
                
                case BrokerageMessageType.Warning:
                    _algorithm.Error("Brokerage Warning: " + message.Message);
                    _api.SendUserEmail(_job.AlgorithmId, "Brokerage Warning", message.Message);
                    break;

                case BrokerageMessageType.Error:
                    _algorithm.Error("Brokerage Error: " + message.Message);
                    _algorithm.RunTimeError = new Exception(message.Message);
                    break;

                case BrokerageMessageType.Disconnect:
                    _connected = false;
                    Log.Trace("DefaultBrokerageMessageHandler.Handle(): Disconnected.");

                    // check to see if any non-custom security exchanges are open within the next x minutes
                    var open = (from kvp in _algorithm.Securities
                                let security = kvp.Value
                                where security.Type != SecurityType.Base
                                let exchange = security.Exchange
                                let localTime = _algorithm.UtcTime.ConvertFromUtc(exchange.TimeZone)
                                where exchange.IsOpenDuringBar(localTime, localTime + _openThreshold, security.IsExtendedMarketHours)
                                select security).Any();

                    // if any are open then we need to kill the algorithm
                    if (open)
                    {
                        Log.Trace("DefaultBrokerageMessageHandler.Handle(): Disconnect when exchanges are open, trying to reconnect for " + _initialDelay.TotalMinutes + " minutes.");

                        // wait 15 minutes before killing algorithm
                        StartCheckReconnected(_initialDelay, message);
                    }
                    else
                    {
                        Log.Trace("DefaultBrokerageMessageHandler.Handle(): Disconnect when exchanges are closed, checking back before exchange open.");

                        // if they aren't open, we'll need to check again a little bit before markets open
                        DateTime nextMarketOpenUtc;
                        if (_algorithm.Securities.Count != 0)
                        {
                            nextMarketOpenUtc = (from kvp in _algorithm.Securities
                                                 let security = kvp.Value
                                                 where security.Type != SecurityType.Base
                                                 let exchange = security.Exchange
                                                 let localTime = _algorithm.UtcTime.ConvertFromUtc(exchange.TimeZone)
                                                 let marketOpen = exchange.Hours.GetNextMarketOpen(localTime, security.IsExtendedMarketHours)
                                                 let marketOpenUtc = marketOpen.ConvertToUtc(exchange.TimeZone)
                                                 select marketOpenUtc).Min();
                        }
                        else
                        {
                            // if we have no securities just make next market open an hour from now
                            nextMarketOpenUtc = DateTime.UtcNow.AddHours(1);
                        }

                        var timeUntilNextMarketOpen = nextMarketOpenUtc - DateTime.UtcNow - _openThreshold;
                        Log.Trace("DefaultBrokerageMessageHandler.Handle(): TimeUntilNextMarketOpen: " + timeUntilNextMarketOpen);

                        // wake up 5 minutes before market open and check if we've reconnected
                        StartCheckReconnected(timeUntilNextMarketOpen, message);
                    }
                    break;

                case BrokerageMessageType.Reconnect:
                    _connected = true;
                    Log.Trace("DefaultBrokerageMessageHandler.Handle(): Reconnected.");

                    if (_cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested)
                    {
                        _cancellationTokenSource.Cancel();
                    }
                    break;
            }
        }
예제 #9
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">The security being ordered</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 virtual bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
 {
     message = null;
     return true;
 }
예제 #10
0
        /// <summary>
        /// Event invocator for the Message event
        /// </summary>
        /// <param name="e">The error</param>
        protected virtual void OnMessage(BrokerageMessageEvent e)
        {
            try
            {
                if (e.Type == BrokerageMessageType.Error)
                {
                    Log.Error("Brokerage.OnMessage(): " + e);
                }
                else
                {
                    Log.Trace("Brokerage.OnMessage(): " + e);
                }

                var handler = Message;
                if (handler != null) handler(this, e);
            }
            catch (Exception err)
            {
                Log.Error(err);
            }
        }
예제 #11
0
            /// <summary>
            /// Prevent orders which would bring the account below a minimum cash balance
            /// </summary>
            public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
            {
                message = null;
                
                // we want to model brokerage requirement of _minimumAccountBalance cash value in account

                var price = order.Status.IsFill() ? order.Price : security.Price;
                var orderCost = order.GetValue(price);
                var cash = _algorithm.Portfolio.Cash;
                var cashAfterOrder = cash - orderCost;
                if (cashAfterOrder < _minimumAccountBalance)
                {
                    // return a message describing why we're not allowing this order
                    message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "InsufficientRemainingCapital", 
                        string.Format("Account must maintain a minimum of ${0} USD at all times. Order ID: {1}", _minimumAccountBalance, order.Id)
                        );
                    return false;
                }
                return true;
            }
예제 #12
0
        /// <summary>
        /// Validates limit/stopmarket order prices, pass security.Price for limit/stop if n/a
        /// </summary>
        private static bool IsValidOrderPrices(Security security, OrderType orderType, OrderDirection orderDirection, decimal stopPrice, decimal limitPrice, ref BrokerageMessageEvent message)
        {
            // validate order price
            var invalidPrice = orderType == OrderType.Limit && orderDirection == OrderDirection.Buy && limitPrice > security.Price ||
                               orderType == OrderType.Limit && orderDirection == OrderDirection.Sell && limitPrice < security.Price ||
                               orderType == OrderType.StopMarket && orderDirection == OrderDirection.Buy && stopPrice <security.Price ||
                                                                                                                       orderType == OrderType.StopMarket && orderDirection == OrderDirection.Sell && stopPrice> security.Price;

            if (invalidPrice)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                    "Limit Buy orders and Stop Sell orders must be below market, Limit Sell orders and Stop Buy orders must be above market."
                                                    );

                return(false);
            }

            // Validate FXCM maximum distance for limit and stop orders:
            // there are two different Max Limits, 15000 pips and 50% rule,
            // whichever comes first (for most pairs, 50% rule comes first)
            var maxDistance = Math.Min(
                // MinimumPriceVariation is 1/10th of a pip
                security.SymbolProperties.MinimumPriceVariation * 10 * 15000,
                security.Price / 2);
            var currentPrice = security.Price;
            var minPrice     = currentPrice - maxDistance;
            var maxPrice     = currentPrice + maxDistance;

            var outOfRangePrice = orderType == OrderType.Limit && orderDirection == OrderDirection.Buy && limitPrice <minPrice ||
                                                                                                                      orderType == OrderType.Limit && orderDirection == OrderDirection.Sell && limitPrice> maxPrice ||
                                  orderType == OrderType.StopMarket && orderDirection == OrderDirection.Buy && stopPrice > maxPrice ||
                                  orderType == OrderType.StopMarket && orderDirection == OrderDirection.Sell && stopPrice < minPrice;

            if (outOfRangePrice)
            {
                var orderPrice = orderType == OrderType.Limit ? limitPrice : stopPrice;

                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                    Invariant($"The {orderType} {orderDirection} order price ({orderPrice}) is too far from the current market price ({currentPrice}).")
                                                    );

                return(false);
            }

            return(true);
        }
예제 #13
0
        /// <summary>
        /// Validates limit/stopmarket order prices, pass security.Price for limit/stop if n/a
        /// </summary>
        private static bool IsValidOrderPrices(Security security, OrderType orderType, OrderDirection orderDirection, decimal stopPrice, decimal limitPrice, ref BrokerageMessageEvent message)
        {
            // validate order price
            var invalidPrice = orderType == OrderType.Limit && orderDirection == OrderDirection.Buy && limitPrice > security.Price ||
                               orderType == OrderType.Limit && orderDirection == OrderDirection.Sell && limitPrice < security.Price ||
                               orderType == OrderType.StopMarket && orderDirection == OrderDirection.Buy && stopPrice <security.Price ||
                                                                                                                       orderType == OrderType.StopMarket && orderDirection == OrderDirection.Sell && stopPrice> security.Price;

            if (invalidPrice)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                    "Limit Buy orders and Stop Sell orders must be below market, Limit Sell orders and Stop Buy orders must be above market."
                                                    );

                return(false);
            }

            return(true);
        }
예제 #14
0
        /// <summary>
        /// Creates wss connection, monitors for disconnection and re-connects when necessary
        /// </summary>
        public override void Connect()
        {
            if (IsConnected)
            {
                return;
            }

            Log.Trace("BaseWebSocketsBrokerage.Connect(): Connecting...");
            WebSocket.Connect();
            Wait(_connectionTimeout, () => WebSocket.IsOpen);

            _cancellationTokenSource = new CancellationTokenSource();
            _connectionMonitorThread = new Thread(() =>
            {
                var nextReconnectionAttemptUtcTime    = DateTime.UtcNow;
                double nextReconnectionAttemptSeconds = 1;

                lock (_lockerConnectionMonitor)
                {
                    LastHeartbeatUtcTime = DateTime.UtcNow;
                }

                try
                {
                    while (!_cancellationTokenSource.IsCancellationRequested)
                    {
                        if (WebSocket.IsOpen)
                        {
                            LastHeartbeatUtcTime = DateTime.UtcNow;
                        }

                        TimeSpan elapsed;
                        lock (_lockerConnectionMonitor)
                        {
                            elapsed = DateTime.UtcNow - LastHeartbeatUtcTime;
                        }

                        if (!_connectionLost && elapsed > TimeSpan.FromSeconds(_heartbeatTimeout))
                        {
                            if (WebSocket.IsOpen)
                            {
                                // connection is still good
                                LastHeartbeatUtcTime = DateTime.UtcNow;
                            }
                            else
                            {
                                _connectionLost = true;
                                nextReconnectionAttemptUtcTime = DateTime.UtcNow.AddSeconds(nextReconnectionAttemptSeconds);

                                OnMessage(BrokerageMessageEvent.Disconnected("Connection with server lost. This could be because of internet connectivity issues."));
                            }
                        }
                        else if (_connectionLost)
                        {
                            try
                            {
                                if (elapsed <= TimeSpan.FromSeconds(_heartbeatTimeout))
                                {
                                    _connectionLost = false;
                                    nextReconnectionAttemptSeconds = 1;

                                    OnMessage(BrokerageMessageEvent.Reconnected("Connection with server restored."));
                                }
                                else
                                {
                                    if (DateTime.UtcNow > nextReconnectionAttemptUtcTime)
                                    {
                                        try
                                        {
                                            Reconnect();
                                        }
                                        catch (Exception err)
                                        {
                                            // double the interval between attempts (capped to 1 minute)
                                            nextReconnectionAttemptSeconds = Math.Min(nextReconnectionAttemptSeconds * 2, 60);
                                            nextReconnectionAttemptUtcTime = DateTime.UtcNow.AddSeconds(nextReconnectionAttemptSeconds);
                                            Log.Error(err);
                                        }
                                    }
                                }
                            }
                            catch (Exception exception)
                            {
                                Log.Error(exception);
                            }
                        }

                        Thread.Sleep(10000);
                    }
                }
                catch (Exception exception)
                {
                    Log.Error(exception);
                }
            });
            _connectionMonitorThread.Start();
            while (!_connectionMonitorThread.IsAlive)
            {
                Thread.Sleep(1);
            }
        }
예제 #15
0
        /// <summary>
        /// Returns true if the specified order is within IB's order size limits
        /// </summary>
        private bool IsForexWithinOrderSizeLimits(string currencyPair, decimal quantity, out BrokerageMessageEvent message)
        {
            /* https://www.interactivebrokers.com/en/?f=%2Fen%2Ftrading%2FforexOrderSize.php
             * Currency    Currency Description	    Minimum Order Size	Maximum Order Size
             * USD	        US Dollar	                25,000              7,000,000
             * AUD	        Australian Dollar	        25,000              6,000,000
             * CAD	        Canadian Dollar	            25,000              6,000,000
             * CHF	        Swiss Franc	                25,000	            6,000,000
             * CNH	        China Renminbi (offshore)	160,000	            40,000,000
             * CZK	        Czech Koruna	            USD 25,000(1)	    USD 7,000,000(1)
             * DKK	        Danish Krone	            150,000	            35,000,000
             * EUR	        Euro	                    20,000	            5,000,000
             * GBP	        British Pound Sterling	    17,000	            4,000,000
             * HKD	        Hong Kong Dollar	        200,000	            50,000,000
             * HUF	        Hungarian Forint	        USD 25,000(1)	    USD 7,000,000(1)
             * ILS	        Israeli Shekel	            USD 25,000(1)	    USD 7,000,000(1)
             * KRW	        Korean Won	                50,000,000	        750,000,000
             * JPY	        Japanese Yen	            2,500,000	        550,000,000
             * MXN	        Mexican Peso	            300,000	            70,000,000
             * NOK	        Norwegian Krone	            150,000	            35,000,000
             * NZD	        New Zealand Dollar	        35,000	            8,000,000
             * RUB	        Russian Ruble	            750,000	            30,000,000
             * SEK	        Swedish Krona	            175,000	            40,000,000
             * SGD	        Singapore Dollar	        35,000	            8,000,000
             */

            message = null;

            // switch on the currency being bought
            string baseCurrency, quoteCurrency;

            Forex.DecomposeCurrencyPair(currencyPair, out baseCurrency, out quoteCurrency);


            decimal max;

            ForexCurrencyLimits.TryGetValue(baseCurrency, out max);

            var orderIsWithinForexSizeLimits = quantity < max;

            if (!orderIsWithinForexSizeLimits)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "OrderSizeLimit",
                                                    string.Format("The maximum allowable order size is {0}{1}.", max, baseCurrency)
                                                    );
            }
            return(orderIsWithinForexSizeLimits);
        }
예제 #16
0
            /// <summary>
            /// Prevent orders which would bring the account below a minimum cash balance
            /// </summary>
            public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
            {
                message = null;

                // we want to model brokerage requirement of _minimumAccountBalance cash value in account

                var orderCost = order.Value;
                var cash = _algorithm.Portfolio.Cash;
                var cashAfterOrder = cash - orderCost;
                if (cashAfterOrder < _minimumAccountBalance)
                {
                    // return a message describing why we're not allowing this order
                    message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "InsufficientRemainingCapital",
                        "Account must maintain a minimum of $500 USD at all times. Order ID: " + order.Id
                        );
                    return false;
                }
                return true;
            }
예제 #17
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.Forex && security.Type != SecurityType.Cfd)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                    "This model 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)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                    "This model does not support " + order.Type + " order type."
                                                    );

                return(false);
            }

            // validate order quantity
            if (order.Quantity % 1000 != 0)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                    "The order quantity must be a multiple of 1000."
                                                    );

                return(false);
            }

            // validate stop/limit orders= prices
            var limit = order as LimitOrder;

            if (limit != null)
            {
                return(IsValidOrderPrices(security, OrderType.Limit, limit.Direction, security.Price, limit.LimitPrice, ref message));
            }

            var stopMarket = order as StopMarketOrder;

            if (stopMarket != null)
            {
                return(IsValidOrderPrices(security, OrderType.StopMarket, stopMarket.Direction, stopMarket.StopPrice, security.Price, ref message));
            }

            var stopLimit = order as StopLimitOrder;

            if (stopLimit != null)
            {
                return(IsValidOrderPrices(security, OrderType.StopLimit, stopLimit.Direction, stopLimit.StopPrice, stopLimit.LimitPrice, ref message));
            }

            // validate time in force
            if (order.TimeInForce != TimeInForce.GoodTilCanceled)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                    "This model does not support " + order.TimeInForce.GetType().Name + " time in force."
                                                    );

                return(false);
            }

            return(true);
        }
예제 #18
0
        private void StartCheckReconnected(TimeSpan delay, BrokerageMessageEvent message)
        {
            _cancellationTokenSource = new CancellationTokenSource(delay);

            Task.Run(() =>
            {
                while (!_cancellationTokenSource.IsCancellationRequested)
                {
                    Thread.Sleep(TimeSpan.FromMinutes(1));
                }

                CheckReconnected(message);

            }, _cancellationTokenSource.Token);
        }
예제 #19
0
 /// <summary>
 /// Please note that the order's queue priority will be reset, and the order ID of the modified order will be different from that of the original order.
 /// Also note: this is implemented as cancelling and replacing your order.
 /// There's a chance that the order meant to be cancelled gets filled and its replacement still gets placed.
 /// </summary>
 /// <param name="security">The security of the order</param>
 /// <param name="order">The order to be updated</param>
 /// <param name="request">The requested update to be made to the order</param>
 /// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
 /// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
 public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
 {
     message =
         new BrokerageMessageEvent(
             BrokerageMessageType.Warning,
             0,
             "You must cancel and re-create instead.");
     return(false);
 }
예제 #20
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">The security of the order</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)
        {
            if (!IsValidOrderSize(security, order.Quantity, out message))
            {
                return(false);
            }

            message = null;

            if (order.Type is OrderType.StopMarket or OrderType.StopLimit)
            {
                if (!security.HasData)
                {
                    message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                        "There is no data for this symbol yet, please check the security.HasData flag to ensure there is at least one data point."
                                                        );

                    return(false);
                }

                var stopPrice = (order as StopMarketOrder)?.StopPrice;
                if (!stopPrice.HasValue)
                {
                    stopPrice = (order as StopLimitOrder)?.StopPrice;
                }

                switch (order.Direction)
                {
                case OrderDirection.Sell:
                    if (stopPrice > security.BidPrice)
                    {
                        message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                            StringExtensions.Invariant($"Trigger price too high: must be below current market price.")
                                                            );
                    }
                    break;

                case OrderDirection.Buy:
                    if (stopPrice < security.AskPrice)
                    {
                        message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                            StringExtensions.Invariant($"Trigger price too low: must be above current market price.")
                                                            );
                    }
                    break;
                }

                if (message != null)
                {
                    return(false);
                }
            }

            if (security.Type != SecurityType.Crypto)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                    StringExtensions.Invariant($"The {this.GetType().Name} does not support {security.Type} security type.")
                                                    );

                return(false);
            }
            return(base.CanSubmitOrder(security, order, out message));
        }
        /// <summary>
        /// Returns true if the specified order is within IB's order size limits
        /// </summary>
        private bool IsForexWithinOrderSizeLimits(string currencyPair, int quantity, out BrokerageMessageEvent message)
        {
            /* https://www.interactivebrokers.com/en/?f=%2Fen%2Ftrading%2FforexOrderSize.php
            Currency    Currency Description	    Minimum Order Size	Maximum Order Size
            USD	        US Dollar	        	    25,000              7,000,000
            AUD	        Australian Dollar	        25,000              6,000,000
            CAD	        Canadian Dollar	            25,000              6,000,000
            CHF	        Swiss Franc	                25,000	            6,000,000
            CNH	        China Renminbi (offshore)	160,000	            40,000,000
            CZK	        Czech Koruna	            USD 25,000(1)	    USD 7,000,000(1)
            DKK	        Danish Krone	            150,000	            35,000,000
            EUR	        Euro	                    20,000	            5,000,000
            GBP	        British Pound Sterling	    17,000	            4,000,000
            HKD	        Hong Kong Dollar	        200,000	            50,000,000
            HUF	        Hungarian Forint	        USD 25,000(1)	    USD 7,000,000(1)
            ILS	        Israeli Shekel	            USD 25,000(1)	    USD 7,000,000(1)
            KRW	        Korean Won	                50,000,000	        750,000,000
            JPY	        Japanese Yen	            2,500,000	        550,000,000
            MXN	        Mexican Peso	            300,000	            70,000,000
            NOK	        Norwegian Krone	            150,000	            35,000,000
            NZD	        New Zealand Dollar	        35,000	            8,000,000
            RUB	        Russian Ruble	            750,000	            30,000,000
            SEK	        Swedish Krona	            175,000	            40,000,000
            SGD	        Singapore Dollar	        35,000	            8,000,000
             */

            message = null;

            // switch on the currency being bought
            string baseCurrency, quoteCurrency;
            Forex.DecomposeCurrencyPair(currencyPair, out baseCurrency, out quoteCurrency);


            decimal max;
            ForexCurrencyLimits.TryGetValue(baseCurrency, out max);

            var orderIsWithinForexSizeLimits = quantity < max;
            if (!orderIsWithinForexSizeLimits)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "OrderSizeLimit",
                    string.Format("The maximum allowable order size is {0}{1}.", max, baseCurrency)
                    );
            }
            return orderIsWithinForexSizeLimits;
        }
예제 #22
0
 /// <summary>
 /// Returns true if the brokerage would allow updating the order as specified by the request
 /// </summary>
 /// <param name="security">The security of the order</param>
 /// <param name="order">The order to be updated</param>
 /// <param name="request">The requested update to be made to the order</param>
 /// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
 /// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
 public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
 {
     message = null;
     return(true);
 }
        /// <summary>
        /// Returns true if the brokerage would allow updating the order as specified by the request
        /// </summary>
        /// <param name="security">The security of the order</param>
        /// <param name="order">The order to be updated</param>
        /// <param name="request">The requested update to be made to the order</param>
        /// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
        /// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
        public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
        {
            message = null;

            // Tradier doesn't allow updating order quantities
            if (request.Quantity != null && request.Quantity != order.Quantity)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "UpdateRejected",
                                                    "Traider does not support updating order quantities."
                                                    );

                return(false);
            }

            return(true);
        }
예제 #24
0
        /// <summary>
        /// Checks whether an order can be updated or not in the Bitfinex brokerage model
        /// </summary>
        /// <param name="security">The security of the order</param>
        /// <param name="order">The order to be updated</param>
        /// <param name="request">The update request</param>
        /// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
        /// <returns>True if the update requested quantity is valid, false otherwise</returns>
        public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
        {
            // If the requested quantity is null is going to be ignored by the moment ApplyUpdateOrderRequest() method is call
            if (request.Quantity == null)
            {
                message = null;
                return(true);
            }

            // Check if the requested quantity is valid
            var requestedQuantity = (decimal)request.Quantity;

            return(IsValidOrderSize(security, requestedQuantity, out message));
        }
        /// <summary>
        /// Handles the message
        /// </summary>
        /// <param name="message">The message to be handled</param>
        public void Handle(BrokerageMessageEvent message)
        {
            // based on message type dispatch to result handler
            switch (message.Type)
            {
            case BrokerageMessageType.Information:
                _algorithm.Debug($"Brokerage Info: {message.Message}");
                break;

            case BrokerageMessageType.Warning:
                _algorithm.Error($"Brokerage Warning: {message.Message}");
                break;

            case BrokerageMessageType.Error:
                // unexpected error, we need to close down shop
                _algorithm.SetRuntimeError(new Exception(message.Message), "Brokerage Error");
                break;

            case BrokerageMessageType.Disconnect:
                _connected = false;
                Log.Trace("DefaultBrokerageMessageHandler.Handle(): Disconnected.");

                // check to see if any non-custom security exchanges are open within the next x minutes
                var open = (from kvp in _algorithm.Securities
                            let security = kvp.Value
                                           where security.Type != SecurityType.Base
                                           let exchange = security.Exchange
                                                          let localTime = _algorithm.UtcTime.ConvertFromUtc(exchange.TimeZone)
                                                                          where exchange.IsOpenDuringBar(
                                localTime,
                                localTime + _openThreshold,
                                _algorithm.SubscriptionManager.SubscriptionDataConfigService
                                .GetSubscriptionDataConfigs(security.Symbol)
                                .IsExtendedMarketHours())
                                                                          select security).Any();

                // if any are open then we need to kill the algorithm
                if (open)
                {
                    Log.Trace("DefaultBrokerageMessageHandler.Handle(): Disconnect when exchanges are open, " +
                              Invariant($"trying to reconnect for {_initialDelay.TotalMinutes} minutes.")
                              );

                    // wait 15 minutes before killing algorithm
                    StartCheckReconnected(_initialDelay, message);
                }
                else
                {
                    Log.Trace("DefaultBrokerageMessageHandler.Handle(): Disconnect when exchanges are closed, checking back before exchange open.");

                    // if they aren't open, we'll need to check again a little bit before markets open
                    DateTime nextMarketOpenUtc;
                    if (_algorithm.Securities.Count != 0)
                    {
                        nextMarketOpenUtc = (from kvp in _algorithm.Securities
                                             let security = kvp.Value
                                                            where security.Type != SecurityType.Base
                                                            let exchange = security.Exchange
                                                                           let localTime = _algorithm.UtcTime.ConvertFromUtc(exchange.TimeZone)
                                                                                           let marketOpen = exchange.Hours.GetNextMarketOpen(localTime,
                                                                                                                                             _algorithm.SubscriptionManager.SubscriptionDataConfigService
                                                                                                                                             .GetSubscriptionDataConfigs(security.Symbol)
                                                                                                                                             .IsExtendedMarketHours())
                                                                                                            let marketOpenUtc = marketOpen.ConvertToUtc(exchange.TimeZone)
                                                                                                                                select marketOpenUtc).Min();
                    }
                    else
                    {
                        // if we have no securities just make next market open an hour from now
                        nextMarketOpenUtc = DateTime.UtcNow.AddHours(1);
                    }

                    var timeUntilNextMarketOpen = nextMarketOpenUtc - DateTime.UtcNow - _openThreshold;
                    Log.Trace(Invariant($"DefaultBrokerageMessageHandler.Handle(): TimeUntilNextMarketOpen: {timeUntilNextMarketOpen}"));

                    // wake up 5 minutes before market open and check if we've reconnected
                    StartCheckReconnected(timeUntilNextMarketOpen, message);
                }
                break;

            case BrokerageMessageType.Reconnect:
                _connected = true;
                Log.Trace("DefaultBrokerageMessageHandler.Handle(): Reconnected.");

                if (_cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested)
                {
                    _cancellationTokenSource.Cancel();
                }
                break;
            }
        }
예제 #26
0
        /// <summary>
        /// Returns true if the brokerage would allow updating the order as specified by the request
        /// </summary>
        /// <param name="security">The security of the order</param>
        /// <param name="order">The order to be updated</param>
        /// <param name="request">The requested update to be made to the order</param>
        /// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
        /// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
        public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
        {
            message = null;

            // validate order quantity
            if (request.Quantity != null && request.Quantity % security.SymbolProperties.LotSize != 0)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                    Invariant($"The order quantity must be a multiple of LotSize: [{security.SymbolProperties.LotSize}].")
                                                    );

                return(false);
            }

            // determine direction via the new, updated quantity
            var newQuantity = request.Quantity ?? order.Quantity;
            var direction   = newQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;

            // use security.Price if null, allows to pass checks
            var stopPrice  = request.StopPrice ?? security.Price;
            var limitPrice = request.LimitPrice ?? security.Price;

            return(IsValidOrderPrices(security, order.Type, direction, stopPrice, limitPrice, ref message));
        }
예제 #27
0
        /// <summary>
        /// Returns true if the brokerage would allow updating the order as specified by the request
        /// </summary>
        /// <param name="security">The security of the order</param>
        /// <param name="order">The order to be updated</param>
        /// <param name="request">The requested update to be made to the order</param>
        /// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
        /// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
        public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
        {
            message = null;

            // validate order quantity
            if (request.Quantity != null && request.Quantity % 1000 != 0)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                    "The order quantity must be a multiple of 1000."
                    );

                return false;
            }
            
            // determine direction via the new, updated quantity
            var newQuantity = request.Quantity ?? order.Quantity;
            var direction = newQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;

            // use security.Price if null, allows to pass checks
            var stopPrice = request.StopPrice ?? security.Price;
            var limitPrice = request.LimitPrice ?? security.Price;

            return IsValidOrderPrices(security, order.Type, direction, stopPrice, limitPrice, ref message);
        }
예제 #28
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.Forex && security.Type != SecurityType.Cfd)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                    Invariant($"The {nameof(FxcmBrokerageModel)} 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)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                    Invariant($"The {nameof(FxcmBrokerageModel)} does not support {order.Type} order type.")
                                                    );

                return(false);
            }

            // validate order quantity
            if (order.Quantity % security.SymbolProperties.LotSize != 0)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                    Invariant($"The order quantity must be a multiple of LotSize: [{security.SymbolProperties.LotSize}].")
                                                    );

                return(false);
            }

            // validate stop/limit orders prices
            var limit = order as LimitOrder;

            if (limit != null)
            {
                return(IsValidOrderPrices(security, OrderType.Limit, limit.Direction, security.Price, limit.LimitPrice, ref message));
            }

            var stopMarket = order as StopMarketOrder;

            if (stopMarket != null)
            {
                return(IsValidOrderPrices(security, OrderType.StopMarket, stopMarket.Direction, stopMarket.StopPrice, security.Price, ref message));
            }

            // validate time in force
            if (order.TimeInForce != TimeInForce.GoodTilCanceled)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                    Invariant($"The {nameof(FxcmBrokerageModel)} does not support {order.TimeInForce.GetType().Name} time in force.")
                                                    );

                return(false);
            }

            return(true);
        }
예제 #29
0
        /// <summary>
        /// Validates limit/stopmarket order prices, pass security.Price for limit/stop if n/a
        /// </summary>
        private static bool IsValidOrderPrices(Security security, OrderType orderType, OrderDirection orderDirection, decimal stopPrice, decimal limitPrice, ref BrokerageMessageEvent message)
        {
            // validate order price
            var invalidPrice = orderType == OrderType.Limit && orderDirection == OrderDirection.Buy && limitPrice > security.Price ||
                orderType == OrderType.Limit && orderDirection == OrderDirection.Sell && limitPrice < security.Price ||
                orderType == OrderType.StopMarket && orderDirection == OrderDirection.Buy && stopPrice < security.Price ||
                orderType == OrderType.StopMarket && orderDirection == OrderDirection.Sell && stopPrice > security.Price;

            if (invalidPrice)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                    "Limit Buy orders and Stop Sell orders must be below market, Limit Sell orders and Stop Buy orders must be above market."
                    );

                return false;
            }

            return true;
        }
예제 #30
0
        /// <summary>
        /// New order handler
        /// </summary>
        /// <param name="order">The new order</param>
        private void HandleNewOrder(Order order)
        {
            if (_orders.TryAdd(order.Id, order))
            {
                // check to see if we have enough money to place the order
                if (!_algorithm.Transactions.GetSufficientCapitalForOrder(_algorithm.Portfolio, order))
                {
                    order.Status = OrderStatus.Invalid;
                    _algorithm.Error(string.Format("Order Error: id: {0}, Insufficient buying power to complete order (Value:{1}).", order.Id, order.Value));
                    return;
                }

                // verify that our current brokerage can actually take the order
                BrokerageMessageEvent message;
                if (!_algorithm.LiveMode && !_algorithm.BrokerageModel.CanSubmitOrder(_algorithm.Securities[order.Symbol], order, out message))
                {
                    // if we couldn't actually process the order, mark it as invalid and bail
                    order.Status = OrderStatus.Invalid;
                    if (message == null) message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidOrder", "BrokerageModel declared unable to submit order: " + order.Id);
                    _algorithm.Error("OrderID: " + message);
                    return;
                }

                // set the order status based on whether or not we successfully submitted the order to the market
                if (_brokerage.PlaceOrder(order))
                {
                    order.Status = OrderStatus.Submitted;
                }
                else
                {
                    order.Status = OrderStatus.Invalid;
                    _algorithm.Error("Brokerage failed to place order: " + order.Id);
                }
            }
            else
            {
                Log.Error("BrokerageTransactionHandler.HandleNewOrder(): Unable to add new order, order not processed.");
            }
        }
        /// <summary>
        /// Handles a request to update order properties
        /// </summary>
        private OrderResponse HandleUpdateOrderRequest(UpdateOrderRequest request)
        {
            Order order;
            OrderTicket ticket;
            if (!_orders.TryGetValue(request.OrderId, out order) || !_orderTickets.TryGetValue(request.OrderId, out ticket))
            {
                Log.Error("BrokerageTransactionHandler.HandleUpdateOrderRequest(): Unable to update order with ID " + request.OrderId);
                return OrderResponse.UnableToFindOrder(request);
            }
            
            if (!CanUpdateOrder(order))
            {
                return OrderResponse.InvalidStatus(request, order);
            }

            // verify that our current brokerage can actually update the order
            BrokerageMessageEvent message;
            if (!_algorithm.LiveMode && !_algorithm.BrokerageModel.CanUpdateOrder(_algorithm.Securities[order.Symbol], order, request, out message))
            {
                // if we couldn't actually process the order, mark it as invalid and bail
                order.Status = OrderStatus.Invalid;
                if (message == null) message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidOrder", "BrokerageModel declared unable to update order: " + order.Id);
                var response = OrderResponse.Error(request, OrderResponseErrorCode.BrokerageModelRefusedToUpdateOrder, "OrderID: " + order.Id + " " + message);
                _algorithm.Error(response.ErrorMessage);
                HandleOrderEvent(new OrderEvent(order, _algorithm.UtcTime, 0m, "BrokerageModel declared unable to update order"));
                return response;
            }

            // modify the values of the order object
            order.ApplyUpdateOrderRequest(request);
            ticket.SetOrder(order);

            bool orderUpdated;
            try
            {
                orderUpdated = _brokerage.UpdateOrder(order);
            }
            catch (Exception err)
            {
                Log.Error(err);
                orderUpdated = false;
            }
            
            if (!orderUpdated)
            {
                // we failed to update the order for some reason
                var errorMessage = "Brokerage failed to update order with id " + request.OrderId;
                _algorithm.Error(errorMessage);
                HandleOrderEvent(new OrderEvent(order, _algorithm.UtcTime, 0m, "Brokerage failed to update order"));
                return OrderResponse.Error(request, OrderResponseErrorCode.BrokerageFailedToUpdateOrder, errorMessage);
            }

            return OrderResponse.Success(request);
        }
예제 #32
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">The security being ordered</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 virtual bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
 {
     message = null;
     return(true);
 }
 private void CheckReconnected(BrokerageMessageEvent message)
 {
     if (!_connected)
     {
         Log.Error("DefaultBrokerageMessageHandler.Handle(): Still disconnected, goodbye.");
         _results.ErrorMessage("Brokerage Disconnect: " + message.Message);
         _algorithm.RunTimeError = new Exception(message.Message);
     }
 }
예제 #34
0
 /// <summary>
 /// Returns true if the brokerage would allow updating the order as specified by the request
 /// </summary>
 /// <param name="security">The security of the order</param>
 /// <param name="order">The order to be updated</param>
 /// <param name="request">The requested update to be made to the order</param>
 /// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
 /// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
 public virtual bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
 {
     message = null;
     return true;
 }
예제 #35
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">The security of the order</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;

            // Binance API provides minimum order size in quote currency
            // and hence we have to check current order size using available price and order quantity
            var quantityIsValid = true;

            switch (order)
            {
            case LimitOrder limitOrder:
                quantityIsValid &= IsOrderSizeLargeEnough(limitOrder.LimitPrice);
                break;

            case MarketOrder:
                if (!security.HasData)
                {
                    message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                        "There is no data for this symbol yet, please check the security.HasData flag to ensure there is at least one data point."
                                                        );

                    return(false);
                }

                var price = order.Direction == OrderDirection.Buy ? security.AskPrice : security.BidPrice;
                quantityIsValid &= IsOrderSizeLargeEnough(price);
                break;

            case StopLimitOrder stopLimitOrder:
                quantityIsValid &= IsOrderSizeLargeEnough(stopLimitOrder.LimitPrice);
                // Binance Trading UI requires this check too...
                quantityIsValid &= IsOrderSizeLargeEnough(stopLimitOrder.StopPrice);
                break;

            case StopMarketOrder:
                // despite Binance API allows you to post STOP_LOSS and TAKE_PROFIT order types
                // they always fails with the content
                // {"code":-1013,"msg":"Take profit orders are not supported for this symbol."}
                // currently no symbols supporting TAKE_PROFIT or STOP_LOSS orders

                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                    Invariant($"{order.Type} orders are not supported for this symbol. Please check 'https://api.binance.com/api/v3/exchangeInfo?symbol={security.SymbolProperties.MarketTicker}' to see supported order types.")
                                                    );
                return(false);

            default:
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                    Invariant($"{order.Type} orders are not supported by Binance.")
                                                    );
                return(false);
            }


            if (!quantityIsValid)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                    Invariant($"The minimum order size (in quote currency) for {security.Symbol.Value} is {security.SymbolProperties.MinimumOrderSize}. Order quantity was {order.Quantity}.")
                                                    );

                return(false);
            }

            if (security.Type != SecurityType.Crypto)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                    StringExtensions.Invariant($"The {nameof(BinanceBrokerageModel)} does not support {security.Type} security type.")
                                                    );

                return(false);
            }
            return(base.CanSubmitOrder(security, order, out message));

            bool IsOrderSizeLargeEnough(decimal price) =>
            order.AbsoluteQuantity * price > security.SymbolProperties.MinimumOrderSize;
        }
        /// <summary>
        /// Handles a request to submit a new order
        /// </summary>
        private OrderResponse HandleSubmitOrderRequest(SubmitOrderRequest request)
        {
            OrderTicket ticket;
            var order = Order.CreateOrder(request);

            // ensure the order is tagged with a currency
            var security = _algorithm.Securities[order.Symbol];
            order.PriceCurrency = security.SymbolProperties.QuoteCurrency;

            if (!_orders.TryAdd(order.Id, order))
            {
                Log.Error("BrokerageTransactionHandler.HandleSubmitOrderRequest(): Unable to add new order, order not processed.");
                return OrderResponse.Error(request, OrderResponseErrorCode.OrderAlreadyExists, "Cannot process submit request because order with id {0} already exists");
            }
            if (!_orderTickets.TryGetValue(order.Id, out ticket))
            {
                Log.Error("BrokerageTransactionHandler.HandleSubmitOrderRequest(): Unable to retrieve order ticket, order not processed.");
                return OrderResponse.UnableToFindOrder(request);
            }

            // update the ticket's internal storage with this new order reference
            ticket.SetOrder(order);

            // check to see if we have enough money to place the order
            bool sufficientCapitalForOrder;
            try
            {
                sufficientCapitalForOrder = _algorithm.Transactions.GetSufficientCapitalForOrder(_algorithm.Portfolio, order);
            }
            catch (Exception err)
            {
                Log.Error(err);
                _algorithm.Error(string.Format("Order Error: id: {0}, Error executing margin models: {1}", order.Id, err.Message));
                HandleOrderEvent(new OrderEvent(order, _algorithm.UtcTime, 0m, "Error executing margin models"));
                return OrderResponse.Error(request, OrderResponseErrorCode.ProcessingError, "Error in GetSufficientCapitalForOrder");
            }

            if (!sufficientCapitalForOrder)
            {
                order.Status = OrderStatus.Invalid;
                var response = OrderResponse.Error(request, OrderResponseErrorCode.InsufficientBuyingPower, string.Format("Order Error: id: {0}, Insufficient buying power to complete order (Value:{1}).", order.Id, order.GetValue(security).SmartRounding()));
                _algorithm.Error(response.ErrorMessage);
                HandleOrderEvent(new OrderEvent(order, _algorithm.UtcTime, 0m, "Insufficient buying power to complete order"));
                return response;
            }

            // verify that our current brokerage can actually take the order
            BrokerageMessageEvent message;
            if (!_algorithm.LiveMode && !_algorithm.BrokerageModel.CanSubmitOrder(security, order, out message))
            {
                // if we couldn't actually process the order, mark it as invalid and bail
                order.Status = OrderStatus.Invalid;
                if (message == null) message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidOrder", "BrokerageModel declared unable to submit order: " + order.Id);
                var response = OrderResponse.Error(request, OrderResponseErrorCode.BrokerageModelRefusedToSubmitOrder, "OrderID: " + order.Id + " " + message);
                _algorithm.Error(response.ErrorMessage);
                HandleOrderEvent(new OrderEvent(order, _algorithm.UtcTime, 0m, "BrokerageModel declared unable to submit order"));
                return response;
            }

            // set the order status based on whether or not we successfully submitted the order to the market
            bool orderPlaced;
            try
            {
                orderPlaced = _brokerage.PlaceOrder(order);
            }
            catch (Exception err)
            {
                Log.Error(err);
                orderPlaced = false;
             }

            if (!orderPlaced)
            {
                // we failed to submit the order, invalidate it
                order.Status = OrderStatus.Invalid;
                var errorMessage = "Brokerage failed to place order: " + order.Id;
                var response = OrderResponse.Error(request, OrderResponseErrorCode.BrokerageFailedToSubmitOrder, errorMessage);
                _algorithm.Error(response.ErrorMessage);
                HandleOrderEvent(new OrderEvent(order, _algorithm.UtcTime, 0m, "Brokerage failed to place order"));
                return response;
            }
            
            order.Status = OrderStatus.Submitted;
            return OrderResponse.Success(request);
        }
예제 #37
0
        /// <summary>
        /// Checks if the order quantity is valid, it means, the order size is bigger than the minimum size allowed
        /// </summary>
        /// <param name="security">The security of the order</param>
        /// <param name="orderQuantity">The quantity of the order to be processed</param>
        /// <param name="message">If this function returns false, a brokerage message detailing why the order may be invalid</param>
        /// <returns>True if the order quantity is bigger than the minimum allowed, false otherwise</returns>
        public static bool IsValidOrderSize(Security security, decimal orderQuantity, out BrokerageMessageEvent message)
        {
            var minimumOrderSize = security.SymbolProperties.MinimumOrderSize;

            if (minimumOrderSize != null && Math.Abs(orderQuantity) < minimumOrderSize)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
                                                    Invariant($"The minimum order quantity for {security.Symbol.Value} is {minimumOrderSize}. Order quantity was {orderQuantity}")
                                                    );

                return(false);
            }

            message = null;
            return(true);
        }
예제 #38
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);
        }
예제 #39
0
        /// <summary>
        /// Returns true if the brokerage would allow updating the order as specified by the request
        /// </summary>
        /// <param name="security">The security of the order</param>
        /// <param name="order">The order to be updated</param>
        /// <param name="request">The requested update to be made to the order</param>
        /// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
        /// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
        public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
        {
            message = null;

            if (order.SecurityType == SecurityType.Forex && request.Quantity != null)
            {
                return(IsForexWithinOrderSizeLimits(order.Symbol.Value, request.Quantity.Value, out message));
            }

            return(true);
        }
        /// <summary>
        /// Handles the message
        /// </summary>
        /// <param name="message">The message to be handled</param>
        public void Handle(BrokerageMessageEvent message)
        {
            // based on message type dispatch to result handler
            switch (message.Type)
            {
                case BrokerageMessageType.Information:
                    _results.DebugMessage("Brokerage Info: " + message.Message);
                    break;
                
                case BrokerageMessageType.Warning:
                    _results.ErrorMessage("Brokerage Warning: " + message.Message);
                    _api.SendUserEmail(_job.AlgorithmId, "Brokerage Warning", message.Message);
                    break;

                case BrokerageMessageType.Error:
                    _results.ErrorMessage("Brokerage Error: " + message.Message);
                    _algorithm.RunTimeError = new Exception(message.Message);
                    break;

                case BrokerageMessageType.Disconnect:
                    _connected = false;
                    Log.Trace("DefaultBrokerageMessageHandler.Handle(): Disconnected.");

                    // check to see if any non-custom security exchanges are open within the next x minutes
                    var open = (from kvp in _algorithm.Securities
                                let security = kvp.Value
                                where security.Type != SecurityType.Base
                                let exchange = security.Exchange
                                let localTime = _algorithm.UtcTime.ConvertFromUtc(exchange.TimeZone)
                                where exchange.IsOpenDuringBar(localTime, localTime + _openThreshold, security.IsExtendedMarketHours)
                                select security).Any();

                    // if any are open then we need to kill the algorithm
                    if (open)
                    {
                        // wait 15 minutes before killing algorithm
                        Task.Delay(_initialDelay).ContinueWith(_ => CheckReconnected(message));
                    }
                    else
                    {
                        Log.Trace("DefaultBrokerageMessageHandler.Handle(): Disconnect when exchanges are closed, checking back before exchange open.");

                        // if they aren't open, we'll need to check again a little bit before markets open
                        var nextMarketOpenUtc = (from kvp in _algorithm.Securities
                                                 let security = kvp.Value
                                                 where security.Type != SecurityType.Base
                                                 let exchange = security.Exchange
                                                 let localTime = _algorithm.UtcTime.ConvertFromUtc(exchange.TimeZone)
                                                 let marketOpen = exchange.Hours.GetNextMarketOpen(localTime, security.IsExtendedMarketHours)
                                                 let marketOpenUtc = marketOpen.ConvertToUtc(exchange.TimeZone)
                                                 select marketOpenUtc).Min();

                        var timeUntilNextMarketOpen = nextMarketOpenUtc - DateTime.UtcNow - _openThreshold;

                        // wake up 5 minutes before market open and check if we've reconnected
                        Task.Delay(timeUntilNextMarketOpen).ContinueWith(_ => CheckReconnected(message));
                    }
                    break;

                case BrokerageMessageType.Reconnect:
                    _connected = true;
                    Log.Trace("DefaultBrokerageMessageHandler.Handle(): Reconnected.");
                    break;
            }
        }
 /// <summary>
 /// Process the brokerage message event. Trigger any actions in the algorithm or notifications system required.
 /// </summary>
 /// <param name="message">Message object</param>
 public void Handle(BrokerageMessageEvent message)
 {
     var toLog = _algo.Time.ToString("o") + " Event: " + message.Message;
     _algo.Debug(toLog);
     _algo.Log(toLog);
 }