/// <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> /// 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> /// 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> /// Processes the order request /// </summary> /// <param name="request">The request to be processed</param> /// <returns>The order ticket for the request</returns> public OrderTicket ProcessRequest(OrderRequest request) { if (_algorithm != null && _algorithm.IsWarmingUp) { throw new Exception(OrderResponse.WarmingUp(request).ToString()); } var submit = request as SubmitOrderRequest; if (submit != null) { submit.SetOrderId(GetIncrementOrderId()); } return(_orderProcessor.Process(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> /// 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) { if (IsWarmingUp) { return OrderResponse.WarmingUp(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) { return OrderResponse.ZeroQuantity(request); } if (Math.Abs(request.Quantity) < security.SymbolProperties.LotSize) { return OrderResponse.Error(request, OrderResponseErrorCode.OrderQuantityLessThanLoteSize, $"Unable to {request.OrderRequestType.ToString().ToLower()} order with id {request.OrderId} which quantity ({Math.Abs(request.Quantity)}) is less than lot size ({security.SymbolProperties.LotSize})."); } 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 || security.Type == SecurityType.Crypto) { Cash baseCash; var baseCurrency = ((IBaseCurrencySymbol)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 10k if (!LiveMode && 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.Subtract(Orders.MarketOnCloseOrder.DefaultSubmissionTimeBuffer); 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); }