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,150 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Optimizer;
using QuantConnect.Optimizer.Objectives;
using QuantConnect.Optimizer.Parameters;
using QuantConnect.Orders;
using QuantConnect.Packets;
using QuantConnect.Statistics;
using QuantConnect.Util;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace QuantConnect.Tests.Optimizer.Analysis
{
/// <summary>
/// End-to-end tests for <see cref="LeanOptimizer"/>'s analyzer wiring via the <see cref="LeanOptimizer.Ended"/> event.
/// </summary>
[TestFixture, Parallelizable(ParallelScope.Self)]
public class LeanOptimizerAnalysisTests
{
[Test]
public void Ended_AttachesAnalysis_WhenBacktestsCarrySharpeRatios()
{
using var resetEvent = new ManualResetEvent(false);
var packet = new OptimizationNodePacket
{
Criterion = new Target("Profit", new Maximization(), null),
OptimizationParameters = new HashSet<OptimizationParameter>
{
new OptimizationStepParameter("x", 1, 4, 1),
new OptimizationStepParameter("y", 10, 40, 10)
},
MaximumConcurrentBacktests = 8
};
using var optimizer = new SharpeEmittingFakeLeanOptimizer(packet);
OptimizationResult result = null;
optimizer.Ended += (s, solution) =>
{
result = solution;
optimizer.DisposeSafely();
resetEvent.Set();
};
optimizer.Start();
resetEvent.WaitOne();
Assert.NotNull(result);
Assert.NotNull(result.Analysis, "Analysis should be populated when backtests have Sharpe ratios");
Assert.Greater(result.Analysis.BacktestCountUsed, 0);
Assert.NotNull(result.Analysis.Best);
Assert.NotNull(result.Analysis.OverallSharpe);
Assert.AreEqual(2, result.Analysis.Parameters.Count);
}
[Test]
public void Ended_LeavesAnalysisNull_WhenNoBacktestCarriesSharpe()
{
// FakeLeanOptimizer's payload carries no Sharpe; analyzer must safely skip.
using var resetEvent = new ManualResetEvent(false);
var packet = new OptimizationNodePacket
{
Criterion = new Target("Profit", new Maximization(), null),
OptimizationParameters = new HashSet<OptimizationParameter>
{
new OptimizationStepParameter("ema-slow", 1, 5, 1),
new OptimizationStepParameter("ema-fast", 10, 50, 10)
},
MaximumConcurrentBacktests = 8
};
using var optimizer = new FakeLeanOptimizer(packet);
OptimizationResult result = null;
optimizer.Ended += (s, solution) =>
{
result = solution;
optimizer.DisposeSafely();
resetEvent.Set();
};
optimizer.Start();
resetEvent.WaitOne();
Assert.NotNull(result, "Ended must still fire even with no analyzable backtests");
Assert.IsNull(result.Analysis, "Analysis should be null when no backtest carries a Sharpe ratio");
}
/// <summary>
/// <see cref="LeanOptimizer"/> fake that emits backtest JSON shaped like a real one, with a deterministic Sharpe.
/// </summary>
private sealed class SharpeEmittingFakeLeanOptimizer : LeanOptimizer
{
public SharpeEmittingFakeLeanOptimizer(OptimizationNodePacket nodePacket) : base(nodePacket)
{
}
protected override string RunLean(ParameterSet parameterSet, string backtestName)
{
var id = Guid.NewGuid().ToString();
Task.Delay(10).ContinueWith(_ =>
{
var x = parameterSet.Value.TryGetValue("x", out var xs) && decimal.TryParse(xs, NumberStyles.Any, CultureInfo.InvariantCulture, out var xv) ? xv : 0m;
var y = parameterSet.Value.TryGetValue("y", out var ys) && decimal.TryParse(ys, NumberStyles.Any, CultureInfo.InvariantCulture, out var yv) ? yv : 0m;
// Math.Pow is double-only; cross into double for the surface and back.
var sharpe = (decimal)(1.0 - 0.05 * Math.Pow((double)x - 3, 2) - 0.0005 * Math.Pow((double)y - 25, 2));
// Build a real BacktestResult and serialize via the LEAN-wide JsonSerializer
// so the JSON shape matches what BacktestingResultHandler produces.
var result = new QuantConnect.Packets.BacktestResult
{
// Statistics dict is what the optimizer's Criterion targets (e.g. "Statistics.Profit").
Statistics = new Dictionary<string, string>
{
["Profit"] = (x + y).ToString(CultureInfo.InvariantCulture)
},
// Typed TotalPerformance.PortfolioStatistics is what the analyzer reads.
TotalPerformance = new AlgorithmPerformance(),
Orders = Enumerable.Range(1, 10).ToDictionary(i => i, i => (Order)new MarketOrder()),
Analysis = Array.Empty<QuantConnect.Analysis>()
};
result.TotalPerformance.PortfolioStatistics.SharpeRatio = sharpe;
NewResult(result.SerializeJsonToString(), id);
});
return id;
}
protected override void AbortLean(string backtestId) { }
protected override void SendUpdate() { }
}
}
}
@@ -0,0 +1,232 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Optimizer;
using QuantConnect.Optimizer.Analysis;
using QuantConnect.Optimizer.Parameters;
using QuantConnect.Orders;
using QuantConnect.Packets;
using QuantConnect.Statistics;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace QuantConnect.Tests.Optimizer.Analysis
{
[TestFixture, Parallelizable(ParallelScope.Self)]
public class OptimizationAnalyzerTests
{
[Test]
public void Run_ProducesOverallSharpeStats()
{
// 3x3 grid of synthetic Sharpe values.
var sharpes = new decimal[,]
{
{ 0.10m, 0.20m, 0.30m },
{ 0.15m, 0.25m, 0.35m },
{ 0.18m, 0.28m, 0.38m }
};
var backtests = BuildGridBacktests(sharpes, totalOrders: 5);
var parameters = BuildGridParameters(xCount: 3, yCount: 3);
var analyzer = new OptimizationAnalyzer();
var analysis = analyzer.Run(new OptimizationAnalysisRunParameters(backtests, parameters));
Assert.NotNull(analysis);
Assert.AreEqual(9, analysis.BacktestCountUsed);
Assert.AreEqual(9, analysis.BacktestCountTotal);
// Mean = average of {0.10..0.38}.
Assert.That(analysis.OverallSharpe.Mean, Is.EqualTo(0.2433m).Within(0.001m));
Assert.AreEqual(0.10m, analysis.OverallSharpe.Min);
Assert.AreEqual(0.38m, analysis.OverallSharpe.Max);
}
[Test]
public void Run_BestBacktestIsArgmaxSharpe()
{
var sharpes = new decimal[,]
{
{ 0.10m, 0.20m, 0.30m },
{ 0.15m, 0.25m, 0.35m },
{ 0.18m, 0.28m, 0.99m } // peak at (2, 2)
};
var backtests = BuildGridBacktests(sharpes, totalOrders: 5);
var parameters = BuildGridParameters(xCount: 3, yCount: 3);
var analysis = new OptimizationAnalyzer().Run(new OptimizationAnalysisRunParameters(backtests, parameters));
Assert.NotNull(analysis.Best);
Assert.AreEqual(0.99m, analysis.Best.SharpeRatio);
// Parameters at (xIndex=2, yIndex=2). Grid x: {1,2,3}; y: {10,20,30}.
Assert.AreEqual(3m, analysis.Best.Parameters["x"]);
Assert.AreEqual(30m, analysis.Best.Parameters["y"]);
}
[Test]
public void Run_FindsInteriorMode()
{
// 3x3 with a single interior peak at (1, 1): should produce one mode with 4 neighbors.
var sharpes = new decimal[,]
{
{ 0.10m, 0.20m, 0.10m },
{ 0.20m, 0.99m, 0.20m },
{ 0.10m, 0.20m, 0.10m }
};
var backtests = BuildGridBacktests(sharpes, totalOrders: 5);
var parameters = BuildGridParameters(xCount: 3, yCount: 3);
var analysis = new OptimizationAnalyzer().Run(new OptimizationAnalysisRunParameters(backtests, parameters));
Assert.AreEqual(1, analysis.Modes.Count);
Assert.AreEqual(0.99m, analysis.Modes[0].SharpeRatio);
Assert.AreEqual(4, analysis.Modes[0].NeighborCount);
}
[Test]
public void Run_ClusterCountRespectsSqrtCap()
{
// 4 backtests -> ceil(sqrt(4)) = 2 -> max 2 clusters.
var sharpes = new decimal[,]
{
{ 0.10m, 0.20m },
{ 0.30m, 0.40m }
};
var backtests = BuildGridBacktests(sharpes, totalOrders: 5);
var parameters = BuildGridParameters(xCount: 2, yCount: 2);
var analysis = new OptimizationAnalyzer().Run(new OptimizationAnalysisRunParameters(backtests, parameters));
Assert.LessOrEqual(analysis.Clusters.Count, 2);
}
[Test]
public void Run_BuildsFailedBacktestSummary_FromZeroOrderBacktests()
{
// 2x2 grid; every backtest has zero orders and carries known analysis tags.
var sharpes = new decimal[,]
{
{ 0m, 0m },
{ 0m, 0m }
};
var backtests = BuildGridBacktests(
sharpes,
totalOrders: 0,
analysisNames: new[] { "FlatEquityCurveAnalysis", "ExecutionSpeedAnalysis" });
var parameters = BuildGridParameters(xCount: 2, yCount: 2);
var analysis = new OptimizationAnalyzer().Run(new OptimizationAnalysisRunParameters(backtests, parameters));
Assert.NotNull(analysis.FailedBacktests);
Assert.AreEqual(4, analysis.FailedBacktests.ZeroOrderCount);
Assert.AreEqual(4, analysis.FailedBacktests.InspectedCount);
Assert.AreEqual(4, analysis.FailedBacktests.AnalysisNameCounts["FlatEquityCurveAnalysis"]);
Assert.AreEqual(4, analysis.FailedBacktests.AnalysisNameCounts["ExecutionSpeedAnalysis"]);
}
[Test]
public void Run_OmitsFailedBacktestSummary_WhenAllBacktestsTrade()
{
var sharpes = new decimal[,]
{
{ 0.10m, 0.20m },
{ 0.30m, 0.40m }
};
var backtests = BuildGridBacktests(sharpes, totalOrders: 5);
var parameters = BuildGridParameters(xCount: 2, yCount: 2);
var analysis = new OptimizationAnalyzer().Run(new OptimizationAnalysisRunParameters(backtests, parameters));
Assert.IsNull(analysis.FailedBacktests);
}
[Test]
public void ExtractFrom_ParsesSharpeAndAnalysisNamesFromBacktestJson()
{
var parameterSet = new ParameterSet(0, new Dictionary<string, string> { ["x"] = "1", ["y"] = "10" });
var json = BuildBacktestJson(0.75m, totalOrders: 12, new[] { "FlatEquityCurveAnalysis" });
var metrics = OptimizationBacktestMetrics.ExtractFrom("bt-0", parameterSet, json);
Assert.NotNull(metrics);
Assert.NotNull(metrics.TotalPerformance?.PortfolioStatistics);
Assert.AreEqual(0.75m, metrics.SharpeRatio);
Assert.AreEqual(0.75m, metrics.TotalPerformance.PortfolioStatistics.SharpeRatio);
Assert.AreEqual(12, metrics.TotalOrders);
CollectionAssert.AreEqual(new[] { "FlatEquityCurveAnalysis" }, metrics.AnalysisNames.ToArray());
Assert.AreEqual(1m, metrics.Parameters["x"]);
Assert.AreEqual(10m, metrics.Parameters["y"]);
}
// ── helpers ──────────────────────────────────────────────────────────────
private static List<OptimizationBacktestMetrics> BuildGridBacktests(
decimal[,] sharpes,
int totalOrders,
string[] analysisNames = null)
{
var result = new List<OptimizationBacktestMetrics>();
var xCount = sharpes.GetLength(0);
var yCount = sharpes.GetLength(1);
var id = 0;
for (var i = 0; i < xCount; i++)
{
for (var j = 0; j < yCount; j++)
{
var paramSet = new ParameterSet(id, new Dictionary<string, string>
{
["x"] = (i + 1).ToString(CultureInfo.InvariantCulture),
["y"] = ((j + 1) * 10).ToString(CultureInfo.InvariantCulture)
});
var json = BuildBacktestJson(sharpes[i, j], totalOrders, analysisNames);
result.Add(OptimizationBacktestMetrics.ExtractFrom($"backtest-{id}", paramSet, json));
id++;
}
}
return result;
}
private static string BuildBacktestJson(decimal sharpe, int totalOrders, string[] analysisNames)
{
// Build a real BacktestResult and serialize through the LEAN-wide JsonSerializer
// (CamelCaseNamingStrategy) so the JSON shape matches what BacktestingResultHandler
// produces in production — which is what OptimizationBacktestMetrics.ExtractFrom
// round-trips through DeserializeJson<BacktestResult>.
var result = new QuantConnect.Packets.BacktestResult
{
TotalPerformance = new AlgorithmPerformance(),
Orders = Enumerable.Range(1, totalOrders).ToDictionary(i => i, i => (Order)new MarketOrder()),
Analysis = (analysisNames ?? System.Array.Empty<string>())
.Select(n => new QuantConnect.Analysis(n, "issue", null, null, System.Array.Empty<string>()))
.ToList()
};
result.TotalPerformance.PortfolioStatistics.SharpeRatio = sharpe;
return result.SerializeJsonToString();
}
private static HashSet<OptimizationParameter> BuildGridParameters(int xCount, int yCount)
{
return new HashSet<OptimizationParameter>
{
new OptimizationStepParameter("x", 1, xCount, 1),
new OptimizationStepParameter("y", 10, yCount * 10, 10)
};
}
}
}
+79
View File
@@ -0,0 +1,79 @@
/*
* 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.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using QuantConnect.Optimizer;
using QuantConnect.Optimizer.Parameters;
namespace QuantConnect.Tests.Optimizer
{
public class FakeLeanOptimizer : LeanOptimizer
{
private readonly HashSet<string> _backtests = new HashSet<string>();
public event EventHandler Update;
public FakeLeanOptimizer(OptimizationNodePacket nodePacket)
: base(nodePacket)
{
}
protected override string RunLean(ParameterSet parameterSet, string backtestName)
{
var id = Guid.NewGuid().ToString();
_backtests.Add(id);
Task.Delay(100).ContinueWith(task =>
{
try
{
var sum = parameterSet.Value.Where(pair => pair.Key != "skipFromResultSum").Sum(s => s.Value.ToDecimal());
if (sum != 29)
{
NewResult(BacktestResult.Create(sum, sum / 100).ToJson(), id);
}
else
{
// fail some backtests by passing empty json
NewResult(string.Empty, id);
}
}
catch
{
}
});
return id;
}
protected override void AbortLean(string backtestId)
{
_backtests.Remove(backtestId);
}
protected override void SendUpdate()
{
OnUpdate();
}
private void OnUpdate()
{
Update?.Invoke(this, EventArgs.Empty);
}
}
}
+322
View File
@@ -0,0 +1,322 @@
/*
* 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;
using NUnit.Framework;
using QuantConnect.Optimizer;
using QuantConnect.Util;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using QuantConnect.Configuration;
using QuantConnect.Optimizer.Objectives;
using QuantConnect.Optimizer.Parameters;
using QuantConnect.Optimizer.Strategies;
namespace QuantConnect.Tests.Optimizer
{
[TestFixture, Parallelizable(ParallelScope.Children)]
public class LeanOptimizerTests
{
// These tests run in parallel and the LeanOptimizer constructor reads "optimization-update-interval" from the
// shared, global Config, while TrackEstimation writes it. The underlying config store is not thread-safe, so a
// write racing a concurrent read can corrupt it. Serialize the construction-time reads against that write.
private static readonly object _configLock = new object();
private static FakeLeanOptimizer CreateOptimizer(OptimizationNodePacket packet)
{
lock (_configLock)
{
return new FakeLeanOptimizer(packet);
}
}
[TestCase("QuantConnect.Optimizer.Strategies.GridSearchOptimizationStrategy")]
[TestCase("QuantConnect.Optimizer.Strategies.EulerSearchOptimizationStrategy")]
public void MaximizeNoTarget(string strategyName)
{
using var resetEvent = new ManualResetEvent(false);
var packet = new OptimizationNodePacket
{
OptimizationStrategy = strategyName,
Criterion = new Target("Profit",
new Maximization(),
null),
OptimizationParameters = new HashSet<OptimizationParameter>
{
new OptimizationStepParameter("ema-slow", 1, 10, 1),
new OptimizationStepParameter("ema-fast", 10, 100, 3)
},
MaximumConcurrentBacktests = 20,
OptimizationStrategySettings = new StepBaseOptimizationStrategySettings { DefaultSegmentAmount = 10 }
};
using var optimizer = CreateOptimizer(packet);
OptimizationResult result = null;
optimizer.Ended += (s, solution) =>
{
result = solution;
optimizer.DisposeSafely();
resetEvent.Set();
};
optimizer.Start();
resetEvent.WaitOne();
Assert.NotNull(result);
Assert.AreEqual(
110,
JsonConvert.DeserializeObject<BacktestResult>(result.JsonBacktestResult).Statistics.Profit);
Assert.AreEqual(10, result.ParameterSet.Value["ema-slow"].ToDecimal());
Assert.AreEqual(100, result.ParameterSet.Value["ema-fast"].ToDecimal());
}
[TestCase("QuantConnect.Optimizer.Strategies.GridSearchOptimizationStrategy")]
[TestCase("QuantConnect.Optimizer.Strategies.EulerSearchOptimizationStrategy")]
public void MinimizeWithTarget(string strategyName)
{
using var resetEvent = new ManualResetEvent(false);
var packet = new OptimizationNodePacket
{
OptimizationStrategy = strategyName,
Criterion = new Target("Profit", new Minimization(), 20),
OptimizationParameters = new HashSet<OptimizationParameter>
{
new OptimizationStepParameter("ema-slow", 1, 10, 1),
new OptimizationStepParameter("ema-fast", 10, 100, 3)
},
MaximumConcurrentBacktests = 20,
OptimizationStrategySettings = new StepBaseOptimizationStrategySettings { DefaultSegmentAmount = 10 }
};
using var optimizer = CreateOptimizer(packet);
OptimizationResult result = null;
optimizer.Ended += (s, solution) =>
{
result = solution;
optimizer.DisposeSafely();
resetEvent.Set();
};
optimizer.Start();
resetEvent.WaitOne();
Assert.NotNull(result);
Assert.GreaterOrEqual(
20,
JsonConvert.DeserializeObject<BacktestResult>(result.JsonBacktestResult).Statistics.Profit);
}
[Test]
public void MaximizeGridWithConstraints()
{
using var resetEvent = new ManualResetEvent(false);
var packet = new OptimizationNodePacket
{
Criterion = new Target("Profit", new Maximization(), null),
OptimizationParameters = new HashSet<OptimizationParameter>
{
new OptimizationStepParameter("ema-slow", 1, 10, 1m),
new OptimizationStepParameter("ema-fast", 10, 100, 3m)
},
Constraints = new List<Constraint>
{
new Constraint("Drawdown", ComparisonOperatorTypes.LessOrEqual, 0.15m)
},
MaximumConcurrentBacktests = 20
};
using var optimizer = CreateOptimizer(packet);
OptimizationResult result = null;
optimizer.Ended += (s, solution) =>
{
result = solution;
optimizer.DisposeSafely();
resetEvent.Set();
};
optimizer.Start();
resetEvent.WaitOne();
Assert.NotNull(result);
Assert.AreEqual(
15,
JsonConvert.DeserializeObject<BacktestResult>(result.JsonBacktestResult).Statistics.Profit);
Assert.AreEqual(
0.15m,
JsonConvert.DeserializeObject<BacktestResult>(result.JsonBacktestResult).Statistics.Drawdown);
}
[Test]
public void MaximizeEulerWithConstraints()
{
using var resetEvent = new ManualResetEvent(false);
var packet = new OptimizationNodePacket
{
OptimizationStrategy = "QuantConnect.Optimizer.Strategies.EulerSearchOptimizationStrategy",
Criterion = new Target("Profit", new Maximization(), null),
OptimizationParameters = new HashSet<OptimizationParameter>
{
new OptimizationStepParameter("ema-slow", 1, 10, 1),
new OptimizationStepParameter("ema-fast", 10, 100, 10m, 0.1m)
},
Constraints = new List<Constraint>
{
new Constraint("Drawdown", ComparisonOperatorTypes.LessOrEqual, 0.15m)
},
MaximumConcurrentBacktests = 20,
OptimizationStrategySettings = new StepBaseOptimizationStrategySettings { DefaultSegmentAmount = 10 }
};
using var optimizer = CreateOptimizer(packet);
OptimizationResult result = null;
optimizer.Ended += (s, solution) =>
{
result = solution;
optimizer.DisposeSafely();
resetEvent.Set();
};
optimizer.Start();
resetEvent.WaitOne();
Assert.NotNull(result);
Assert.AreEqual(
15,
JsonConvert.DeserializeObject<BacktestResult>(result.JsonBacktestResult).Statistics.Profit);
Assert.AreEqual(
0.15m,
JsonConvert.DeserializeObject<BacktestResult>(result.JsonBacktestResult).Statistics.Drawdown);
}
[Test]
public void MinimizeWithTargetAndConstraints()
{
using var resetEvent = new ManualResetEvent(false);
var packet = new OptimizationNodePacket
{
Criterion = new Target("Profit", new Minimization(), 20),
OptimizationParameters = new HashSet<OptimizationParameter>
{
new OptimizationStepParameter("ema-slow", 1, 10, 1),
new OptimizationStepParameter("ema-fast", 10, 100, 3)
},
Constraints = new List<Constraint>
{
new Constraint("Drawdown", ComparisonOperatorTypes.LessOrEqual, 0.15m)
},
MaximumConcurrentBacktests = 20
};
using var optimizer = CreateOptimizer(packet);
OptimizationResult result = null;
optimizer.Ended += (s, solution) =>
{
result = solution;
optimizer.DisposeSafely();
resetEvent.Set();
};
optimizer.Start();
resetEvent.WaitOne();
Assert.NotNull(result);
Assert.GreaterOrEqual(
20,
JsonConvert.DeserializeObject<BacktestResult>(result.JsonBacktestResult).Statistics.Profit);
Assert.GreaterOrEqual(
0.15m,
JsonConvert.DeserializeObject<BacktestResult>(result.JsonBacktestResult).Statistics.Drawdown);
}
[Test]
public void TrackEstimation()
{
lock (_configLock)
{
Config.Set("optimization-update-interval", 1);
}
OptimizationResult result = null;
using var resetEvent = new ManualResetEvent(false);
var packet = new OptimizationNodePacket
{
Criterion = new Target("Profit", new Minimization(), null),
OptimizationParameters = new HashSet<OptimizationParameter>
{
new OptimizationStepParameter("ema-slow", 1, 10, 1),
new OptimizationStepParameter("ema-fast", 10, 100, 10)
},
Constraints = new List<Constraint>
{
new Constraint("Drawdown", ComparisonOperatorTypes.LessOrEqual, 0.15m)
},
MaximumConcurrentBacktests = 5
};
using var optimizer = CreateOptimizer(packet);
// keep stats up-to-date
int totalBacktest = optimizer.GetCurrentEstimate();
int totalUpdates = 0;
int completedTests = 0;
int failed = 0;
optimizer.Update += (s, e) =>
{
var runtimeStats = optimizer.GetRuntimeStatistics();
Assert.LessOrEqual(int.Parse(runtimeStats["Running"], CultureInfo.InvariantCulture), packet.MaximumConcurrentBacktests);
Assert.LessOrEqual(completedTests, int.Parse(runtimeStats["Completed"], CultureInfo.InvariantCulture));
Assert.LessOrEqual(failed, int.Parse(runtimeStats["Failed"], CultureInfo.InvariantCulture));
Assert.AreEqual(totalBacktest, optimizer.GetCurrentEstimate());
completedTests = int.Parse(runtimeStats["Completed"], CultureInfo.InvariantCulture);
failed = int.Parse(runtimeStats["Failed"], CultureInfo.InvariantCulture);
if (completedTests > 0)
{
// 'ms' aren't stored so might be 0
Assert.GreaterOrEqual(TimeSpan.Parse(runtimeStats["Average Length"], CultureInfo.InvariantCulture), TimeSpan.Zero);
}
totalUpdates++;
};
optimizer.Ended += (s, solution) =>
{
result = solution;
optimizer.DisposeSafely();
resetEvent.Set();
};
optimizer.Start();
resetEvent.WaitOne();
var runtimeStatistics = optimizer.GetRuntimeStatistics();
Assert.NotZero(int.Parse(runtimeStatistics["Completed"], CultureInfo.InvariantCulture));
Assert.NotZero(int.Parse(runtimeStatistics["Failed"], CultureInfo.InvariantCulture));
// there are 2 forced estimation updates (start and end); any additional in-progress updates depend on
// timing and are not guaranteed (e.g. when backtests complete/fail fast), so only the guaranteed
// minimum is asserted here to avoid a flaky "expected > 2 but was 2" failure.
Assert.GreaterOrEqual(totalUpdates, 2);
Assert.AreEqual(int.Parse(runtimeStatistics["Completed"], CultureInfo.InvariantCulture)
+ int.Parse(runtimeStatistics["Failed"], CultureInfo.InvariantCulture)
+ int.Parse(runtimeStatistics["Running"], CultureInfo.InvariantCulture), totalBacktest);
}
}
}
+48
View File
@@ -0,0 +1,48 @@
/*
* 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.Globalization;
namespace QuantConnect.Tests.Optimizer
{
internal class BacktestResult
{
private static JsonSerializerSettings _jsonSettings = new JsonSerializerSettings { Culture = CultureInfo.InvariantCulture, NullValueHandling = NullValueHandling.Ignore};
public Statistics Statistics { get; set; }
public static BacktestResult Create(decimal? profit = null, decimal? drawdown = null)
{
return new BacktestResult
{
Statistics = new Statistics
{
Profit = profit,
Drawdown = drawdown
}
};
}
public string ToJson() => JsonConvert.SerializeObject(this, _jsonSettings);
}
public class Statistics
{
public decimal? Profit { get; set; }
public decimal? Drawdown { get; set; }
}
}
@@ -0,0 +1,110 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Optimizer.Objectives;
using QuantConnect.Util;
using System;
namespace QuantConnect.Tests.Optimizer.Objectives
{
[TestFixture, Parallelizable(ParallelScope.All)]
public class ConstraintTests
{
[TestCase(101.0)]
[TestCase(1000.0)]
public void Meet(decimal value)
{
var constraint = new Constraint("Profit", ComparisonOperatorTypes.Greater, 100m);
Assert.IsTrue(constraint.IsMet(BacktestResult.Create(value).ToJson()));
}
[TestCase(1.0)]
[TestCase(99.9)]
[TestCase(100d)]
public void Fails(decimal value)
{
var constraint = new Constraint("Profit", ComparisonOperatorTypes.Greater, 100m);
Assert.IsFalse(constraint.IsMet(BacktestResult.Create(value).ToJson()));
}
[Test]
public void ThrowIfTargetNull()
{
Assert.Throws<ArgumentNullException>(() =>
{
var constraint = new Constraint("Drawdown", ComparisonOperatorTypes.Less, null);
});
}
[TestCase("")]
[TestCase(null)]
public void ThrowIfResultNullOrEmpty(string json)
{
var constraint = new Constraint("Drawdown", ComparisonOperatorTypes.Less, 10);
Assert.Throws<ArgumentNullException>(() =>
{
constraint.IsMet(json);
});
}
[Test]
public void IgnoreBadJson()
{
var constraint = new Constraint("Drawdown", ComparisonOperatorTypes.Less, 0.1m);
Assert.IsFalse(constraint.IsMet("{\"Drawdown\":\"10.0%\"}"));
}
[TestCase("Drawdown")]
[TestCase("Statistics.Drawdown")]
[TestCase("['Statistics'].['Drawdown']")]
public void ParseName(string targetName)
{
var target = new Constraint(targetName, ComparisonOperatorTypes.Equals, 100);
Assert.AreEqual("['Statistics'].['Drawdown']", target.Target);
}
[Test]
public void FromJson()
{
var json = "{\"operator\": \"equals\",\"target\": \"pin ocho.Gepetto\",\"targetValue\": 11}";
var constraint = (Constraint)JsonConvert.DeserializeObject(json, typeof(Constraint));
Assert.AreEqual("['pin ocho'].['Gepetto']", constraint.Target);
Assert.AreEqual(ComparisonOperatorTypes.Equals, constraint.Operator);
Assert.AreEqual(11, constraint.TargetValue.Value);
}
[Test]
public void RoundTrip()
{
var origin = new Constraint("['Statistics'].['Drawdown']", ComparisonOperatorTypes.Equals, 100);
var json = JsonConvert.SerializeObject(origin);
var actual = JsonConvert.DeserializeObject<Constraint>(json);
Assert.NotNull(actual);
Assert.AreEqual(origin.Target, actual.Target);
Assert.AreEqual(origin.Operator, actual.Operator);
Assert.AreEqual(origin.TargetValue, actual.TargetValue);
}
}
}
+129
View File
@@ -0,0 +1,129 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Optimizer.Objectives;
using System;
using Newtonsoft.Json;
namespace QuantConnect.Tests.Optimizer.Objectives
{
[TestFixture, Parallelizable(ParallelScope.All)]
public class ExtremumTests
{
private static TestCaseData[] Extremums => new TestCaseData[]
{
new TestCaseData(new Maximization()),
new TestCaseData(new Minimization())
};
[TestCase("\"max\"")]
[TestCase("\"min\"")]
[TestCase("\"Max\"")]
[TestCase("\"miN\"")]
public void Deserialize(string extremum)
{
var actual = JsonConvert.DeserializeObject<Extremum>(extremum);
Extremum expected = extremum.Equals("\"max\"", StringComparison.OrdinalIgnoreCase)
? new Maximization() as Extremum
: new Minimization();
Assert.NotNull(actual);
Assert.AreEqual(expected.GetType(), actual.GetType());
}
[TestCase("\"\"")]
[TestCase("\"n/a\"")]
public void ThrowsIfNotRecognized(string extremum)
{
Assert.Throws<InvalidOperationException>(() =>
{
JsonConvert.DeserializeObject<Extremum>(extremum);
});
}
[Test, TestCaseSource(nameof(Extremums))]
public void Serialize(Extremum extremum)
{
var json = JsonConvert.SerializeObject(extremum);
var actual = JsonConvert.DeserializeObject<Extremum>(json);
Assert.NotNull(actual);
Assert.AreEqual(extremum.GetType(), actual.GetType());
}
[TestCase(0, 10)]
[TestCase(10, 10)]
[TestCase(10, 0)]
public void Linear(decimal current, decimal candidate)
{
Func<decimal, decimal, bool> comparer = (a, b) => a <= b;
var strategy = new Extremum(comparer);
Assert.AreEqual(comparer(current, candidate), strategy.Better(current, candidate));
}
[TestCase(101, 10)]
[TestCase(100, 10)]
[TestCase(99, 10)]
public void NonLinear(decimal current, decimal candidate)
{
Func<decimal, decimal, bool> comparer = (a, b) => a >= b * b;
var strategy = new Extremum(comparer);
Assert.AreEqual(comparer(current, candidate), strategy.Better(current, candidate));
}
[TestFixture]
public class MaximizationTests
{
Maximization _strategy = new Maximization();
[Test]
public void Greater()
{
Assert.IsTrue(_strategy.Better(0, 10));
}
[TestCase(10, 10)]
[TestCase(10, 0)]
public void LessThanOrEqual(decimal current, decimal candidate)
{
Assert.IsFalse(_strategy.Better(current, candidate));
}
}
[TestFixture]
public class MinimizationTests
{
Minimization _strategy = new Minimization();
[Test]
public void Less()
{
Assert.IsTrue(_strategy.Better(10, 0));
}
[TestCase(10, 10)]
[TestCase(0, 10)]
public void GreatThanOrEqual(decimal current, decimal candidate)
{
Assert.IsFalse(_strategy.Better(current, candidate));
}
}
}
}
+171
View File
@@ -0,0 +1,171 @@
/*
* 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.Optimizer.Objectives;
using System;
namespace QuantConnect.Tests.Optimizer.Objectives
{
[TestFixture, Parallelizable(ParallelScope.All)]
public class TargetTests
{
[Test]
public void MoveAheadNoTarget()
{
var target = new Target("Profit", new Maximization(), null);
Assert.IsTrue(target.MoveAhead(BacktestResult.Create(profit: 10m).ToJson()));
Assert.AreEqual(10m, target.Current);
Assert.IsFalse(target.MoveAhead(BacktestResult.Create(profit: 5m).ToJson()));
Assert.AreEqual(10m, target.Current);
Assert.IsTrue(target.MoveAhead(BacktestResult.Create(profit: 15m).ToJson()));
Assert.AreEqual(15m, target.Current);
}
[Test]
public void MoveAheadReachFirst()
{
var target = new Target("Profit", new Minimization(), 100);
bool reached = false;
target.Reached += (s, e) =>
{
reached = true;
};
Assert.IsTrue(target.MoveAhead(BacktestResult.Create(profit: 10m).ToJson()));
target.CheckCompliance();
Assert.AreEqual(10m, target.Current);
Assert.IsTrue(reached);
}
[Test]
public void MoveAheadReachLast()
{
var target = new Target("Profit", new Minimization(), 100);
bool reached = false;
target.Reached += (s, e) =>
{
reached = true;
};
for (var profit = 500m; profit > 0; profit -= 50)
{
Assert.IsTrue(target.MoveAhead(BacktestResult.Create(profit: profit).ToJson()));
Assert.AreEqual(profit, target.Current);
target.CheckCompliance();
}
Assert.IsTrue(reached);
}
[TestCase("")]
[TestCase(null)]
public void ThrowIfNullOrEmpty(string json)
{
var target = new Target("Profit", new Maximization(), null);
Assert.Throws<ArgumentNullException>(() =>
{
target.MoveAhead(json);
});
}
[Test]
public void IgnoreBadJson()
{
var target = new Target("Profit", new Maximization(), null);
Assert.IsFalse(target.MoveAhead("{\"Profit\":10}"));
Assert.AreEqual(null, target.Current);
}
[TestCase("Sharpe Ratio")]
[TestCase("Statistics.Sharpe Ratio")]
[TestCase("['Statistics'].['Sharpe Ratio']")]
public void ParseTargetName(string targetName)
{
var target = new Target(targetName, new Minimization(), 100);
Assert.AreEqual("['Statistics'].['Sharpe Ratio']", target.Target);
}
[TestCase("null")]
[TestCase("11")]
public void FromJson(string value)
{
var json = $"{{\"extremum\": \"max\",\"target\": \"pin ocho.Gepetto\",\"targetValue\": {value}}}";
var target = (Target)JsonConvert.DeserializeObject(json, typeof(Target));
Assert.AreEqual("['pin ocho'].['Gepetto']", target.Target);
Assert.AreEqual(typeof(Maximization), target.Extremum.GetType());
if (value == "null")
{
Assert.IsNull(target.TargetValue);
}
else
{
Assert.AreEqual(11, target.TargetValue);
}
}
[Test]
public void RoundTrip()
{
var origin = new Target("['Statistics'].['Drawdown']", new Maximization(), 100);
var json = JsonConvert.SerializeObject(origin);
var actual = JsonConvert.DeserializeObject<Target>(json);
Assert.NotNull(actual);
Assert.AreEqual(origin.Target, actual.Target);
Assert.AreEqual(origin.Extremum.GetType(), actual.Extremum.GetType());
Assert.AreEqual(origin.TargetValue, actual.TargetValue);
}
[TestCase("['TotalPerformance'].['TradeStatistics'].['ProfitToMaxDrawdownRatio']", "-1")]
[TestCase("['totalPerformance'].['tradeStatistics'].['profitToMaxDrawdownRatio']", "-1")]
[TestCase("['TotalPerformance'].['TradeStatistics'].['lossRate']", "1")]
[TestCase("['totalPerformance'].['tradeStatistics'].['lossRate']", "1")]
[TestCase("['Statistics'].['Start Equity']", "100000")]
[TestCase("['statistics'].['start equity']", "100000")]
[TestCase("['Statistics'].['Sharpe Ratio']", "-5.283")]
[TestCase("['statistics'].['sharpe ratio']", "-5.283")]
[TestCase("['Statistics'].['Sharp Ratio']", null)]
[TestCase("['statistics'].['sharp ratio']", null)]
public void TargetMoveAheadIsCaseInsensitive(string target, string expected)
{
Assert.AreEqual(expected, (Target.GetTokenInJsonBacktest(jsonBacktestResultExample, target))?.Value<string>());
}
private string jsonBacktestResultExample = @"{
""totalPerformance"": {
""tradeStatistics"": {
""lossRate"": ""1"",
""profitToMaxDrawdownRatio"": ""-1"",
}
},
""statistics"": {
""Start Equity"": ""100000"",
""Sharpe Ratio"": ""-5.283"",
}
}";
}
}
@@ -0,0 +1,161 @@
/*
* 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;
using NUnit.Framework;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using QuantConnect.Optimizer;
using QuantConnect.Optimizer.Parameters;
using QuantConnect.Optimizer.Objectives;
using QuantConnect.Optimizer.Strategies;
using QuantConnect.Packets;
using QuantConnect.Util;
namespace QuantConnect.Tests.Optimizer
{
[TestFixture, Parallelizable(ParallelScope.All)]
public class OptimizationNodePacketTest
{
private static JsonSerializerSettings _jsonSettings = new JsonSerializerSettings() { Culture = CultureInfo.InvariantCulture };
[TestCase("QuantConnect.Optimizer.EulerSearchOptimizationStrategy", typeof(StepBaseOptimizationStrategySettings))]
[TestCase("QuantConnect.Optimizer.GridSearchOptimizationStrategy", typeof(OptimizationStrategySettings))]
public void RoundTrip(string strategyName, Type settingType)
{
var optimizationNodePacket = new OptimizationNodePacket()
{
CompileId = Guid.NewGuid().ToString(),
OptimizationId = Guid.NewGuid().ToString(),
OptimizationStrategy = strategyName,
Criterion = new Target("Profit", new Maximization(), 100.5m),
Constraints = new List<Constraint>
{
new Constraint("Drawdown", ComparisonOperatorTypes.LessOrEqual, 0.1m),
new Constraint("Profit", ComparisonOperatorTypes.Greater, 100)
},
OptimizationParameters = new HashSet<OptimizationParameter>()
{
new OptimizationStepParameter("ema-slow", 1, 100, 1m),
new OptimizationStepParameter("ema-fast", -10, 0, 0.5m),
new StaticOptimizationParameter("pinocho", "11")
},
MaximumConcurrentBacktests = 10,
OptimizationStrategySettings = (OptimizationStrategySettings)Activator.CreateInstance(settingType)
};
var serialize = JsonConvert.SerializeObject(optimizationNodePacket, _jsonSettings);
var result = JsonConvert.DeserializeObject<OptimizationNodePacket>(serialize, _jsonSettings);
// common
Assert.AreEqual(PacketType.OptimizationNode, result.Type);
// assert strategy
Assert.AreEqual(optimizationNodePacket.OptimizationStrategy, result.OptimizationStrategy);
Assert.AreEqual(optimizationNodePacket.OptimizationId, result.OptimizationId);
Assert.AreEqual(optimizationNodePacket.CompileId, result.CompileId);
// assert optimization parameters
foreach (var expected in optimizationNodePacket.OptimizationParameters.OfType<OptimizationStepParameter>())
{
var actual = result.OptimizationParameters.FirstOrDefault(s => s.Name == expected.Name) as OptimizationStepParameter;
Assert.NotNull(actual);
Assert.AreEqual(expected.MinValue, actual.MinValue);
Assert.AreEqual(expected.MaxValue, actual.MaxValue);
Assert.AreEqual(expected.Step, actual.Step);
}
// assert optimization parameters
var staticOptimizationParameters = optimizationNodePacket.OptimizationParameters.OfType<StaticOptimizationParameter>().ToList();
Assert.AreEqual(1, staticOptimizationParameters.Count);
foreach (var parameter in staticOptimizationParameters)
{
Assert.AreEqual("11", parameter.Value);
Assert.AreEqual("pinocho", parameter.Name);
}
// assert target
Assert.AreEqual(optimizationNodePacket.Criterion.Target, result.Criterion.Target);
Assert.AreEqual(optimizationNodePacket.Criterion.Extremum.GetType(), result.Criterion.Extremum.GetType());
Assert.AreEqual(optimizationNodePacket.Criterion.TargetValue, result.Criterion.TargetValue);
// assert constraints
foreach (var expected in optimizationNodePacket.Constraints)
{
var actual = result.Constraints.FirstOrDefault(s => s.Target == expected.Target);
Assert.NotNull(actual);
Assert.AreEqual(expected.Operator, actual.Operator);
Assert.AreEqual(expected.TargetValue, actual.TargetValue);
}
// others
Assert.AreEqual(optimizationNodePacket.MaximumConcurrentBacktests, result.MaximumConcurrentBacktests);
Assert.AreEqual(settingType, result.OptimizationStrategySettings.GetType());
}
[TestCase("StepBaseOptimizationStrategySettings", "{\"OptimizationParameters\": [{\"Name\":\"sleep-ms\",\"min\":0,\"max\":0,\"Step\":1}," +
"{\"Name\":\"total-trades\",\"min\":0,\"max\":2,\"Step\":1}]," +
"\"criterion\": {\"extremum\" : \"min\",\"target\": \"Statistics.Sharpe Ratio\",\"targetValue\": 10}," +
"\"constraints\": [{\"target\": \"Statistics.Sharpe Ratio\",\"operator\": \"equals\",\"targetValue\": 11}]," +
$"\"optimizationStrategySettings\":{{\"$type\":\"QuantConnect.Optimizer.Strategies.StepBaseOptimizationStrategySettings, QuantConnect.Optimizer\",\"default-segment-amount\":0,\"max-run-time\":\"10675199.02:48:05.4775807\"}}}}")]
[TestCase("OptimizationStrategySettings", "{\"OptimizationParameters\": [{\"Name\":\"sleep-ms\",\"min\":0,\"max\":0,\"Step\":1}," +
"{\"Name\":\"total-trades\",\"min\":0,\"max\":2,\"Step\":1}]," +
"\"criterion\": {\"extremum\" : \"min\",\"target\": \"Statistics.Sharpe Ratio\",\"targetValue\": 10}," +
"\"constraints\": [{\"target\": \"Statistics.Sharpe Ratio\",\"operator\": \"equals\",\"targetValue\": 11}]," +
$"\"optimizationStrategySettings\":{{\"$type\":\"QuantConnect.Optimizer.Strategies.OptimizationStrategySettings, QuantConnect.Optimizer\",\"default-segment-amount\":0,\"max-run-time\":\"10675199.02:48:05.4775807\"}}}}")]
[TestCase("StepBaseOptimizationStrategySettings", "{\"OptimizationParameters\": [{\"Name\":\"sleep-ms\",\"min\":0,\"max\":0,\"Step\":1}," +
"{\"Name\":\"total-trades\",\"min\":0,\"max\":2,\"Step\":1}]," +
"\"criterion\": {\"extremum\" : \"min\",\"target\": \"Statistics.Sharpe Ratio\",\"target-value\": 10}," +
"\"constraints\": [{\"target\": \"Statistics.Sharpe Ratio\",\"operator\": \"equals\",\"target-value\": 11}]," +
$"\"optimizationStrategySettings\":{{\"$type\":\"QuantConnect.Optimizer.Strategies.StepBaseOptimizationStrategySettings, QuantConnect.Optimizer\",\"default-segment-amount\":0,\"max-run-time\":\"10675199.02:48:05.4775807\"}}}}")]
[TestCase("OptimizationStrategySettings", "{\"OptimizationParameters\": [{\"Name\":\"sleep-ms\",\"min\":0,\"max\":0,\"Step\":1}," +
"{\"Name\":\"total-trades\",\"min\":0,\"max\":2,\"Step\":1}]," +
"\"criterion\": {\"extremum\" : \"min\",\"target\": \"Statistics.Sharpe Ratio\",\"target-value\": 10}," +
"\"constraints\": [{\"target\": \"Statistics.Sharpe Ratio\",\"operator\": \"equals\",\"target-value\": 11}]," +
$"\"optimizationStrategySettings\":{{\"$type\":\"QuantConnect.Optimizer.Strategies.OptimizationStrategySettings, QuantConnect.Optimizer\",\"default-segment-amount\":0,\"max-run-time\":\"10675199.02:48:05.4775807\"}}}}")]
public void FromJson(string settingTypeName ,string json)
{
var packet = (OptimizationNodePacket)JsonConvert.DeserializeObject(json, typeof(OptimizationNodePacket));
Assert.AreEqual("['Statistics'].['Sharpe Ratio']", packet.Criterion.Target);
Assert.AreEqual(typeof(Minimization), packet.Criterion.Extremum.GetType());
Assert.AreEqual(2, packet.OptimizationParameters.Count);
Assert.AreEqual(1, packet.Constraints.Count);
Assert.AreEqual("['Statistics'].['Sharpe Ratio']", packet.Constraints.Single().Target);
Assert.AreEqual(settingTypeName, packet.OptimizationStrategySettings.GetType().Name);
}
[TestCase("{\"OptimizationParameters\": [{\"Name\":\"sleep-ms\",\"min\":0,\"max\":0,\"Step\":1}," +
"{\"Name\":\"total-trades\",\"min\":0,\"max\":2,\"Step\":1}]," +
"\"criterion\": {\"extremum\" : \"min\",\"target\": \"Statistics.Sharpe Ratio\",\"targetValue\": 10}," +
"\"constraints\": [{\"target\": \"Statistics.Sharpe Ratio\",\"operator\": \"equals\",\"targetValue\": 11}]}")]
[TestCase("{\"OptimizationParameters\": [{\"Name\":\"sleep-ms\",\"min\":0,\"max\":0,\"Step\":1}," +
"{\"Name\":\"total-trades\",\"min\":0,\"max\":2,\"Step\":1}]," +
"\"criterion\": {\"extremum\" : \"min\",\"target\": \"Statistics.Sharpe Ratio\",\"target-value\": 10}," +
"\"constraints\": [{\"target\": \"Statistics.Sharpe Ratio\",\"operator\": \"equals\",\"target-value\": 11}]}")]
public void FromJsonNoSettings(string json)
{
var packet = (OptimizationNodePacket)JsonConvert.DeserializeObject(json, typeof(OptimizationNodePacket));
Assert.AreEqual("['Statistics'].['Sharpe Ratio']", packet.Criterion.Target);
Assert.AreEqual(typeof(Minimization), packet.Criterion.Extremum.GetType());
Assert.AreEqual(2, packet.OptimizationParameters.Count);
Assert.AreEqual(1, packet.Constraints.Count);
Assert.AreEqual("['Statistics'].['Sharpe Ratio']", packet.Constraints.Single().Target);
Assert.IsNull(packet.OptimizationStrategySettings);
}
}
}
@@ -0,0 +1,68 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Optimizer.Parameters;
using System;
using System.Collections.Generic;
namespace QuantConnect.Tests.Optimizer.Parameters
{
[TestFixture, Parallelizable(ParallelScope.Fixtures)]
public class OptimizationParameterEnumeratorTests
{
private static TestCaseData[] OptimizationParameters => new[]
{
new TestCaseData(new OptimizationStepParameter("ema-fast", -100, 0, 1m)),
new TestCaseData(new OptimizationStepParameter("ema-slow", 100, 1000, 10m))
};
[Test, TestCaseSource(nameof(OptimizationParameters))]
public void NotIEnumerable(OptimizationParameter optimizationParameter)
{
Assert.IsNotInstanceOf<IEnumerable<string>>(optimizationParameter);
}
[TestFixture]
public class StepParameter
{
private static TestCaseData[] OptimizationParameters => new[]
{
new TestCaseData(new OptimizationStepParameter("ema-fast", -100, 0, 1m)),
new TestCaseData(new OptimizationStepParameter("ema-fast", -10, 10, 0.1m)),
new TestCaseData(new OptimizationStepParameter("ema-fast", 1, 100, 1m)),
new TestCaseData(new OptimizationStepParameter("ema-fast", 100, 100, 0.5m))
};
[Test, TestCaseSource(nameof(OptimizationParameters))]
public void Enumerate(OptimizationStepParameter optimizationParameter)
{
using var enumerator = new OptimizationStepParameterEnumerator(optimizationParameter);
int total = 0;
for (decimal value = optimizationParameter.MinValue; value <= optimizationParameter.MaxValue; value += optimizationParameter.Step.Value)
{
total++;
Assert.IsTrue(enumerator.MoveNext());
Assert.AreEqual(value, enumerator.Current.ToDecimal());
}
Assert.AreEqual(Math.Floor((optimizationParameter.MaxValue - optimizationParameter.MinValue) / optimizationParameter.Step.Value) + 1, total);
Assert.IsFalse(enumerator.MoveNext());
}
}
}
}
@@ -0,0 +1,272 @@
/*
* 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;
using NUnit.Framework;
using QuantConnect.Optimizer.Parameters;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Serialization;
namespace QuantConnect.Tests.Optimizer.Parameters
{
[TestFixture, Parallelizable(ParallelScope.Fixtures)]
public class OptimizationParameterTests
{
[TestFixture]
public class StepParameter
{
private static TestCaseData[] OptimizationParameters => new[]
{
new TestCaseData(new OptimizationStepParameter("ema-fast", 1, 100, 1m)),
new TestCaseData(new OptimizationStepParameter("ema-fast", 1, 100, 1m, 0.0005m))
};
[TestCase(5)]
[TestCase(0.5)]
public void StepShouldBePositiveAlways(double step)
{
var optimizationParameter = new OptimizationStepParameter("ema-fast", 1, 100, new decimal(step));
Assert.NotNull(optimizationParameter.Step);
Assert.Positive(optimizationParameter.Step.Value);
Assert.AreEqual(optimizationParameter.Step, optimizationParameter.MinStep);
Assert.AreEqual(Math.Abs(step), optimizationParameter.Step);
}
[TestCase(-5)]
[TestCase(-0.5)]
[TestCase(0)]
public void ThrowIfStepIsNegativeOrZero(double step)
{
Assert.Throws<ArgumentException>(() =>
{
var optimizationParameter = new OptimizationStepParameter("ema-fast", 1, 100, new decimal(step));
});
}
[TestCase(1, 0.1)]
[TestCase(5, 5)]
public void StepShouldBeGreatOrEqualThanMinStep(decimal step, decimal minStep)
{
var optimizationParameter = new OptimizationStepParameter("ema-fast", 1, 100, step, minStep);
var actual = Math.Max(Math.Abs(step), Math.Abs(minStep));
Assert.AreEqual(actual, optimizationParameter.Step);
}
[TestCase(5, 10)]
[TestCase(0.1, 0.2)]
public void ThrowsIfStepLessThanMinStep(decimal step, decimal minStep)
{
Assert.Throws<ArgumentException>(() =>
{
var optimizationParameter = new OptimizationStepParameter("ema-fast", 1, 100, step, minStep);
});
}
[TestCase(0, 0)]
[TestCase(0.5, 0)]
[TestCase(0, 2)]
public void PreventZero(decimal step, decimal minStep)
{
Assert.Throws<ArgumentException>(() =>
{
var optimizationParameter = new OptimizationStepParameter("ema-fast", 1, 100, step, minStep);
});
}
[Test, TestCaseSource(nameof(OptimizationParameters))]
public void Serialize(OptimizationStepParameter parameterSet)
{
var json = JsonConvert.SerializeObject(parameterSet);
var optimizationParameter = JsonConvert.DeserializeObject<OptimizationParameter>(json) as OptimizationStepParameter;
Assert.NotNull(optimizationParameter);
Assert.AreEqual(parameterSet.Name, optimizationParameter.Name);
Assert.AreEqual(parameterSet.MinValue, optimizationParameter.MinValue);
Assert.AreEqual(parameterSet.MaxValue, optimizationParameter.MaxValue);
Assert.AreEqual(parameterSet.Step, optimizationParameter.Step);
Assert.AreEqual(parameterSet.MinStep, optimizationParameter.MinStep);
}
[Test, TestCaseSource(nameof(OptimizationParameters))]
public void SerializeCollection(OptimizationStepParameter parameterSet)
{
var json = JsonConvert.SerializeObject(new[] { parameterSet as OptimizationParameter });
var optimizationParameters = JsonConvert.DeserializeObject<List<OptimizationParameter>>(json);
Assert.AreEqual(1, optimizationParameters.Count);
var parsed = optimizationParameters[0] as OptimizationStepParameter;
Assert.NotNull(parsed);
Assert.AreEqual(parameterSet.Name, parsed.Name);
Assert.AreEqual(parameterSet.MinValue, parsed.MinValue);
Assert.AreEqual(parameterSet.MaxValue, parsed.MaxValue);
Assert.AreEqual(parameterSet.Step, parsed.Step);
Assert.AreEqual(parameterSet.MinStep, parsed.MinStep);
}
[Test]
public void StaticParameterRoundTripSerialization()
{
var expected = "{\"value\":\"50.0\",\"name\":\"ema-fast\"}";
var staticParameter = JsonConvert.DeserializeObject<StaticOptimizationParameter>(expected);
Assert.IsNotNull(staticParameter);
Assert.AreEqual("50.0", staticParameter.Value);
Assert.AreEqual("ema-fast", staticParameter.Name);
var serialized = JsonConvert.SerializeObject(staticParameter, new JsonSerializerSettings()
{
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy
{
ProcessDictionaryKeys = false,
OverrideSpecifiedNames = true
}
}
});
Assert.AreEqual(expected, serialized);
}
[Test]
public void DeserializeSingle()
{
var json = @"
{
""name"":""ema-fast"",
""min"": 50,
""max"": 150,
""step"": 50,
}";
var optimizationParameter = JsonConvert.DeserializeObject<OptimizationParameter>(json) as OptimizationStepParameter;
Assert.NotNull(optimizationParameter);
Assert.AreEqual("ema-fast", optimizationParameter.Name);
Assert.AreEqual(50, optimizationParameter.MinValue);
Assert.AreEqual(150, optimizationParameter.MaxValue);
Assert.AreEqual(50, optimizationParameter.Step);
Assert.AreEqual(50, optimizationParameter.MinStep);
}
[Test]
public void DeserializeSingleCaseInsensitive()
{
var json = @"
{
""Name"":""ema-fast"",
""mIn"": 50,
""maX"": 150,
""STEP"": 50,
}";
var optimizationParameter = JsonConvert.DeserializeObject<OptimizationParameter>(json) as OptimizationStepParameter;
Assert.NotNull(optimizationParameter);
Assert.AreEqual("ema-fast", optimizationParameter.Name);
Assert.AreEqual(50, optimizationParameter.MinValue);
Assert.AreEqual(150, optimizationParameter.MaxValue);
Assert.AreEqual(50, optimizationParameter.Step);
}
[Test]
public void DeserializeSingleWithOptional()
{
var json = @"
{
""name"":""ema-fast"",
""min"": 50,
""max"": 150,
""step"": 50,
""min-step"": 0.001
}";
var optimizationParameter = JsonConvert.DeserializeObject<OptimizationParameter>(json) as OptimizationStepParameter;
Assert.NotNull(optimizationParameter);
Assert.AreEqual("ema-fast", optimizationParameter.Name);
Assert.AreEqual(50, optimizationParameter.MinValue);
Assert.AreEqual(150, optimizationParameter.MaxValue);
Assert.AreEqual(50, optimizationParameter.Step);
Assert.AreEqual(0.001, optimizationParameter.MinStep);
}
[Test]
public void DeserializeCollection()
{
var json = @"[
{
""name"":""ema-fast"",
""min"": 50,
""max"": 150,
""step"": 50,
},{
""name"":""ema-slow"",
""min"": 50,
""max"": 250,
""step"": 10,
}]";
var optimizationParameters = JsonConvert.DeserializeObject<List<OptimizationParameter>>(json)
.OfType<OptimizationStepParameter>()
.ToList();
Assert.AreEqual(2, optimizationParameters.Count);
Assert.AreEqual("ema-fast", optimizationParameters[0].Name);
Assert.AreEqual(50, optimizationParameters[0].MinValue);
Assert.AreEqual(150, optimizationParameters[0].MaxValue);
Assert.AreEqual(50, optimizationParameters[0].Step);
Assert.AreEqual("ema-slow", optimizationParameters[1].Name);
Assert.AreEqual(50, optimizationParameters[1].MinValue);
Assert.AreEqual(250, optimizationParameters[1].MaxValue);
Assert.AreEqual(10, optimizationParameters[1].Step);
}
[Test]
public void ThrowIfNotStepParameter()
{
var json = @"
{
""name"":""ema-fast"",
""values"": [""a"",""b"",""c"",""d""]
}";
Assert.Throws<ArgumentException>(() =>
{
JsonConvert.DeserializeObject<OptimizationParameter>(json);
});
}
[TestCase(-1, -10, -1)]
[TestCase(10, 1, 1)]
public void ThrowIfMinGreatThanMax(decimal min, decimal max, decimal step)
{
Assert.Throws<ArgumentException>(() =>
{
var param = new OptimizationStepParameter("ema-fast", min, max, step);
});
}
}
}
}
@@ -0,0 +1,201 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Optimizer;
using QuantConnect.Optimizer.Objectives;
using QuantConnect.Optimizer.Parameters;
using QuantConnect.Optimizer.Strategies;
using System;
using System.Collections.Generic;
namespace QuantConnect.Tests.Optimizer.Strategies
{
[TestFixture, Parallelizable(ParallelScope.Fixtures)]
public class EulerSearchOptimizationStrategyTests : OptimizationStrategyTests
{
[TestFixture]
public class EulerSearchTests
{
private EulerSearchOptimizationStrategy _strategy;
[SetUp]
public void Init()
{
this._strategy = new EulerSearchOptimizationStrategy();
}
[TestCase(10)]
[TestCase(5)]
[TestCase(2)]
public void Depth(int _defaultSegmentAmount)
{
var param = new OptimizationStepParameter("ema-fast", 10, 100, 10, 0.1m);
var set = new HashSet<OptimizationParameter> { param, new StaticOptimizationParameter("pepe", "pipi") };
_strategy.Initialize(
new Target("Profit", new Maximization(), null),
new List<Constraint>(),
set,
new StepBaseOptimizationStrategySettings { DefaultSegmentAmount = _defaultSegmentAmount });
Queue<OptimizationResult> _pendingOptimizationResults = new Queue<OptimizationResult>();
int depth = -1;
_strategy.NewParameterSet += (s, parameterSet) =>
{
if (_pendingOptimizationResults.Count == 0)
{
depth++;
}
_pendingOptimizationResults.Enqueue(new OptimizationResult(_stringify(_profit(parameterSet), _drawdown(parameterSet)), parameterSet, ""));
};
_strategy.PushNewResults(OptimizationResult.Initial);
while (_pendingOptimizationResults.TryDequeue(out var item))
{
_strategy.PushNewResults(item);
}
Assert.AreEqual(Math.Ceiling(Math.Log((double)(param.Step / param.MinStep), _defaultSegmentAmount)), depth);
}
[Test]
public void ThrowIfNoSettingsPassed()
{
Assert.Throws<ArgumentNullException>(() =>
{
_strategy.Initialize(
new Target("Profit", new Maximization(), null),
new List<Constraint>(),
new HashSet<OptimizationParameter>
{
new OptimizationStepParameter("ema-fast", 10, 100, 10, 0.1m),
new StaticOptimizationParameter("pepe", "pipi")
},
null);
});
}
[TestCase(5)]
[TestCase(10)]
public void Reduce(int amountOfSegments)
{
var param = new OptimizationStepParameter("ema-fast", 10, 100, 10, 0.1m);
var set = new HashSet<OptimizationParameter> { param, new StaticOptimizationParameter("pepe", "pipi") };
_strategy.Initialize(
new Target("Profit", new Maximization(), null),
new List<Constraint>(),
set,
new StepBaseOptimizationStrategySettings { DefaultSegmentAmount = amountOfSegments });
Queue<OptimizationResult> pendingOptimizationResults = new Queue<OptimizationResult>();
int depth = -1;
_strategy.NewParameterSet += (s, parameterSet) =>
{
if (pendingOptimizationResults.Count == 0)
{
depth++;
}
pendingOptimizationResults.Enqueue(new OptimizationResult(_stringify(_profit(parameterSet), _drawdown(parameterSet)), parameterSet, ""));
};
_strategy.PushNewResults(OptimizationResult.Initial);
var step = param.Step ?? 1;
var datapoint = param.MinValue;
while (pendingOptimizationResults.Count > 0)
{
var count = pendingOptimizationResults.Count;
for (int i = 0; i < count; i++)
{
var optimizationResult = pendingOptimizationResults.Dequeue();
Assert.AreEqual(datapoint + step * i,
optimizationResult.ParameterSet.Value["ema-fast"].ToDecimal());
_strategy.PushNewResults(optimizationResult);
}
step = Math.Max(param.MinStep.Value, step / amountOfSegments);
datapoint = param.MaxValue - step * ((decimal)amountOfSegments / 2);
}
}
}
protected override IOptimizationStrategy CreateStrategy()
{
return new EulerSearchOptimizationStrategy();
}
protected override OptimizationStrategySettings CreateSettings()
{
return new StepBaseOptimizationStrategySettings(){DefaultSegmentAmount = 10};
}
private static TestCaseData[] StrategySettings => new[]
{
new TestCaseData(new Maximization(), OptimizationStepParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "5.0"}, { "ema-fast" , "6.0"} })),
new TestCaseData(new Minimization(), OptimizationStepParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "1"}, { "ema-fast" , "3"} })),
new TestCaseData(new Maximization(), OptimizationMixedParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "5"}, { "ema-fast" , "6.0" }, { "skipFromResultSum", "SPY" } })),
new TestCaseData(new Minimization(), OptimizationMixedParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "1"}, { "ema-fast" , "3"}, { "skipFromResultSum", "SPY" } }))
};
[Test, TestCaseSource(nameof(StrategySettings))]
public override void StepInsideNoTargetNoConstraints(Extremum extremum, HashSet<OptimizationParameter> optimizationParameters, ParameterSet solution)
{
base.StepInsideNoTargetNoConstraints(extremum, optimizationParameters, solution);
}
private static TestCaseData[] OptimizeWithConstraint => new[]
{
new TestCaseData(0.05m, OptimizationStepParameters,new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "1.1"}, { "ema-fast" , "3.8"} })),
new TestCaseData(0.06m, OptimizationStepParameters,new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "1.9"}, { "ema-fast" , "4.0"} })),
new TestCaseData(0.05m, OptimizationMixedParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "1"}, { "ema-fast" , "3.9" }, { "skipFromResultSum", "SPY" } })),
new TestCaseData(0.06m, OptimizationMixedParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "2"}, { "ema-fast" , "3.9" }, { "skipFromResultSum", "SPY" } }))
};
[Test, TestCaseSource(nameof(OptimizeWithConstraint))]
public override void StepInsideWithConstraints(decimal drawdown, HashSet<OptimizationParameter> optimizationParameters, ParameterSet solution)
{
base.StepInsideWithConstraints(drawdown, optimizationParameters, solution);
}
private static TestCaseData[] OptimizeWithTarget => new[]
{
new TestCaseData(0m, OptimizationStepParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "1"}, { "ema-fast" , "3"} })),
new TestCaseData(4m, OptimizationStepParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "1"}, { "ema-fast" , "3"} })),
new TestCaseData(5m, OptimizationStepParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "1"}, { "ema-fast" , "5"} })),
new TestCaseData(8m, OptimizationStepParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "3"}, { "ema-fast" , "5"} })),
new TestCaseData(0m, OptimizationMixedParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "1"}, { "ema-fast" , "3"}, { "skipFromResultSum", "SPY" } })),
new TestCaseData(5m, OptimizationMixedParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "1"}, { "ema-fast" , "5"}, { "skipFromResultSum", "SPY" } })),
new TestCaseData(8m, OptimizationMixedParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "3"}, { "ema-fast" , "5"}, { "skipFromResultSum", "SPY" } }))
};
[Test, TestCaseSource(nameof(OptimizeWithTarget))]
public override void StepInsideWithTarget(decimal targetValue, HashSet<OptimizationParameter> optimizationParameters, ParameterSet solution)
{
base.StepInsideWithTarget(targetValue, optimizationParameters, solution);
}
private static TestCaseData[] OptimizeWithTargetNotReached => new[]
{
new TestCaseData(15m, OptimizationStepParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "5.0"}, { "ema-fast" , "6.0" } })),
new TestCaseData(155m, OptimizationMixedParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "5"}, { "ema-fast" , "6.0"}, { "skipFromResultSum", "SPY" } }))
};
[Test, TestCaseSource(nameof(OptimizeWithTargetNotReached))]
public override void TargetNotReached(decimal targetValue, HashSet<OptimizationParameter> optimizationParameters, ParameterSet solution)
{
base.TargetNotReached(targetValue, optimizationParameters, solution);
}
}
}
@@ -0,0 +1,432 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
using QuantConnect.Optimizer;
using QuantConnect.Optimizer.Objectives;
using QuantConnect.Optimizer.Parameters;
using QuantConnect.Optimizer.Strategies;
using System;
using System.Collections.Generic;
using System.Linq;
using Math = System.Math;
namespace QuantConnect.Tests.Optimizer.Strategies
{
[TestFixture, Parallelizable(ParallelScope.Fixtures)]
public class GridSearchOptimizationStrategyTests : OptimizationStrategyTests
{
[TestFixture]
public class GridSearchTests
{
private GridSearchOptimizationStrategy _strategy;
[SetUp]
public void Init()
{
this._strategy = new GridSearchOptimizationStrategy();
}
[TestCase(1)]
[TestCase(0.5)]
public void SinglePoint(decimal step)
{
var args = new HashSet<OptimizationParameter>()
{
new OptimizationStepParameter("ema-fast", 0, 0, step),
new OptimizationStepParameter("ema-slow", 0, 0, step),
new OptimizationStepParameter("ema-custom", 1, 1, step)
};
_strategy.Initialize(new Target("Profit", new Maximization(), null), new List<Constraint>(), args, new StepBaseOptimizationStrategySettings());
_strategy.NewParameterSet += (s, parameterSet) =>
{
Assert.AreEqual(0, parameterSet.Value["ema-fast"].ToDecimal());
Assert.AreEqual(0, parameterSet.Value["ema-slow"].ToDecimal());
Assert.AreEqual(1, parameterSet.Value["ema-custom"].ToDecimal());
};
_strategy.PushNewResults(OptimizationResult.Initial);
}
[TestCase(10, 100, 1)]
[TestCase(10, 100, 500)]
public void Step1D(decimal min, decimal max, decimal step)
{
var param = new OptimizationStepParameter("ema-fast", min, max, step);
var set = new HashSet<OptimizationParameter>() { param };
_strategy.Initialize(new Target("Profit", new Maximization(), null), new List<Constraint>(), set, new StepBaseOptimizationStrategySettings());
var counter = 0;
using (var enumerator = new EnqueueableEnumerator<ParameterSet>())
{
_strategy.NewParameterSet += (s, parameterSet) =>
{
enumerator.Enqueue(parameterSet);
};
_strategy.PushNewResults(OptimizationResult.Initial);
using (var paramEnumerator = new OptimizationStepParameterEnumerator(param))
{
while (paramEnumerator.MoveNext())
{
var value = paramEnumerator.Current;
counter++;
Assert.IsTrue(enumerator.MoveNext());
var suggestion = enumerator.Current;
Assert.IsNotNull(suggestion);
Assert.IsTrue(suggestion.Value.All(s => set.Any(arg => arg.Name == s.Key)));
Assert.AreEqual(1, suggestion.Value.Count);
Assert.AreEqual(value, suggestion.Value["ema-fast"]);
}
}
Assert.AreEqual(0, enumerator.Count);
}
Assert.Greater(counter, 0);
Assert.AreEqual(Math.Floor((param.MaxValue - param.MinValue) / param.Step.Value) + 1, counter);
}
[TestCase(1, 1, 1)]
[TestCase(10, 100, 1)]
[TestCase(10, 100, 500)]
public void Estimate1D(decimal min, decimal max, decimal step)
{
var param = new OptimizationStepParameter("ema-fast", min, max, step);
var staticParam = new StaticOptimizationParameter("pepe", "SPY");
var set = new HashSet<OptimizationParameter> { param, staticParam };
_strategy.Initialize(new Target("Profit", new Maximization(), null), new List<Constraint>(), set, new StepBaseOptimizationStrategySettings());
Assert.AreEqual(Math.Floor(Math.Abs(max - min) / Math.Abs(step)) + 1, _strategy.GetTotalBacktestEstimate());
}
private static TestCaseData[] OptimizationStepParameter2D => new[]{
new TestCaseData(new decimal[,] {{10, 100, 1}, {20, 200, 1}}),
new TestCaseData(new decimal[,] {{10.5m, 100.5m, 1.5m}, { 20m, 209.9m, 3.5m}})
};
[Test, TestCaseSource(nameof(OptimizationStepParameter2D))]
public void Step2D(decimal[,] data)
{
var args = new HashSet<OptimizationParameter>()
{
new OptimizationStepParameter("ema-fast", data[0,0], data[0,1], data[0,2]),
new OptimizationStepParameter("ema-slow", data[1,0], data[1,1], data[1,2])
};
_strategy.Initialize(new Target("Profit", new Maximization(), null), new List<Constraint>(), args, new StepBaseOptimizationStrategySettings());
var counter = 0;
using (var enumerator = new EnqueueableEnumerator<ParameterSet>())
{
_strategy.NewParameterSet += (s, parameterSet) =>
{
enumerator.Enqueue(parameterSet);
};
_strategy.PushNewResults(OptimizationResult.Initial);
var fastParam = args.First(arg => arg.Name == "ema-fast") as OptimizationStepParameter;
var slowParam = args.First(arg => arg.Name == "ema-slow") as OptimizationStepParameter;
using (var fastEnumerator = new OptimizationStepParameterEnumerator(fastParam))
{
using (var slowEnumerator = new OptimizationStepParameterEnumerator(slowParam))
{
while (fastEnumerator.MoveNext())
{
var fast = fastEnumerator.Current;
slowEnumerator.Reset();
while (slowEnumerator.MoveNext())
{
var slow = slowEnumerator.Current;
counter++;
Assert.IsTrue(enumerator.MoveNext());
var suggestion = enumerator.Current;
Assert.IsNotNull(suggestion);
Assert.IsTrue(suggestion.Value.All(s => args.Any(arg => arg.Name == s.Key)));
Assert.AreEqual(2, suggestion.Value.Count);
Assert.AreEqual(fast, suggestion.Value["ema-fast"]);
Assert.AreEqual(slow, suggestion.Value["ema-slow"]);
}
}
}
}
Assert.AreEqual(0, enumerator.Count);
}
Assert.Greater(counter, 0);
var total = 1m;
foreach (var arg in args.Cast<OptimizationStepParameter>())
{
total *= Math.Floor((arg.MaxValue - arg.MinValue) / arg.Step.Value) + 1;
}
Assert.AreEqual(total, counter);
}
[Test, TestCaseSource(nameof(OptimizationStepParameter2D))]
public void Estimate2D(decimal[,] data)
{
var args = new HashSet<OptimizationParameter>()
{
new OptimizationStepParameter("ema-fast", data[0,0], data[0,1], data[0,2]),
new OptimizationStepParameter("ema-slow", data[1,0], data[1,1], data[1,2]),
new StaticOptimizationParameter("pepe", "SPY")
};
_strategy.Initialize(new Target("Profit", new Maximization(), null), new List<Constraint>(), args, new StepBaseOptimizationStrategySettings());
var total = 1m;
foreach (var arg in args.OfType<OptimizationStepParameter>())
{
total *= Math.Floor((arg.MaxValue - arg.MinValue) / arg.Step.Value) + 1;
}
Assert.AreEqual(total, _strategy.GetTotalBacktestEstimate());
}
[Test]
public void Step3D()
{
var args = new HashSet<OptimizationParameter>()
{
new OptimizationStepParameter("ema-fast", 10, 100, 1),
new OptimizationStepParameter("ema-slow", 20, 200, 4),
new OptimizationStepParameter("ema-custom", 30, 300, 30),
new StaticOptimizationParameter("pepe", "SPY")
};
_strategy.Initialize(new Target("Profit", new Maximization(), null), null, args, new StepBaseOptimizationStrategySettings());
var counter = 0;
using (var enumerator = new EnqueueableEnumerator<ParameterSet>())
{
_strategy.NewParameterSet += (s, parameterSet) =>
{
enumerator.Enqueue(parameterSet);
};
_strategy.PushNewResults(OptimizationResult.Initial);
var fastParam = args.First(arg => arg.Name == "ema-fast") as OptimizationStepParameter;
var slowParam = args.First(arg => arg.Name == "ema-slow") as OptimizationStepParameter;
var customParam = args.First(arg => arg.Name == "ema-custom") as OptimizationStepParameter;
using (var fastEnumerator = new OptimizationStepParameterEnumerator(fastParam))
{
using (var slowEnumerator = new OptimizationStepParameterEnumerator(slowParam))
{
using (var customEnumerator = new OptimizationStepParameterEnumerator(customParam))
{
while (fastEnumerator.MoveNext())
{
var fast = fastEnumerator.Current;
slowEnumerator.Reset();
while (slowEnumerator.MoveNext())
{
var slow = slowEnumerator.Current;
customEnumerator.Reset();
while (customEnumerator.MoveNext())
{
var custom = customEnumerator.Current;
counter++;
Assert.IsTrue(enumerator.MoveNext());
var parameterSet = enumerator.Current;
Assert.IsNotNull(parameterSet);
Assert.IsTrue(parameterSet.Value.All(s =>
args.Any(arg => arg.Name == s.Key)));
Assert.AreEqual(4, parameterSet.Value.Count);
Assert.AreEqual(fast, parameterSet.Value["ema-fast"]);
Assert.AreEqual(slow, parameterSet.Value["ema-slow"]);
Assert.AreEqual(custom, parameterSet.Value["ema-custom"]);
Assert.AreEqual("SPY", parameterSet.Value["pepe"]);
}
}
}
}
}
}
Assert.AreEqual(0, enumerator.Count);
}
Assert.Greater(counter, 0);
var total = 1m;
foreach (var arg in args.OfType<OptimizationStepParameter>())
{
total *= (arg.MaxValue - arg.MinValue) / arg.Step.Value + 1;
}
Assert.AreEqual(total, counter);
}
[Test]
public void Estimate3D()
{
var args = new HashSet<OptimizationParameter>()
{
new OptimizationStepParameter("ema-fast", 10, 100, 1),
new OptimizationStepParameter("ema-slow", 20, 200, 4),
new OptimizationStepParameter("ema-custom", 30, 300, 2)
};
_strategy.Initialize(new Target("Profit", new Maximization(), null), null, args, new StepBaseOptimizationStrategySettings());
var total = 1m;
foreach (var arg in args.Cast<OptimizationStepParameter>())
{
total *= (arg.MaxValue - arg.MinValue) / arg.Step.Value + 1;
}
Assert.AreEqual(total, _strategy.GetTotalBacktestEstimate());
}
[Test]
public void NoStackOverflowException()
{
var depth = 100;
var args = new HashSet<OptimizationParameter>();
for (int i = 0; i < depth; i++)
{
args.Add(new OptimizationStepParameter($"ema-{i}", 10, 100, 1));
}
_strategy.Initialize(new Target("Profit", new Maximization(), null), new List<Constraint>(), args, new StepBaseOptimizationStrategySettings());
var counter = 0;
_strategy.NewParameterSet += (s, parameterSet) =>
{
counter++;
Assert.AreEqual(depth, parameterSet.Value.Count);
if (counter == 10000)
{
throw new Exception("Break loop due to large amount of data");
}
};
Assert.Throws<Exception>(() =>
{
_strategy.PushNewResults(OptimizationResult.Initial);
});
Assert.AreEqual(10000, counter);
}
[Test]
public void IncrementParameterSetId()
{
int nextId = 1,
last = 1;
var set = new HashSet<OptimizationParameter>()
{
new OptimizationStepParameter("ema-fast", 10, 100, 1)
};
_strategy.Initialize(new Target("Profit", new Maximization(), null), null, set, new StepBaseOptimizationStrategySettings());
_strategy.NewParameterSet += (s, parameterSet) =>
{
Assert.AreEqual(nextId++, parameterSet.Id);
};
last = nextId;
_strategy.PushNewResults(OptimizationResult.Initial);
Assert.Greater(nextId, last);
last = nextId;
_strategy.PushNewResults(OptimizationResult.Initial);
Assert.Greater(nextId, last);
}
}
protected override IOptimizationStrategy CreateStrategy()
{
return new GridSearchOptimizationStrategy();
}
protected override OptimizationStrategySettings CreateSettings()
{
return new StepBaseOptimizationStrategySettings { DefaultSegmentAmount = 10 };
}
private static TestCaseData[] StrategySettings => new[]
{
new TestCaseData(new Maximization(), OptimizationStepParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "5"}, { "ema-fast" , "5"} })),
new TestCaseData(new Minimization(), OptimizationStepParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "1"}, { "ema-fast" , "3"} })),
new TestCaseData(new Maximization(), OptimizationMixedParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "5"}, { "ema-fast" , "5"}, { "skipFromResultSum", "SPY" } })),
new TestCaseData(new Minimization(), OptimizationMixedParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "1"}, { "ema-fast" , "3"}, { "skipFromResultSum", "SPY" } }))
};
[Test, TestCaseSource(nameof(StrategySettings))]
public override void StepInsideNoTargetNoConstraints(Extremum extremum, HashSet<OptimizationParameter> optimizationParameters, ParameterSet solution)
{
base.StepInsideNoTargetNoConstraints(extremum, optimizationParameters, solution);
}
private static TestCaseData[] OptimizeWithConstraint => new[]
{
new TestCaseData(0.05m, OptimizationStepParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "1"}, { "ema-fast" , "3"} })),
new TestCaseData(0.06m, OptimizationStepParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "2"}, { "ema-fast" , "3"} })),
new TestCaseData(0.05m, OptimizationMixedParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "1"}, { "ema-fast" , "3"}, { "skipFromResultSum", "SPY" } })),
new TestCaseData(0.06m, OptimizationMixedParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "2"}, { "ema-fast" , "3"}, { "skipFromResultSum", "SPY" } }))
};
[Test, TestCaseSource(nameof(OptimizeWithConstraint))]
public override void StepInsideWithConstraints(decimal drawdown, HashSet<OptimizationParameter> optimizationParameters, ParameterSet solution)
{
base.StepInsideWithConstraints(drawdown, optimizationParameters, solution);
}
private static TestCaseData[] OptimizeWithTarget => new[]
{
new TestCaseData(0m, OptimizationStepParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "1"}, { "ema-fast" , "3"} })),
new TestCaseData(4m, OptimizationStepParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "1"}, { "ema-fast" , "3"} })),
new TestCaseData(5m, OptimizationStepParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "1"}, { "ema-fast" , "5"} })),
new TestCaseData(8m, OptimizationStepParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "3"}, { "ema-fast" , "5"} })),
new TestCaseData(0m, OptimizationMixedParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "1"}, { "ema-fast" , "3"}, { "skipFromResultSum", "SPY" } })),
new TestCaseData(5m, OptimizationMixedParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "1"}, { "ema-fast" , "5"}, { "skipFromResultSum", "SPY" } })),
new TestCaseData(8m, OptimizationMixedParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "3"}, { "ema-fast" , "5"}, { "skipFromResultSum", "SPY" } }))
};
[Test, TestCaseSource(nameof(OptimizeWithTarget))]
public override void StepInsideWithTarget(decimal targetValue, HashSet<OptimizationParameter> optimizationParameters, ParameterSet solution)
{
base.StepInsideWithTarget(targetValue, optimizationParameters, solution);
}
private static TestCaseData[] OptimizeWithTargetNotReached => new[]
{
new TestCaseData(15m, OptimizationStepParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "5"}, { "ema-fast" , "5"} })),
new TestCaseData(155m, OptimizationMixedParameters, new ParameterSet(-1, new Dictionary<string, string>{{"ema-slow", "5"}, { "ema-fast" , "5"}, { "skipFromResultSum", "SPY" } }))
};
[Test, TestCaseSource(nameof(OptimizeWithTargetNotReached))]
public override void TargetNotReached(decimal targetValue, HashSet<OptimizationParameter> optimizationParameters, ParameterSet solution)
{
base.TargetNotReached(targetValue, optimizationParameters, solution);
}
}
}
@@ -0,0 +1,231 @@
/*
* 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.Linq;
using NUnit.Framework;
using QuantConnect.Optimizer;
using QuantConnect.Optimizer.Objectives;
using QuantConnect.Optimizer.Parameters;
using QuantConnect.Optimizer.Strategies;
using QuantConnect.Util;
using System;
using System.Collections.Generic;
using System.Linq;
namespace QuantConnect.Tests.Optimizer.Strategies
{
public abstract class OptimizationStrategyTests
{
protected IOptimizationStrategy Strategy { get; set; }
protected static Func<ParameterSet, decimal> _profit { get; } = parameterSet => parameterSet.Value.Where(pair => pair.Key != "skipFromResultSum").Sum(arg => arg.Value.ToDecimal());
protected static Func<ParameterSet, decimal> _drawdown { get; } = parameterSet => parameterSet.Value.Where(pair => pair.Key != "skipFromResultSum").Sum(arg => arg.Value.ToDecimal()) / 100.0m;
protected static Func<string, string, decimal> _parse { get; } = (dump, parameter) => JObject.Parse(dump).SelectToken($"Statistics.{parameter}").Value<decimal>();
protected static Func<decimal, decimal, string> _stringify { get; } = (profit, drawdown) => BacktestResult.Create(profit, drawdown).ToJson();
private Queue<OptimizationResult> _pendingOptimizationResults;
[SetUp]
public void Init()
{
_pendingOptimizationResults = new Queue<OptimizationResult>();
Strategy = CreateStrategy();
Strategy.NewParameterSet += (s, parameterSet) =>
{
_pendingOptimizationResults.Enqueue(new OptimizationResult(_stringify(_profit(parameterSet), _drawdown(parameterSet)), parameterSet, ""));
};
}
[Test]
public void ThrowOnReinitialization()
{
int nextId = 1;
Strategy.NewParameterSet += (s, parameterSet) =>
{
Assert.AreEqual(nextId++, parameterSet.Id);
};
var set1 = new HashSet<OptimizationParameter>()
{
new OptimizationStepParameter("ema-fast", 10, 100, 1)
};
Strategy.Initialize(new Target("Profit", new Maximization(), null), new List<Constraint>(), set1, CreateSettings());
Strategy.PushNewResults(OptimizationResult.Initial);
Assert.Greater(nextId, 1);
var set2 = new HashSet<OptimizationParameter>()
{
new OptimizationStepParameter("ema-fast", 10, 100, 1),
new OptimizationStepParameter("ema-slow", 10, 100, 2)
};
Assert.Throws<InvalidOperationException>(() =>
{
Strategy.Initialize(new Target("Profit", new Minimization(), null), null, set2, CreateSettings());
});
}
[Test]
public void ThrowIfNotInitialized()
{
var strategy = new GridSearchOptimizationStrategy();
Assert.Throws<InvalidOperationException>(() =>
{
strategy.PushNewResults(OptimizationResult.Initial);
});
}
protected static HashSet<OptimizationParameter> OptimizationStepParameters = new HashSet<OptimizationParameter>
{
new OptimizationStepParameter("ema-slow", 1, 5, 1, 0.1m),
new OptimizationStepParameter("ema-fast", 3, 6, 2,0.1m)
};
protected static HashSet<OptimizationParameter> OptimizationMixedParameters = new HashSet<OptimizationParameter>
{
new OptimizationStepParameter("ema-slow", 1, 5, 1),
new OptimizationStepParameter("ema-fast", 3, 6.75m, 2,0.1m),
new StaticOptimizationParameter("skipFromResultSum", "SPY")
};
public virtual void StepInsideNoTargetNoConstraints(Extremum extremum, HashSet<OptimizationParameter> optimizationParameters, ParameterSet solution)
{
Strategy.Initialize(
new Target("Profit", extremum, null),
null,
optimizationParameters,
CreateSettings());
Strategy.PushNewResults(OptimizationResult.Initial);
while (_pendingOptimizationResults.TryDequeue(out var item))
{
Strategy.PushNewResults(item);
}
Assert.AreEqual(_profit(solution), _parse(Strategy.Solution.JsonBacktestResult, "Profit"));
foreach (var arg in Strategy.Solution.ParameterSet.Value)
{
Assert.AreEqual(solution.Value[arg.Key], arg.Value);
}
}
public virtual void StepInsideWithConstraints(decimal drawdown, HashSet<OptimizationParameter> optimizationParameters, ParameterSet solution)
{
Strategy.Initialize(
new Target("Profit", new Maximization(), null),
new List<Constraint> { new Constraint("Drawdown", ComparisonOperatorTypes.Less, drawdown) },
optimizationParameters,
CreateSettings());
Strategy.PushNewResults(OptimizationResult.Initial);
while (_pendingOptimizationResults.TryDequeue(out var item))
{
Strategy.PushNewResults(item);
}
Assert.AreEqual(_profit(solution), _parse(Strategy.Solution.JsonBacktestResult, "Profit"));
Assert.AreEqual(_drawdown(solution), _parse(Strategy.Solution.JsonBacktestResult, "Drawdown"));
foreach (var arg in Strategy.Solution.ParameterSet.Value)
{
Assert.AreEqual(solution.Value[arg.Key], arg.Value);
}
}
public virtual void StepInsideWithTarget(decimal targetValue, HashSet<OptimizationParameter> optimizationParameters, ParameterSet solution)
{
bool reached = false;
var target = new Target("Profit", new Maximization(), targetValue);
target.Reached += (s, e) =>
{
reached = true;
};
Strategy.Initialize(
target,
null,
optimizationParameters,
CreateSettings());
Strategy.PushNewResults(OptimizationResult.Initial);
while (!reached && _pendingOptimizationResults.TryDequeue(out var item))
{
Strategy.PushNewResults(item);
}
Assert.IsTrue(reached);
Assert.AreEqual(_profit(solution), _parse(Strategy.Solution.JsonBacktestResult, "Profit"));
foreach (var arg in Strategy.Solution.ParameterSet.Value)
{
Assert.AreEqual(solution.Value[arg.Key], arg.Value);
}
}
public virtual void TargetNotReached(decimal targetValue, HashSet<OptimizationParameter> optimizationParameters, ParameterSet solution)
{
bool reached = false;
var target = new Target("Profit", new Maximization(), targetValue);
target.Reached += (s, e) =>
{
reached = true;
};
Strategy.Initialize(
target,
null,
optimizationParameters,
CreateSettings());
Strategy.PushNewResults(OptimizationResult.Initial);
while (!reached && _pendingOptimizationResults.TryDequeue(out var item))
{
Strategy.PushNewResults(item);
}
Assert.IsFalse(reached);
Assert.AreEqual(_profit(solution), _parse(Strategy.Solution.JsonBacktestResult, "Profit"));
foreach (var arg in Strategy.Solution.ParameterSet.Value)
{
Assert.AreEqual(solution.Value[arg.Key], arg.Value);
}
}
protected static TestCaseData[] Estimations => new[]
{
new TestCaseData(OptimizationStepParameters, 10),
new TestCaseData(OptimizationMixedParameters,10)
};
[Test, TestCaseSource(nameof(Estimations))]
public virtual void Estimate(HashSet<OptimizationParameter> optimizationParameters, int expected)
{
Strategy.Initialize(
new Target("Profit", new Maximization(), null),
null,
optimizationParameters,
CreateSettings());
Assert.AreEqual(expected, Strategy.GetTotalBacktestEstimate());
}
protected abstract IOptimizationStrategy CreateStrategy();
protected abstract OptimizationStrategySettings CreateSettings();
}
}
@@ -0,0 +1,123 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
using QuantConnect.Optimizer;
using QuantConnect.Optimizer.Objectives;
using QuantConnect.Optimizer.Parameters;
using QuantConnect.Optimizer.Strategies;
using System;
using System.Collections.Generic;
using System.Linq;
using Math = System.Math;
using OptimizationParameter = QuantConnect.Optimizer.Parameters.OptimizationParameter;
namespace QuantConnect.Tests.Optimizer.Strategies
{
[TestFixture, Parallelizable(ParallelScope.Fixtures)]
public class StepBaseOptimizationStrategyTests
{
private GridSearchOptimizationStrategy _strategy;
[SetUp]
public void Init()
{
this._strategy = new GridSearchOptimizationStrategy();
}
[Test]
public void ThrowIfNoStepProvidedWhenNoSegmentValue()
{
var optimizationParameter = new OptimizationStepParameter("ema-fast", 1, 100);
Assert.Throws<ArgumentException>(() =>
{
_strategy.Initialize(
new Target("Profit", new Maximization(), null),
new List<Constraint>(),
new HashSet<OptimizationParameter> { optimizationParameter },
new StepBaseOptimizationStrategySettings());
});
}
[TestCase(4)]
[TestCase(5)]
[TestCase(10)]
public void CalculateStep(int numberOfSegments)
{
var set = new HashSet<OptimizationParameter>
{
new OptimizationStepParameter("ema-fast", 1, 100),
new OptimizationStepParameter("ema-slow", -10, -10),
new OptimizationStepParameter("ema-custom", -100, -1)
};
_strategy.Initialize(
new Target("Profit", new Maximization(), null),
new List<Constraint>(),
set,
new StepBaseOptimizationStrategySettings { DefaultSegmentAmount = numberOfSegments });
foreach (var parameter in set)
{
var stepParameter = parameter as OptimizationStepParameter;
Assert.NotNull(stepParameter);
var actual = Math.Abs(stepParameter.MaxValue - stepParameter.MinValue) /
numberOfSegments;
Assert.AreEqual(actual, stepParameter.Step);
Assert.AreEqual(actual / 10, stepParameter.MinStep);
}
}
private static TestCaseData[] StepBaseSettings => new[]
{
new TestCaseData(new StepBaseOptimizationStrategySettings {DefaultSegmentAmount = 0}),
new TestCaseData(new StepBaseOptimizationStrategySettings {DefaultSegmentAmount = -1}),
new TestCaseData(null),
new TestCaseData(new StepBaseOptimizationStrategySettings()),
new TestCaseData(new OptimizationStrategySettings())
};
[Test, TestCaseSource(nameof(StepBaseSettings))]
public void ThrowExceptionIfCantCalculateStep(OptimizationStrategySettings settings)
{
var set = new HashSet<OptimizationParameter>
{
new OptimizationStepParameter("ema-fast", 1, 100),
new OptimizationStepParameter("ema-slow", -10, -10),
new OptimizationStepParameter("ema-custom", -100, -1)
};
foreach (var parameter in set)
{
var stepParameter = parameter as OptimizationStepParameter;
Assert.NotNull(stepParameter);
Assert.Null(stepParameter.Step);
Assert.Null(stepParameter.MinStep);
}
Assert.Throws<ArgumentException>(() =>
{
_strategy.Initialize(
new Target("Profit", new Maximization(), null),
new List<Constraint>(),
set,
settings);
});
}
}
}