Lean  $LEAN_TAG$
ConsoleLeanOptimizer.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 QuantConnect.Util;
17 using System;
18 using System.Collections.Concurrent;
19 using System.Diagnostics;
20 using System.IO;
21 using System.Linq;
23 using Log = QuantConnect.Logging.Log;
24 
26 {
27  /// <summary>
28  /// Optimizer implementation that launches Lean as a local process
29  /// </summary>
31  {
32  private readonly string _leanLocation;
33  private readonly string _rootResultDirectory;
34  private readonly string _extraLeanArguments;
35  private readonly ConcurrentDictionary<string, Process> _processByBacktestId;
36 
37  /// <summary>
38  /// Creates a new instance
39  /// </summary>
40  /// <param name="nodePacket">The optimization node packet to handle</param>
41  public ConsoleLeanOptimizer(OptimizationNodePacket nodePacket) : base(nodePacket)
42  {
43  _processByBacktestId = new ConcurrentDictionary<string, Process>();
44 
45  _rootResultDirectory = Configuration.Config.Get("results-destination-folder",
46  Path.Combine(Directory.GetCurrentDirectory(), $"opt-{nodePacket.OptimizationId}"));
47  Directory.CreateDirectory(_rootResultDirectory);
48 
49  _leanLocation = Configuration.Config.Get("lean-binaries-location",
50  Path.Combine(Directory.GetCurrentDirectory(), "../../../Launcher/bin/Debug/QuantConnect.Lean.Launcher"));
51 
52  var closeLeanAutomatically = Configuration.Config.GetBool("optimizer-close-automatically", true);
53  _extraLeanArguments = $"--close-automatically {closeLeanAutomatically}";
54 
55  var algorithmTypeName = Configuration.Config.Get("algorithm-type-name");
56  if (!string.IsNullOrEmpty(algorithmTypeName))
57  {
58  _extraLeanArguments += $" --algorithm-type-name \"{algorithmTypeName}\"";
59  }
60 
61  var algorithmLanguage = Configuration.Config.Get("algorithm-language");
62  if (!string.IsNullOrEmpty(algorithmLanguage))
63  {
64  _extraLeanArguments += $" --algorithm-language \"{algorithmLanguage}\"";
65  }
66 
67  var algorithmLocation = Configuration.Config.Get("algorithm-location");
68  if (!string.IsNullOrEmpty(algorithmLocation))
69  {
70  _extraLeanArguments += $" --algorithm-location \"{algorithmLocation}\"";
71  }
72  }
73 
74  /// <summary>
75  /// Handles starting Lean for a given parameter set
76  /// </summary>
77  /// <param name="parameterSet">The parameter set for the backtest to run</param>
78  /// <param name="backtestName">The backtest name to use</param>
79  /// <returns>The new unique backtest id</returns>
80  protected override string RunLean(ParameterSet parameterSet, string backtestName)
81  {
82  var backtestId = Guid.NewGuid().ToString();
83  var optimizationId = NodePacket.OptimizationId;
84  // start each lean instance in its own directory so they store their logs & results, else they fight for the log.txt file
85  var resultDirectory = Path.Combine(_rootResultDirectory, backtestId);
86  Directory.CreateDirectory(resultDirectory);
87 
88  // Use ProcessStartInfo class
89  var startInfo = new ProcessStartInfo
90  {
91  FileName = _leanLocation,
92  WorkingDirectory = Directory.GetParent(_leanLocation).FullName,
93  Arguments = $"--results-destination-folder \"{resultDirectory}\" --algorithm-id \"{backtestId}\" --optimization-id \"{optimizationId}\" --parameters {parameterSet} --backtest-name \"{backtestName}\" {_extraLeanArguments}",
94  WindowStyle = ProcessWindowStyle.Minimized
95  };
96 
97  var process = new Process
98  {
99  StartInfo = startInfo,
100  EnableRaisingEvents = true
101  };
102  _processByBacktestId[backtestId] = process;
103 
104  process.Exited += (sender, args) =>
105  {
106  if (Disposed)
107  {
108  // handle abort
109  return;
110  }
111 
112  _processByBacktestId.TryRemove(backtestId, out process);
113  var backtestResult = $"{backtestId}.json";
114  var resultJson = Path.Combine(_rootResultDirectory, backtestId, backtestResult);
115  NewResult(File.Exists(resultJson) ? File.ReadAllText(resultJson) : null, backtestId);
116  process.DisposeSafely();
117  };
118 
119  process.Start();
120 
121  return backtestId;
122  }
123 
124  /// <summary>
125  /// Stops lean process
126  /// </summary>
127  /// <param name="backtestId">Specified backtest id</param>
128  protected override void AbortLean(string backtestId)
129  {
130  Process process;
131  if (_processByBacktestId.TryRemove(backtestId, out process))
132  {
133  process.Kill();
134  process.DisposeSafely();
135  }
136  }
137 
138  /// <summary>
139  /// Sends an update of the current optimization status to the user
140  /// </summary>
141  protected override void SendUpdate()
142  {
143  // end handler will already log a nice message on end
144  if (Status != OptimizationStatus.Completed && Status != OptimizationStatus.Aborted)
145  {
146  var currentEstimate = GetCurrentEstimate();
147  var stats = GetRuntimeStatistics();
148  var message = $"ConsoleLeanOptimizer.SendUpdate(): {currentEstimate} {string.Join(", ", stats.Select(pair => $"{pair.Key}:{pair.Value}"))}";
149  var currentBestBacktest = Strategy.Solution;
150  if (currentBestBacktest != null)
151  {
152  message += $". Best id:'{currentBestBacktest.BacktestId}'. {OptimizationTarget}. Parameters ({currentBestBacktest.ParameterSet})";
153  }
154  Log.Trace(message);
155  }
156  }
157  }
158 }