/// <summary> /// Add an order to collection and return the unique order id or negative if an error. /// </summary> /// <param name="request">A request detailing the order to be submitted</param> /// <returns>New unique, increasing orderid</returns> public OrderTicket AddOrder(SubmitOrderRequest request) { var response = !_algorithm.IsWarmingUp ? OrderResponse.Success(request) : OrderResponse.WarmingUp(request); request.SetResponse(response); var ticket = new OrderTicket(_algorithm.Transactions, request); _orderTickets.TryAdd(ticket.OrderId, ticket); // send the order to be processed after creating the ticket if (response.IsSuccess) { _orderRequestQueue.Add(request); } else { // add it to the orders collection for recall later var order = Order.CreateOrder(request); // ensure the order is tagged with a currency var security = _algorithm.Securities[order.Symbol]; order.PriceCurrency = security.SymbolProperties.QuoteCurrency; order.Status = OrderStatus.Invalid; order.Tag = "Algorithm warming up."; ticket.SetOrder(order); _orders.TryAdd(request.OrderId, order); } return(ticket); }
/// <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> /// Add an order to collection and return the unique order id or negative if an error. /// </summary> /// <param name="request">A request detailing the order to be submitted</param> /// <returns>New unique, increasing orderid</returns> public OrderTicket AddOrder(SubmitOrderRequest request) { var response = !_algorithm.IsWarmingUp ? OrderResponse.Success(request) : OrderResponse.WarmingUp(request); request.SetResponse(response); var ticket = new OrderTicket(_algorithm.Transactions, request); _orderTickets.TryAdd(ticket.OrderId, ticket); // send the order to be processed after creating the ticket if (response.IsSuccess) { _orderRequestQueue.Add(request); } else { // add it to the orders collection for recall later var order = Order.CreateOrder(request); order.Status = OrderStatus.Invalid; order.Tag = "Algorithm warming up."; ticket.SetOrder(order); _orders.TryAdd(request.OrderId, order); } return(ticket); }
/// <summary> /// Remove this order from outstanding queue: user is requesting a cancel. /// </summary> /// <param name="request">Request containing the specific order id to remove</param> public OrderTicket CancelOrder(CancelOrderRequest request) { OrderTicket ticket; if (!_orderTickets.TryGetValue(request.OrderId, out ticket)) { Log.Error("BrokerageTransactionHandler.CancelOrder(): Unable to locate ticket for order."); return(OrderTicket.InvalidCancelOrderId(_algorithm.Transactions, request)); } try { // if we couldn't set this request as the cancellation then another thread/someone // else is already doing it or it in fact has already been cancelled if (!ticket.TrySetCancelRequest(request)) { // the ticket has already been cancelled request.SetResponse(OrderResponse.Error(request, OrderResponseErrorCode.InvalidRequest, "Cancellation is already in progress.")); return(ticket); } //Error check var order = GetOrderByIdInternal(request.OrderId); if (order != null && request.Tag != null) { order.Tag = request.Tag; } if (order == null) { Log.Error("BrokerageTransactionHandler.CancelOrder(): Cannot find this id."); request.SetResponse(OrderResponse.UnableToFindOrder(request)); } else if (order.Status.IsClosed()) { Log.Error("BrokerageTransactionHandler.CancelOrder(): Order already " + order.Status); request.SetResponse(OrderResponse.InvalidStatus(request, order)); } else if (_algorithm.IsWarmingUp) { request.SetResponse(OrderResponse.WarmingUp(request)); } else { // send the request to be processed request.SetResponse(OrderResponse.Success(request), OrderRequestStatus.Processing); _orderRequestQueue.Add(request); } } catch (Exception err) { Log.Error(err); request.SetResponse(OrderResponse.Error(request, OrderResponseErrorCode.ProcessingError, err.Message)); } return(ticket); }
/// <summary> /// Add an order to collection and return the unique order id or negative if an error. /// </summary> /// <param name="request">A request detailing the order to be submitted</param> /// <returns>New unique, increasing orderid</returns> public OrderTicket AddOrder(SubmitOrderRequest request) { request.SetResponse(OrderResponse.Success(request), OrderRequestStatus.Processing); var ticket = new OrderTicket(_algorithm.Transactions, request); _orderTickets.TryAdd(ticket.OrderId, ticket); // send the order to be processed after creating the ticket _orderRequestQueue.Enqueue(request); return(ticket); }
/// <summary> /// Handles a request to cancel an order /// </summary> private OrderResponse HandleCancelOrderRequest(CancelOrderRequest request) { Order order; OrderTicket ticket; if (!_orders.TryGetValue(request.OrderId, out order) || !_orderTickets.TryGetValue(request.OrderId, out ticket)) { Log.Error("BrokerageTransactionHandler.HandleCancelOrderRequest(): Unable to cancel order with ID " + request.OrderId + "."); return(OrderResponse.UnableToFindOrder(request)); } if (order.Status.IsClosed()) { return(OrderResponse.InvalidStatus(request, order)); } ticket.SetOrder(order); bool orderCanceled; try { orderCanceled = _brokerage.CancelOrder(order); } catch (Exception err) { Log.Error(err); orderCanceled = false; } if (!orderCanceled) { // failed to cancel the order var message = "Brokerage failed to cancel order with id " + order.Id; _algorithm.Error(message); HandleOrderEvent(new OrderEvent(order, _algorithm.UtcTime, 0m, "Brokerage failed to cancel order")); return(OrderResponse.Error(request, OrderResponseErrorCode.BrokerageFailedToCancelOrder, message)); } // we succeeded to cancel the order order.Status = OrderStatus.Canceled; if (request.Tag != null) { // update the tag, useful for 'why' we canceled the order order.Tag = request.Tag; } return(OrderResponse.Success(request)); }
/// <summary> /// Handles a request to cancel an order /// </summary> private OrderResponse HandleCancelOrderRequest(CancelOrderRequest request) { Order order; OrderTicket ticket; if (!_orders.TryGetValue(request.OrderId, out order) || !_orderTickets.TryGetValue(request.OrderId, out ticket)) { Log.Error("BrokerageTransactionHandler.HandleCancelOrderRequest(): Unable to cancel order with ID " + request.OrderId + "."); return(OrderResponse.UnableToFindOrder(request)); } if (order.Status.IsClosed()) { return(OrderResponse.InvalidStatus(request, order)); } ticket.SetOrder(order); bool orderCanceled; try { orderCanceled = _brokerage.CancelOrder(order); } catch (Exception err) { Log.Error(err); orderCanceled = false; } if (!orderCanceled) { // we failed to cancel the order for some reason order.Status = OrderStatus.Invalid; } else { // we succeeded to cancel the order order.Status = OrderStatus.Canceled; } if (request.Tag != null) { // update the tag, useful for 'why' we canceled the order order.Tag = request.Tag; } return(OrderResponse.Success(request)); }
/// <summary> /// Update an order yet to be filled such as stop or limit orders. /// </summary> /// <param name="request">Request detailing how the order should be updated</param> /// <remarks>Does not apply if the order is already fully filled</remarks> public OrderTicket UpdateOrder(UpdateOrderRequest request) { OrderTicket ticket; if (!_orderTickets.TryGetValue(request.OrderId, out ticket)) { return(OrderTicket.InvalidUpdateOrderId(_algorithm.Transactions, request)); } ticket.AddUpdateRequest(request); try { //Update the order from the behaviour var order = GetOrderByIdInternal(request.OrderId); if (order == null) { // can't update an order that doesn't exist! request.SetResponse(OrderResponse.UnableToFindOrder(request)); } else if (order.Status.IsClosed()) { // can't update a completed order request.SetResponse(OrderResponse.InvalidStatus(request, order)); } else if (request.Quantity.HasValue && request.Quantity.Value == 0) { request.SetResponse(OrderResponse.ZeroQuantity(request)); } else if (_algorithm.IsWarmingUp) { request.SetResponse(OrderResponse.WarmingUp(request)); } else { request.SetResponse(OrderResponse.Success(request), OrderRequestStatus.Processing); _orderRequestQueue.Add(request); } } catch (Exception err) { Log.Error(err); request.SetResponse(OrderResponse.Error(request, OrderResponseErrorCode.ProcessingError, err.Message)); } return(ticket); }
/// <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)); } // 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 order.Status = OrderStatus.Invalid; return(OrderResponse.Error(request, OrderResponseErrorCode.BrokerageFailedToUpdateOrder, "Brokerage failed to update order with id " + request.OrderId)); } return(OrderResponse.Success(request)); }
/// <summary> /// Remove this order from outstanding queue: user is requesting a cancel. /// </summary> /// <param name="request">Request containing the specific order id to remove</param> public OrderTicket CancelOrder(CancelOrderRequest request) { OrderTicket ticket; if (!_orderTickets.TryGetValue(request.OrderId, out ticket)) { Log.Error("BrokerageTransactionHandler.CancelOrder(): Unable to locate ticket for order."); return(OrderTicket.InvalidCancelOrderId(_algorithm.Transactions, request)); } ticket.SetCancelRequest(request); try { //Error check var order = GetOrderById(request.OrderId); if (order == null) { Log.Error("BrokerageTransactionHandler.CancelOrder(): Cannot find this id."); request.SetResponse(OrderResponse.UnableToFindOrder(request)); } else if (order.Status.IsClosed()) { Log.Error("BrokerageTransactionHandler.CancelOrder(): Order already filled"); request.SetResponse(OrderResponse.InvalidStatus(request, order)); } else { // send the request to be processed request.SetResponse(OrderResponse.Success(request), OrderRequestStatus.Processing); _orderRequestQueue.Enqueue(request); } } catch (Exception err) { Log.Error("TransactionManager.RemoveOrder(): " + err.Message); request.SetResponse(OrderResponse.Error(request, OrderResponseErrorCode.ProcessingError, err.Message)); } return(ticket); }
/// <summary> /// Perform preorder checks to ensure we have sufficient capital, /// the market is open, and we haven't exceeded maximum realistic orders per day. /// </summary> /// <returns>OrderResponse. If no error, order request is submitted.</returns> private OrderResponse PreOrderChecksImpl(SubmitOrderRequest request) { //Most order methods use security objects; so this isn't really used. // todo: Left here for now but should review Security security; if (!Securities.TryGetValue(request.Symbol, out security)) { return(OrderResponse.Error(request, OrderResponseErrorCode.MissingSecurity, "You haven't requested " + request.Symbol.ToString() + " data. Add this with AddSecurity() in the Initialize() Method.")); } //Ordering 0 is useless. if (request.Quantity == 0 || request.Symbol == null || request.Symbol == QuantConnect.Symbol.Empty || Math.Abs(request.Quantity) < security.SymbolProperties.LotSize) { return(OrderResponse.ZeroQuantity(request)); } if (!security.IsTradable) { return(OrderResponse.Error(request, OrderResponseErrorCode.NonTradableSecurity, "The security with symbol '" + request.Symbol.ToString() + "' is marked as non-tradable.")); } var price = security.Price; //Check the exchange is open before sending a market on close orders if (request.OrderType == OrderType.MarketOnClose && !security.Exchange.ExchangeOpen) { return(OrderResponse.Error(request, OrderResponseErrorCode.ExchangeNotOpen, request.OrderType + " order and exchange not open.")); } //Check the exchange is open before sending a exercise orders if (request.OrderType == OrderType.OptionExercise && !security.Exchange.ExchangeOpen) { return(OrderResponse.Error(request, OrderResponseErrorCode.ExchangeNotOpen, request.OrderType + " order and exchange not open.")); } if (price == 0) { return(OrderResponse.Error(request, OrderResponseErrorCode.SecurityPriceZero, request.Symbol.ToString() + ": asset price is $0. If using custom data make sure you've set the 'Value' property.")); } // check quote currency existence/conversion rate on all orders Cash quoteCash; var quoteCurrency = security.QuoteCurrency.Symbol; if (!Portfolio.CashBook.TryGetValue(quoteCurrency, out quoteCash)) { return(OrderResponse.Error(request, OrderResponseErrorCode.QuoteCurrencyRequired, request.Symbol.Value + ": requires " + quoteCurrency + " in the cashbook to trade.")); } if (security.QuoteCurrency.ConversionRate == 0m) { return(OrderResponse.Error(request, OrderResponseErrorCode.ConversionRateZero, request.Symbol.Value + ": requires " + quoteCurrency + " to have a non-zero conversion rate. This can be caused by lack of data.")); } // need to also check base currency existence/conversion rate on forex orders if (security.Type == SecurityType.Forex) { Cash baseCash; var baseCurrency = ((Forex)security).BaseCurrencySymbol; if (!Portfolio.CashBook.TryGetValue(baseCurrency, out baseCash)) { return(OrderResponse.Error(request, OrderResponseErrorCode.ForexBaseAndQuoteCurrenciesRequired, request.Symbol.Value + ": requires " + baseCurrency + " and " + quoteCurrency + " in the cashbook to trade.")); } if (baseCash.ConversionRate == 0m) { return(OrderResponse.Error(request, OrderResponseErrorCode.ForexConversionRateZero, request.Symbol.Value + ": requires " + baseCurrency + " and " + quoteCurrency + " to have non-zero conversion rates. This can be caused by lack of data.")); } } //Make sure the security has some data: if (!security.HasData) { return(OrderResponse.Error(request, OrderResponseErrorCode.SecurityHasNoData, "There is no data for this symbol yet, please check the security.HasData flag to ensure there is at least one data point.")); } //We've already processed too many orders: max 100 per day or the memory usage explodes if (Transactions.OrdersCount > _maxOrders) { Status = AlgorithmStatus.Stopped; return(OrderResponse.Error(request, OrderResponseErrorCode.ExceededMaximumOrders, string.Format("You have exceeded maximum number of orders ({0}), for unlimited orders upgrade your account.", _maxOrders))); } if (request.OrderType == OrderType.OptionExercise) { if (security.Type != SecurityType.Option) { return(OrderResponse.Error(request, OrderResponseErrorCode.NonExercisableSecurity, "The security with symbol '" + request.Symbol.ToString() + "' is not exercisable.")); } if (security.Holdings.IsShort) { return(OrderResponse.Error(request, OrderResponseErrorCode.UnsupportedRequestType, "The security with symbol '" + request.Symbol.ToString() + "' has a short option position. Only long option positions are exercisable.")); } if (request.Quantity > security.Holdings.Quantity) { return(OrderResponse.Error(request, OrderResponseErrorCode.UnsupportedRequestType, "Cannot exercise more contracts of '" + request.Symbol.ToString() + "' than is currently available in the portfolio. ")); } if (request.Quantity <= 0.0m) { OrderResponse.ZeroQuantity(request); } } if (request.OrderType == OrderType.MarketOnClose) { var nextMarketClose = security.Exchange.Hours.GetNextMarketClose(security.LocalTime, false); // must be submitted with at least 10 minutes in trading day, add buffer allow order submission var latestSubmissionTime = nextMarketClose.AddMinutes(-15.50); if (!security.Exchange.ExchangeOpen || Time > latestSubmissionTime) { // tell the user we require a 16 minute buffer, on minute data in live a user will receive the 3:44->3:45 bar at 3:45, // this is already too late to submit one of these orders, so make the user do it at the 3:43->3:44 bar so it's submitted // to the brokerage before 3:45. return(OrderResponse.Error(request, OrderResponseErrorCode.MarketOnCloseOrderTooLate, "MarketOnClose orders must be placed with at least a 16 minute buffer before market close.")); } } // passes all initial order checks return(OrderResponse.Success(request)); }
/// <summary> /// Handles a request to submit a new order /// </summary> private OrderResponse HandleSubmitOrderRequest(SubmitOrderRequest request) { OrderTicket ticket; var order = Order.CreateOrder(request); 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 security = _algorithm.Securities[order.Symbol]; 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(_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); } 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> /// Perform preorder checks to ensure we have sufficient capital, /// the market is open, and we haven't exceeded maximum realistic orders per day. /// </summary> /// <returns>OrderResponse. If no error, order request is submitted.</returns> private OrderResponse PreOrderChecksImpl(SubmitOrderRequest request) { //Ordering 0 is useless. if (request.Quantity == 0 || request.Symbol == null || request.Symbol == Symbol.Empty) { return(OrderResponse.ZeroQuantity(request)); } //If we're not tracking this symbol: throw error: if (!Securities.ContainsKey(request.Symbol) && !_sentNoDataError) { _sentNoDataError = true; return(OrderResponse.Error(request, OrderResponseErrorCode.MissingSecurity, "You haven't requested " + request.Symbol.SID + " data. Add this with AddSecurity() in the Initialize() Method.")); } //Set a temporary price for validating order for market orders: var security = Securities[request.Symbol]; var price = security.Price; //Check the exchange is open before sending a market on close orders //Allow market orders, they'll just execute when the exchange reopens if (request.OrderType == OrderType.MarketOnClose && !security.Exchange.ExchangeOpen) { return(OrderResponse.Error(request, OrderResponseErrorCode.ExchangeNotOpen, request.OrderType + " order and exchange not open.")); } if (price == 0) { return(OrderResponse.Error(request, OrderResponseErrorCode.SecurityPriceZero, request.Symbol.SID + ": asset price is $0. If using custom data make sure you've set the 'Value' property.")); } if (security.Type == SecurityType.Forex) { // for forex pairs we need to verify that the conversions to USD have values as well string baseCurrency, quoteCurrency; Forex.DecomposeCurrencyPair(security.Symbol.Value, out baseCurrency, out quoteCurrency); // verify they're in the portfolio Cash baseCash, quoteCash; if (!Portfolio.CashBook.TryGetValue(baseCurrency, out baseCash) || !Portfolio.CashBook.TryGetValue(quoteCurrency, out quoteCash)) { return(OrderResponse.Error(request, OrderResponseErrorCode.ForexBaseAndQuoteCurrenciesRequired, request.Symbol.Value + ": requires " + baseCurrency + " and " + quoteCurrency + " in the cashbook to trade.")); } // verify we have conversion rates for each leg of the pair back into the account currency if (baseCash.ConversionRate == 0m || quoteCash.ConversionRate == 0m) { return(OrderResponse.Error(request, OrderResponseErrorCode.ForexConversionRateZero, request.Symbol.Value + ": requires " + baseCurrency + " and " + quoteCurrency + " to have non-zero conversion rates. This can be caused by lack of data.")); } } //Make sure the security has some data: if (!security.HasData) { return(OrderResponse.Error(request, OrderResponseErrorCode.SecurityHasNoData, "There is no data for this symbol yet, please check the security.HasData flag to ensure there is at least one data point.")); } //We've already processed too many orders: max 100 per day or the memory usage explodes if (Transactions.OrdersCount > _maxOrders) { _quit = true; return(OrderResponse.Error(request, OrderResponseErrorCode.ExceededMaximumOrders, string.Format("You have exceeded maximum number of orders ({0}), for unlimited orders upgrade your account.", _maxOrders))); } if (request.OrderType == OrderType.MarketOnClose) { // must be submitted with at least 10 minutes in trading day, add buffer allow order submission var latestSubmissionTime = (Time.Date + security.Exchange.MarketClose).AddMinutes(-10.75); if (Time > latestSubmissionTime) { // tell the user we require an 11 minute buffer, on minute data in live a user will receive the 3:49->3:50 bar at 3:50, // this is already too late to submit one of these orders, so make the user do it at the 3:48->3:49 bar so it's submitted // to the brokerage before 3:50. return(OrderResponse.Error(request, OrderResponseErrorCode.MarketOnCloseOrderTooLate, "MarketOnClose orders must be placed with at least a 11 minute buffer before market close.")); } } // passes all initial order checks return(OrderResponse.Success(request)); }
/// <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; // rounds off the order towards 0 to the nearest multiple of lot size order.Quantity = RoundOffOrder(order, security); 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)); } // rounds the order prices RoundOrderPrices(order, security); // save current security time and prices order.OrderSubmissionData = new OrderSubmissionData(security.GetLastData()); // update the ticket's internal storage with this new order reference ticket.SetOrder(order); if (order.Quantity == 0) { order.Status = OrderStatus.Invalid; var response = OrderResponse.ZeroQuantity(request); _algorithm.Error(response.ErrorMessage); HandleOrderEvent(new OrderEvent(order, _algorithm.UtcTime, 0m, "Unable to add order for zero quantity")); return(response); } // check to see if we have enough money to place the order HasSufficientBuyingPowerForOrderResult hasSufficientBuyingPowerResult; try { hasSufficientBuyingPowerResult = security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(_algorithm.Portfolio, security, 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 (!hasSufficientBuyingPowerResult.IsSufficient) { order.Status = OrderStatus.Invalid; var errorMessage = $"Order Error: id: {order.Id}, Insufficient buying power to complete order (Value:{order.GetValue(security).SmartRounding()}), Reason: {hasSufficientBuyingPowerResult.Reason}"; var response = OrderResponse.Error(request, OrderResponseErrorCode.InsufficientBuyingPower, errorMessage); _algorithm.Error(response.ErrorMessage); HandleOrderEvent(new OrderEvent(order, _algorithm.UtcTime, 0m, errorMessage)); return(response); } // verify that our current brokerage can actually take the order BrokerageMessageEvent message; if (!_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); } return(OrderResponse.Success(request)); }
/// <summary> /// Perform preorder checks to ensure we have sufficient capital, /// the market is open, and we haven't exceeded maximum realistic orders per day. /// </summary> /// <returns>OrderResponse. If no error, order request is submitted.</returns> private OrderResponse PreOrderChecksImpl(SubmitOrderRequest request) { //Ordering 0 is useless. if (request.Quantity == 0 || request.Symbol == null || request.Symbol == QuantConnect.Symbol.Empty) { return(OrderResponse.ZeroQuantity(request)); } //If we're not tracking this symbol: throw error: if (!Securities.ContainsKey(request.Symbol) && !_sentNoDataError) { _sentNoDataError = true; return(OrderResponse.Error(request, OrderResponseErrorCode.MissingSecurity, "You haven't requested " + request.Symbol.ToString() + " data. Add this with AddSecurity() in the Initialize() Method.")); } //Set a temporary price for validating order for market orders: var security = Securities[request.Symbol]; var price = security.Price; //Check the exchange is open before sending a market on close orders if (request.OrderType == OrderType.MarketOnClose && !security.Exchange.ExchangeOpen) { return(OrderResponse.Error(request, OrderResponseErrorCode.ExchangeNotOpen, request.OrderType + " order and exchange not open.")); } if (price == 0) { return(OrderResponse.Error(request, OrderResponseErrorCode.SecurityPriceZero, request.Symbol.ToString() + ": asset price is $0. If using custom data make sure you've set the 'Value' property.")); } // check quote currency existence/conversion rate on all orders Cash quoteCash; var quoteCurrency = security.QuoteCurrency.Symbol; if (!Portfolio.CashBook.TryGetValue(quoteCurrency, out quoteCash)) { return(OrderResponse.Error(request, OrderResponseErrorCode.QuoteCurrencyRequired, request.Symbol.Value + ": requires " + quoteCurrency + " in the cashbook to trade.")); } if (security.QuoteCurrency.ConversionRate == 0m) { return(OrderResponse.Error(request, OrderResponseErrorCode.ConversionRateZero, request.Symbol.Value + ": requires " + quoteCurrency + " to have a non-zero conversion rate. This can be caused by lack of data.")); } // need to also check base currency existence/conversion rate on forex orders if (security.Type == SecurityType.Forex) { Cash baseCash; var baseCurrency = ((Forex)security).BaseCurrencySymbol; if (!Portfolio.CashBook.TryGetValue(baseCurrency, out baseCash)) { return(OrderResponse.Error(request, OrderResponseErrorCode.ForexBaseAndQuoteCurrenciesRequired, request.Symbol.Value + ": requires " + baseCurrency + " and " + quoteCurrency + " in the cashbook to trade.")); } if (baseCash.ConversionRate == 0m) { return(OrderResponse.Error(request, OrderResponseErrorCode.ForexConversionRateZero, request.Symbol.Value + ": requires " + baseCurrency + " and " + quoteCurrency + " to have non-zero conversion rates. This can be caused by lack of data.")); } } //Make sure the security has some data: if (!security.HasData) { return(OrderResponse.Error(request, OrderResponseErrorCode.SecurityHasNoData, "There is no data for this symbol yet, please check the security.HasData flag to ensure there is at least one data point.")); } //We've already processed too many orders: max 100 per day or the memory usage explodes if (Transactions.OrdersCount > _maxOrders) { Status = AlgorithmStatus.Stopped; return(OrderResponse.Error(request, OrderResponseErrorCode.ExceededMaximumOrders, string.Format("You have exceeded maximum number of orders ({0}), for unlimited orders upgrade your account.", _maxOrders))); } if (request.OrderType == OrderType.MarketOnClose) { var nextMarketClose = security.Exchange.Hours.GetNextMarketClose(security.LocalTime, false); // must be submitted with at least 10 minutes in trading day, add buffer allow order submission var latestSubmissionTime = nextMarketClose.AddMinutes(-10.75); if (!security.Exchange.ExchangeOpen || Time > latestSubmissionTime) { // tell the user we require an 11 minute buffer, on minute data in live a user will receive the 3:49->3:50 bar at 3:50, // this is already too late to submit one of these orders, so make the user do it at the 3:48->3:49 bar so it's submitted // to the brokerage before 3:50. return(OrderResponse.Error(request, OrderResponseErrorCode.MarketOnCloseOrderTooLate, "MarketOnClose orders must be placed with at least a 11 minute buffer before market close.")); } } // passes all initial order checks return(OrderResponse.Success(request)); }