Skip to content

Commit 27cf67d

Browse files
committed
Address review pt 4
1 parent 1f02c99 commit 27cf67d

3 files changed

Lines changed: 41 additions & 42 deletions

File tree

Common/Optimizer/OptimizationBacktestMetrics.cs

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
*/
1616

1717
using Newtonsoft.Json;
18-
using Newtonsoft.Json.Linq;
1918
using QuantConnect.Logging;
2019
using QuantConnect.Optimizer.Parameters;
20+
using QuantConnect.Packets;
2121
using QuantConnect.Statistics;
2222
using System.Collections.Generic;
2323
using System.Globalization;
@@ -65,40 +65,35 @@ public static OptimizationBacktestMetrics ExtractFrom(string backtestId, Paramet
6565
return null;
6666
}
6767

68-
AlgorithmPerformance totalPerformance = null;
69-
var totalOrders = 0;
70-
IReadOnlyList<string> analysisNames = System.Array.Empty<string>();
71-
68+
BacktestResult parsed = null;
7269
if (!string.IsNullOrEmpty(jsonBacktestResult))
7370
{
7471
try
7572
{
76-
var jo = JObject.Parse(jsonBacktestResult);
77-
// Use case-insensitive lookups: BacktestingResultHandler serializes with CamelCaseNamingStrategy.
78-
totalPerformance = jo.GetValue("TotalPerformance", System.StringComparison.OrdinalIgnoreCase)?.ToObject<AlgorithmPerformance>();
79-
totalOrders = (jo.GetValue("Orders", System.StringComparison.OrdinalIgnoreCase) as JObject)?.Count ?? 0;
80-
var analyses = jo.GetValue("Analysis", System.StringComparison.OrdinalIgnoreCase)?.ToObject<List<QuantConnect.Analysis>>();
81-
if (analyses != null)
82-
{
83-
analysisNames = analyses
84-
.Where(a => !string.IsNullOrEmpty(a?.Name))
85-
.Select(a => a.Name)
86-
.ToList();
87-
}
73+
// DeserializeJson uses the LEAN-wide JsonSerializer (CamelCaseNamingStrategy + OrderJsonConverter),
74+
// so polymorphic Orders and camelCase JSON both work without extra configuration.
75+
parsed = jsonBacktestResult.DeserializeJson<BacktestResult>();
8876
}
8977
catch (JsonException ex)
9078
{
9179
Log.Error(ex, $"OptimizationBacktestMetrics.ExtractFrom(): failed to parse backtest result for '{backtestId}'");
9280
}
9381
}
9482

83+
var analysisNames = parsed?.Analysis == null
84+
? (IReadOnlyList<string>)System.Array.Empty<string>()
85+
: parsed.Analysis
86+
.Where(a => !string.IsNullOrEmpty(a?.Name))
87+
.Select(a => a.Name)
88+
.ToList();
89+
9590
return new OptimizationBacktestMetrics
9691
{
9792
BacktestId = backtestId,
9893
Parameters = parameters,
99-
SharpeRatio = totalPerformance?.PortfolioStatistics?.SharpeRatio ?? 0m,
100-
TotalPerformance = totalPerformance,
101-
TotalOrders = totalOrders,
94+
SharpeRatio = parsed?.TotalPerformance?.PortfolioStatistics?.SharpeRatio ?? 0m,
95+
TotalPerformance = parsed?.TotalPerformance,
96+
TotalOrders = parsed?.Orders?.Count ?? 0,
10297
AnalysisNames = analysisNames
10398
};
10499
}

Tests/Optimizer/Analysis/LeanOptimizerAnalysisTests.cs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
using QuantConnect.Optimizer;
2020
using QuantConnect.Optimizer.Objectives;
2121
using QuantConnect.Optimizer.Parameters;
22+
using QuantConnect.Orders;
23+
using QuantConnect.Packets;
24+
using QuantConnect.Statistics;
2225
using QuantConnect.Util;
2326
using System;
2427
using System.Collections.Generic;
@@ -120,23 +123,22 @@ protected override string RunLean(ParameterSet parameterSet, string backtestName
120123
var y = parameterSet.Value.TryGetValue("y", out var ys) && decimal.TryParse(ys, NumberStyles.Any, CultureInfo.InvariantCulture, out var yv) ? yv : 0m;
121124
// Math.Pow is double-only; cross into double for the surface and back.
122125
var sharpe = (decimal)(1.0 - 0.05 * Math.Pow((double)x - 3, 2) - 0.0005 * Math.Pow((double)y - 25, 2));
123-
var orders = Enumerable.Range(1, 10).ToDictionary(i => i, i => new { Id = i });
124-
var payload = new
126+
// Build a real BacktestResult and serialize via the LEAN-wide JsonSerializer
127+
// so the JSON shape matches what BacktestingResultHandler produces.
128+
var result = new QuantConnect.Packets.BacktestResult
125129
{
126130
// Statistics dict is what the optimizer's Criterion targets (e.g. "Statistics.Profit").
127131
Statistics = new Dictionary<string, string>
128132
{
129133
["Profit"] = (x + y).ToString(CultureInfo.InvariantCulture)
130134
},
131135
// Typed TotalPerformance.PortfolioStatistics is what the analyzer reads.
132-
TotalPerformance = new
133-
{
134-
PortfolioStatistics = new { SharpeRatio = sharpe }
135-
},
136-
Orders = orders,
136+
TotalPerformance = new AlgorithmPerformance(),
137+
Orders = Enumerable.Range(1, 10).ToDictionary(i => i, i => (Order)new MarketOrder()),
137138
Analysis = Array.Empty<QuantConnect.Analysis>()
138139
};
139-
NewResult(JsonConvert.SerializeObject(payload), id);
140+
result.TotalPerformance.PortfolioStatistics.SharpeRatio = sharpe;
141+
NewResult(result.SerializeJsonToString(), id);
140142
});
141143
return id;
142144
}

Tests/Optimizer/Analysis/OptimizationAnalyzerTests.cs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
using QuantConnect.Optimizer;
2020
using QuantConnect.Optimizer.Analysis;
2121
using QuantConnect.Optimizer.Parameters;
22+
using QuantConnect.Orders;
23+
using QuantConnect.Packets;
24+
using QuantConnect.Statistics;
2225
using System.Collections.Generic;
2326
using System.Globalization;
2427
using System.Linq;
@@ -201,21 +204,20 @@ private static List<OptimizationBacktestMetrics> BuildGridBacktests(
201204

202205
private static string BuildBacktestJson(decimal sharpe, int totalOrders, string[] analysisNames)
203206
{
204-
var analyses = (analysisNames ?? System.Array.Empty<string>())
205-
.Select(n => new QuantConnect.Analysis(n, "issue", null, null, System.Array.Empty<string>()))
206-
.ToList();
207-
// Mirror BacktestingResultHandler's output: typed TotalPerformance.PortfolioStatistics
208-
// plus an Orders dict the analyzer counts for the failure breakdown.
209-
var orders = Enumerable.Range(1, totalOrders).ToDictionary(i => i, i => new { Id = i });
210-
return JsonConvert.SerializeObject(new
207+
// Build a real BacktestResult and serialize through the LEAN-wide JsonSerializer
208+
// (CamelCaseNamingStrategy) so the JSON shape matches what BacktestingResultHandler
209+
// produces in production — which is what OptimizationBacktestMetrics.ExtractFrom
210+
// round-trips through DeserializeJson<BacktestResult>.
211+
var result = new QuantConnect.Packets.BacktestResult
211212
{
212-
TotalPerformance = new
213-
{
214-
PortfolioStatistics = new { SharpeRatio = sharpe }
215-
},
216-
Orders = orders,
217-
Analysis = analyses
218-
});
213+
TotalPerformance = new AlgorithmPerformance(),
214+
Orders = Enumerable.Range(1, totalOrders).ToDictionary(i => i, i => (Order)new MarketOrder()),
215+
Analysis = (analysisNames ?? System.Array.Empty<string>())
216+
.Select(n => new QuantConnect.Analysis(n, "issue", null, null, System.Array.Empty<string>()))
217+
.ToList()
218+
};
219+
result.TotalPerformance.PortfolioStatistics.SharpeRatio = sharpe;
220+
return result.SerializeJsonToString();
219221
}
220222

221223
private static HashSet<OptimizationParameter> BuildGridParameters(int xCount, int yCount)

0 commit comments

Comments
 (0)