/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections.Generic; using System.Linq; using System.Threading; using QuantConnect.Securities; using static QuantConnect.StringExtensions; namespace QuantConnect.Orders { /// /// Provides a single reference to an order for the algorithm to maintain. As the order gets /// updated this ticket will also get updated /// public sealed class OrderTicket { private readonly object _lock = new object(); private Order _order; private OrderStatus? _orderStatusOverride; private CancelOrderRequest _cancelRequest; private FillState _fillState; private List _orderEventsImpl; private List _updateRequestsImpl; private readonly SubmitOrderRequest _submitRequest; private readonly ManualResetEvent _orderStatusClosedEvent; private readonly ManualResetEvent _orderSetEvent; // we pull this in to provide some behavior/simplicity to the ticket API private readonly SecurityTransactionManager _transactionManager; private List _orderEvents { get => _orderEventsImpl ??= new List(); } private List _updateRequests { get => _updateRequestsImpl ??= new List(); } /// /// Gets the order id of this ticket /// public int OrderId { get { return _submitRequest.OrderId; } } /// /// Gets the current status of this order ticket /// public OrderStatus Status { get { if (_orderStatusOverride.HasValue) return _orderStatusOverride.Value; return _order == null ? OrderStatus.New : _order.Status; } } /// /// Gets the symbol being ordered /// public Symbol Symbol { get { return _submitRequest.Symbol; } } /// /// Gets the 's /// public SecurityType SecurityType { get { return _submitRequest.SecurityType; } } /// /// Gets the number of units ordered /// public decimal Quantity { get { return _order == null ? _submitRequest.Quantity : _order.Quantity; } } /// /// Gets the average fill price for this ticket. If no fills have been processed /// then this will return a value of zero. /// public decimal AverageFillPrice { get { return _fillState.AverageFillPrice; } } /// /// Gets the total qantity filled for this ticket. If no fills have been processed /// then this will return a value of zero. /// public decimal QuantityFilled { get { return _fillState.QuantityFilled; } } /// /// Gets the remaining quantity for this order ticket. /// This is the difference between the total quantity ordered and the total quantity filled. /// public decimal QuantityRemaining { get { var currentState = _fillState; return Quantity - currentState.QuantityFilled; } } /// /// Gets the time this order was last updated /// public DateTime Time { get { return GetMostRecentOrderRequest().Time; } } /// /// Gets the type of order /// public OrderType OrderType { get { return _submitRequest.OrderType; } } /// /// Gets the order's current tag /// public string Tag { get { return _order == null ? _submitRequest.Tag : _order.Tag; } } /// /// Gets the that initiated this order /// public SubmitOrderRequest SubmitRequest { get { return _submitRequest; } } /// /// Gets a list of containing an item for each /// that was sent for this order id /// public IReadOnlyList UpdateRequests { get { lock (_lock) { // Avoid creating the update requests list if not necessary if (_updateRequestsImpl == null) { return Array.Empty(); } return _updateRequestsImpl.ToList(); } } } /// /// Gets the if this order was canceled. If this order /// was not canceled, this will return null /// public CancelOrderRequest CancelRequest { get { lock (_lock) { return _cancelRequest; } } } /// /// Gets a list of all order events for this ticket /// public IReadOnlyList OrderEvents { get { lock (_lock) { return _orderEvents.ToList(); } } } /// /// Gets a wait handle that can be used to wait until this order has filled /// public WaitHandle OrderClosed { get { return _orderStatusClosedEvent; } } /// /// Returns true if the order has been set for this ticket /// public bool HasOrder => _order != null; /// /// Gets a wait handle that can be used to wait until the order has been set /// public WaitHandle OrderSet => _orderSetEvent; /// /// Initializes a new instance of the class /// /// The transaction manager used for submitting updates and cancels for this ticket /// The order request that initiated this order ticket public OrderTicket(SecurityTransactionManager transactionManager, SubmitOrderRequest submitRequest) { _submitRequest = submitRequest; _transactionManager = transactionManager; _orderStatusClosedEvent = new ManualResetEvent(false); _orderSetEvent = new ManualResetEvent(false); _fillState = new FillState(0m, 0m); } /// /// Gets the specified field from the ticket /// /// The order field to get /// The value of the field /// Field out of range /// Field out of range for order type public decimal Get(OrderField field) { return Get(field); } /// /// Gets the specified field from the ticket and tries to convert it to the specified type /// /// The order field to get /// The value of the field /// Field out of range /// Field out of range for order type public T Get(OrderField field) { object fieldValue = null; switch (field) { case OrderField.LimitPrice: if (_submitRequest.OrderType == OrderType.ComboLimit) { fieldValue = AccessOrder(this, field, o => o.GroupOrderManager.LimitPrice, r => r.LimitPrice); } else if (_submitRequest.OrderType == OrderType.ComboLegLimit) { fieldValue = AccessOrder(this, field, o => o.LimitPrice, r => r.LimitPrice); } else if (_submitRequest.OrderType == OrderType.Limit) { fieldValue = AccessOrder(this, field, o => o.LimitPrice, r => r.LimitPrice); } else if (_submitRequest.OrderType == OrderType.StopLimit) { fieldValue = AccessOrder(this, field, o => o.LimitPrice, r => r.LimitPrice); } else if (_submitRequest.OrderType == OrderType.LimitIfTouched) { fieldValue = AccessOrder(this, field, o => o.LimitPrice, r => r.LimitPrice); } break; case OrderField.StopPrice: if (_submitRequest.OrderType == OrderType.StopLimit) { fieldValue = AccessOrder(this, field, o => o.StopPrice, r => r.StopPrice); } else if (_submitRequest.OrderType == OrderType.StopMarket) { fieldValue = AccessOrder(this, field, o => o.StopPrice, r => r.StopPrice); } else if (_submitRequest.OrderType == OrderType.TrailingStop) { fieldValue = AccessOrder(this, field, o => o.StopPrice, r => r.StopPrice); } break; case OrderField.TriggerPrice: fieldValue = AccessOrder(this, field, o => o.TriggerPrice, r => r.TriggerPrice); break; case OrderField.TrailingAmount: fieldValue = AccessOrder(this, field, o => o.TrailingAmount, r => r.TrailingAmount); break; case OrderField.TrailingAsPercentage: fieldValue = AccessOrder(this, field, o => o.TrailingAsPercentage, r => r.TrailingAsPercentage); break; default: throw new ArgumentOutOfRangeException(nameof(field), field, null); } if (fieldValue == null) { throw new ArgumentException(Messages.OrderTicket.GetFieldError(this, field)); } return (T)fieldValue; } /// /// Submits an with the to update /// the ticket with data specified in /// /// Defines what properties of the order should be updated /// The from updating the order public OrderResponse Update(UpdateOrderFields fields) { var ticket = _transactionManager.UpdateOrder(new UpdateOrderRequest(_transactionManager.UtcTime, SubmitRequest.OrderId, fields)); return ticket.UpdateRequests.Last().Response; } /// /// Submits an with the to update /// the ticket with tag specified in /// /// The new tag for this order ticket /// from updating the order public OrderResponse UpdateTag(string tag) { var fields = new UpdateOrderFields() { Tag = tag }; return Update(fields); } /// /// Submits an with the to update /// the ticket with quantity specified in and with tag specified in /// /// The new quantity for this order ticket /// The new tag for this order ticket /// from updating the order public OrderResponse UpdateQuantity(decimal quantity, string tag = null) { var fields = new UpdateOrderFields() { Quantity = quantity, Tag = tag }; return Update(fields); } /// /// Submits an with the to update /// the ticker with limit price specified in and with tag specified in /// /// The new limit price for this order ticket /// The new tag for this order ticket /// from updating the order public OrderResponse UpdateLimitPrice(decimal limitPrice, string tag = null) { var fields = new UpdateOrderFields() { LimitPrice = limitPrice, Tag = tag }; return Update(fields); } /// /// Submits an with the to update /// the ticker with stop price specified in and with tag specified in /// /// The new stop price for this order ticket /// The new tag for this order ticket /// from updating the order public OrderResponse UpdateStopPrice(decimal stopPrice, string tag = null) { var fields = new UpdateOrderFields() { StopPrice = stopPrice, Tag = tag }; return Update(fields); } /// /// Submits an with the to update /// the ticker with trigger price specified in and with tag specified in /// /// The new price which, when touched, will trigger the setting of a limit order. /// The new tag for this order ticket /// from updating the order public OrderResponse UpdateTriggerPrice(decimal triggerPrice, string tag = null) { var fields = new UpdateOrderFields() { TriggerPrice = triggerPrice, Tag = tag }; return Update(fields); } /// /// Submits an with the to update /// the ticker with stop trailing amount specified in and with tag specified in /// /// The new trailing amount for this order ticket /// The new tag for this order ticket /// from updating the order public OrderResponse UpdateStopTrailingAmount(decimal trailingAmount, string tag = null) { var fields = new UpdateOrderFields() { TrailingAmount = trailingAmount, Tag = tag }; return Update(fields); } /// /// Submits a new request to cancel this order /// public OrderResponse Cancel(string tag = null) { var request = new CancelOrderRequest(_transactionManager.UtcTime, OrderId, tag); lock (_lock) { // don't submit duplicate cancel requests, if the cancel request wasn't flagged as error // this could happen when trying to cancel an order which status is still new and hasn't even been submitted to the brokerage if (_cancelRequest != null && _cancelRequest.Status != OrderRequestStatus.Error) { return OrderResponse.Error(request, OrderResponseErrorCode.RequestCanceled, Messages.OrderTicket.CancelRequestAlreadySubmitted(this)); } } var ticket = _transactionManager.ProcessRequest(request); return ticket.CancelRequest.Response; } /// /// Gets the most recent for this ticket /// /// The most recent for this ticket public OrderResponse GetMostRecentOrderResponse() { return GetMostRecentOrderRequest().Response; } /// /// Gets the most recent for this ticket /// /// The most recent for this ticket public OrderRequest GetMostRecentOrderRequest() { lock (_lock) { if (_cancelRequest != null) { return _cancelRequest; } // Avoid creating the update requests list if not necessary if (_updateRequestsImpl != null) { var lastUpdate = _updateRequestsImpl.LastOrDefault(); if (lastUpdate != null) { return lastUpdate; } } } return SubmitRequest; } /// /// Adds an order event to this ticket /// /// The order event to be added internal void AddOrderEvent(OrderEvent orderEvent) { lock (_lock) { _orderEvents.Add(orderEvent); // Update the ticket and order if (orderEvent.FillQuantity != 0) { var filledQuantity = _fillState.QuantityFilled; var averageFillPrice = _fillState.AverageFillPrice; if (_order.Type != OrderType.OptionExercise) { // keep running totals of quantity filled and the average fill price so we // don't need to compute these on demand filledQuantity += orderEvent.FillQuantity; var quantityWeightedFillPrice = _orderEvents.Where(x => x.Status.IsFill()) .Aggregate(0m, (d, x) => d + x.AbsoluteFillQuantity * x.FillPrice); averageFillPrice = quantityWeightedFillPrice / Math.Abs(filledQuantity); _order.Price = averageFillPrice; } // For ITM option exercise orders we set the order price to the strike price. // For OTM the fill price should be zero, which is the default for OptionExerciseOrders else if (orderEvent.IsInTheMoney) { _order.Price = Symbol.ID.StrikePrice; // We update the ticket only if the fill price is not zero (this fixes issue #2846 where average price // is skewed by the removal of the option). if (orderEvent.FillPrice != 0) { filledQuantity += orderEvent.FillQuantity; averageFillPrice = _order.Price; } } _fillState = new FillState(averageFillPrice, filledQuantity); } } // fire the wait handle indicating this order is closed if (orderEvent.Status.IsClosed()) { _orderStatusClosedEvent.Set(); } } /// /// Updates the internal order object with the current state /// /// The order internal void SetOrder(Order order) { if (_order != null && _order.Id != order.Id) { throw new ArgumentException("Order id mismatch"); } _order = order; _orderSetEvent.Set(); } /// /// Adds a new to this ticket. /// /// The recently processed internal void AddUpdateRequest(UpdateOrderRequest request) { if (request.OrderId != OrderId) { throw new ArgumentException("Received UpdateOrderRequest for incorrect order id."); } lock (_lock) { _updateRequests.Add(request); } } /// /// Sets the for this ticket. This can only be performed once. /// /// /// This method is thread safe. /// /// The that canceled this ticket. /// False if the the CancelRequest has already been set, true if this call set it internal bool TrySetCancelRequest(CancelOrderRequest request) { if (request.OrderId != OrderId) { throw new ArgumentException("Received CancelOrderRequest for incorrect order id."); } lock (_lock) { // don't submit duplicate cancel requests, if the cancel request wasn't flagged as error // this could happen when trying to cancel an order which status is still new and hasn't even been submitted to the brokerage if (_cancelRequest != null && _cancelRequest.Status != OrderRequestStatus.Error) { return false; } _cancelRequest = request; } return true; } /// /// Creates a new that represents trying to cancel an order for which no ticket exists /// public static OrderTicket InvalidCancelOrderId(SecurityTransactionManager transactionManager, CancelOrderRequest request) { var submit = new SubmitOrderRequest(OrderType.Market, SecurityType.Base, Symbol.Empty, 0, 0, 0, DateTime.MaxValue, request.Tag); submit.SetResponse(OrderResponse.UnableToFindOrder(request)); submit.SetOrderId(request.OrderId); var ticket = new OrderTicket(transactionManager, submit); request.SetResponse(OrderResponse.UnableToFindOrder(request)); ticket.TrySetCancelRequest(request); ticket._orderStatusOverride = OrderStatus.Invalid; return ticket; } /// /// Creates a new that represents trying to update an order for which no ticket exists /// public static OrderTicket InvalidUpdateOrderId(SecurityTransactionManager transactionManager, UpdateOrderRequest request) { var submit = new SubmitOrderRequest(OrderType.Market, SecurityType.Base, Symbol.Empty, 0, 0, 0, DateTime.MaxValue, request.Tag); submit.SetResponse(OrderResponse.UnableToFindOrder(request)); submit.SetOrderId(request.OrderId); var ticket = new OrderTicket(transactionManager, submit); request.SetResponse(OrderResponse.UnableToFindOrder(request)); ticket.AddUpdateRequest(request); ticket._orderStatusOverride = OrderStatus.Invalid; return ticket; } /// /// Creates a new that represents trying to submit a new order that had errors embodied in the /// public static OrderTicket InvalidSubmitRequest(SecurityTransactionManager transactionManager, SubmitOrderRequest request, OrderResponse response) { request.SetResponse(response); return new OrderTicket(transactionManager, request) { _orderStatusOverride = OrderStatus.Invalid }; } /// /// Returns a string that represents the current object. /// /// /// A string that represents the current object. /// /// 2 public override string ToString() { var requestCount = 1; var responseCount = _submitRequest.Response == OrderResponse.Unprocessed ? 0 : 1; lock (_lock) { // Avoid creating the update requests list if not necessary if (_updateRequestsImpl != null) { requestCount += _updateRequestsImpl.Count; responseCount += _updateRequestsImpl.Count(x => x.Response != OrderResponse.Unprocessed); } requestCount += _cancelRequest == null ? 0 : 1; responseCount += _cancelRequest == null || _cancelRequest.Response == OrderResponse.Unprocessed ? 0 : 1; } return Messages.OrderTicket.ToString(this, _order, requestCount, responseCount); } /// /// This is provided for API backward compatibility and will resolve to the order ID, except during /// an error, where it will return the integer value of the from /// the most recent response /// public static implicit operator int(OrderTicket ticket) { var response = ticket.GetMostRecentOrderResponse(); if (response != null && response.IsError) { return (int) response.ErrorCode; } return ticket.OrderId; } private static P AccessOrder(OrderTicket ticket, OrderField field, Func orderSelector, Func requestSelector) where T : Order { var order = ticket._order; if (order == null) { return requestSelector(ticket._submitRequest); } var typedOrder = order as T; if (typedOrder != null) { return orderSelector(typedOrder); } throw new ArgumentException(Invariant($"Unable to access property {field} on order of type {order.Type}")); } /// /// Reference wrapper for decimal average fill price and quantity filled. /// In order to update the average fill price and quantity filled, we create a new instance of this class /// so we avoid potential race conditions when accessing these properties /// (e.g. the decimals might be being updated and in a invalid state when being read) /// private class FillState { public decimal AverageFillPrice { get; } public decimal QuantityFilled { get; } public FillState(decimal averageFillPrice, decimal quantityFilled) { AverageFillPrice = averageFillPrice; QuantityFilled = quantityFilled; } } } }