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