/* * 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.Globalization; using System.Linq; using QuantConnect.Optimizer.Objectives; using QuantConnect.Optimizer.Parameters; namespace QuantConnect.Optimizer.Strategies { /// /// Base class for any optimization built on top of brute force optimization method /// public abstract class StepBaseOptimizationStrategy : IOptimizationStrategy { private int _i; /// /// Indicates was strategy initialized or no /// protected bool Initialized { get; set; } /// /// Optimization parameters /// protected HashSet OptimizationParameters { get; set; } /// /// Optimization target, i.e. maximize or minimize /// protected Target Target { get; set; } /// /// Optimization constraints; if it doesn't comply just drop the backtest /// protected IEnumerable Constraints { get; set; } /// /// Keep the best found solution - lean computed job result and corresponding parameter set /// public OptimizationResult Solution { get; protected set; } /// /// Advanced strategy settings /// public OptimizationStrategySettings Settings { get; protected set; } /// /// Fires when new parameter set is generated /// public event EventHandler NewParameterSet; /// /// Initializes the strategy using generator, extremum settings and optimization parameters /// /// The optimization target /// The optimization constraints to apply on backtest results /// Optimization parameters /// Optimization strategy settings public virtual void Initialize(Target target, IReadOnlyList constraints, HashSet parameters, OptimizationStrategySettings settings) { if (Initialized) { throw new InvalidOperationException($"GridSearchOptimizationStrategy.Initialize: can not be re-initialized."); } Target = target; Constraints = constraints; OptimizationParameters = parameters; Settings = settings; foreach (var optimizationParameter in OptimizationParameters.OfType()) { // if the Step optimization parameter does not provide a step to use, we calculate one based on settings if (!optimizationParameter.Step.HasValue) { var stepSettings = Settings as StepBaseOptimizationStrategySettings; if (stepSettings == null) { throw new ArgumentException($"OptimizationStrategySettings is not of {nameof(StepBaseOptimizationStrategySettings)} type", nameof(settings)); } CalculateStep(optimizationParameter, stepSettings.DefaultSegmentAmount); } } Initialized = true; } /// /// Checks whether new lean compute job better than previous and run new iteration if necessary. /// /// Lean compute job result and corresponding parameter set public abstract void PushNewResults(OptimizationResult result); /// /// Calculate number of parameter sets within grid /// /// Number of parameter sets for given optimization parameters public int GetTotalBacktestEstimate() { var total = 1; foreach (var arg in OptimizationParameters) { total *= Estimate(arg); } return total; } /// /// Calculates number od data points for step based optimization parameter based on min/max and step values /// private int Estimate(OptimizationParameter parameter) { if (parameter is StaticOptimizationParameter) { return 1; } var stepParameter = parameter as OptimizationStepParameter; if (stepParameter == null) { throw new InvalidOperationException($"Cannot estimate parameter of type {parameter.GetType().FullName}"); } if (!stepParameter.Step.HasValue) { throw new InvalidOperationException("Optimization parameter cannot be estimated due to step value is not initialized"); } return (int)Math.Floor((stepParameter.MaxValue - stepParameter.MinValue) / stepParameter.Step.Value) + 1; } /// /// Handles new parameter set /// /// New parameter set protected virtual void OnNewParameterSet(ParameterSet parameterSet) { NewParameterSet?.Invoke(this, parameterSet); } protected virtual void ProcessNewResult(OptimizationResult result) { // check if the incoming result is not the initial seed if (result.Id > 0) { if (Constraints?.All(constraint => constraint.IsMet(result.JsonBacktestResult)) != false) { if (Target.MoveAhead(result.JsonBacktestResult)) { Solution = result; Target.CheckCompliance(); } } } } /// /// Enumerate all possible arrangements /// /// /// Collection of possible combinations for given optimization parameters settings protected IEnumerable Step(HashSet args) { foreach (var step in Recursive(new Queue(args))) { yield return new ParameterSet( ++_i, step.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)); } } /// /// Calculate step and min step values based on default number of fragments /// private void CalculateStep(OptimizationStepParameter parameter, int defaultSegmentAmount) { if (defaultSegmentAmount < 1) { throw new ArgumentException($"Number of segments should be positive number, but specified '{defaultSegmentAmount}'", nameof(defaultSegmentAmount)); } parameter.Step = Math.Abs(parameter.MaxValue - parameter.MinValue) / defaultSegmentAmount; parameter.MinStep = parameter.Step / 10; } private IEnumerable> Recursive(Queue args) { if (args.Count == 1) { var optimizationParameterLast = args.Dequeue(); using (var optimizationParameterLastEnumerator = GetEnumerator(optimizationParameterLast)) { while (optimizationParameterLastEnumerator.MoveNext()) { yield return new Dictionary() { {optimizationParameterLast.Name, optimizationParameterLastEnumerator.Current} }; } } yield break; } var optimizationParameter = args.Dequeue(); using (var optimizationParameterEnumerator = GetEnumerator(optimizationParameter)) { while (optimizationParameterEnumerator.MoveNext()) { foreach (var inner in Recursive(new Queue(args))) { inner.Add(optimizationParameter.Name, optimizationParameterEnumerator.Current); yield return inner; } } } } private IEnumerator GetEnumerator(OptimizationParameter parameter) { var staticOptimizationParameter = parameter as StaticOptimizationParameter; if (staticOptimizationParameter != null) { return new List { staticOptimizationParameter.Value }.GetEnumerator(); } var stepParameter = parameter as OptimizationStepParameter; if (stepParameter == null) { throw new InvalidOperationException(""); } return new OptimizationStepParameterEnumerator(stepParameter); } } }