/// <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; }
/// <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; }
/// <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; }
/// <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; } }
/// <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; }
/// <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); } }
/// <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; }
/// <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); }
/// <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); }
/// <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); } }
/// <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); }
/// <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; }
/// <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); }
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); }
/// <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); }
/// <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; }
/// <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); }
/// <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; } }
/// <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)); }
/// <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); }
/// <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); }
/// <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; }
/// <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); }
/// <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); } }
/// <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; }
/// <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); }
/// <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); }
/// <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); }
/// <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); }