/*
* 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}");
}
}
}
}