chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user