Lean  $LEAN_TAG$
OrderTicket.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.Collections.Generic;
18 using System.Diagnostics;
19 using System.Linq;
20 using System.Threading;
22 using static QuantConnect.StringExtensions;
23 
24 namespace QuantConnect.Orders
25 {
26  /// <summary>
27  /// Provides a single reference to an order for the algorithm to maintain. As the order gets
28  /// updated this ticket will also get updated
29  /// </summary>
30  public sealed class OrderTicket
31  {
32  private readonly object _orderEventsLock = new object();
33  private readonly object _updateRequestsLock = new object();
34  private readonly object _cancelRequestLock = new object();
35 
36  private Order _order;
37  private OrderStatus? _orderStatusOverride;
38  private CancelOrderRequest _cancelRequest;
39 
40  private decimal _quantityFilled;
41  private decimal _averageFillPrice;
42 
43  private readonly int _orderId;
44  private readonly List<OrderEvent> _orderEvents;
45  private readonly SubmitOrderRequest _submitRequest;
46  private readonly ManualResetEvent _orderStatusClosedEvent;
47  private readonly List<UpdateOrderRequest> _updateRequests;
48  private readonly ManualResetEvent _orderSetEvent;
49 
50  // we pull this in to provide some behavior/simplicity to the ticket API
51  private readonly SecurityTransactionManager _transactionManager;
52 
53  /// <summary>
54  /// Gets the order id of this ticket
55  /// </summary>
56  public int OrderId
57  {
58  get { return _orderId; }
59  }
60 
61  /// <summary>
62  /// Gets the current status of this order ticket
63  /// </summary>
64  public OrderStatus Status
65  {
66  get
67  {
68  if (_orderStatusOverride.HasValue) return _orderStatusOverride.Value;
69  return _order == null ? OrderStatus.New : _order.Status;
70  }
71  }
72 
73  /// <summary>
74  /// Gets the symbol being ordered
75  /// </summary>
76  public Symbol Symbol
77  {
78  get { return _submitRequest.Symbol; }
79  }
80 
81  /// <summary>
82  /// Gets the <see cref="Symbol"/>'s <see cref="SecurityType"/>
83  /// </summary>
85  {
86  get { return _submitRequest.SecurityType; }
87  }
88 
89  /// <summary>
90  /// Gets the number of units ordered
91  /// </summary>
92  public decimal Quantity
93  {
94  get { return _order == null ? _submitRequest.Quantity : _order.Quantity; }
95  }
96 
97  /// <summary>
98  /// Gets the average fill price for this ticket. If no fills have been processed
99  /// then this will return a value of zero.
100  /// </summary>
101  public decimal AverageFillPrice
102  {
103  get { return _averageFillPrice; }
104  }
105 
106  /// <summary>
107  /// Gets the total qantity filled for this ticket. If no fills have been processed
108  /// then this will return a value of zero.
109  /// </summary>
110  public decimal QuantityFilled
111  {
112  get { return _quantityFilled; }
113  }
114 
115  /// <summary>
116  /// Gets the time this order was last updated
117  /// </summary>
118  public DateTime Time
119  {
120  get { return GetMostRecentOrderRequest().Time; }
121  }
122 
123  /// <summary>
124  /// Gets the type of order
125  /// </summary>
126  public OrderType OrderType
127  {
128  get { return _submitRequest.OrderType; }
129  }
130 
131  /// <summary>
132  /// Gets the order's current tag
133  /// </summary>
134  public string Tag
135  {
136  get { return _order == null ? _submitRequest.Tag : _order.Tag; }
137  }
138 
139  /// <summary>
140  /// Gets the <see cref="SubmitOrderRequest"/> that initiated this order
141  /// </summary>
143  {
144  get { return _submitRequest; }
145  }
146 
147  /// <summary>
148  /// Gets a list of <see cref="UpdateOrderRequest"/> containing an item for each
149  /// <see cref="UpdateOrderRequest"/> that was sent for this order id
150  /// </summary>
151  public IReadOnlyList<UpdateOrderRequest> UpdateRequests
152  {
153  get
154  {
155  lock (_updateRequestsLock)
156  {
157  return _updateRequests.ToList();
158  }
159  }
160  }
161 
162  /// <summary>
163  /// Gets the <see cref="CancelOrderRequest"/> if this order was canceled. If this order
164  /// was not canceled, this will return null
165  /// </summary>
167  {
168  get
169  {
170  lock (_cancelRequestLock)
171  {
172  return _cancelRequest;
173  }
174  }
175  }
176 
177  /// <summary>
178  /// Gets a list of all order events for this ticket
179  /// </summary>
180  public IReadOnlyList<OrderEvent> OrderEvents
181  {
182  get
183  {
184  lock (_orderEventsLock)
185  {
186  return _orderEvents.ToList();
187  }
188  }
189  }
190 
191  /// <summary>
192  /// Gets a wait handle that can be used to wait until this order has filled
193  /// </summary>
194  public WaitHandle OrderClosed
195  {
196  get { return _orderStatusClosedEvent; }
197  }
198 
199  /// <summary>
200  /// Returns true if the order has been set for this ticket
201  /// </summary>
202  public bool HasOrder => _order != null;
203 
204  /// <summary>
205  /// Gets a wait handle that can be used to wait until the order has been set
206  /// </summary>
207  public WaitHandle OrderSet => _orderSetEvent;
208 
209  /// <summary>
210  /// Initializes a new instance of the <see cref="OrderTicket"/> class
211  /// </summary>
212  /// <param name="transactionManager">The transaction manager used for submitting updates and cancels for this ticket</param>
213  /// <param name="submitRequest">The order request that initiated this order ticket</param>
214  public OrderTicket(SecurityTransactionManager transactionManager, SubmitOrderRequest submitRequest)
215  {
216  _submitRequest = submitRequest;
217  _orderId = submitRequest.OrderId;
218  _transactionManager = transactionManager;
219 
220  _orderEvents = new List<OrderEvent>();
221  _updateRequests = new List<UpdateOrderRequest>();
222  _orderStatusClosedEvent = new ManualResetEvent(false);
223  _orderSetEvent = new ManualResetEvent(false);
224  }
225 
226  /// <summary>
227  /// Gets the specified field from the ticket
228  /// </summary>
229  /// <param name="field">The order field to get</param>
230  /// <returns>The value of the field</returns>
231  /// <exception cref="ArgumentException">Field out of range</exception>
232  /// <exception cref="ArgumentOutOfRangeException">Field out of range for order type</exception>
233  public decimal Get(OrderField field)
234  {
235  return Get<decimal>(field);
236  }
237 
238  /// <summary>
239  /// Gets the specified field from the ticket and tries to convert it to the specified type
240  /// </summary>
241  /// <param name="field">The order field to get</param>
242  /// <returns>The value of the field</returns>
243  /// <exception cref="ArgumentException">Field out of range</exception>
244  /// <exception cref="ArgumentOutOfRangeException">Field out of range for order type</exception>
245  public T Get<T>(OrderField field)
246  {
247  object fieldValue = null;
248 
249  switch (field)
250  {
251  case OrderField.LimitPrice:
252  if (_submitRequest.OrderType == OrderType.ComboLimit)
253  {
254  fieldValue = AccessOrder<ComboLimitOrder, decimal>(this, field, o => o.GroupOrderManager.LimitPrice, r => r.LimitPrice);
255  }
256  else if (_submitRequest.OrderType == OrderType.ComboLegLimit)
257  {
258  fieldValue = AccessOrder<ComboLegLimitOrder, decimal>(this, field, o => o.LimitPrice, r => r.LimitPrice);
259  }
260  else if (_submitRequest.OrderType == OrderType.Limit)
261  {
262  fieldValue = AccessOrder<LimitOrder, decimal>(this, field, o => o.LimitPrice, r => r.LimitPrice);
263  }
264  else if (_submitRequest.OrderType == OrderType.StopLimit)
265  {
266  fieldValue = AccessOrder<StopLimitOrder, decimal>(this, field, o => o.LimitPrice, r => r.LimitPrice);
267  }
268  else if (_submitRequest.OrderType == OrderType.LimitIfTouched)
269  {
270  fieldValue = AccessOrder<LimitIfTouchedOrder, decimal>(this, field, o => o.LimitPrice, r => r.LimitPrice);
271  }
272  break;
273 
274  case OrderField.StopPrice:
275  if (_submitRequest.OrderType == OrderType.StopLimit)
276  {
277  fieldValue = AccessOrder<StopLimitOrder, decimal>(this, field, o => o.StopPrice, r => r.StopPrice);
278  }
279  else if (_submitRequest.OrderType == OrderType.StopMarket)
280  {
281  fieldValue = AccessOrder<StopMarketOrder, decimal>(this, field, o => o.StopPrice, r => r.StopPrice);
282  }
283  else if (_submitRequest.OrderType == OrderType.TrailingStop)
284  {
285  fieldValue = AccessOrder<TrailingStopOrder, decimal>(this, field, o => o.StopPrice, r => r.StopPrice);
286  }
287  break;
288 
289  case OrderField.TriggerPrice:
290  fieldValue = AccessOrder<LimitIfTouchedOrder, decimal>(this, field, o => o.TriggerPrice, r => r.TriggerPrice);
291  break;
292 
293  case OrderField.TrailingAmount:
294  fieldValue = AccessOrder<TrailingStopOrder, decimal>(this, field, o => o.TrailingAmount, r => r.TrailingAmount);
295  break;
296 
297  case OrderField.TrailingAsPercentage:
298  fieldValue = AccessOrder<TrailingStopOrder, bool>(this, field, o => o.TrailingAsPercentage, r => r.TrailingAsPercentage);
299  break;
300 
301  default:
302  throw new ArgumentOutOfRangeException(nameof(field), field, null);
303  }
304 
305  if (fieldValue == null)
306  {
307  throw new ArgumentException(Messages.OrderTicket.GetFieldError(this, field));
308  }
309 
310  return (T)fieldValue;
311  }
312 
313  /// <summary>
314  /// Submits an <see cref="UpdateOrderRequest"/> with the <see cref="SecurityTransactionManager"/> to update
315  /// the ticket with data specified in <paramref name="fields"/>
316  /// </summary>
317  /// <param name="fields">Defines what properties of the order should be updated</param>
318  /// <returns>The <see cref="OrderResponse"/> from updating the order</returns>
320  {
321  var ticket = _transactionManager.UpdateOrder(new UpdateOrderRequest(_transactionManager.UtcTime, SubmitRequest.OrderId, fields));
322  return ticket.UpdateRequests.Last().Response;
323  }
324 
325  /// <summary>
326  /// Submits an <see cref="UpdateOrderRequest"/> with the <see cref="SecurityTransactionManager"/> to update
327  /// the ticket with tag specified in <paramref name="tag"/>
328  /// </summary>
329  /// <param name="tag">The new tag for this order ticket</param>
330  /// <returns><see cref="OrderResponse"/> from updating the order</returns>
331  public OrderResponse UpdateTag(string tag)
332  {
333  var fields = new UpdateOrderFields()
334  {
335  Tag = tag
336  };
337  return Update(fields);
338  }
339 
340  /// <summary>
341  /// Submits an <see cref="UpdateOrderRequest"/> with the <see cref="SecurityTransactionManager"/> to update
342  /// the ticket with quantity specified in <paramref name="quantity"/> and with tag specified in <paramref name="quantity"/>
343  /// </summary>
344  /// <param name="quantity">The new quantity for this order ticket</param>
345  /// <param name="tag">The new tag for this order ticket</param>
346  /// <returns><see cref="OrderResponse"/> from updating the order</returns>
347  public OrderResponse UpdateQuantity(decimal quantity, string tag = null)
348  {
349  var fields = new UpdateOrderFields()
350  {
351  Quantity = quantity,
352  Tag = tag
353  };
354  return Update(fields);
355  }
356 
357  /// <summary>
358  /// Submits an <see cref="UpdateOrderRequest"/> with the <see cref="SecurityTransactionManager"/> to update
359  /// the ticker with limit price specified in <paramref name="limitPrice"/> and with tag specified in <paramref name="tag"/>
360  /// </summary>
361  /// <param name="limitPrice">The new limit price for this order ticket</param>
362  /// <param name="tag">The new tag for this order ticket</param>
363  /// <returns><see cref="OrderResponse"/> from updating the order</returns>
364  public OrderResponse UpdateLimitPrice(decimal limitPrice, string tag = null)
365  {
366  var fields = new UpdateOrderFields()
367  {
368  LimitPrice = limitPrice,
369  Tag = tag
370  };
371  return Update(fields);
372  }
373 
374  /// <summary>
375  /// Submits an <see cref="UpdateOrderRequest"/> with the <see cref="SecurityTransactionManager"/> to update
376  /// the ticker with stop price specified in <paramref name="stopPrice"/> and with tag specified in <paramref name="tag"/>
377  /// </summary>
378  /// <param name="stopPrice">The new stop price for this order ticket</param>
379  /// <param name="tag">The new tag for this order ticket</param>
380  /// <returns><see cref="OrderResponse"/> from updating the order</returns>
381  public OrderResponse UpdateStopPrice(decimal stopPrice, string tag = null)
382  {
383  var fields = new UpdateOrderFields()
384  {
385  StopPrice = stopPrice,
386  Tag = tag
387  };
388  return Update(fields);
389  }
390 
391  /// <summary>
392  /// Submits an <see cref="UpdateOrderRequest"/> with the <see cref="SecurityTransactionManager"/> to update
393  /// the ticker with trigger price specified in <paramref name="triggerPrice"/> and with tag specified in <paramref name="tag"/>
394  /// </summary>
395  /// <param name="triggerPrice">The new price which, when touched, will trigger the setting of a limit order.</param>
396  /// <param name="tag">The new tag for this order ticket</param>
397  /// <returns><see cref="OrderResponse"/> from updating the order</returns>
398  public OrderResponse UpdateTriggerPrice(decimal triggerPrice, string tag = null)
399  {
400  var fields = new UpdateOrderFields()
401  {
402  TriggerPrice = triggerPrice,
403  Tag = tag
404  };
405  return Update(fields);
406  }
407 
408  /// <summary>
409  /// Submits an <see cref="UpdateOrderRequest"/> with the <see cref="SecurityTransactionManager"/> to update
410  /// the ticker with stop trailing amount specified in <paramref name="trailingAmount"/> and with tag specified in <paramref name="tag"/>
411  /// </summary>
412  /// <param name="trailingAmount">The new trailing amount for this order ticket</param>
413  /// <param name="tag">The new tag for this order ticket</param>
414  /// <returns><see cref="OrderResponse"/> from updating the order</returns>
415  public OrderResponse UpdateStopTrailingAmount(decimal trailingAmount, string tag = null)
416  {
417  var fields = new UpdateOrderFields()
418  {
419  TrailingAmount = trailingAmount,
420  Tag = tag
421  };
422  return Update(fields);
423  }
424 
425  /// <summary>
426  /// Submits a new request to cancel this order
427  /// </summary>
428  public OrderResponse Cancel(string tag = null)
429  {
430  var request = new CancelOrderRequest(_transactionManager.UtcTime, OrderId, tag);
431  lock (_cancelRequestLock)
432  {
433  // don't submit duplicate cancel requests, if the cancel request wasn't flagged as error
434  // this could happen when trying to cancel an order which status is still new and hasn't even been submitted to the brokerage
435  if (_cancelRequest != null && _cancelRequest.Status != OrderRequestStatus.Error)
436  {
437  return OrderResponse.Error(request, OrderResponseErrorCode.RequestCanceled,
438  Messages.OrderTicket.CancelRequestAlreadySubmitted(this));
439  }
440  }
441 
442  var ticket = _transactionManager.ProcessRequest(request);
443  return ticket.CancelRequest.Response;
444  }
445 
446  /// <summary>
447  /// Gets the most recent <see cref="OrderResponse"/> for this ticket
448  /// </summary>
449  /// <returns>The most recent <see cref="OrderResponse"/> for this ticket</returns>
451  {
453  }
454 
455  /// <summary>
456  /// Gets the most recent <see cref="OrderRequest"/> for this ticket
457  /// </summary>
458  /// <returns>The most recent <see cref="OrderRequest"/> for this ticket</returns>
460  {
461  lock (_cancelRequestLock)
462  {
463  if (_cancelRequest != null)
464  {
465  return _cancelRequest;
466  }
467  }
468 
469  var lastUpdate = _updateRequests.LastOrDefault();
470  if (lastUpdate != null)
471  {
472  return lastUpdate;
473  }
474  return SubmitRequest;
475  }
476 
477  /// <summary>
478  /// Adds an order event to this ticket
479  /// </summary>
480  /// <param name="orderEvent">The order event to be added</param>
481  internal void AddOrderEvent(OrderEvent orderEvent)
482  {
483  lock (_orderEventsLock)
484  {
485  _orderEvents.Add(orderEvent);
486 
487  // Update the ticket and order
488  if (orderEvent.FillQuantity != 0)
489  {
490  if (_order.Type != OrderType.OptionExercise)
491  {
492  // keep running totals of quantity filled and the average fill price so we
493  // don't need to compute these on demand
494  _quantityFilled += orderEvent.FillQuantity;
495  var quantityWeightedFillPrice = _orderEvents.Where(x => x.Status.IsFill())
496  .Aggregate(0m, (d, x) => d + x.AbsoluteFillQuantity * x.FillPrice);
497  _averageFillPrice = quantityWeightedFillPrice / Math.Abs(_quantityFilled);
498 
499  _order.Price = _averageFillPrice;
500  }
501  // For ITM option exercise orders we set the order price to the strike price.
502  // For OTM the fill price should be zero, which is the default for OptionExerciseOrders
503  else if (orderEvent.IsInTheMoney)
504  {
505  _order.Price = Symbol.ID.StrikePrice;
506 
507  // We update the ticket only if the fill price is not zero (this fixes issue #2846 where average price
508  // is skewed by the removal of the option).
509  if (orderEvent.FillPrice != 0)
510  {
511  _quantityFilled += orderEvent.FillQuantity;
512  _averageFillPrice = _order.Price;
513  }
514  }
515  }
516  }
517 
518  // fire the wait handle indicating this order is closed
519  if (orderEvent.Status.IsClosed())
520  {
521  _orderStatusClosedEvent.Set();
522  }
523  }
524 
525  /// <summary>
526  /// Updates the internal order object with the current state
527  /// </summary>
528  /// <param name="order">The order</param>
529  internal void SetOrder(Order order)
530  {
531  if (_order != null && _order.Id != order.Id)
532  {
533  throw new ArgumentException("Order id mismatch");
534  }
535 
536  _order = order;
537 
538  _orderSetEvent.Set();
539  }
540 
541  /// <summary>
542  /// Adds a new <see cref="UpdateOrderRequest"/> to this ticket.
543  /// </summary>
544  /// <param name="request">The recently processed <see cref="UpdateOrderRequest"/></param>
545  internal void AddUpdateRequest(UpdateOrderRequest request)
546  {
547  if (request.OrderId != OrderId)
548  {
549  throw new ArgumentException("Received UpdateOrderRequest for incorrect order id.");
550  }
551 
552  lock (_updateRequestsLock)
553  {
554  _updateRequests.Add(request);
555  }
556  }
557 
558  /// <summary>
559  /// Sets the <see cref="CancelOrderRequest"/> for this ticket. This can only be performed once.
560  /// </summary>
561  /// <remarks>
562  /// This method is thread safe.
563  /// </remarks>
564  /// <param name="request">The <see cref="CancelOrderRequest"/> that canceled this ticket.</param>
565  /// <returns>False if the the CancelRequest has already been set, true if this call set it</returns>
566  internal bool TrySetCancelRequest(CancelOrderRequest request)
567  {
568  if (request.OrderId != OrderId)
569  {
570  throw new ArgumentException("Received CancelOrderRequest for incorrect order id.");
571  }
572 
573  lock (_cancelRequestLock)
574  {
575  // don't submit duplicate cancel requests, if the cancel request wasn't flagged as error
576  // this could happen when trying to cancel an order which status is still new and hasn't even been submitted to the brokerage
577  if (_cancelRequest != null && _cancelRequest.Status != OrderRequestStatus.Error)
578  {
579  return false;
580  }
581  _cancelRequest = request;
582  }
583 
584  return true;
585  }
586 
587  /// <summary>
588  /// Creates a new <see cref="OrderTicket"/> that represents trying to cancel an order for which no ticket exists
589  /// </summary>
591  {
592  var submit = new SubmitOrderRequest(OrderType.Market, SecurityType.Base, Symbol.Empty, 0, 0, 0, DateTime.MaxValue, request.Tag);
593  submit.SetResponse(OrderResponse.UnableToFindOrder(request));
594  submit.SetOrderId(request.OrderId);
595  var ticket = new OrderTicket(transactionManager, submit);
596  request.SetResponse(OrderResponse.UnableToFindOrder(request));
597  ticket.TrySetCancelRequest(request);
598  ticket._orderStatusOverride = OrderStatus.Invalid;
599  return ticket;
600  }
601 
602  /// <summary>
603  /// Creates a new <see cref="OrderTicket"/> that represents trying to update an order for which no ticket exists
604  /// </summary>
606  {
607  var submit = new SubmitOrderRequest(OrderType.Market, SecurityType.Base, Symbol.Empty, 0, 0, 0, DateTime.MaxValue, request.Tag);
608  submit.SetResponse(OrderResponse.UnableToFindOrder(request));
609  submit.SetOrderId(request.OrderId);
610  var ticket = new OrderTicket(transactionManager, submit);
611  request.SetResponse(OrderResponse.UnableToFindOrder(request));
612  ticket.AddUpdateRequest(request);
613  ticket._orderStatusOverride = OrderStatus.Invalid;
614  return ticket;
615  }
616 
617  /// <summary>
618  /// Creates a new <see cref="OrderTicket"/> that represents trying to submit a new order that had errors embodied in the <paramref name="response"/>
619  /// </summary>
621  {
622  request.SetResponse(response);
623  return new OrderTicket(transactionManager, request) { _orderStatusOverride = OrderStatus.Invalid };
624  }
625 
626  /// <summary>
627  /// Returns a string that represents the current object.
628  /// </summary>
629  /// <returns>
630  /// A string that represents the current object.
631  /// </returns>
632  /// <filterpriority>2</filterpriority>
633  public override string ToString()
634  {
635  return Messages.OrderTicket.ToString(this, _order, RequestCount(), ResponseCount());
636  }
637 
638  private int ResponseCount()
639  {
640  var count = (_submitRequest.Response == OrderResponse.Unprocessed ? 0 : 1) +
641  _updateRequests.Count(x => x.Response != OrderResponse.Unprocessed);
642 
643  lock (_cancelRequestLock)
644  {
645  count += _cancelRequest == null || _cancelRequest.Response == OrderResponse.Unprocessed ? 0 : 1;
646  }
647 
648  return count;
649  }
650 
651  private int RequestCount()
652  {
653  var count = 1 + _updateRequests.Count;
654 
655  lock (_cancelRequestLock)
656  {
657  count += _cancelRequest == null ? 0 : 1;
658  }
659 
660  return count;
661  }
662 
663  /// <summary>
664  /// This is provided for API backward compatibility and will resolve to the order ID, except during
665  /// an error, where it will return the integer value of the <see cref="OrderResponseErrorCode"/> from
666  /// the most recent response
667  /// </summary>
668  public static implicit operator int(OrderTicket ticket)
669  {
670  var response = ticket.GetMostRecentOrderResponse();
671  if (response != null && response.IsError)
672  {
673  return (int) response.ErrorCode;
674  }
675  return ticket.OrderId;
676  }
677 
678  private static P AccessOrder<T, P>(OrderTicket ticket, OrderField field, Func<T, P> orderSelector, Func<SubmitOrderRequest, P> requestSelector)
679  where T : Order
680  {
681  var order = ticket._order;
682  if (order == null)
683  {
684  return requestSelector(ticket._submitRequest);
685  }
686  var typedOrder = order as T;
687  if (typedOrder != null)
688  {
689  return orderSelector(typedOrder);
690  }
691  throw new ArgumentException(Invariant($"Unable to access property {field} on order of type {order.Type}"));
692  }
693  }
694 }