/* * 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 { /// /// Provides an implementation of that can deserialize Orders /// public class OrderJsonConverter : JsonConverter { /// /// Gets a value indicating whether this can write JSON. /// /// /// true if this can write JSON; otherwise, false. /// public override bool CanWrite { get { return false; } } /// /// Determines whether this instance can convert the specified object type. /// /// Type of the object. /// /// true if this instance can convert the specified object type; otherwise, false. /// public override bool CanConvert(Type objectType) { return typeof(Order).IsAssignableFrom(objectType); } /// /// Writes the JSON representation of the object. /// /// The to write to.The value.The calling serializer. public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException("The OrderJsonConverter does not implement a WriteJson method;."); } /// /// Reads the JSON representation of the object. /// /// The to read from.Type of the object.The existing value of object being read.The calling serializer. /// /// The object value. /// public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var jObject = JObject.Load(reader); var order = CreateOrderFromJObject(jObject); return order; } /// /// Create an order from a simple JObject /// /// /// Order Object public static Order CreateOrderFromJObject(JObject jObject) { // create order instance based on order type field var orderType = (OrderType)(jObject["Type"]?.Value() ?? jObject["type"].Value()); var order = CreateOrder(orderType, jObject); // populate common order properties order.Id = jObject["Id"]?.Value() ?? jObject["id"].Value(); var jsonStatus = jObject["Status"] ?? jObject["status"]; var jsonTime = jObject["Time"] ?? jObject["time"]; if (jsonStatus.Type == JTokenType.Integer) { order.Status = (OrderStatus)jsonStatus.Value(); } 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(), true); } if (jsonTime != null && jsonTime.Type != JTokenType.Null) { order.Time = jsonTime.Value(); } 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() ?? jObject["createdTime"].Value()); } var orderSubmissionData = jObject["OrderSubmissionData"] ?? jObject["orderSubmissionData"]; if (orderSubmissionData != null && orderSubmissionData.Type != JTokenType.Null) { var bidPrice = orderSubmissionData["BidPrice"]?.Value() ?? orderSubmissionData["bidPrice"].Value(); var askPrice = orderSubmissionData["AskPrice"]?.Value() ?? orderSubmissionData["askPrice"].Value(); var lastPrice = orderSubmissionData["LastPrice"]?.Value() ?? orderSubmissionData["lastPrice"].Value(); order.OrderSubmissionData = new OrderSubmissionData(bidPrice, askPrice, lastPrice); } var priceAdjustmentMode = jObject["PriceAdjustmentMode"] ?? jObject["priceAdjustmentMode"]; if (priceAdjustmentMode != null && priceAdjustmentMode.Type != JTokenType.Null) { var value = priceAdjustmentMode.Value(); 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(); } if (lastFillTime != null && lastFillTime.Type != JTokenType.Null) { order.LastFillTime = lastFillTime.Value(); } if (lastUpdateTime != null && lastUpdateTime.Type != JTokenType.Null) { order.LastUpdateTime = lastUpdateTime.Value(); } var tag = jObject["Tag"] ?? jObject["tag"]; if (tag != null && tag.Type != JTokenType.Null) { order.Tag = tag.Value(); } else { order.Tag = string.Empty; } order.Quantity = jObject["Quantity"]?.Value() ?? jObject["quantity"].Value(); var orderPrice = jObject["Price"] ?? jObject["price"]; if (orderPrice != null && orderPrice.Type != JTokenType.Null) { order.Price = orderPrice.Value(); } else { order.Price = default(decimal); } var priceCurrency = jObject["PriceCurrency"] ?? jObject["priceCurrency"]; if (priceCurrency != null && priceCurrency.Type != JTokenType.Null) { order.PriceCurrency = priceCurrency.Value(); } order.BrokerId = jObject["BrokerId"]?.Select(x => x.Value()).ToList() ?? jObject["brokerId"].Select(x => x.Value()).ToList(); var jsonContingentId = jObject["ContingentId"] ?? jObject["contingentId"]; if (jsonContingentId != null && jsonContingentId.Type != JTokenType.Null) { order.ContingentId = jsonContingentId.Value(); } 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()); var ticker = jObject.SelectTokens("Symbol.Value").Single().Value(); order.Symbol = new Symbol(sid, ticker); } else if (jObject.SelectTokens("symbol.id").Any()) { var sid = SecurityIdentifier.Parse(jObject.SelectTokens("symbol.id").Single().Value()); var ticker = jObject.SelectTokens("symbol.value").Single().Value(); 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(); } // 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() ?? jObject["securityType"].Value()); 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(); } else if (symbolValueCamelCase.Any()) { // provide compatibility for orders in camel case ticker = symbolValueCamelCase.Single().Value(); } else { ticker = jObject["Symbol"]?.Value() ?? jObject["symbol"]?.Value(); } if (market == null && !SymbolPropertiesDatabase.FromDataFolder().TryGetMarket(ticker, securityType, out market)) { market = DefaultBrokerageModel.DefaultMarketMap[securityType]; } order.Symbol = Symbol.Create(ticker, securityType, market); } return order; } /// /// Creates an order of the correct type /// 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() ?? jObject["limitPrice"]?.Value() ?? default(decimal) }; break; case OrderType.StopMarket: order = new StopMarketOrder { StopPrice = jObject["stopPrice"]?.Value() ?? jObject["StopPrice"]?.Value() ?? default(decimal) }; break; case OrderType.StopLimit: order = new StopLimitOrder { LimitPrice = jObject["LimitPrice"]?.Value() ?? jObject["limitPrice"]?.Value() ?? default(decimal), StopPrice = jObject["stopPrice"]?.Value() ?? jObject["StopPrice"]?.Value() ?? default(decimal) }; break; case OrderType.TrailingStop: order = new TrailingStopOrder { StopPrice = jObject["StopPrice"]?.Value() ?? jObject["stopPrice"]?.Value() ?? default(decimal), TrailingAmount = jObject["TrailingAmount"]?.Value() ?? jObject["trailingAmount"]?.Value() ?? default(decimal), TrailingAsPercentage = jObject["TrailingAsPercentage"]?.Value() ?? jObject["trailingAsPercentage"]?.Value() ?? default(bool) }; break; case OrderType.LimitIfTouched: order = new LimitIfTouchedOrder { LimitPrice = jObject["LimitPrice"]?.Value() ?? jObject["limitPrice"]?.Value() ?? default(decimal), TriggerPrice = jObject["TriggerPrice"]?.Value() ?? jObject["triggerPrice"]?.Value() ?? 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() ?? jObject["limitPrice"]?.Value() ?? default(decimal) }; break; default: throw new ArgumentOutOfRangeException(); } return order; } /// /// Creates a Time In Force of the correct type /// 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(); switch (value) { case 0: return TimeInForce.GoodTilCanceled; case 1: return TimeInForce.Day; case 2: var expiry = jObject["DurationValue"].Value(); return TimeInForce.GoodTilDate(expiry); default: throw new Exception($"Unknown time in force value: {value}"); } } // convert with TimeInForceJsonConverter return timeInForce.ToObject(); } /// /// Deserializes the GroupOrderManager from the JSON object /// 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() ?? groupOrderManagerJObject["id"].Value(), groupOrderManagerJObject["Count"]?.Value() ?? groupOrderManagerJObject["count"].Value(), groupOrderManagerJObject["Quantity"]?.Value() ?? groupOrderManagerJObject["quantity"].Value(), groupOrderManagerJObject["LimitPrice"]?.Value() ?? groupOrderManagerJObject["limitPrice"].Value() ); foreach (var orderId in (groupOrderManagerJObject["OrderIds"]?.Values() ?? groupOrderManagerJObject["orderIds"].Values())) { result.OrderIds.Add(orderId); } return result; } } }