Files
2026-07-13 13:02:50 +08:00

388 lines
17 KiB
C#

/*
* 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.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using QuantConnect.Brokerages;
using QuantConnect.Securities;
namespace QuantConnect.Orders
{
/// <summary>
/// Provides an implementation of <see cref="JsonConverter"/> that can deserialize Orders
/// </summary>
public class OrderJsonConverter : JsonConverter
{
/// <summary>
/// Gets a value indicating whether this <see cref="T:Newtonsoft.Json.JsonConverter"/> can write JSON.
/// </summary>
/// <value>
/// <c>true</c> if this <see cref="T:Newtonsoft.Json.JsonConverter"/> can write JSON; otherwise, <c>false</c>.
/// </value>
public override bool CanWrite
{
get { return false; }
}
/// <summary>
/// Determines whether this instance can convert the specified object type.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// </returns>
public override bool CanConvert(Type objectType)
{
return typeof(Order).IsAssignableFrom(objectType);
}
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter"/> to write to.</param><param name="value">The value.</param><param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException("The OrderJsonConverter does not implement a WriteJson method;.");
}
/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader"/> to read from.</param><param name="objectType">Type of the object.</param><param name="existingValue">The existing value of object being read.</param><param name="serializer">The calling serializer.</param>
/// <returns>
/// The object value.
/// </returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var order = CreateOrderFromJObject(jObject);
return order;
}
/// <summary>
/// Create an order from a simple JObject
/// </summary>
/// <param name="jObject"></param>
/// <returns>Order Object</returns>
public static Order CreateOrderFromJObject(JObject jObject)
{
// create order instance based on order type field
var orderType = (OrderType)(jObject["Type"]?.Value<int>() ?? jObject["type"].Value<int>());
var order = CreateOrder(orderType, jObject);
// populate common order properties
order.Id = jObject["Id"]?.Value<int>() ?? jObject["id"].Value<int>();
var jsonStatus = jObject["Status"] ?? jObject["status"];
var jsonTime = jObject["Time"] ?? jObject["time"];
if (jsonStatus.Type == JTokenType.Integer)
{
order.Status = (OrderStatus)jsonStatus.Value<int>();
}
else if (jsonStatus.Type == JTokenType.Null)
{
order.Status = OrderStatus.Canceled;
}
else
{
// The `Status` tag can sometimes appear as a string of the enum value in the LiveResultPacket.
order.Status = (OrderStatus)Enum.Parse(typeof(OrderStatus), jsonStatus.Value<string>(), true);
}
if (jsonTime != null && jsonTime.Type != JTokenType.Null)
{
order.Time = jsonTime.Value<DateTime>();
}
else
{
// `Time` can potentially be null in some LiveResultPacket instances, but
// `CreatedTime` will always be there if `Time` is absent.
order.Time = (jObject["CreatedTime"]?.Value<DateTime>() ?? jObject["createdTime"].Value<DateTime>());
}
var orderSubmissionData = jObject["OrderSubmissionData"] ?? jObject["orderSubmissionData"];
if (orderSubmissionData != null && orderSubmissionData.Type != JTokenType.Null)
{
var bidPrice = orderSubmissionData["BidPrice"]?.Value<decimal>() ?? orderSubmissionData["bidPrice"].Value<decimal>();
var askPrice = orderSubmissionData["AskPrice"]?.Value<decimal>() ?? orderSubmissionData["askPrice"].Value<decimal>();
var lastPrice = orderSubmissionData["LastPrice"]?.Value<decimal>() ?? orderSubmissionData["lastPrice"].Value<decimal>();
order.OrderSubmissionData = new OrderSubmissionData(bidPrice, askPrice, lastPrice);
}
var priceAdjustmentMode = jObject["PriceAdjustmentMode"] ?? jObject["priceAdjustmentMode"];
if (priceAdjustmentMode != null && priceAdjustmentMode.Type != JTokenType.Null)
{
var value = priceAdjustmentMode.Value<int>();
order.PriceAdjustmentMode = (DataNormalizationMode)value;
}
var lastFillTime = jObject["LastFillTime"] ?? jObject["lastFillTime"];
var lastUpdateTime = jObject["LastUpdateTime"] ?? jObject["lastUpdateTime"];
var canceledTime = jObject["CanceledTime"] ?? jObject["canceledTime"];
if (canceledTime != null && canceledTime.Type != JTokenType.Null)
{
order.CanceledTime = canceledTime.Value<DateTime>();
}
if (lastFillTime != null && lastFillTime.Type != JTokenType.Null)
{
order.LastFillTime = lastFillTime.Value<DateTime>();
}
if (lastUpdateTime != null && lastUpdateTime.Type != JTokenType.Null)
{
order.LastUpdateTime = lastUpdateTime.Value<DateTime>();
}
var tag = jObject["Tag"] ?? jObject["tag"];
if (tag != null && tag.Type != JTokenType.Null)
{
order.Tag = tag.Value<string>();
}
else
{
order.Tag = string.Empty;
}
order.Quantity = jObject["Quantity"]?.Value<decimal>() ?? jObject["quantity"].Value<decimal>();
var orderPrice = jObject["Price"] ?? jObject["price"];
if (orderPrice != null && orderPrice.Type != JTokenType.Null)
{
order.Price = orderPrice.Value<decimal>();
}
else
{
order.Price = default(decimal);
}
var priceCurrency = jObject["PriceCurrency"] ?? jObject["priceCurrency"];
if (priceCurrency != null && priceCurrency.Type != JTokenType.Null)
{
order.PriceCurrency = priceCurrency.Value<string>();
}
order.BrokerId = jObject["BrokerId"]?.Select(x => x.Value<string>()).ToList() ?? jObject["brokerId"].Select(x => x.Value<string>()).ToList();
var jsonContingentId = jObject["ContingentId"] ?? jObject["contingentId"];
if (jsonContingentId != null && jsonContingentId.Type != JTokenType.Null)
{
order.ContingentId = jsonContingentId.Value<int>();
}
var timeInForce = jObject["Properties"]?["TimeInForce"] ?? jObject["TimeInForce"] ?? jObject["Duration"];
if (timeInForce == null)
{
timeInForce = jObject["properties"]?["timeInForce"] ?? jObject["timeInForce"] ?? jObject["duration"];
}
order.Properties.TimeInForce = (timeInForce != null)
? CreateTimeInForce(timeInForce, jObject)
: TimeInForce.GoodTilCanceled;
if (jObject.SelectTokens("Symbol.ID").Any())
{
var sid = SecurityIdentifier.Parse(jObject.SelectTokens("Symbol.ID").Single().Value<string>());
var ticker = jObject.SelectTokens("Symbol.Value").Single().Value<string>();
order.Symbol = new Symbol(sid, ticker);
}
else if (jObject.SelectTokens("symbol.id").Any())
{
var sid = SecurityIdentifier.Parse(jObject.SelectTokens("symbol.id").Single().Value<string>());
var ticker = jObject.SelectTokens("symbol.value").Single().Value<string>();
order.Symbol = new Symbol(sid, ticker);
}
else
{
string market = null;
//does data have market?
var suppliedMarket = jObject.SelectTokens("Symbol.ID.Market") ?? jObject.SelectTokens("symbol.ID.Market");
if (suppliedMarket.Any())
{
market = suppliedMarket.Single().Value<string>();
}
// we only get the security type if we need it, because it might not be there in other cases
var securityType = (SecurityType)(jObject["SecurityType"]?.Value<int>() ?? jObject["securityType"].Value<int>());
var symbolValueUpperCase = jObject.SelectTokens("Symbol.Value");
var symbolValueCamelCase = jObject.SelectTokens("symbol.value");
string ticker = default;
if (symbolValueUpperCase.Any())
{
// provide for backwards compatibility
ticker = symbolValueUpperCase.Single().Value<string>();
}
else if (symbolValueCamelCase.Any())
{
// provide compatibility for orders in camel case
ticker = symbolValueCamelCase.Single().Value<string>();
}
else
{
ticker = jObject["Symbol"]?.Value<string>() ?? jObject["symbol"]?.Value<string>();
}
if (market == null && !SymbolPropertiesDatabase.FromDataFolder().TryGetMarket(ticker, securityType, out market))
{
market = DefaultBrokerageModel.DefaultMarketMap[securityType];
}
order.Symbol = Symbol.Create(ticker, securityType, market);
}
return order;
}
/// <summary>
/// Creates an order of the correct type
/// </summary>
private static Order CreateOrder(OrderType orderType, JObject jObject)
{
Order order;
switch (orderType)
{
case OrderType.Market:
order = new MarketOrder();
break;
case OrderType.Limit:
order = new LimitOrder { LimitPrice = jObject["LimitPrice"]?.Value<decimal>() ?? jObject["limitPrice"]?.Value<decimal>() ?? default(decimal) };
break;
case OrderType.StopMarket:
order = new StopMarketOrder
{
StopPrice = jObject["stopPrice"]?.Value<decimal>() ?? jObject["StopPrice"]?.Value<decimal>() ?? default(decimal)
};
break;
case OrderType.StopLimit:
order = new StopLimitOrder
{
LimitPrice = jObject["LimitPrice"]?.Value<decimal>() ?? jObject["limitPrice"]?.Value<decimal>() ?? default(decimal),
StopPrice = jObject["stopPrice"]?.Value<decimal>() ?? jObject["StopPrice"]?.Value<decimal>() ?? default(decimal)
};
break;
case OrderType.TrailingStop:
order = new TrailingStopOrder
{
StopPrice = jObject["StopPrice"]?.Value<decimal>() ?? jObject["stopPrice"]?.Value<decimal>() ?? default(decimal),
TrailingAmount = jObject["TrailingAmount"]?.Value<decimal>() ?? jObject["trailingAmount"]?.Value<decimal>() ?? default(decimal),
TrailingAsPercentage = jObject["TrailingAsPercentage"]?.Value<bool>() ?? jObject["trailingAsPercentage"]?.Value<bool>() ?? default(bool)
};
break;
case OrderType.LimitIfTouched:
order = new LimitIfTouchedOrder
{
LimitPrice = jObject["LimitPrice"]?.Value<decimal>() ?? jObject["limitPrice"]?.Value<decimal>() ?? default(decimal),
TriggerPrice = jObject["TriggerPrice"]?.Value<decimal>() ?? jObject["triggerPrice"]?.Value<decimal>() ?? default(decimal)
};
break;
case OrderType.MarketOnOpen:
order = new MarketOnOpenOrder();
break;
case OrderType.MarketOnClose:
order = new MarketOnCloseOrder();
break;
case OrderType.OptionExercise:
order = new OptionExerciseOrder();
break;
case OrderType.ComboMarket:
order = new ComboMarketOrder() { GroupOrderManager = DeserializeGroupOrderManager(jObject) };
break;
case OrderType.ComboLimit:
order = new ComboLimitOrder() { GroupOrderManager = DeserializeGroupOrderManager(jObject) };
break;
case OrderType.ComboLegLimit:
order = new ComboLegLimitOrder
{
GroupOrderManager = DeserializeGroupOrderManager(jObject),
LimitPrice = jObject["LimitPrice"]?.Value<decimal>() ?? jObject["limitPrice"]?.Value<decimal>() ?? default(decimal)
};
break;
default:
throw new ArgumentOutOfRangeException();
}
return order;
}
/// <summary>
/// Creates a Time In Force of the correct type
/// </summary>
private static TimeInForce CreateTimeInForce(JToken timeInForce, JObject jObject)
{
// for backward-compatibility support deserialization of old JSON format
if (timeInForce is JValue)
{
var value = timeInForce.Value<int>();
switch (value)
{
case 0:
return TimeInForce.GoodTilCanceled;
case 1:
return TimeInForce.Day;
case 2:
var expiry = jObject["DurationValue"].Value<DateTime>();
return TimeInForce.GoodTilDate(expiry);
default:
throw new Exception($"Unknown time in force value: {value}");
}
}
// convert with TimeInForceJsonConverter
return timeInForce.ToObject<TimeInForce>();
}
/// <summary>
/// Deserializes the GroupOrderManager from the JSON object
/// </summary>
private static GroupOrderManager DeserializeGroupOrderManager(JObject jObject)
{
var groupOrderManagerJObject = jObject["GroupOrderManager"] ?? jObject["groupOrderManager"];
// this should never happen
if (groupOrderManagerJObject == null)
{
throw new ArgumentException("OrderJsonConverter.DeserializeGroupOrderManager(): JObject does not have a GroupOrderManager");
}
var result = new GroupOrderManager(
groupOrderManagerJObject["Id"]?.Value<int>() ?? groupOrderManagerJObject["id"].Value<int>(),
groupOrderManagerJObject["Count"]?.Value<int>() ?? groupOrderManagerJObject["count"].Value<int>(),
groupOrderManagerJObject["Quantity"]?.Value<decimal>() ?? groupOrderManagerJObject["quantity"].Value<decimal>(),
groupOrderManagerJObject["LimitPrice"]?.Value<decimal>() ?? groupOrderManagerJObject["limitPrice"].Value<decimal>()
);
foreach (var orderId in (groupOrderManagerJObject["OrderIds"]?.Values<int>() ?? groupOrderManagerJObject["orderIds"].Values<int>()))
{
result.OrderIds.Add(orderId);
}
return result;
}
}
}