From 1b8dac9899e0e6a7ea42e212d5d526d9f5aec2f0 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Wed, 14 Jan 2026 17:29:00 -0400 Subject: [PATCH 1/7] Add total performance statistics to live result files --- Common/Packets/BacktestResultPacket.cs | 6 ------ Common/Packets/BacktestResultParameters.cs | 8 +------- Common/Packets/BaseResultParameters.cs | 10 +++++++++- Common/Packets/LiveResultPacket.cs | 8 ++++---- Common/Packets/LiveResultParameters.cs | 6 ++++-- Common/Result.cs | 10 +++++++++- Engine/Results/LiveTradingResultHandler.cs | 11 ++++++----- 7 files changed, 33 insertions(+), 26 deletions(-) diff --git a/Common/Packets/BacktestResultPacket.cs b/Common/Packets/BacktestResultPacket.cs index 7a6e2317dd78..6a33c47e1465 100644 --- a/Common/Packets/BacktestResultPacket.cs +++ b/Common/Packets/BacktestResultPacket.cs @@ -208,11 +208,6 @@ public class BacktestResult : Result /// public Dictionary RollingWindow { get; set; } = new Dictionary(); - /// - /// Rolling window detailed statistics. - /// - public AlgorithmPerformance TotalPerformance { get; set; } - /// /// Default Constructor /// @@ -226,7 +221,6 @@ public BacktestResult() public BacktestResult(BacktestResultParameters parameters) : base(parameters) { RollingWindow = parameters.RollingWindow; - TotalPerformance = parameters.TotalPerformance; } } } // End of Namespace: diff --git a/Common/Packets/BacktestResultParameters.cs b/Common/Packets/BacktestResultParameters.cs index 31a89506876a..7481750c5617 100644 --- a/Common/Packets/BacktestResultParameters.cs +++ b/Common/Packets/BacktestResultParameters.cs @@ -18,7 +18,6 @@ using QuantConnect.Orders; using QuantConnect.Statistics; using System.Collections.Generic; -using QuantConnect.Securities.Positions; namespace QuantConnect.Packets { @@ -32,10 +31,6 @@ public class BacktestResultParameters : BaseResultParameters /// public Dictionary RollingWindow { get; set; } - /// - /// Rolling window detailed statistics. - /// - public AlgorithmPerformance TotalPerformance { get; set; } /// /// Creates a new instance /// @@ -49,10 +44,9 @@ public BacktestResultParameters(IDictionary charts, AlgorithmPerformance totalPerformance = null, AlgorithmConfiguration algorithmConfiguration = null, IDictionary state = null) - : base(charts, orders, profitLoss, statistics, runtimeStatistics, orderEvents, algorithmConfiguration, state) + : base(charts, orders, profitLoss, statistics, runtimeStatistics, orderEvents, totalPerformance, algorithmConfiguration, state) { RollingWindow = rollingWindow; - TotalPerformance = totalPerformance; } } } diff --git a/Common/Packets/BaseResultParameters.cs b/Common/Packets/BaseResultParameters.cs index fcf6cfe2d8c3..e75c7001c412 100644 --- a/Common/Packets/BaseResultParameters.cs +++ b/Common/Packets/BaseResultParameters.cs @@ -14,8 +14,9 @@ * */ -using System; using QuantConnect.Orders; +using QuantConnect.Statistics; +using System; using System.Collections.Generic; namespace QuantConnect.Packets @@ -65,6 +66,11 @@ public class BaseResultParameters /// public AlgorithmConfiguration AlgorithmConfiguration { get; set; } + /// + /// Rolling window detailed statistics. + /// + public AlgorithmPerformance TotalPerformance { get; set; } + /// /// Creates a new instance /// @@ -74,6 +80,7 @@ public BaseResultParameters(IDictionary charts, IDictionary statistics, IDictionary runtimeStatistics, List orderEvents, + AlgorithmPerformance totalPerformance = null, AlgorithmConfiguration algorithmConfiguration = null, IDictionary state = null) { @@ -85,6 +92,7 @@ public BaseResultParameters(IDictionary charts, OrderEvents = orderEvents; AlgorithmConfiguration = algorithmConfiguration; State = state; + TotalPerformance = totalPerformance; } } } diff --git a/Common/Packets/LiveResultPacket.cs b/Common/Packets/LiveResultPacket.cs index f054c61beb87..38d032328a62 100644 --- a/Common/Packets/LiveResultPacket.cs +++ b/Common/Packets/LiveResultPacket.cs @@ -14,13 +14,13 @@ * */ -using System; -using System.Linq; using Newtonsoft.Json; -using QuantConnect.Orders; using QuantConnect.Logging; +using QuantConnect.Orders; using QuantConnect.Securities; +using System; using System.Collections.Generic; +using System.Linq; namespace QuantConnect.Packets { @@ -109,7 +109,7 @@ public static LiveResultPacket CreateEmpty(LiveNodePacket job) return new LiveResultPacket(job, new LiveResult(new LiveResultParameters( new Dictionary(), new Dictionary(), new Dictionary(), new Dictionary(), new CashBook(), new Dictionary(), - new SortedDictionary(), new List(), new Dictionary(), + new SortedDictionary(), new List(), null, new Dictionary(), new AlgorithmConfiguration(), new Dictionary()))); } } // End Queue Packet: diff --git a/Common/Packets/LiveResultParameters.cs b/Common/Packets/LiveResultParameters.cs index cc4b4a3b2b2f..60e14fa246ea 100644 --- a/Common/Packets/LiveResultParameters.cs +++ b/Common/Packets/LiveResultParameters.cs @@ -14,9 +14,10 @@ * */ -using System; using QuantConnect.Orders; using QuantConnect.Securities; +using QuantConnect.Statistics; +using System; using System.Collections.Generic; namespace QuantConnect.Packets @@ -52,10 +53,11 @@ public LiveResultParameters(IDictionary charts, IDictionary statistics, IDictionary runtimeStatistics, List orderEvents, + AlgorithmPerformance totalPerformance = null, IDictionary serverStatistics = null, AlgorithmConfiguration algorithmConfiguration = null, IDictionary state = null) - : base(charts, orders, profitLoss, statistics, runtimeStatistics, orderEvents, algorithmConfiguration, state) + : base(charts, orders, profitLoss, statistics, runtimeStatistics, orderEvents, totalPerformance, algorithmConfiguration, state) { Holdings = holdings; CashBook = cashBook; diff --git a/Common/Result.cs b/Common/Result.cs index 932a571c257c..13f040b83ad6 100644 --- a/Common/Result.cs +++ b/Common/Result.cs @@ -13,10 +13,11 @@ * limitations under the License. */ -using System; using Newtonsoft.Json; using QuantConnect.Orders; using QuantConnect.Packets; +using QuantConnect.Statistics; +using System; using System.Collections.Generic; namespace QuantConnect @@ -83,6 +84,12 @@ public class Result [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public AlgorithmConfiguration AlgorithmConfiguration { get; set; } + /// + /// Rolling window detailed statistics. + /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public AlgorithmPerformance TotalPerformance { get; set; } + /// /// Creates new empty instance /// @@ -103,6 +110,7 @@ public Result(BaseResultParameters parameters) OrderEvents = parameters.OrderEvents; AlgorithmConfiguration = parameters.AlgorithmConfiguration; State = parameters.State; + TotalPerformance = parameters.TotalPerformance; } } } diff --git a/Engine/Results/LiveTradingResultHandler.cs b/Engine/Results/LiveTradingResultHandler.cs index 2e375cf2274c..3103d8560e2d 100644 --- a/Engine/Results/LiveTradingResultHandler.cs +++ b/Engine/Results/LiveTradingResultHandler.cs @@ -216,9 +216,8 @@ private void Update() //Add the algorithm statistics first. - var summary = GenerateStatisticsResults(performanceCharts).Summary; - var runtimeStatistics = GetAlgorithmRuntimeStatistics(summary); - + var statistics = GenerateStatisticsResults(performanceCharts); + var runtimeStatistics = GetAlgorithmRuntimeStatistics(statistics.Summary); // since we're sending multiple packets, let's do it async and forget about it // chart data can get big so let's break them up into groups @@ -248,7 +247,9 @@ private void Update() var deltaStatistics = new Dictionary(); var orders = new Dictionary(TransactionHandler.Orders); - var complete = new LiveResultPacket(_job, new LiveResult(new LiveResultParameters(chartComplete, orders, Algorithm.Transactions.TransactionRecord, holdings, Algorithm.Portfolio.CashBook, deltaStatistics, runtimeStatistics, orderEvents, serverStatistics, state: GetAlgorithmState()))); + var complete = new LiveResultPacket(_job, new LiveResult(new LiveResultParameters(chartComplete, orders, + Algorithm.Transactions.TransactionRecord, holdings, Algorithm.Portfolio.CashBook, deltaStatistics, + runtimeStatistics, orderEvents, statistics.TotalPerformance, serverStatistics, state: GetAlgorithmState()))); StoreResult(complete); _nextChartsUpdate = DateTime.UtcNow.Add(ChartUpdateInterval); Log.Debug("LiveTradingResultHandler.Update(): End-store result"); @@ -761,7 +762,7 @@ protected void SendFinalResult() result = new LiveResultPacket(_job, new LiveResult(new LiveResultParameters(charts, orders, profitLoss, new Dictionary(), Algorithm.Portfolio.CashBook, statisticsResults.Summary, runtime, GetOrderEventsToStore(), - algorithmConfiguration: AlgorithmConfiguration.Create(Algorithm, null), state: endState))); + algorithmConfiguration: AlgorithmConfiguration.Create(Algorithm, null), state: endState, totalPerformance: statisticsResults.TotalPerformance))); } else { From 2ba892909e0c84a4b2e877a7b606b28eb00fe680 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Thu, 15 Jan 2026 11:42:54 -0400 Subject: [PATCH 2/7] Truncate closed trades in live results --- Engine/Results/LiveTradingResultHandler.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Engine/Results/LiveTradingResultHandler.cs b/Engine/Results/LiveTradingResultHandler.cs index 3103d8560e2d..416e02daac32 100644 --- a/Engine/Results/LiveTradingResultHandler.cs +++ b/Engine/Results/LiveTradingResultHandler.cs @@ -983,6 +983,9 @@ private static void Truncate(LiveResult result, DateTime start, DateTime stop) (x.LastFillTime != null && x.LastFillTime >= start && x.LastFillTime <= stop) || (x.LastUpdateTime != null && x.LastUpdateTime >= start && x.LastUpdateTime <= stop) ).ToDictionary(x => x.Id); + result.TotalPerformance.ClosedTrades = result.TotalPerformance.ClosedTrades + .Where(x => x.ExitTime >= start && x.ExitTime <= stop) + .ToList(); //Log.Trace("LiveTradingResultHandler.Truncate: Truncate Outgoing: " + result.Charts["Strategy Equity"].Series["Equity"].Values.Count); } From 7e587925e47e0a565074a4e71035cd3bbfc4d1a3 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Thu, 15 Jan 2026 11:58:00 -0400 Subject: [PATCH 3/7] Avoid adding totalPerformance to live minute result file --- Engine/Results/LiveTradingResultHandler.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Engine/Results/LiveTradingResultHandler.cs b/Engine/Results/LiveTradingResultHandler.cs index 416e02daac32..6ea564e62da2 100644 --- a/Engine/Results/LiveTradingResultHandler.cs +++ b/Engine/Results/LiveTradingResultHandler.cs @@ -855,8 +855,15 @@ protected override void StoreResult(Packet packet) // swap out our charts with the sampled data minuteCharts.Remove(PortfolioMarginKey); live.Results.Charts = minuteCharts; + + var totalPerformance = live.Results.TotalPerformance; + live.Results.TotalPerformance = null; // we don't need to save this in minute data + SaveResults(CreateKey("minute"), live.Results); + // restore total performance + live.Results.TotalPerformance = totalPerformance; + // 10 minute resolution data, save today var tenminuteSampler = new SeriesSampler(TimeSpan.FromMinutes(10)); var tenminuteCharts = tenminuteSampler.SampleCharts(live.Results.Charts, start, stop); @@ -983,9 +990,14 @@ private static void Truncate(LiveResult result, DateTime start, DateTime stop) (x.LastFillTime != null && x.LastFillTime >= start && x.LastFillTime <= stop) || (x.LastUpdateTime != null && x.LastUpdateTime >= start && x.LastUpdateTime <= stop) ).ToDictionary(x => x.Id); - result.TotalPerformance.ClosedTrades = result.TotalPerformance.ClosedTrades - .Where(x => x.ExitTime >= start && x.ExitTime <= stop) - .ToList(); + + var closedTrades = result.TotalPerformance?.ClosedTrades; + if (closedTrades != null && closedTrades.Count > 0) + { + result.TotalPerformance.ClosedTrades = closedTrades + .Where(x => x.ExitTime >= start && x.ExitTime <= stop) + .ToList(); + } //Log.Trace("LiveTradingResultHandler.Truncate: Truncate Outgoing: " + result.Charts["Strategy Equity"].Series["Equity"].Values.Count); } From 6f33572417e7f8f4b04d06579d69cec3f7088fe1 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Thu, 15 Jan 2026 13:47:02 -0400 Subject: [PATCH 4/7] Deprecated Trade.Symbol in favor of new Trade.Symbols --- Common/Statistics/Trade.cs | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/Common/Statistics/Trade.cs b/Common/Statistics/Trade.cs index 0a05a35c97ac..f9d2acd3fc49 100644 --- a/Common/Statistics/Trade.cs +++ b/Common/Statistics/Trade.cs @@ -13,6 +13,7 @@ * limitations under the License. */ +using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -23,10 +24,41 @@ namespace QuantConnect.Statistics /// public class Trade { + private List _symbols; + /// /// The symbol of the traded instrument /// - public Symbol Symbol { get; set; } + [Obsolete("Use Symbols property instead")] + [JsonIgnore] + public Symbol Symbol + { + get + { + return _symbols != null && _symbols.Count > 0 ? _symbols[0] : Symbol.Empty; + } + set + { + if (_symbols == null) + { + _symbols = new List() { value }; + } + else + { + _symbols.Clear(); + _symbols.Add(value); + } + } + } + + /// + /// The symbol associated to the traded instruments + /// + public List Symbols + { + get { return _symbols; } + set { _symbols = value; } + } /// /// The date and time the trade was opened From 240c41588d75515c1cc8ff4f91f22344ed334478 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Thu, 15 Jan 2026 17:21:45 -0400 Subject: [PATCH 5/7] Fixes for Trade serialization --- Common/Statistics/Trade.cs | 18 +++++----- Common/Statistics/TradeBuilder.cs | 12 +++---- .../Statistics/PortfolioStatisticsTests.cs | 4 +-- .../Common/Statistics/TradeStatisticsTests.cs | 34 +++++++++---------- 4 files changed, 33 insertions(+), 35 deletions(-) diff --git a/Common/Statistics/Trade.cs b/Common/Statistics/Trade.cs index f9d2acd3fc49..35ef2a35c6bb 100644 --- a/Common/Statistics/Trade.cs +++ b/Common/Statistics/Trade.cs @@ -37,20 +37,18 @@ public Symbol Symbol { return _symbols != null && _symbols.Count > 0 ? _symbols[0] : Symbol.Empty; } - set + private set { - if (_symbols == null) - { - _symbols = new List() { value }; - } - else - { - _symbols.Clear(); - _symbols.Add(value); - } + _symbols = new List() { value }; } } + /// + /// Just needed so that "Symbol" is never serialized but can be deserialized, if present, for backward compatibility + /// + [JsonProperty("Symbol")] + private Symbol SymbolForDeserialization { set => Symbol = value; } + /// /// The symbol associated to the traded instruments /// diff --git a/Common/Statistics/TradeBuilder.cs b/Common/Statistics/TradeBuilder.cs index 93ff8b3bfccb..b0b0cb010c30 100644 --- a/Common/Statistics/TradeBuilder.cs +++ b/Common/Statistics/TradeBuilder.cs @@ -227,7 +227,7 @@ private void ProcessFillUsingFillToFill(OrderEvent fill, decimal orderFee, decim { new Trade { - Symbol = fill.Symbol, + Symbols = [fill.Symbol], EntryTime = fill.UtcTime, EntryPrice = fill.FillPrice, Direction = fill.FillQuantity > 0 ? TradeDirection.Long : TradeDirection.Short, @@ -251,7 +251,7 @@ private void ProcessFillUsingFillToFill(OrderEvent fill, decimal orderFee, decim // execution has same direction of trade position.PendingTrades.Add(new Trade { - Symbol = fill.Symbol, + Symbols = [fill.Symbol], EntryTime = fill.UtcTime, EntryPrice = fill.FillPrice, Direction = fill.FillQuantity > 0 ? TradeDirection.Long : TradeDirection.Short, @@ -295,7 +295,7 @@ private void ProcessFillUsingFillToFill(OrderEvent fill, decimal orderFee, decim var newTrade = new Trade { - Symbol = trade.Symbol, + Symbols = trade.Symbols, EntryTime = trade.EntryTime, EntryPrice = trade.EntryPrice, Direction = trade.Direction, @@ -329,7 +329,7 @@ private void ProcessFillUsingFillToFill(OrderEvent fill, decimal orderFee, decim { new Trade { - Symbol = fill.Symbol, + Symbols =[fill.Symbol], EntryTime = fill.UtcTime, EntryPrice = fill.FillPrice, Direction = fill.FillQuantity > 0 ? TradeDirection.Long : TradeDirection.Short, @@ -412,7 +412,7 @@ private void ProcessFillUsingFlatToFlat(OrderEvent fill, decimal orderFee, decim var direction = Math.Sign(fill.FillQuantity) < 0 ? TradeDirection.Long : TradeDirection.Short; var trade = new Trade { - Symbol = fill.Symbol, + Symbols = [fill.Symbol], EntryTime = entryTime, EntryPrice = entryAveragePrice, Direction = direction, @@ -515,7 +515,7 @@ private void ProcessFillUsingFlatToReduced(OrderEvent fill, decimal orderFee, de var direction = totalExecutedQuantity < 0 ? TradeDirection.Long : TradeDirection.Short; var trade = new Trade { - Symbol = fill.Symbol, + Symbols = [fill.Symbol], EntryTime = entryTime, EntryPrice = entryPrice, Direction = direction, diff --git a/Tests/Common/Statistics/PortfolioStatisticsTests.cs b/Tests/Common/Statistics/PortfolioStatisticsTests.cs index 7387f74df4ea..c3288b19f228 100644 --- a/Tests/Common/Statistics/PortfolioStatisticsTests.cs +++ b/Tests/Common/Statistics/PortfolioStatisticsTests.cs @@ -178,7 +178,7 @@ private List CreateITMOptionAssignment(bool win) { new Trade { - Symbol = Symbols.SPY_C_192_Feb19_2016, + Symbols = [Symbols.SPY_C_192_Feb19_2016], EntryTime = time, EntryPrice = 80m, Direction = TradeDirection.Long, @@ -193,7 +193,7 @@ private List CreateITMOptionAssignment(bool win) }, new Trade { - Symbol = Symbols.SPY, + Symbols =[Symbols.SPY], EntryTime = time.AddMinutes(20), EntryPrice = 192m, Direction = TradeDirection.Long, diff --git a/Tests/Common/Statistics/TradeStatisticsTests.cs b/Tests/Common/Statistics/TradeStatisticsTests.cs index 754c16174c52..4d5b15a8e690 100644 --- a/Tests/Common/Statistics/TradeStatisticsTests.cs +++ b/Tests/Common/Statistics/TradeStatisticsTests.cs @@ -124,7 +124,7 @@ private IEnumerable CreateThreeWinners() { new Trade { - Symbol = Symbols.EURUSD, + Symbols =[Symbols.EURUSD], EntryTime = time, EntryPrice = 1.07m, Direction = TradeDirection.Long, @@ -138,7 +138,7 @@ private IEnumerable CreateThreeWinners() }, new Trade { - Symbol = Symbols.EURUSD, + Symbols =[Symbols.EURUSD], EntryTime = time.AddMinutes(10), EntryPrice = 1.08m, Direction = TradeDirection.Long, @@ -152,7 +152,7 @@ private IEnumerable CreateThreeWinners() }, new Trade { - Symbol = Symbols.EURUSD, + Symbols =[Symbols.EURUSD], EntryTime = time.AddMinutes(30), EntryPrice = 1.08m, Direction = TradeDirection.Long, @@ -220,7 +220,7 @@ private IEnumerable CreateThreeLosers() { new Trade { - Symbol = Symbols.EURUSD, + Symbols =[Symbols.EURUSD], EntryTime = time, EntryPrice = 1.07m, Direction = TradeDirection.Short, @@ -234,7 +234,7 @@ private IEnumerable CreateThreeLosers() }, new Trade { - Symbol = Symbols.EURUSD, + Symbols =[Symbols.EURUSD], EntryTime = time.AddMinutes(10), EntryPrice = 1.08m, Direction = TradeDirection.Short, @@ -248,7 +248,7 @@ private IEnumerable CreateThreeLosers() }, new Trade { - Symbol = Symbols.EURUSD, + Symbols =[Symbols.EURUSD], EntryTime = time.AddMinutes(30), EntryPrice = 1.08m, Direction = TradeDirection.Short, @@ -316,7 +316,7 @@ private IEnumerable CreateTwoLosersOneWinner() { new Trade { - Symbol = Symbols.EURUSD, + Symbols =[Symbols.EURUSD], EntryTime = time, EntryPrice = 1.07m, Direction = TradeDirection.Short, @@ -330,7 +330,7 @@ private IEnumerable CreateTwoLosersOneWinner() }, new Trade { - Symbol = Symbols.EURUSD, + Symbols =[Symbols.EURUSD], EntryTime = time.AddMinutes(10), EntryPrice = 1.08m, Direction = TradeDirection.Short, @@ -344,7 +344,7 @@ private IEnumerable CreateTwoLosersOneWinner() }, new Trade { - Symbol = Symbols.EURUSD, + Symbols =[Symbols.EURUSD], EntryTime = time.AddMinutes(30), EntryPrice = 1.08m, Direction = TradeDirection.Long, @@ -412,7 +412,7 @@ private IEnumerable CreateOneWinnerTwoLosers() { new Trade { - Symbol = Symbols.EURUSD, + Symbols =[Symbols.EURUSD], EntryTime = time, EntryPrice = 1.08m, Direction = TradeDirection.Long, @@ -426,7 +426,7 @@ private IEnumerable CreateOneWinnerTwoLosers() }, new Trade { - Symbol = Symbols.EURUSD, + Symbols =[Symbols.EURUSD], EntryTime = time.AddMinutes(20), EntryPrice = 1.07m, Direction = TradeDirection.Short, @@ -440,7 +440,7 @@ private IEnumerable CreateOneWinnerTwoLosers() }, new Trade { - Symbol = Symbols.EURUSD, + Symbols =[Symbols.EURUSD], EntryTime = time.AddMinutes(30), EntryPrice = 1.08m, Direction = TradeDirection.Short, @@ -508,7 +508,7 @@ private IEnumerable CreateOneLoserTwoWinners() { new Trade { - Symbol = Symbols.EURUSD, + Symbols =[Symbols.EURUSD], EntryTime = time, EntryPrice = 1.07m, Direction = TradeDirection.Short, @@ -522,7 +522,7 @@ private IEnumerable CreateOneLoserTwoWinners() }, new Trade { - Symbol = Symbols.EURUSD, + Symbols =[Symbols.EURUSD], EntryTime = time.AddMinutes(10), EntryPrice = 1.08m, Direction = TradeDirection.Long, @@ -536,7 +536,7 @@ private IEnumerable CreateOneLoserTwoWinners() }, new Trade { - Symbol = Symbols.EURUSD, + Symbols =[Symbols.EURUSD], EntryTime = time.AddMinutes(30), EntryPrice = 1.08m, Direction = TradeDirection.Long, @@ -618,7 +618,7 @@ private IEnumerable CreateITMOptionAssignment(bool win) { new Trade { - Symbol = Symbols.SPY_C_192_Feb19_2016, + Symbols = [Symbols.SPY_C_192_Feb19_2016], EntryTime = time, EntryPrice = 80m, Direction = TradeDirection.Long, @@ -633,7 +633,7 @@ private IEnumerable CreateITMOptionAssignment(bool win) }, new Trade { - Symbol = Symbols.SPY, + Symbols =[Symbols.SPY], EntryTime = time.AddMinutes(20), EntryPrice = 192m, Direction = TradeDirection.Long, From c707c7d3e144fcbac5f40246054318c436b950ab Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Fri, 16 Jan 2026 14:08:11 -0400 Subject: [PATCH 6/7] Add trades json serialization tests --- Tests/Common/Statistics/TradeTests.cs | 125 ++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 Tests/Common/Statistics/TradeTests.cs diff --git a/Tests/Common/Statistics/TradeTests.cs b/Tests/Common/Statistics/TradeTests.cs new file mode 100644 index 000000000000..f681803d1107 --- /dev/null +++ b/Tests/Common/Statistics/TradeTests.cs @@ -0,0 +1,125 @@ +/* + * 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 Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NUnit.Framework; +using QuantConnect.Statistics; +using System; + +namespace QuantConnect.Tests.Common.Statistics +{ + [TestFixture] + class TradeTests + { + [Test] + public void JsonSerializationRoundTrip() + { + var trade = MakeTrade(); + + var json = JsonConvert.SerializeObject(trade); + var deserializedTrade = JsonConvert.DeserializeObject(json); + CollectionAssert.AreEqual(trade.Symbols, deserializedTrade.Symbols); + Assert.AreEqual(trade.EntryTime, deserializedTrade.EntryTime); + Assert.AreEqual(trade.EntryPrice, deserializedTrade.EntryPrice); + Assert.AreEqual(trade.Direction, deserializedTrade.Direction); + Assert.AreEqual(trade.Quantity, deserializedTrade.Quantity); + Assert.AreEqual(trade.ExitTime, deserializedTrade.ExitTime); + Assert.AreEqual(trade.ExitPrice, deserializedTrade.ExitPrice); + Assert.AreEqual(trade.ProfitLoss, deserializedTrade.ProfitLoss); + Assert.AreEqual(trade.TotalFees, deserializedTrade.TotalFees); + Assert.AreEqual(trade.MAE, deserializedTrade.MAE); + Assert.AreEqual(trade.MFE, deserializedTrade.MFE); + + // For backwards compatibility, also verify Symbol property is set correctly + Assert.IsNotNull(trade.Symbol); + Assert.AreEqual(trade.Symbols[0], trade.Symbol); + Assert.AreEqual(trade.Symbol, deserializedTrade.Symbol); + } + + [Test] + public void DeprecatedSymbolIsNotSerialized() + { + var trade = MakeTrade(); + var jsonStr = JsonConvert.SerializeObject(trade); + var json = JObject.Parse(jsonStr); + Assert.IsFalse(json.ContainsKey("Symbol")); + } + + [Test] + public void CanDeserializeOldFormatWithSymbol() + { + var jsonTrade = @" +{ + ""Symbol"": { + ""value"": ""EURUSD"", + ""id"": ""EURUSD 8G"", + ""permtick"": ""EURUSD"" + }, + ""EntryTime"": ""2023-01-02T12:31:45"", + ""EntryPrice"": 1.07, + ""Direction"": 0, + ""Quantity"": 1000.0, + ""ExitTime"": ""2023-01-02T12:51:45"", + ""ExitPrice"": 1.09, + ""ProfitLoss"": 20.0, + ""TotalFees"": 2.5, + ""MAE"": -5.0, + ""MFE"": 30.0, + ""Duration"": ""00:20:00"", + ""EndTradeDrawdown"": -10.0, + ""IsWin"": false, + ""OrderIds"": [] +}"; + var deserializedTrade = JsonConvert.DeserializeObject(jsonTrade); + Assert.IsNotNull(deserializedTrade); + CollectionAssert.AreEqual(new[] { Symbols.EURUSD }, deserializedTrade.Symbols); + Assert.AreEqual(new DateTime(2023, 1, 2, 12, 31, 45), deserializedTrade.EntryTime); + Assert.AreEqual(1.07m, deserializedTrade.EntryPrice); + Assert.AreEqual(TradeDirection.Long, deserializedTrade.Direction); + Assert.AreEqual(1000m, deserializedTrade.Quantity); + Assert.AreEqual(new DateTime(2023, 1, 2, 12, 51, 45), deserializedTrade.ExitTime); + Assert.AreEqual(1.09m, deserializedTrade.ExitPrice); + Assert.AreEqual(20m, deserializedTrade.ProfitLoss); + Assert.AreEqual(2.5m, deserializedTrade.TotalFees); + Assert.AreEqual(-5m, deserializedTrade.MAE); + Assert.AreEqual(30m, deserializedTrade.MFE); + // For backwards compatibility, also verify Symbol property is set correctly + Assert.IsNotNull(deserializedTrade.Symbol); + Assert.AreEqual(deserializedTrade.Symbols[0], deserializedTrade.Symbol); + } + + private static Trade MakeTrade() + { + var entryTime = new DateTime(2023, 1, 2, 12, 31, 45); + var exitTime = entryTime.AddMinutes(20); + var trade = new Trade + { + Symbols = [Symbols.EURUSD], + EntryTime = entryTime, + EntryPrice = 1.07m, + Direction = TradeDirection.Long, + Quantity = 1000, + ExitTime = exitTime, + ExitPrice = 1.09m, + ProfitLoss = 20, + TotalFees = 2.5m, + MAE = -5, + MFE = 30 + }; + return trade; + } + } +} From cbe09c6009e46ab9ce42ea2eae71f52cfa5dde82 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Fri, 16 Jan 2026 15:16:18 -0400 Subject: [PATCH 7/7] Cleanup --- Tests/Common/Statistics/TradeTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Common/Statistics/TradeTests.cs b/Tests/Common/Statistics/TradeTests.cs index f681803d1107..87599010b188 100644 --- a/Tests/Common/Statistics/TradeTests.cs +++ b/Tests/Common/Statistics/TradeTests.cs @@ -22,7 +22,7 @@ namespace QuantConnect.Tests.Common.Statistics { [TestFixture] - class TradeTests + public class TradeTests { [Test] public void JsonSerializationRoundTrip()