chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:02:50 +08:00
commit 0fc60fdcb1
5008 changed files with 910633 additions and 0 deletions
@@ -0,0 +1,50 @@
/*
* 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 QuantConnect.Optimizer.Parameters;
using System.Collections.Generic;
namespace QuantConnect.Optimizer.Analysis
{
/// <summary>
/// Bundles the inputs to the optimization analyzer: per-backtest metrics and the parameter grid spec.
/// </summary>
public class OptimizationAnalysisRunParameters
{
/// <summary>
/// Completed backtests from the optimization, already reduced to the metrics the analyzer reads.
/// </summary>
public IReadOnlyList<OptimizationBacktestMetrics> CompletedBacktests { get; }
/// <summary>
/// The optimization parameter grid spec.
/// </summary>
public IReadOnlyCollection<OptimizationParameter> OptimizationParameters { get; }
/// <summary>
/// Initializes a new instance of the <see cref="OptimizationAnalysisRunParameters"/> class.
/// </summary>
/// <param name="completedBacktests">The completed backtest metrics.</param>
/// <param name="optimizationParameters">The parameter grid spec.</param>
public OptimizationAnalysisRunParameters(
IReadOnlyList<OptimizationBacktestMetrics> completedBacktests,
IReadOnlyCollection<OptimizationParameter> optimizationParameters)
{
CompletedBacktests = completedBacktests;
OptimizationParameters = optimizationParameters;
}
}
}
+43
View File
@@ -0,0 +1,43 @@
/*
* 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 System.Collections.Generic;
namespace QuantConnect.Optimizer
{
/// <summary>
/// Per-backtest identity + Sharpe ratio shared by all optimization-analysis records that describe one backtest.
/// </summary>
public class BacktestSummary
{
/// <summary>
/// The backtest id; kept for programmatic access but not serialized into the analysis JSON.
/// </summary>
[JsonIgnore]
public string BacktestId { get; set; }
/// <summary>
/// Parameter values the backtest was run with.
/// </summary>
public IReadOnlyDictionary<string, decimal> Parameters { get; set; }
/// <summary>
/// The backtest's Sharpe ratio.
/// </summary>
public decimal SharpeRatio { get; set; }
}
}
+56
View File
@@ -0,0 +1,56 @@
/*
* 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.Collections.Generic;
namespace QuantConnect.Optimizer
{
/// <summary>
/// One k-means cluster of backtests in standardized parameter space.
/// </summary>
public class Cluster
{
/// <summary>
/// Cluster centroid in original parameter units.
/// </summary>
public IReadOnlyDictionary<string, decimal> Centroid { get; set; }
/// <summary>
/// Number of backtests assigned to this cluster.
/// </summary>
public int MemberCount { get; set; }
/// <summary>
/// Mean Sharpe ratio across the cluster's members.
/// </summary>
public decimal SharpeMean { get; set; }
/// <summary>
/// Sample standard deviation of Sharpe ratios within this cluster.
/// </summary>
public decimal SharpeStdDev { get; set; }
/// <summary>
/// Minimum Sharpe ratio within this cluster.
/// </summary>
public decimal SharpeMin { get; set; }
/// <summary>
/// Maximum Sharpe ratio within this cluster.
/// </summary>
public decimal SharpeMax { get; set; }
}
}
+41
View File
@@ -0,0 +1,41 @@
/*
* 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.Collections.Generic;
namespace QuantConnect.Optimizer
{
/// <summary>
/// Breakdown of backtests in an optimization that produced zero orders.
/// </summary>
public class FailedBacktestSummary
{
/// <summary>
/// Total number of backtests that produced zero orders.
/// </summary>
public int ZeroOrderCount { get; set; }
/// <summary>
/// Number of zero-order backtests inspected for analysis tags; may be smaller than <see cref="ZeroOrderCount"/>.
/// </summary>
public int InspectedCount { get; set; }
/// <summary>
/// Map of analysis-tag name to the number of inspected backtests carrying that tag.
/// </summary>
public IReadOnlyDictionary<string, int> AnalysisNameCounts { get; set; }
}
}
+44
View File
@@ -0,0 +1,44 @@
/*
* 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.
*
*/
namespace QuantConnect.Optimizer
{
/// <summary>
/// One linear piece of a piecewise interpolant on [<see cref="XLo"/>, <see cref="XHi"/>], evaluated as y(x) = A + B * (x - XLo).
/// </summary>
public class LinearSegment
{
/// <summary>
/// Lower bound of this segment.
/// </summary>
public decimal XLo { get; set; }
/// <summary>
/// Upper bound of this segment.
/// </summary>
public decimal XHi { get; set; }
/// <summary>
/// Sharpe ratio at <see cref="XLo"/>.
/// </summary>
public decimal A { get; set; }
/// <summary>
/// Slope through the segment.
/// </summary>
public decimal B { get; set; }
}
}
+29
View File
@@ -0,0 +1,29 @@
/*
* 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.
*
*/
namespace QuantConnect.Optimizer
{
/// <summary>
/// A local maximum of the Sharpe surface on the parameter grid; strictly greater than every face-neighbor's Sharpe.
/// </summary>
public class Mode : BacktestSummary
{
/// <summary>
/// Number of face-neighbors this backtest was compared against.
/// </summary>
public int NeighborCount { get; set; }
}
}
+87
View File
@@ -0,0 +1,87 @@
/*
* 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 QuantConnect.Util;
using System;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
namespace QuantConnect.Optimizer.Objectives
{
/// <summary>
/// A backtest optimization constraint.
/// Allows specifying statistical constraints for the optimization, eg. a backtest can't have a DrawDown less than 10%
/// </summary>
public class Constraint : Objective
{
/// <summary>
/// The target comparison operation, eg. 'Greater'
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public ComparisonOperatorTypes Operator { get; set; }
/// <summary>
/// Empty Constraint constructor
/// </summary>
public Constraint()
{
}
/// <summary>
/// Creates a new instance
/// </summary>
public Constraint(string target, ComparisonOperatorTypes @operator, decimal? targetValue) : base(target, targetValue)
{
Operator = @operator;
if (!TargetValue.HasValue)
{
throw new ArgumentNullException(nameof(targetValue), Messages.Constraint.ConstraintTargetValueNotSpecified);
}
}
/// <summary>
/// Asserts the constraint is met
/// </summary>
public bool IsMet(string jsonBacktestResult)
{
if (string.IsNullOrEmpty(jsonBacktestResult))
{
throw new ArgumentNullException(nameof(jsonBacktestResult), $"Constraint.IsMet(): {Messages.OptimizerObjectivesCommon.NullOrEmptyBacktestResult}");
}
var token = Objectives.Target.GetTokenInJsonBacktest(jsonBacktestResult, Target);
if (token == null)
{
return false;
}
return Operator.Compare(
token.Value<string>().ToNormalizedDecimal(),
TargetValue.Value);
}
/// <summary>
/// Pretty representation of a constraint
/// </summary>
public override string ToString()
{
return $"{Target} '{Operator}' {TargetValue.Value}";
}
}
}
+47
View File
@@ -0,0 +1,47 @@
/*
* 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 Newtonsoft.Json;
namespace QuantConnect.Optimizer.Objectives
{
/// <summary>
/// Define the way to compare current real-values and the new one (candidates).
/// It's encapsulated in different abstraction to allow configure the direction of optimization, i.e. max or min.
/// </summary>
[JsonConverter(typeof(ExtremumJsonConverter))]
public class Extremum
{
private Func<decimal, decimal, bool> _comparer;
/// <summary>
/// Create an instance of <see cref="Extremum"/> to compare values.
/// </summary>
/// <param name="comparer">The way old and new values should be compared</param>
public Extremum(Func<decimal, decimal, bool> comparer)
{
_comparer = comparer;
}
/// <summary>
/// Compares two values; identifies whether condition is met or not.
/// </summary>
/// <param name="current">Left operand</param>
/// <param name="candidate">Right operand</param>
/// <returns>Returns the result of comparer with this arguments</returns>
public bool Better(decimal current, decimal candidate) => _comparer(current, candidate);
}
}
@@ -0,0 +1,56 @@
/*
* 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 QuantConnect.Util;
namespace QuantConnect.Optimizer.Objectives
{
/// <summary>
/// Class for converting string values to Maximization or Minimization strategy objects
/// </summary>
public class ExtremumJsonConverter : TypeChangeJsonConverter<Extremum, string>
{
/// <summary>
/// Don't populate any property
/// </summary>
protected override bool PopulateProperties => false;
/// <summary>
/// Converts a Extremum object into a string
/// </summary>
protected override string Convert(Extremum value)
{
return value.GetType() == typeof(Maximization)
? "max"
: "min";
}
/// <summary>
/// Converts a string into its corresponding Extremum object
/// </summary>
/// <param name="value"></param>
protected override Extremum Convert(string value)
{
switch (value.ToLowerInvariant())
{
case "max": return new Maximization();
case "min": return new Minimization();
default:
throw new InvalidOperationException($"ExtremumJsonConverter.Convert: {Messages.ExtremumJsonConverter.UnrecognizedTargetDirection}");
}
}
}
}
@@ -0,0 +1,30 @@
/*
* 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.
*/
namespace QuantConnect.Optimizer.Objectives
{
/// <summary>
/// Defines standard maximization strategy, i.e. right operand is greater than left
/// </summary>
public class Maximization : Extremum
{
/// <summary>
/// Creates an instance of <see cref="Maximization"/>
/// </summary>
public Maximization() : base((v1, v2) => v1 < v2)
{
}
}
}
@@ -0,0 +1,30 @@
/*
* 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.
*/
namespace QuantConnect.Optimizer.Objectives
{
/// <summary>
/// Defines standard minimization strategy, i.e. right operand is less than left
/// </summary>
public class Minimization : Extremum
{
/// <summary>
/// Creates an instance of <see cref="Minimization"/>
/// </summary>
public Minimization() : base((v1, v2) => v1 > v2)
{
}
}
}
+99
View File
@@ -0,0 +1,99 @@
/*
* 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 System.Text.RegularExpressions;
using Newtonsoft.Json;
namespace QuantConnect.Optimizer.Objectives
{
/// <summary>
/// Base class for optimization <see cref="Objectives.Target"/> and <see cref="Constraint"/>
/// </summary>
public abstract class Objective
{
private readonly Regex _targetTemplate = new Regex("['(.+)']");
private string _target;
/// <summary>
/// Target; property of json file we want to track
/// </summary>
public string Target
{
get => _target;
set
{
_target = value != null ? string.Join(".", value.Split('.').Select(s => _targetTemplate.Match(s).Success ? s : $"['{s}']")) : value;
}
}
/// <summary>
/// Target value
/// </summary>
/// <remarks>For <see cref="Objectives.Target"/> if defined and backtest complies with the targets then finish optimization</remarks>
/// <remarks>For <see cref="Constraint"/> non optional, the value of the target constraint</remarks>
public decimal? TargetValue { get; set; }
/// <summary>
/// Creates a new instance of Objective class
/// </summary>
protected Objective()
{
}
/// <summary>
/// Creates a new instance
/// </summary>
protected Objective(string target, decimal? targetValue)
{
if (string.IsNullOrEmpty(target))
{
throw new ArgumentNullException(nameof(target), Messages.Objective.NullOrEmptyObjective);
}
var objective = target;
if (!objective.Contains('.', StringComparison.InvariantCulture))
{
// default path
objective = $"Statistics.{objective}";
}
// escape empty space in json path
Target = objective;
TargetValue = targetValue;
}
#region Backwards Compatibility
/// <summary>
/// Target value
/// </summary>
/// <remarks>For <see cref="Objectives.Target"/> if defined and backtest complies with the targets then finish optimization</remarks>
/// <remarks>For <see cref="Constraint"/> non optional, the value of the target constraint</remarks>
[JsonProperty("target-value")]
public decimal? OldTargetValue
{
set
{
TargetValue = value;
}
get
{
return TargetValue;
}
}
#endregion
}
}
+133
View File
@@ -0,0 +1,133 @@
/*
* 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 System;
namespace QuantConnect.Optimizer.Objectives
{
/// <summary>
/// The optimization statistical target
/// </summary>
public class Target: Objective
{
/// <summary>
/// Defines the direction of optimization, i.e. maximization or minimization
/// </summary>
public Extremum Extremum { get; set; }
/// <summary>
/// Current value
/// </summary>
[JsonIgnore]
public decimal? Current { get; private set; }
/// <summary>
/// Fires when target complies specified value
/// </summary>
public event EventHandler Reached;
/// <summary>
/// Creates a new instance
/// </summary>
public Target(string target, Extremum extremum, decimal? targetValue): base(target, targetValue)
{
Extremum = extremum;
}
/// <summary>
/// Creates a new instance
/// </summary>
public Target()
{
}
/// <summary>
/// Pretty representation of this optimization target
/// </summary>
public override string ToString()
{
return Messages.Target.ToString(this);
}
/// <summary>
/// Check backtest result
/// </summary>
/// <param name="jsonBacktestResult">Backtest result json</param>
/// <returns>true if found a better solution; otherwise false</returns>
public bool MoveAhead(string jsonBacktestResult)
{
if (string.IsNullOrEmpty(jsonBacktestResult))
{
throw new ArgumentNullException(nameof(jsonBacktestResult), $"Target.MoveAhead(): {Messages.OptimizerObjectivesCommon.NullOrEmptyBacktestResult}");
}
var token = GetTokenInJsonBacktest(jsonBacktestResult, Target);
if (token == null)
{
return false;
}
var computedValue = token.Value<string>().ToNormalizedDecimal();
if (!Current.HasValue || Extremum.Better(Current.Value, computedValue))
{
Current = computedValue;
return true;
}
return false;
}
/// <summary>
/// Try comply target value
/// </summary>
public void CheckCompliance()
{
if (IsComplied())
{
Reached?.Invoke(this, EventArgs.Empty);
}
}
public static JToken GetTokenInJsonBacktest(string jsonBacktestResult, string target)
{
var jObject = JObject.Parse(jsonBacktestResult);
var path = target.Replace("[", string.Empty, StringComparison.InvariantCultureIgnoreCase)
.Replace("]", string.Empty, StringComparison.InvariantCultureIgnoreCase)
.Replace("\'", string.Empty, StringComparison.InvariantCultureIgnoreCase).Split(".");
JToken token = null;
foreach (var key in path)
{
if (jObject.TryGetValue(key, StringComparison.OrdinalIgnoreCase, out token))
{
if (token is not JValue)
{
jObject = token.ToObject<JObject>();
}
}
else
{
return null;
}
}
return token;
}
private bool IsComplied() => TargetValue.HasValue && Current.HasValue && (TargetValue.Value == Current.Value || Extremum.Better(TargetValue.Value, Current.Value));
}
}
+77
View File
@@ -0,0 +1,77 @@
/*
* 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 System.Collections.Generic;
using System.ComponentModel;
namespace QuantConnect.Optimizer
{
/// <summary>
/// Aggregate diagnostic produced by analyzing a completed optimization.
/// </summary>
public class OptimizationAnalysis
{
/// <summary>
/// Natural-language interpretation of the analysis produced by a downstream AI consumer; empty until populated.
/// </summary>
[DefaultValue("")]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Interpretation { get; set; } = string.Empty;
/// <summary>
/// Total number of backtests observed, including failures.
/// </summary>
public int BacktestCountTotal { get; set; }
/// <summary>
/// Number of backtests used in the analysis after filtering failures.
/// </summary>
public int BacktestCountUsed { get; set; }
/// <summary>
/// Sharpe ratio statistics across all used backtests.
/// </summary>
public SharpeSummary OverallSharpe { get; set; }
/// <summary>
/// The best-performing backtest (argmax of Sharpe).
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public BacktestSummary Best { get; set; }
/// <summary>
/// Per-parameter sensitivity report; one entry per optimized parameter.
/// </summary>
public IReadOnlyList<ParameterReport> Parameters { get; set; }
/// <summary>
/// K-means clusters in standardized parameter space, ordered by mean Sharpe descending.
/// </summary>
public IReadOnlyList<Cluster> Clusters { get; set; }
/// <summary>
/// Local maxima of the Sharpe surface on the parameter grid, ordered by Sharpe descending.
/// </summary>
public IReadOnlyList<Mode> Modes { get; set; }
/// <summary>
/// Breakdown of zero-order backtests; null when none exist.
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public FailedBacktestSummary FailedBacktests { get; set; }
}
}
@@ -0,0 +1,115 @@
/*
* 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 QuantConnect.Logging;
using QuantConnect.Optimizer.Parameters;
using QuantConnect.Packets;
using QuantConnect.Statistics;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace QuantConnect.Optimizer
{
/// <summary>
/// Lightweight per-backtest record extracted at <see cref="LeanOptimizer"/> time to avoid retaining the full backtest JSON.
/// </summary>
public class OptimizationBacktestMetrics : BacktestSummary
{
/// <summary>
/// The backtest's total performance (wraps <see cref="QuantConnect.Statistics.TradeStatistics"/>,
/// <see cref="QuantConnect.Statistics.PortfolioStatistics"/>, and <see cref="QuantConnect.Statistics.Trade"/> list); null when absent from the backtest result.
/// </summary>
public AlgorithmPerformance TotalPerformance { get; set; }
/// <summary>
/// Number of orders the backtest produced.
/// </summary>
public int TotalOrders { get; set; }
/// <summary>
/// Names of the diagnostic <see cref="QuantConnect.Analysis"/> entries the backtest attached.
/// </summary>
public IReadOnlyList<string> AnalysisNames { get; set; }
/// <summary>
/// Extracts the fields the analyzer needs from a backtest result JSON; returns null when the parameter set is invalid.
/// </summary>
/// <param name="backtestId">The backtest id.</param>
/// <param name="parameterSet">The parameter set the backtest was run with.</param>
/// <param name="jsonBacktestResult">The serialized backtest result JSON.</param>
public static OptimizationBacktestMetrics ExtractFrom(string backtestId, ParameterSet parameterSet, string jsonBacktestResult)
{
if (parameterSet == null)
{
return null;
}
var parameters = ParseParameterSet(parameterSet);
if (parameters.Count == 0)
{
return null;
}
BacktestResult parsed = null;
if (!string.IsNullOrEmpty(jsonBacktestResult))
{
try
{
// DeserializeJson uses the LEAN-wide JsonSerializer (CamelCaseNamingStrategy + OrderJsonConverter),
// so polymorphic Orders and camelCase JSON both work without extra configuration.
parsed = jsonBacktestResult.DeserializeJson<BacktestResult>();
}
catch (JsonException ex)
{
Log.Error(ex, $"OptimizationBacktestMetrics.ExtractFrom(): failed to parse backtest result for '{backtestId}'");
}
}
var analysisNames = parsed?.Analysis == null
? (IReadOnlyList<string>)System.Array.Empty<string>()
: parsed.Analysis
.Where(a => !string.IsNullOrEmpty(a?.Name))
.Select(a => a.Name)
.ToList();
return new OptimizationBacktestMetrics
{
BacktestId = backtestId,
Parameters = parameters,
SharpeRatio = parsed?.TotalPerformance?.PortfolioStatistics?.SharpeRatio ?? 0m,
TotalPerformance = parsed?.TotalPerformance,
TotalOrders = parsed?.Orders?.Count ?? 0,
AnalysisNames = analysisNames
};
}
private static Dictionary<string, decimal> ParseParameterSet(ParameterSet parameterSet)
{
var result = new Dictionary<string, decimal>();
if (parameterSet?.Value == null) return result;
foreach (var kv in parameterSet.Value)
{
if (decimal.TryParse(kv.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var d))
{
result[kv.Key] = d;
}
}
return result;
}
}
}
+43
View File
@@ -0,0 +1,43 @@
/*
* 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.
*/
namespace QuantConnect.Optimizer
{
/// <summary>
/// The different optimization status
/// </summary>
public enum OptimizationStatus
{
/// <summary>
/// Just created and not running optimization (0)
/// </summary>
New,
/// <summary>
/// We failed or we were aborted (1)
/// </summary>
Aborted,
/// <summary>
/// We are running (2)
/// </summary>
Running,
/// <summary>
/// Optimization job has completed (3)
/// </summary>
Completed
}
}
+78
View File
@@ -0,0 +1,78 @@
/*
* 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 System.Collections.Generic;
namespace QuantConnect.Optimizer
{
/// <summary>
/// Sensitivity report for a single optimized parameter.
/// </summary>
public class ParameterReport
{
/// <summary>
/// Parameter name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Lower bound of the parameter sweep.
/// </summary>
public decimal SearchedMin { get; set; }
/// <summary>
/// Upper bound of the parameter sweep.
/// </summary>
public decimal SearchedMax { get; set; }
/// <summary>
/// Sweep step size; null when not provided in the optimization configuration.
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public decimal? Step { get; set; }
/// <summary>
/// Mean Sharpe range (max - min) across every 1-D slice.
/// </summary>
public decimal MeanWithinSliceSharpeRange { get; set; }
/// <summary>
/// Maximum Sharpe range (max - min) across every 1-D slice.
/// </summary>
public decimal MaxWithinSliceSharpeRange { get; set; }
/// <summary>
/// Worst-case Sharpe change between two adjacent grid values, scaled by <see cref="Step"/>.
/// </summary>
public decimal MaxAbsDerivativePerStep { get; set; }
/// <summary>
/// This parameter's value at the best backtest.
/// </summary>
public decimal BestValue { get; set; }
/// <summary>
/// True when <see cref="BestValue"/> lies within half a step of <see cref="SearchedMin"/> or <see cref="SearchedMax"/>.
/// </summary>
public bool BestAtSearchedEdge { get; set; }
/// <summary>
/// One-dimensional slices used for the sensitivity analysis.
/// </summary>
public IReadOnlyList<SliceFit> Slices { get; set; }
}
}
@@ -0,0 +1,75 @@
/*
* 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;
namespace QuantConnect.Optimizer.Parameters
{
/// <summary>
/// Defines the optimization parameter meta information
/// </summary>
[JsonConverter(typeof(OptimizationParameterJsonConverter))]
public abstract class OptimizationParameter
{
/// <summary>
/// Name of optimization parameter
/// </summary>
[JsonProperty("name")]
public string Name { get; }
/// <summary>
/// Create an instance of <see cref="OptimizationParameter"/> based on configuration
/// </summary>
/// <param name="name">parameter name</param>
protected OptimizationParameter(string name)
{
Name = name;
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>
/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
public bool Equals(OptimizationParameter other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return string.Equals(Name, other?.Name);
}
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object. </param>
/// <returns>
/// true if the specified object is equal to the current object; otherwise, false.
/// </returns>
public override bool Equals(object obj)
{
return Equals(obj as OptimizationParameter);
}
/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns>
/// A hash code for the current object.
/// </returns>
public override int GetHashCode() => this.Name.GetHashCode();
}
}
@@ -0,0 +1,121 @@
/*
* 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 System;
using System.Reflection;
namespace QuantConnect.Optimizer.Parameters
{
/// <summary>
/// Override <see cref="OptimizationParameter"/> deserialization method.
/// Can handle <see cref="OptimizationStepParameter"/> instances
/// </summary>
public class OptimizationParameterJsonConverter : JsonConverter
{
/// <summary>
/// Writes a JSON object from a OptimizationParameter object
/// </summary>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JObject jo = new JObject();
Type type = value.GetType();
foreach (PropertyInfo prop in type.GetProperties())
{
if (prop.CanRead)
{
var attribute = prop.GetCustomAttribute<JsonPropertyAttribute>();
object propVal = prop.GetValue(value, null);
if (propVal != null)
{
jo.Add(attribute.PropertyName ?? prop.Name, JToken.FromObject(propVal, serializer));
}
}
}
jo.WriteTo(writer);
}
/// <summary>
/// Creates a Optimization Parameter object from a JSON object
/// </summary>
public override object ReadJson(
JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer
)
{
JObject token = JObject.Load(reader);
var parameterName = (token.GetValue("name", StringComparison.OrdinalIgnoreCase) ?? token.GetValue("key", StringComparison.OrdinalIgnoreCase))?.Value<string>();
if (string.IsNullOrEmpty(parameterName))
{
throw new ArgumentException(Messages.OptimizationParameterJsonConverter.OptimizationParameterNotSpecified);
}
JToken value;
JToken minToken;
JToken maxToken;
OptimizationParameter optimizationParameter = null;
if (token.TryGetValue("min", StringComparison.OrdinalIgnoreCase, out minToken) &&
token.TryGetValue("max", StringComparison.OrdinalIgnoreCase, out maxToken))
{
var stepToken = token.GetValue("step", StringComparison.OrdinalIgnoreCase)?.Value<decimal>();
var minStepToken = token.GetValue("minStep", StringComparison.OrdinalIgnoreCase)?.Value<decimal>() ?? token.GetValue("min-step", StringComparison.OrdinalIgnoreCase)?.Value<decimal>();
if (stepToken.HasValue)
{
if (minStepToken.HasValue)
{
optimizationParameter = new OptimizationStepParameter(parameterName,
minToken.Value<decimal>(),
maxToken.Value<decimal>(),
stepToken.Value,
minStepToken.Value);
}
else
{
optimizationParameter = new OptimizationStepParameter(parameterName,
minToken.Value<decimal>(),
maxToken.Value<decimal>(),
stepToken.Value);
}
}
else
{
optimizationParameter = new OptimizationStepParameter(parameterName,
minToken.Value<decimal>(),
maxToken.Value<decimal>());
}
}
else if(token.TryGetValue("value", StringComparison.OrdinalIgnoreCase, out value))
{
optimizationParameter = new StaticOptimizationParameter(parameterName, value.Value<string>());
}
if (optimizationParameter == null)
{
throw new ArgumentException(Messages.OptimizationParameterJsonConverter.OptimizationParameterNotSupported);
}
return optimizationParameter;
}
/// <summary>
/// Determines if an OptimizationParameter is assignable from the given object type
/// </summary>
public override bool CanConvert(Type objectType) => typeof(OptimizationParameter).IsAssignableFrom(objectType);
}
}
@@ -0,0 +1,115 @@
/*
* 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 System;
namespace QuantConnect.Optimizer.Parameters
{
/// <summary>
/// Defines the step based optimization parameter
/// </summary>
public class OptimizationStepParameter : OptimizationParameter
{
/// <summary>
/// Minimum value of optimization parameter, applicable for boundary conditions
/// </summary>
[JsonProperty("min")]
public decimal MinValue { get; }
/// <summary>
/// Maximum value of optimization parameter, applicable for boundary conditions
/// </summary>
[JsonProperty("max")]
public decimal MaxValue { get; }
/// <summary>
/// Movement, should be positive
/// </summary>
[JsonProperty("step")]
public decimal? Step { get; set; }
#pragma warning disable CS1574
/// <summary>
/// Minimal possible movement for current parameter, should be positive
/// </summary>
/// <remarks>Used by <see cref="Strategies.EulerSearchOptimizationStrategy"/> to determine when this parameter can no longer be optimized</remarks>
[JsonProperty("minStep")]
#pragma warning restore CS1574
public decimal? MinStep { get; set; }
/// <summary>
/// Create an instance of <see cref="OptimizationParameter"/> based on configuration
/// </summary>
/// <param name="name">parameter name</param>
/// <param name="min">minimal value</param>
/// <param name="max">maximal value</param>
public OptimizationStepParameter(string name, decimal min, decimal max)
: base(name)
{
if (min > max)
{
throw new ArgumentException(Messages.OptimizationStepParameter.InvalidStepRange(min, max));
}
MinValue = min;
MaxValue = max;
}
/// <summary>
/// Create an instance of <see cref="OptimizationParameter"/> based on configuration
/// </summary>
/// <param name="name">parameter name</param>
/// <param name="min">minimal value</param>
/// <param name="max">maximal value</param>
/// <param name="step">movement</param>
public OptimizationStepParameter(string name, decimal min, decimal max, decimal step)
: this(name, min, max, step, step)
{
}
/// <summary>
/// Create an instance of <see cref="OptimizationParameter"/> based on configuration
/// </summary>
/// <param name="name">parameter name</param>
/// <param name="min">minimal value</param>
/// <param name="max">maximal value</param>
/// <param name="step">movement</param>
/// <param name="minStep">minimal possible movement</param>
public OptimizationStepParameter(string name, decimal min, decimal max, decimal step, decimal minStep) : this(name, min, max)
{
// with zero step algorithm can go to infinite loop, use default step value
if (step <= 0)
{
throw new ArgumentException(Messages.OptimizationStepParameter.NonPositiveStepValue(nameof(step), step));
}
// EulerSearch algorithm can go to infinite range division if Min step is not provided, use Step as default
if (minStep <= 0)
{
throw new ArgumentException(Messages.OptimizationStepParameter.NonPositiveStepValue(nameof(minStep), minStep));
}
if (step < minStep)
{
throw new ArgumentException(Messages.OptimizationStepParameter.StepLessThanMinStep);
}
Step = step;
MinStep = minStep;
}
}
}
@@ -0,0 +1,63 @@
/*
* 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.Linq;
using Newtonsoft.Json;
using QuantConnect.Api;
using QuantConnect.Util;
using System.Collections.Generic;
namespace QuantConnect.Optimizer.Parameters
{
/// <summary>
/// Represents a single combination of optimization parameters
/// </summary>
[JsonConverter(typeof(ParameterSetJsonConverter))]
public class ParameterSet
{
/// <summary>
/// The unique identifier within scope (current optimization job)
/// </summary>
/// <remarks>Internal id, useful for the optimization strategy to id each generated parameter sets,
/// even before there is any backtest id</remarks>
[JsonProperty(PropertyName = "id")]
public int Id { get; }
/// <summary>
/// Represent a combination as key value of parameters, i.e. order doesn't matter
/// </summary>
[JsonProperty(PropertyName = "value", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyDictionary<string, string> Value { get; }
/// <summary>
/// Creates an instance of <see cref="ParameterSet"/> based on new combination of optimization parameters
/// </summary>
/// <param name="id">Unique identifier</param>
/// <param name="value">Combination of optimization parameters</param>
public ParameterSet(int id, IReadOnlyDictionary<string, string> value)
{
Id = id;
Value = value;
}
/// <summary>
/// String representation of this parameter set
/// </summary>
public override string ToString()
{
return string.Join(',', Value.OrderBy(kvp => kvp.Key).Select(arg => $"{arg.Key}:{arg.Value}"));
}
}
}
@@ -0,0 +1,41 @@
/*
* 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;
namespace QuantConnect.Optimizer.Parameters
{
/// <summary>
/// Defines the step based optimization parameter
/// </summary>
public class StaticOptimizationParameter : OptimizationParameter
{
/// <summary>
/// Minimum value of optimization parameter, applicable for boundary conditions
/// </summary>
[JsonProperty("value")]
public string Value { get; }
/// <summary>
/// Creates a new instance
/// </summary>
/// <param name="name">The name of the parameter</param>
/// <param name="value">The fixed value of this parameter</param>
public StaticOptimizationParameter(string name, string value) : base(name)
{
Value = value;
}
}
}
+49
View File
@@ -0,0 +1,49 @@
/*
* 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.
*
*/
namespace QuantConnect.Optimizer
{
/// <summary>
/// Sharpe ratio statistics across all used backtests in an optimization.
/// </summary>
public class SharpeSummary
{
/// <summary>
/// Arithmetic mean of Sharpe ratios.
/// </summary>
public decimal Mean { get; set; }
/// <summary>
/// Sample standard deviation of Sharpe ratios.
/// </summary>
public decimal StdDev { get; set; }
/// <summary>
/// Minimum Sharpe ratio observed.
/// </summary>
public decimal Min { get; set; }
/// <summary>
/// Maximum Sharpe ratio observed.
/// </summary>
public decimal Max { get; set; }
/// <summary>
/// Median Sharpe ratio.
/// </summary>
public decimal Median { get; set; }
}
}
+46
View File
@@ -0,0 +1,46 @@
/*
* 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.Collections.Generic;
namespace QuantConnect.Optimizer
{
/// <summary>
/// One-dimensional cross-section of the parameter space: one parameter varies while every other is held constant.
/// </summary>
public class SliceFit
{
/// <summary>
/// Values of the other parameters held constant for this slice.
/// </summary>
public IReadOnlyDictionary<string, decimal> FixedParameters { get; set; }
/// <summary>
/// Max Sharpe minus min Sharpe across this slice.
/// </summary>
public decimal SharpeRange { get; set; }
/// <summary>
/// Maximum absolute slope across this slice's linear segments.
/// </summary>
public decimal MaxAbsDerivative { get; set; }
/// <summary>
/// Piecewise linear pieces of the fit; one per adjacent pair of grid points.
/// </summary>
public IReadOnlyList<LinearSegment> Segments { get; set; }
}
}