Lean  $LEAN_TAG$
StepBaseOptimizationStrategy.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.Collections.Generic;
18 using System.Globalization;
19 using System.Linq;
22 
24 {
25  /// <summary>
26  /// Base class for any optimization built on top of brute force optimization method
27  /// </summary>
29  {
30  private int _i;
31 
32  /// <summary>
33  /// Indicates was strategy initialized or no
34  /// </summary>
35  protected bool Initialized { get; set; }
36 
37  /// <summary>
38  /// Optimization parameters
39  /// </summary>
40  protected HashSet<OptimizationParameter> OptimizationParameters { get; set; }
41 
42  /// <summary>
43  /// Optimization target, i.e. maximize or minimize
44  /// </summary>
45  protected Target Target { get; set; }
46 
47  /// <summary>
48  /// Optimization constraints; if it doesn't comply just drop the backtest
49  /// </summary>
50  protected IEnumerable<Constraint> Constraints { get; set; }
51 
52  /// <summary>
53  /// Keep the best found solution - lean computed job result and corresponding parameter set
54  /// </summary>
55  public OptimizationResult Solution { get; protected set; }
56 
57  /// <summary>
58  /// Advanced strategy settings
59  /// </summary>
60  public OptimizationStrategySettings Settings { get; protected set; }
61 
62  /// <summary>
63  /// Fires when new parameter set is generated
64  /// </summary>
65  public event EventHandler<ParameterSet> NewParameterSet;
66 
67  /// <summary>
68  /// Initializes the strategy using generator, extremum settings and optimization parameters
69  /// </summary>
70  /// <param name="target">The optimization target</param>
71  /// <param name="constraints">The optimization constraints to apply on backtest results</param>
72  /// <param name="parameters">Optimization parameters</param>
73  /// <param name="settings">Optimization strategy settings</param>
74  public virtual void Initialize(Target target, IReadOnlyList<Constraint> constraints, HashSet<OptimizationParameter> parameters, OptimizationStrategySettings settings)
75  {
76  if (Initialized)
77  {
78  throw new InvalidOperationException($"GridSearchOptimizationStrategy.Initialize: can not be re-initialized.");
79  }
80 
81  Target = target;
82  Constraints = constraints;
83  OptimizationParameters = parameters;
84  Settings = settings;
85 
86  foreach (var optimizationParameter in OptimizationParameters.OfType<OptimizationStepParameter>())
87  {
88  // if the Step optimization parameter does not provide a step to use, we calculate one based on settings
89  if (!optimizationParameter.Step.HasValue)
90  {
91  var stepSettings = Settings as StepBaseOptimizationStrategySettings;
92  if (stepSettings == null)
93  {
94  throw new ArgumentException($"OptimizationStrategySettings is not of {nameof(StepBaseOptimizationStrategySettings)} type", nameof(settings));
95  }
96  CalculateStep(optimizationParameter, stepSettings.DefaultSegmentAmount);
97  }
98  }
99 
100  Initialized = true;
101  }
102 
103  /// <summary>
104  /// Checks whether new lean compute job better than previous and run new iteration if necessary.
105  /// </summary>
106  /// <param name="result">Lean compute job result and corresponding parameter set</param>
107  public abstract void PushNewResults(OptimizationResult result);
108 
109  /// <summary>
110  /// Calculate number of parameter sets within grid
111  /// </summary>
112  /// <returns>Number of parameter sets for given optimization parameters</returns>
114  {
115  var total = 1;
116  foreach (var arg in OptimizationParameters)
117  {
118  total *= Estimate(arg);
119  }
120 
121  return total;
122  }
123 
124  /// <summary>
125  /// Calculates number od data points for step based optimization parameter based on min/max and step values
126  /// </summary>
127  private int Estimate(OptimizationParameter parameter)
128  {
129  if (parameter is StaticOptimizationParameter)
130  {
131  return 1;
132  }
133 
134  var stepParameter = parameter as OptimizationStepParameter;
135  if (stepParameter == null)
136  {
137  throw new InvalidOperationException($"Cannot estimate parameter of type {parameter.GetType().FullName}");
138  }
139 
140  if (!stepParameter.Step.HasValue)
141  {
142  throw new InvalidOperationException("Optimization parameter cannot be estimated due to step value is not initialized");
143  }
144 
145  return (int)Math.Floor((stepParameter.MaxValue - stepParameter.MinValue) / stepParameter.Step.Value) + 1;
146  }
147 
148  /// <summary>
149  /// Handles new parameter set
150  /// </summary>
151  /// <param name="parameterSet">New parameter set</param>
152  protected virtual void OnNewParameterSet(ParameterSet parameterSet)
153  {
154  NewParameterSet?.Invoke(this, parameterSet);
155  }
156 
157  protected virtual void ProcessNewResult(OptimizationResult result)
158  {
159  // check if the incoming result is not the initial seed
160  if (result.Id > 0)
161  {
162  if (Constraints?.All(constraint => constraint.IsMet(result.JsonBacktestResult)) != false)
163  {
164  if (Target.MoveAhead(result.JsonBacktestResult))
165  {
166  Solution = result;
168  }
169  }
170  }
171  }
172 
173  /// <summary>
174  /// Enumerate all possible arrangements
175  /// </summary>
176  /// <param name="args"></param>
177  /// <returns>Collection of possible combinations for given optimization parameters settings</returns>
178  protected IEnumerable<ParameterSet> Step(HashSet<OptimizationParameter> args)
179  {
180  foreach (var step in Recursive(new Queue<OptimizationParameter>(args)))
181  {
182  yield return new ParameterSet(
183  ++_i,
184  step.ToDictionary(kvp => kvp.Key, kvp => kvp.Value));
185  }
186  }
187 
188  /// <summary>
189  /// Calculate step and min step values based on default number of fragments
190  /// </summary>
191  private void CalculateStep(OptimizationStepParameter parameter, int defaultSegmentAmount)
192  {
193  if (defaultSegmentAmount < 1)
194  {
195  throw new ArgumentException($"Number of segments should be positive number, but specified '{defaultSegmentAmount}'", nameof(defaultSegmentAmount));
196  }
197 
198  parameter.Step = Math.Abs(parameter.MaxValue - parameter.MinValue) / defaultSegmentAmount;
199  parameter.MinStep = parameter.Step / 10;
200  }
201 
202  private IEnumerable<Dictionary<string, string>> Recursive(Queue<OptimizationParameter> args)
203  {
204  if (args.Count == 1)
205  {
206  var optimizationParameterLast = args.Dequeue();
207  using (var optimizationParameterLastEnumerator = GetEnumerator(optimizationParameterLast))
208  {
209  while (optimizationParameterLastEnumerator.MoveNext())
210  {
211  yield return new Dictionary<string, string>()
212  {
213  {optimizationParameterLast.Name, optimizationParameterLastEnumerator.Current}
214  };
215  }
216  }
217 
218  yield break;
219  }
220 
221  var optimizationParameter = args.Dequeue();
222  using (var optimizationParameterEnumerator = GetEnumerator(optimizationParameter))
223  {
224  while (optimizationParameterEnumerator.MoveNext())
225  {
226  foreach (var inner in Recursive(new Queue<OptimizationParameter>(args)))
227  {
228  inner.Add(optimizationParameter.Name, optimizationParameterEnumerator.Current);
229 
230  yield return inner;
231  }
232  }
233  }
234  }
235 
236  private IEnumerator<string> GetEnumerator(OptimizationParameter parameter)
237  {
238  var staticOptimizationParameter = parameter as StaticOptimizationParameter;
239  if (staticOptimizationParameter != null)
240  {
241  return new List<string> { staticOptimizationParameter.Value }.GetEnumerator();
242  }
243 
244  var stepParameter = parameter as OptimizationStepParameter;
245  if (stepParameter == null)
246  {
247  throw new InvalidOperationException("");
248  }
249 
250  return new OptimizationStepParameterEnumerator(stepParameter);
251  }
252  }
253 }