Lean  $LEAN_TAG$
Messages.Orders.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.Collections.Generic;
17 using System.Linq;
18 using System.Runtime.CompilerServices;
19 
20 using QuantConnect.Orders;
22 
23 using static QuantConnect.StringExtensions;
24 
25 namespace QuantConnect
26 {
27  /// <summary>
28  /// Provides user-facing message construction methods and static messages for the <see cref="Orders"/> namespace
29  /// </summary>
30  public static partial class Messages
31  {
32  /// <summary>
33  /// Provides user-facing messages for the <see cref="Orders.CancelOrderRequest"/> class and its consumers or related classes
34  /// </summary>
35  public static class CancelOrderRequest
36  {
37  /// <summary>
38  /// Parses the given CancelOrderRequest into a string message containing basic information about it
39  /// </summary>
40  [MethodImpl(MethodImplOptions.AggressiveInlining)]
41  public static string ToString(Orders.CancelOrderRequest request)
42  {
43  return Invariant($@"{request.Time.ToStringInvariant()} UTC: Cancel Order: ({request.OrderId}) - {
44  request.Tag} Status: {request.Status}");
45  }
46  }
47 
48  /// <summary>
49  /// Provides user-facing messages for the <see cref="Orders.GroupOrderExtensions"/> class and its consumers or related classes
50  /// </summary>
51  public static class GroupOrderExtensions
52  {
53  /// <summary>
54  /// Returns a string message saying there is insufficient buying power to complete the given orders
55  /// </summary>
56  [MethodImpl(MethodImplOptions.AggressiveInlining)]
57  public static string InsufficientBuyingPowerForOrders(Dictionary<Orders.Order, Securities.Security> securities,
58  HasSufficientBuyingPowerForOrderResult hasSufficientBuyingPowerResult)
59  {
60  var ids = string.Join(",", securities.Keys.Select(o => o.Id));
61  var values = string.Join(",", securities.Select(o => o.Key.GetValue(o.Value).SmartRounding()));
62  return $@"Order Error: ids: [{ids}], Insufficient buying power to complete orders (Value:[{values}]), Reason: {
63  hasSufficientBuyingPowerResult.Reason}.";
64  }
65  }
66 
67  /// <summary>
68  /// Provides user-facing messages for the <see cref="Orders.LimitIfTouchedOrder"/> class and its consumers or related classes
69  /// </summary>
70  public static class LimitIfTouchedOrder
71  {
72  /// <summary>
73  /// Returns an empty string tag
74  /// </summary>
75  [MethodImpl(MethodImplOptions.AggressiveInlining)]
76  public static string Tag(Orders.LimitIfTouchedOrder order)
77  {
78  // No additional information to display
79  return string.Empty;
80  }
81 
82  /// <summary>
83  /// Parses the given LimitIfTouched order to a string message containing basic information
84  /// about it
85  /// </summary>
86  [MethodImpl(MethodImplOptions.AggressiveInlining)]
87  public static string ToString(Orders.LimitIfTouchedOrder order)
88  {
89  var currencySymbol = QuantConnect.Currencies.GetCurrencySymbol(order.PriceCurrency);
90  return Invariant($@"{Order.ToString(order)} at trigger {currencySymbol}{order.TriggerPrice.SmartRounding()
91  } limit {currencySymbol}{order.LimitPrice.SmartRounding()}");
92  }
93  }
94 
95  /// <summary>
96  /// Provides user-facing messages for the <see cref="Orders.LimitOrder"/> class and its consumers or related classes
97  /// </summary>
98  public static class LimitOrder
99  {
100  /// <summary>
101  /// Returns an empty string tag
102  /// </summary>
103  [MethodImpl(MethodImplOptions.AggressiveInlining)]
104  public static string Tag(Orders.LimitOrder order)
105  {
106  // No additional information to display
107  return string.Empty;
108  }
109 
110  /// <summary>
111  /// Parses a Limit order to a string message with basic information about it
112  /// </summary>
113  [MethodImpl(MethodImplOptions.AggressiveInlining)]
114  public static string ToString(Orders.LimitOrder order)
115  {
116  var currencySymbol = QuantConnect.Currencies.GetCurrencySymbol(order.PriceCurrency);
117  return Invariant($"{Order.ToString(order)} at limit {currencySymbol}{order.LimitPrice.SmartRounding()}");
118  }
119  }
120 
121  /// <summary>
122  /// Provides user-facing messages for the <see cref="Orders.Order"/> class and its consumers or related classes
123  /// </summary>
124  public static class Order
125  {
126  /// <summary>
127  /// Parses the given order into a string message with basic information about it
128  /// </summary>
129  [MethodImpl(MethodImplOptions.AggressiveInlining)]
130  public static string ToString(Orders.Order order)
131  {
132  var tag = string.IsNullOrEmpty(order.Tag) ? string.Empty : $": {order.Tag}";
133  return Invariant($@"OrderId: {order.Id} (BrokerId: {string.Join(",", order.BrokerId)}) {order.Status} {
134  order.Type} order for {order.Quantity} unit{(order.Quantity == 1 ? "" : "s")} of {order.Symbol}{tag}");
135  }
136  }
137 
138  /// <summary>
139  /// Provides user-facing messages for the <see cref="Orders.OrderEvent"/> class and its consumers or related classes
140  /// </summary>
141  public static class OrderEvent
142  {
143  /// <summary>
144  /// Parses the given order event into a string message containing basic information about it
145  /// </summary>
146  [MethodImpl(MethodImplOptions.AggressiveInlining)]
147  public static string ToString(Orders.OrderEvent orderEvent)
148  {
149  var message = Invariant($@"Time: {orderEvent.UtcTime} OrderID: {orderEvent.OrderId} EventID: {
150  orderEvent.Id} Symbol: {orderEvent.Symbol.Value} Status: {orderEvent.Status} Quantity: {orderEvent.Quantity}");
151  var currencySymbol = QuantConnect.Currencies.GetCurrencySymbol(orderEvent.FillPriceCurrency);
152 
153  if (orderEvent.FillQuantity != 0)
154  {
155  message += Invariant($@" FillQuantity: {orderEvent.FillQuantity
156  } FillPrice: {currencySymbol}{orderEvent.FillPrice.SmartRounding()}");
157  }
158 
159  if (orderEvent.LimitPrice.HasValue)
160  {
161  message += Invariant($" LimitPrice: {currencySymbol}{orderEvent.LimitPrice.Value.SmartRounding()}");
162  }
163 
164  if (orderEvent.StopPrice.HasValue)
165  {
166  message += Invariant($" StopPrice: {currencySymbol}{orderEvent.StopPrice.Value.SmartRounding()}");
167  }
168 
169  if (orderEvent.TrailingAmount.HasValue)
170  {
171  var trailingAmountString = TrailingStopOrder.TrailingAmount(orderEvent.TrailingAmount.Value,
172  orderEvent.TrailingAsPercentage ?? false, currencySymbol);
173  message += $" TrailingAmount: {trailingAmountString}";
174  }
175 
176  if (orderEvent.TriggerPrice.HasValue)
177  {
178  message += Invariant($" TriggerPrice: {currencySymbol}{orderEvent.TriggerPrice.Value.SmartRounding()}");
179  }
180 
181  // attach the order fee so it ends up in logs properly.
182  if (orderEvent.OrderFee.Value.Amount != 0m)
183  {
184  message += Invariant($" OrderFee: {orderEvent.OrderFee}");
185  }
186 
187  // add message from brokerage
188  if (!string.IsNullOrEmpty(orderEvent.Message))
189  {
190  message += Invariant($" Message: {orderEvent.Message}");
191  }
192 
193  if (orderEvent.Symbol.SecurityType.IsOption())
194  {
195  message += Invariant($" IsAssignment: {orderEvent.IsAssignment}");
196  }
197 
198  return message;
199  }
200 
201  /// <summary>
202  /// Parses the given order event into a string message which summarizes the basic information about it
203  /// </summary>
204  [MethodImpl(MethodImplOptions.AggressiveInlining)]
205  public static string ShortToString(Orders.OrderEvent orderEvent)
206  {
207  var message = Invariant($"{orderEvent.UtcTime} OID:{orderEvent.OrderId} {orderEvent.Symbol.Value} {orderEvent.Status} Q:{orderEvent.Quantity}");
208  var currencySymbol = QuantConnect.Currencies.GetCurrencySymbol(orderEvent.FillPriceCurrency);
209 
210  if (orderEvent.FillQuantity != 0)
211  {
212  message += Invariant($" FQ:{orderEvent.FillQuantity} FP:{currencySymbol}{orderEvent.FillPrice.SmartRounding()}");
213  }
214 
215  if (orderEvent.LimitPrice.HasValue)
216  {
217  message += Invariant($" LP:{currencySymbol}{orderEvent.LimitPrice.Value.SmartRounding()}");
218  }
219 
220  if (orderEvent.StopPrice.HasValue)
221  {
222  message += Invariant($" SP:{currencySymbol}{orderEvent.StopPrice.Value.SmartRounding()}");
223  }
224 
225  if (orderEvent.TrailingAmount.HasValue)
226  {
227  var trailingAmountString = TrailingStopOrder.TrailingAmount(orderEvent.TrailingAmount.Value,
228  orderEvent.TrailingAsPercentage ?? false, currencySymbol);
229  message += $" TA: {trailingAmountString}";
230  }
231 
232  if (orderEvent.TriggerPrice.HasValue)
233  {
234  message += Invariant($" TP:{currencySymbol}{orderEvent.TriggerPrice.Value.SmartRounding()}");
235  }
236 
237  // attach the order fee so it ends up in logs properly.
238  if (orderEvent.OrderFee.Value.Amount != 0m)
239  {
240  message += Invariant($" OF:{currencySymbol}{orderEvent.OrderFee}");
241  }
242 
243  // add message from brokerage
244  if (!string.IsNullOrEmpty(orderEvent.Message))
245  {
246  message += Invariant($" M:{orderEvent.Message}");
247  }
248 
249  if (orderEvent.Symbol.SecurityType.IsOption())
250  {
251  message += Invariant($" IA:{orderEvent.IsAssignment}");
252  }
253 
254  return message;
255  }
256  }
257 
258  /// <summary>
259  /// Provides user-facing messages for the <see cref="Orders.OrderRequest"/> class and its consumers or related classes
260  /// </summary>
261  public static class OrderRequest
262  {
263  /// <summary>
264  /// Parses the given order request into a string message containing basic information about it
265  /// </summary>
266  [MethodImpl(MethodImplOptions.AggressiveInlining)]
267  public static string ToString(Orders.OrderRequest request)
268  {
269  return Invariant($"{request.Time} UTC: Order: ({request.OrderId}) - {request.Tag} Status: {request.Status}");
270  }
271  }
272 
273  /// <summary>
274  /// Provides user-facing messages for the <see cref="Orders.OrderResponse"/> class and its consumers or related classes
275  /// </summary>
276  public static class OrderResponse
277  {
278  /// <summary>
279  /// String message saying: An unexpected error occurred
280  /// </summary>
281  public static string DefaultErrorMessage = "An unexpected error occurred.";
282 
283  /// <summary>
284  /// String message saying: The request has not yet been processed
285  /// </summary>
286  public static string UnprocessedOrderResponseErrorMessage = "The request has not yet been processed.";
287 
288  /// <summary>
289  /// Parses the given order response into a string message containing basic information about it
290  /// </summary>
291  [MethodImpl(MethodImplOptions.AggressiveInlining)]
292  public static string ToString(Orders.OrderResponse response)
293  {
294  if (response == Orders.OrderResponse.Unprocessed)
295  {
296  return "Unprocessed";
297  }
298 
299  if (response.IsError)
300  {
301  return Invariant($"Error: {response.ErrorCode} - {response.ErrorMessage}");
302  }
303 
304  return "Success";
305  }
306 
307  /// <summary>
308  /// Returns a string message saying it was impossible to udpate the order with the id
309  /// from the given request because it already had the status of the given order
310  /// </summary>
311  [MethodImpl(MethodImplOptions.AggressiveInlining)]
312  public static string InvalidStatus(Orders.OrderRequest request, Orders.Order order)
313  {
314  return Invariant($"Unable to update order with id {request.OrderId} because it already has {order.Status} status.");
315  }
316 
317  /// <summary>
318  /// Returns a string message saying it was impossible to update or cancel the order with the
319  /// id from the given request because the submit confirmation had not been received yet
320  /// </summary>
321  [MethodImpl(MethodImplOptions.AggressiveInlining)]
322  public static string InvalidNewStatus(Orders.OrderRequest request, Orders.Order order)
323  {
324  return Invariant($@"Unable to update or cancel order with id {
325  request.OrderId} and status {order.Status} because the submit confirmation has not been received yet.");
326  }
327 
328  /// <summary>
329  /// Returns a string message saying it was impossible to locate the order with the id from the
330  /// given request
331  /// </summary>
332  [MethodImpl(MethodImplOptions.AggressiveInlining)]
333  public static string UnableToFindOrder(Orders.OrderRequest request)
334  {
335  return Invariant($"Unable to locate order with id {request.OrderId}.");
336  }
337 
338  /// <summary>
339  /// Returns a string message saying it was impossible to process the given order request
340  /// that has zero quantity
341  /// </summary>
342  [MethodImpl(MethodImplOptions.AggressiveInlining)]
343  public static string ZeroQuantity(Orders.OrderRequest request)
344  {
345  return Invariant($"Unable to {request.OrderRequestType.ToLower()} order with id {request.OrderId} that has zero quantity.");
346  }
347 
348  /// <summary>
349  /// Returns a string message saying the user has not requested data for the symbol of the given
350  /// request. It also advises the user on how to add this data
351  /// </summary>
352  /// <param name="request"></param>
353  /// <returns></returns>
354  [MethodImpl(MethodImplOptions.AggressiveInlining)]
355  public static string MissingSecurity(Orders.SubmitOrderRequest request)
356  {
357  return Invariant($"You haven't requested {request.Symbol} data. Add this with AddSecurity() in the Initialize() Method.");
358  }
359 
360  /// <summary>
361  /// Returns a string message saying the given order request operation is not allowed
362  /// in Initialize or during warm up. It also advises the user on where it is allowed
363  /// to make it
364  /// </summary>
365  [MethodImpl(MethodImplOptions.AggressiveInlining)]
366  public static string WarmingUp(Orders.OrderRequest request)
367  {
368  return Invariant($@"This operation is not allowed in Initialize or during warm up: OrderRequest.{
369  request.OrderRequestType}. Please move this code to the OnWarmupFinished() method.");
370  }
371  }
372 
373  /// <summary>
374  /// Provides user-facing messages for the <see cref="Orders.OrderTicket"/> class and its consumers or related classes
375  /// </summary>
376  public static class OrderTicket
377  {
378  /// <summary>
379  /// Returns a string message saying it was impossible to get the given field on the order type from the given
380  /// ticket
381  /// </summary>
382  [MethodImpl(MethodImplOptions.AggressiveInlining)]
383  public static string GetFieldError(Orders.OrderTicket ticket, OrderField field)
384  {
385  return Invariant($"Unable to get field {field} on order of type {ticket.SubmitRequest.OrderType}");
386  }
387 
388  /// <summary>
389  /// Returns a string message saying the order associated with the given ticket has already received a
390  /// cancellation request
391  /// </summary>
392  [MethodImpl(MethodImplOptions.AggressiveInlining)]
393  public static string CancelRequestAlreadySubmitted(Orders.OrderTicket ticket)
394  {
395  return Invariant($"Order {ticket.OrderId} has already received a cancellation request.");
396  }
397 
398  /// <summary>
399  /// Parses the given order ticket into a string message containing basic information about it
400  /// </summary>
401  [MethodImpl(MethodImplOptions.AggressiveInlining)]
402  public static string ToString(Orders.OrderTicket ticket, Orders.Order order, int requestCount, int responseCount)
403  {
404  var counts = Invariant($"Request Count: {requestCount} Response Count: {responseCount}");
405  if (order != null)
406  {
407  return Invariant($"{ticket.OrderId}: {order} {counts}");
408  }
409 
410  return Invariant($"{ticket.OrderId}: {counts}");
411  }
412  }
413 
414  /// <summary>
415  /// Provides user-facing messages for the <see cref="Orders.StopLimitOrder"/> class and its consumers or related classes
416  /// </summary>
417  public static class StopLimitOrder
418  {
419  /// <summary>
420  /// Returns an empty string as a tag
421  /// </summary>
422  [MethodImpl(MethodImplOptions.AggressiveInlining)]
423  public static string Tag(Orders.StopLimitOrder order)
424  {
425  // No additional information to display
426  return string.Empty;
427  }
428 
429  /// <summary>
430  /// Parses the given StopLimitOrder object into a string message
431  /// </summary>
432  [MethodImpl(MethodImplOptions.AggressiveInlining)]
433  public static string ToString(Orders.StopLimitOrder order)
434  {
435  var currencySymbol = QuantConnect.Currencies.GetCurrencySymbol(order.PriceCurrency);
436  return Invariant($@"{Order.ToString(order)} at stop {currencySymbol}{order.StopPrice.SmartRounding()
437  } limit {currencySymbol}{order.LimitPrice.SmartRounding()}");
438  }
439  }
440 
441  /// <summary>
442  /// Provides user-facing messages for the <see cref="Orders.StopMarketOrder"/> class and its consumers or related classes
443  /// </summary>
444  public static class StopMarketOrder
445  {
446  /// <summary>
447  /// Returns an empty string as a tag
448  /// </summary>
449  [MethodImpl(MethodImplOptions.AggressiveInlining)]
450  public static string Tag(Orders.StopMarketOrder order)
451  {
452  // No additional information to display
453  return string.Empty;
454  }
455 
456  /// <summary>
457  /// Parses a given StopMarketOrder object into a string message
458  /// </summary>
459  [MethodImpl(MethodImplOptions.AggressiveInlining)]
460  public static string ToString(Orders.StopMarketOrder order)
461  {
462  var currencySymbol = QuantConnect.Currencies.GetCurrencySymbol(order.PriceCurrency);
463  return Invariant($"{Order.ToString(order)} at stop {currencySymbol}{order.StopPrice.SmartRounding()}");
464  }
465  }
466 
467  /// <summary>
468  /// Provides user-facing messages for the <see cref="Orders.TrailingStopOrder"/> class and its consumers or related classes
469  /// </summary>
470  public static class TrailingStopOrder
471  {
472  /// <summary>
473  /// Returns a tag message for the given TrailingStopOrder
474  /// </summary>
475  [MethodImpl(MethodImplOptions.AggressiveInlining)]
476  public static string Tag(Orders.TrailingStopOrder order)
477  {
478  return Invariant($"Trailing Amount: {TrailingAmount(order)}");
479  }
480 
481  /// <summary>
482  /// Parses a TrailingStopOrder into a string
483  /// </summary>
484  [MethodImpl(MethodImplOptions.AggressiveInlining)]
485  public static string ToString(Orders.TrailingStopOrder order)
486  {
487  var currencySymbol = QuantConnect.Currencies.GetCurrencySymbol(order.PriceCurrency);
488  return Invariant($@"{Order.ToString(order)} at stop {currencySymbol}{order.StopPrice.SmartRounding()}. Trailing amount: {
489  TrailingAmountImpl(order, currencySymbol)}");
490  }
491 
492  /// <summary>
493  /// Returns a TrailingAmount string representation for the given TrailingStopOrder
494  /// </summary>
495  [MethodImpl(MethodImplOptions.AggressiveInlining)]
496  public static string TrailingAmount(Orders.TrailingStopOrder order)
497  {
498  return TrailingAmountImpl(order, QuantConnect.Currencies.GetCurrencySymbol(order.PriceCurrency));
499  }
500 
501  /// <summary>
502  /// Returns a message for the given TrailingAmount and PriceCurrency values taking into account if the trailing is as percentage
503  /// </summary>
504  [MethodImpl(MethodImplOptions.AggressiveInlining)]
505  public static string TrailingAmount(decimal trailingAmount, bool trailingAsPercentage, string priceCurrency)
506  {
507  return trailingAsPercentage ? Invariant($"{trailingAmount * 100}%") : Invariant($"{priceCurrency}{trailingAmount}");
508  }
509 
510  [MethodImpl(MethodImplOptions.AggressiveInlining)]
511  private static string TrailingAmountImpl(Orders.TrailingStopOrder order, string currencySymbol)
512  {
513  return TrailingAmount(order.TrailingAmount, order.TrailingAsPercentage, currencySymbol);
514  }
515  }
516 
517  /// <summary>
518  /// Provides user-facing messages for the <see cref="Orders.SubmitOrderRequest"/> class and its consumers or related classes
519  /// </summary>
520  public static class SubmitOrderRequest
521  {
522  /// <summary>
523  /// Parses a given SubmitOrderRequest object to a string message
524  /// </summary>
525  [MethodImpl(MethodImplOptions.AggressiveInlining)]
526  public static string ToString(Orders.SubmitOrderRequest request)
527  {
528  // create a proxy order object to steal its ToString method
529  var proxy = Orders.Order.CreateOrder(request);
530  return Invariant($"{request.Time} UTC: Submit Order: ({request.OrderId}) - {proxy} {request.Tag} Status: {request.Status}");
531  }
532  }
533 
534  /// <summary>
535  /// Provides user-facing messages for the <see cref="Orders.UpdateOrderRequest"/> class and its consumers or related classes
536  /// </summary>
537  public static class UpdateOrderRequest
538  {
539  /// <summary>
540  /// Parses an UpdateOrderRequest to a string
541  /// </summary>
542  [MethodImpl(MethodImplOptions.AggressiveInlining)]
543  public static string ToString(Orders.UpdateOrderRequest request)
544  {
545  var updates = new List<string>(4);
546  if (request.Quantity.HasValue)
547  {
548  updates.Add(Invariant($"Quantity: {request.Quantity.Value}"));
549  }
550  if (request.LimitPrice.HasValue)
551  {
552  updates.Add(Invariant($"LimitPrice: {request.LimitPrice.Value.SmartRounding()}"));
553  }
554  if (request.StopPrice.HasValue)
555  {
556  updates.Add(Invariant($"StopPrice: {request.StopPrice.Value.SmartRounding()}"));
557  }
558  if (request.TrailingAmount.HasValue)
559  {
560  updates.Add(Invariant($"TrailingAmount: {request.TrailingAmount.Value.SmartRounding()}"));
561  }
562  if (request.TriggerPrice.HasValue)
563  {
564  updates.Add(Invariant($"TriggerPrice: {request.TriggerPrice.Value.SmartRounding()}"));
565  }
566 
567  return Invariant($@"{request.Time} UTC: Update Order: ({request.OrderId}) - {string.Join(", ", updates)} {
568  request.Tag} Status: {request.Status}");
569  }
570  }
571  }
572 }