/* * 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.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using QuantConnect.Orders; using QuantConnect.Securities; using static QuantConnect.StringExtensions; namespace QuantConnect { /// /// Provides user-facing message construction methods and static messages for the namespace /// public static partial class Messages { /// /// Provides user-facing messages for the class and its consumers or related classes /// public static class CancelOrderRequest { /// /// Parses the given CancelOrderRequest into a string message containing basic information about it /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ToString(Orders.CancelOrderRequest request) { return Invariant($@"{request.Time.ToStringInvariant()} UTC: Cancel Order: ({request.OrderId}) - { request.Tag} Status: {request.Status}"); } } /// /// Provides user-facing messages for the class and its consumers or related classes /// public static class GroupOrderExtensions { /// /// Returns a string message saying there is insufficient buying power to complete the given orders /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string InsufficientBuyingPowerForOrders(Dictionary securities, HasSufficientBuyingPowerForOrderResult hasSufficientBuyingPowerResult) { var ids = string.Join(",", securities.Keys.Select(o => o.Id)); var values = string.Join(",", securities.Select(o => o.Key.GetValue(o.Value).SmartRounding())); return $@"Order Error: ids: [{ids}], Insufficient buying power to complete orders (Value:[{values}]), Reason: { hasSufficientBuyingPowerResult.Reason}."; } } /// /// Provides user-facing messages for the class and its consumers or related classes /// public static class LimitIfTouchedOrder { /// /// Returns an empty string tag /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string Tag(Orders.LimitIfTouchedOrder order) { // No additional information to display return string.Empty; } /// /// Parses the given LimitIfTouched order to a string message containing basic information /// about it /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ToString(Orders.LimitIfTouchedOrder order) { var currencySymbol = QuantConnect.Currencies.GetCurrencySymbol(order.PriceCurrency); return Invariant($@"{Order.ToString(order)} at trigger {currencySymbol}{order.TriggerPrice.SmartRounding() } limit {currencySymbol}{order.LimitPrice.SmartRounding()}"); } } /// /// Provides user-facing messages for the class and its consumers or related classes /// public static class LimitOrder { /// /// Returns an empty string tag /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string Tag(Orders.LimitOrder order) { // No additional information to display return string.Empty; } /// /// Parses a Limit order to a string message with basic information about it /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ToString(Orders.LimitOrder order) { var currencySymbol = QuantConnect.Currencies.GetCurrencySymbol(order.PriceCurrency); return Invariant($"{Order.ToString(order)} at limit {currencySymbol}{order.LimitPrice.SmartRounding()}"); } } /// /// Provides user-facing messages for the class and its consumers or related classes /// public static class Order { /// /// Parses the given order into a string message with basic information about it /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ToString(Orders.Order order) { var tag = string.IsNullOrEmpty(order.Tag) ? string.Empty : $": {order.Tag}"; return Invariant($@"OrderId: {order.Id} (BrokerId: {string.Join(",", order.BrokerId)}) {order.Status} { order.Type} order for {order.Quantity} unit{(order.Quantity == 1 ? "" : "s")} of {order.Symbol}{tag}"); } } /// /// Provides user-facing messages for the class and its consumers or related classes /// public static class OrderEvent { /// /// Parses the given order event into a string message containing basic information about it /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ToString(Orders.OrderEvent orderEvent) { var message = Invariant($@"Time: {orderEvent.UtcTime} OrderID: {orderEvent.OrderId} EventID: { orderEvent.Id} Symbol: {orderEvent.Symbol.Value} Status: {orderEvent.Status} Quantity: {orderEvent.Quantity}"); var currencySymbol = QuantConnect.Currencies.GetCurrencySymbol(orderEvent.FillPriceCurrency); if (orderEvent.FillQuantity != 0) { message += Invariant($@" FillQuantity: {orderEvent.FillQuantity } FillPrice: {currencySymbol}{orderEvent.FillPrice.SmartRounding()}"); } if (orderEvent.LimitPrice.HasValue) { message += Invariant($" LimitPrice: {currencySymbol}{orderEvent.LimitPrice.Value.SmartRounding()}"); } if (orderEvent.StopPrice.HasValue) { message += Invariant($" StopPrice: {currencySymbol}{orderEvent.StopPrice.Value.SmartRounding()}"); } if (orderEvent.TrailingAmount.HasValue) { var trailingAmountString = TrailingStopOrder.TrailingAmount(orderEvent.TrailingAmount.Value, orderEvent.TrailingAsPercentage ?? false, currencySymbol); message += $" TrailingAmount: {trailingAmountString}"; } if (orderEvent.TriggerPrice.HasValue) { message += Invariant($" TriggerPrice: {currencySymbol}{orderEvent.TriggerPrice.Value.SmartRounding()}"); } // attach the order fee so it ends up in logs properly. if (orderEvent.OrderFee.Value.Amount != 0m) { message += Invariant($" OrderFee: {orderEvent.OrderFee}"); } // add message from brokerage if (!string.IsNullOrEmpty(orderEvent.Message)) { message += Invariant($" Message: {orderEvent.Message}"); } if (orderEvent.Symbol.SecurityType.IsOption()) { message += Invariant($" IsAssignment: {orderEvent.IsAssignment}"); } return message; } /// /// Parses the given order event into a string message which summarizes the basic information about it /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ShortToString(Orders.OrderEvent orderEvent) { var message = Invariant($"{orderEvent.UtcTime} OID:{orderEvent.OrderId} {orderEvent.Symbol.Value} {orderEvent.Status} Q:{orderEvent.Quantity}"); var currencySymbol = QuantConnect.Currencies.GetCurrencySymbol(orderEvent.FillPriceCurrency); if (orderEvent.FillQuantity != 0) { message += Invariant($" FQ:{orderEvent.FillQuantity} FP:{currencySymbol}{orderEvent.FillPrice.SmartRounding()}"); } if (orderEvent.LimitPrice.HasValue) { message += Invariant($" LP:{currencySymbol}{orderEvent.LimitPrice.Value.SmartRounding()}"); } if (orderEvent.StopPrice.HasValue) { message += Invariant($" SP:{currencySymbol}{orderEvent.StopPrice.Value.SmartRounding()}"); } if (orderEvent.TrailingAmount.HasValue) { var trailingAmountString = TrailingStopOrder.TrailingAmount(orderEvent.TrailingAmount.Value, orderEvent.TrailingAsPercentage ?? false, currencySymbol); message += $" TA: {trailingAmountString}"; } if (orderEvent.TriggerPrice.HasValue) { message += Invariant($" TP:{currencySymbol}{orderEvent.TriggerPrice.Value.SmartRounding()}"); } // attach the order fee so it ends up in logs properly. if (orderEvent.OrderFee.Value.Amount != 0m) { message += Invariant($" OF:{currencySymbol}{orderEvent.OrderFee}"); } // add message from brokerage if (!string.IsNullOrEmpty(orderEvent.Message)) { message += Invariant($" M:{orderEvent.Message}"); } if (orderEvent.Symbol.SecurityType.IsOption()) { message += Invariant($" IA:{orderEvent.IsAssignment}"); } return message; } } /// /// Provides user-facing messages for the class and its consumers or related classes /// public static class OrderRequest { /// /// Parses the given order request into a string message containing basic information about it /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ToString(Orders.OrderRequest request) { return Invariant($"{request.Time} UTC: Order: ({request.OrderId}) - {request.Tag} Status: {request.Status}"); } } /// /// Provides user-facing messages for the class and its consumers or related classes /// public static class OrderResponse { /// /// String message saying: An unexpected error occurred /// public static string DefaultErrorMessage = "An unexpected error occurred."; /// /// String message saying: The request has not yet been processed /// public static string UnprocessedOrderResponseErrorMessage = "The request has not yet been processed."; /// /// Parses the given order response into a string message containing basic information about it /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ToString(Orders.OrderResponse response) { if (response == Orders.OrderResponse.Unprocessed) { return "Unprocessed"; } if (response.IsError) { return Invariant($"Error: {response.ErrorCode} - {response.ErrorMessage}"); } return "Success"; } /// /// Returns a string message saying it was impossible to udpate the order with the id /// from the given request because it already had the status of the given order /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string InvalidStatus(Orders.OrderRequest request, Orders.Order order) { return Invariant($"Unable to update order with id {request.OrderId} because it already has {order.Status} status."); } /// /// Returns a string message saying it was impossible to update or cancel the order with the /// id from the given request because the submit confirmation had not been received yet /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string InvalidNewStatus(Orders.OrderRequest request, Orders.Order order) { return Invariant($@"Unable to update or cancel order with id { request.OrderId} and status {order.Status} because the submit confirmation has not been received yet."); } /// /// Returns a string message saying it was impossible to locate the order with the id from the /// given request /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string UnableToFindOrder(Orders.OrderRequest request) { return Invariant($"Unable to locate order with id {request.OrderId}."); } /// /// Returns a string message saying it was impossible to process the given order request /// that has zero quantity /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ZeroQuantity(Orders.OrderRequest request) { return Invariant($"Unable to {request.OrderRequestType.ToLower()} order with id {request.OrderId} that has zero quantity."); } /// /// Returns a string message saying the user has not requested data for the symbol of the given /// request. It also advises the user on how to add this data /// /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string MissingSecurity(Orders.SubmitOrderRequest request) { return Invariant($"You haven't requested {request.Symbol} data. Add this with AddSecurity() in the Initialize() Method."); } /// /// Returns a string message saying the given order request operation is not allowed /// in Initialize or during warm up. It also advises the user on where it is allowed /// to make it /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string WarmingUp(Orders.OrderRequest request) { return Invariant($@"This operation is not allowed in Initialize or during warm up: OrderRequest.{ request.OrderRequestType}. Please move this code to the OnWarmupFinished() method."); } } /// /// Provides user-facing messages for the class and its consumers or related classes /// public static class OrderTicket { /// /// Returns a string message saying it was impossible to get the given field on the order type from the given /// ticket /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string GetFieldError(Orders.OrderTicket ticket, OrderField field) { return Invariant($"Unable to get field {field} on order of type {ticket.SubmitRequest.OrderType}"); } /// /// Returns a string message saying the order associated with the given ticket has already received a /// cancellation request /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string CancelRequestAlreadySubmitted(Orders.OrderTicket ticket) { return Invariant($"Order {ticket.OrderId} has already received a cancellation request."); } /// /// Parses the given order ticket into a string message containing basic information about it /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ToString(Orders.OrderTicket ticket, Orders.Order order, int requestCount, int responseCount) { var counts = Invariant($"Request Count: {requestCount} Response Count: {responseCount}"); if (order != null) { return Invariant($"{ticket.OrderId}: {order} {counts}"); } return Invariant($"{ticket.OrderId}: {counts}"); } } /// /// Provides user-facing messages for the class and its consumers or related classes /// public static class StopLimitOrder { /// /// Returns an empty string as a tag /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string Tag(Orders.StopLimitOrder order) { // No additional information to display return string.Empty; } /// /// Parses the given StopLimitOrder object into a string message /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ToString(Orders.StopLimitOrder order) { var currencySymbol = QuantConnect.Currencies.GetCurrencySymbol(order.PriceCurrency); return Invariant($@"{Order.ToString(order)} at stop {currencySymbol}{order.StopPrice.SmartRounding() } limit {currencySymbol}{order.LimitPrice.SmartRounding()}"); } } /// /// Provides user-facing messages for the class and its consumers or related classes /// public static class StopMarketOrder { /// /// Returns an empty string as a tag /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string Tag(Orders.StopMarketOrder order) { // No additional information to display return string.Empty; } /// /// Parses a given StopMarketOrder object into a string message /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ToString(Orders.StopMarketOrder order) { var currencySymbol = QuantConnect.Currencies.GetCurrencySymbol(order.PriceCurrency); return Invariant($"{Order.ToString(order)} at stop {currencySymbol}{order.StopPrice.SmartRounding()}"); } } /// /// Provides user-facing messages for the class and its consumers or related classes /// public static class TrailingStopOrder { /// /// Returns a tag message for the given TrailingStopOrder /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string Tag(Orders.TrailingStopOrder order) { return Invariant($"Trailing Amount: {TrailingAmount(order)}"); } /// /// Parses a TrailingStopOrder into a string /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ToString(Orders.TrailingStopOrder order) { var currencySymbol = QuantConnect.Currencies.GetCurrencySymbol(order.PriceCurrency); return Invariant($@"{Order.ToString(order)} at stop {currencySymbol}{order.StopPrice.SmartRounding()}. Trailing amount: { TrailingAmountImpl(order, currencySymbol)}"); } /// /// Returns a TrailingAmount string representation for the given TrailingStopOrder /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string TrailingAmount(Orders.TrailingStopOrder order) { return TrailingAmountImpl(order, QuantConnect.Currencies.GetCurrencySymbol(order.PriceCurrency)); } /// /// Returns a message for the given TrailingAmount and PriceCurrency values taking into account if the trailing is as percentage /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string TrailingAmount(decimal trailingAmount, bool trailingAsPercentage, string priceCurrency) { return trailingAsPercentage ? Invariant($"{trailingAmount * 100}%") : Invariant($"{priceCurrency}{trailingAmount}"); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string TrailingAmountImpl(Orders.TrailingStopOrder order, string currencySymbol) { return TrailingAmount(order.TrailingAmount, order.TrailingAsPercentage, currencySymbol); } } /// /// Provides user-facing messages for the class and its consumers or related classes /// public static class SubmitOrderRequest { /// /// Parses a given SubmitOrderRequest object to a string message /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ToString(Orders.SubmitOrderRequest request) { // create a proxy order object to steal its ToString method var proxy = Orders.Order.CreateOrder(request); return Invariant($"{request.Time} UTC: Submit Order: ({request.OrderId}) - {proxy} {request.Tag} Status: {request.Status}"); } } /// /// Provides user-facing messages for the class and its consumers or related classes /// public static class UpdateOrderRequest { /// /// Parses an UpdateOrderRequest to a string /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ToString(Orders.UpdateOrderRequest request) { var updates = new List(4); if (request.Quantity.HasValue) { updates.Add(Invariant($"Quantity: {request.Quantity.Value}")); } if (request.LimitPrice.HasValue) { updates.Add(Invariant($"LimitPrice: {request.LimitPrice.Value.SmartRounding()}")); } if (request.StopPrice.HasValue) { updates.Add(Invariant($"StopPrice: {request.StopPrice.Value.SmartRounding()}")); } if (request.TrailingAmount.HasValue) { updates.Add(Invariant($"TrailingAmount: {request.TrailingAmount.Value.SmartRounding()}")); } if (request.TriggerPrice.HasValue) { updates.Add(Invariant($"TriggerPrice: {request.TriggerPrice.Value.SmartRounding()}")); } return Invariant($@"{request.Time} UTC: Update Order: ({request.OrderId}) - {string.Join(", ", updates)} { request.Tag} Status: {request.Status}"); } } } }