1515
1616using System ;
1717using System . Collections . Generic ;
18- using System . Globalization ;
1918using System . Linq ;
19+ using System . Runtime . CompilerServices ;
2020using Newtonsoft . Json ;
2121using Newtonsoft . Json . Linq ;
2222using QuantConnect . Optimizer . Parameters ;
@@ -30,6 +30,43 @@ namespace QuantConnect.Api
3030 /// </summary>
3131 public class OptimizationBacktestJsonConverter : JsonConverter
3232 {
33+ private static Dictionary < string , int > StatisticsIndices = new ( )
34+ {
35+ { PerformanceMetrics . Alpha , 0 } ,
36+ { PerformanceMetrics . AnnualStandardDeviation , 1 } ,
37+ { PerformanceMetrics . AnnualVariance , 2 } ,
38+ { PerformanceMetrics . AverageLoss , 3 } ,
39+ { PerformanceMetrics . AverageWin , 4 } ,
40+ { PerformanceMetrics . Beta , 5 } ,
41+ { PerformanceMetrics . CompoundingAnnualReturn , 6 } ,
42+ { PerformanceMetrics . Drawdown , 7 } ,
43+ { PerformanceMetrics . EstimatedStrategyCapacity , 8 } ,
44+ { PerformanceMetrics . Expectancy , 9 } ,
45+ { PerformanceMetrics . InformationRatio , 10 } ,
46+ { PerformanceMetrics . LossRate , 11 } ,
47+ { PerformanceMetrics . NetProfit , 12 } ,
48+ { PerformanceMetrics . ProbabilisticSharpeRatio , 13 } ,
49+ { PerformanceMetrics . ProfitLossRatio , 14 } ,
50+ { PerformanceMetrics . SharpeRatio , 15 } ,
51+ { PerformanceMetrics . TotalFees , 16 } ,
52+ { PerformanceMetrics . TotalOrders , 17 } ,
53+ { PerformanceMetrics . TrackingError , 18 } ,
54+ { PerformanceMetrics . TreynorRatio , 19 } ,
55+ { PerformanceMetrics . WinRate , 20 } ,
56+ { PerformanceMetrics . SortinoRatio , 21 } ,
57+ { PerformanceMetrics . StartEquity , 22 } ,
58+ { PerformanceMetrics . EndEquity , 23 } ,
59+ { PerformanceMetrics . DrawdownRecovery , 24 } ,
60+ } ;
61+
62+ private static string [ ] StatisticNames { get ; } = StatisticsIndices
63+ . OrderBy ( kvp => kvp . Value )
64+ . Select ( kvp => kvp . Key )
65+ . ToArray ( ) ;
66+
67+ // Only 21 Lean statistics where supported when the serialized statistics where a json array
68+ private static int ArrayStatisticsCount = 21 ;
69+
3370 /// <summary>
3471 /// Determines whether this instance can convert the specified object type.
3572 /// </summary>
@@ -97,25 +134,23 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
97134 if ( ! optimizationBacktest . Statistics . IsNullOrEmpty ( ) )
98135 {
99136 writer . WritePropertyName ( "statistics" ) ;
100- writer . WriteStartArray ( ) ;
101- foreach ( var keyValuePair in optimizationBacktest . Statistics . OrderBy ( pair => pair . Key ) )
137+ writer . WriteStartObject ( ) ;
138+
139+ var customStatisticsNames = new HashSet < string > ( ) ;
140+
141+ foreach ( var ( name , statisticValue , index ) in optimizationBacktest . Statistics
142+ . Select ( kvp => ( Name : kvp . Key , kvp . Value , Index : StatisticsIndices . TryGetValue ( kvp . Key , out var index ) ? index : int . MaxValue ) )
143+ . OrderBy ( t => t . Index )
144+ . ThenByDescending ( t => t . Name ) )
102145 {
103- switch ( keyValuePair . Key )
104- {
105- case PerformanceMetrics . PortfolioTurnover :
106- case PerformanceMetrics . SortinoRatio :
107- case PerformanceMetrics . StartEquity :
108- case PerformanceMetrics . EndEquity :
109- case PerformanceMetrics . DrawdownRecovery :
110- continue ;
111- }
112- var statistic = keyValuePair . Value . Replace ( "%" , string . Empty ) ;
146+ var statistic = statisticValue . Replace ( "%" , string . Empty , StringComparison . InvariantCulture ) ;
113147 if ( Currencies . TryParse ( statistic , out var result ) )
114148 {
149+ writer . WritePropertyName ( index < StatisticsIndices . Count ? index . ToStringInvariant ( ) : name ) ;
115150 writer . WriteValue ( result ) ;
116151 }
117152 }
118- writer . WriteEndArray ( ) ;
153+ writer . WriteEndObject ( ) ;
119154 }
120155
121156 if ( optimizationBacktest . ParameterSet != null )
@@ -164,34 +199,25 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
164199 Dictionary < string , string > statistics = default ;
165200 if ( jStatistics != null )
166201 {
167- statistics = new Dictionary < string , string >
202+ if ( jStatistics . Type == JTokenType . Array )
168203 {
169- { PerformanceMetrics . Alpha , jStatistics [ 0 ] . Value < string > ( ) } ,
170- { PerformanceMetrics . AnnualStandardDeviation , jStatistics [ 1 ] . Value < string > ( ) } ,
171- { PerformanceMetrics . AnnualVariance , jStatistics [ 2 ] . Value < string > ( ) } ,
172- { PerformanceMetrics . AverageLoss , jStatistics [ 3 ] . Value < string > ( ) } ,
173- { PerformanceMetrics . AverageWin , jStatistics [ 4 ] . Value < string > ( ) } ,
174- { PerformanceMetrics . Beta , jStatistics [ 5 ] . Value < string > ( ) } ,
175- { PerformanceMetrics . CompoundingAnnualReturn , jStatistics [ 6 ] . Value < string > ( ) } ,
176- { PerformanceMetrics . Drawdown , jStatistics [ 7 ] . Value < string > ( ) } ,
177- { PerformanceMetrics . EstimatedStrategyCapacity , jStatistics [ 8 ] . Value < string > ( ) } ,
178- { PerformanceMetrics . Expectancy , jStatistics [ 9 ] . Value < string > ( ) } ,
179- { PerformanceMetrics . InformationRatio , jStatistics [ 10 ] . Value < string > ( ) } ,
180- { PerformanceMetrics . LossRate , jStatistics [ 11 ] . Value < string > ( ) } ,
181- { PerformanceMetrics . NetProfit , jStatistics [ 12 ] . Value < string > ( ) } ,
182- { PerformanceMetrics . ProbabilisticSharpeRatio , jStatistics [ 13 ] . Value < string > ( ) } ,
183- { PerformanceMetrics . ProfitLossRatio , jStatistics [ 14 ] . Value < string > ( ) } ,
184- { PerformanceMetrics . SharpeRatio , jStatistics [ 15 ] . Value < string > ( ) } ,
185- // TODO: Add SortinoRatio
186- // TODO: Add StartingEquity
187- // TODO: Add EndingEquity
188- // TODO: Add DrawdownRecovery
189- { PerformanceMetrics . TotalFees , jStatistics [ 16 ] . Value < string > ( ) } ,
190- { PerformanceMetrics . TotalOrders , jStatistics [ 17 ] . Value < string > ( ) } ,
191- { PerformanceMetrics . TrackingError , jStatistics [ 18 ] . Value < string > ( ) } ,
192- { PerformanceMetrics . TreynorRatio , jStatistics [ 19 ] . Value < string > ( ) } ,
193- { PerformanceMetrics . WinRate , jStatistics [ 20 ] . Value < string > ( ) } ,
194- } ;
204+ var statsCount = Math . Min ( ArrayStatisticsCount , ( jStatistics as JArray ) . Count ) ;
205+ statistics = new Dictionary < string , string > ( StatisticsIndices
206+ . Where ( kvp => kvp . Value < statsCount )
207+ . Select ( kvp => KeyValuePair . Create ( kvp . Key , jStatistics [ kvp . Value ] . Value < string > ( ) ) )
208+ . Where ( kvp => kvp . Value != null ) ) ;
209+ }
210+ else
211+ {
212+ statistics = new ( ) ;
213+ foreach ( var statistic in jStatistics . Children < JProperty > ( ) )
214+ {
215+ var statisticName = TryConvertToLeanStatisticIndex ( statistic . Name , out var index )
216+ ? StatisticNames [ index ]
217+ : statistic . Name ;
218+ statistics [ statisticName ] = statistic . Value . Value < string > ( ) ;
219+ }
220+ }
195221 }
196222
197223 var parameterSet = serializer . Deserialize < ParameterSet > ( jObject [ "parameterSet" ] . CreateReader ( ) ) ;
@@ -220,5 +246,11 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
220246
221247 return optimizationBacktest ;
222248 }
249+
250+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
251+ private static bool TryConvertToLeanStatisticIndex ( string statistic , out int index )
252+ {
253+ return int . TryParse ( statistic , out index ) && index >= 0 && index < StatisticsIndices . Count ;
254+ }
223255 }
224256}
0 commit comments