Lean  $LEAN_TAG$
Program.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.IO;
18 using Newtonsoft.Json;
19 using QuantConnect.Util;
20 using System.Diagnostics;
21 using QuantConnect.Logging;
22 using QuantConnect.Packets;
24 using System.Collections.Generic;
26 
27 namespace QuantConnect.Report
28 {
29  /// <summary>
30  /// Lean Report creates a PDF strategy summary from the backtest and live json objects.
31  /// </summary>
32  class Program
33  {
34  static void Main(string[] args)
35  {
36  // Parse report arguments and merge with config to use in report creator:
37  if (args.Length > 0)
38  {
40  }
41 
42  // initialize required lean handlers
44  var name = Config.Get("strategy-name");
45  var description = Config.Get("strategy-description");
46  var version = Config.Get("strategy-version");
47  var backtestDataFile = Config.Get("backtest-data-source-file");
48  var liveDataFile = Config.Get("live-data-source-file");
49  var destination = Config.Get("report-destination");
50  var reportFormat = Config.Get("report-format");
51  var cssOverrideFile = Config.Get("report-css-override-file", "css/report_override.css");
52  var htmlCustomFile = Config.Get("report-html-custom-file", "template.html");
53 
54  // Parse content from source files into result objects
55  Log.Trace($"QuantConnect.Report.Main(): Parsing source files...{backtestDataFile}, {liveDataFile}");
56  var backtestSettings = new JsonSerializerSettings
57  {
58  Converters = new List<JsonConverter> { new NullResultValueTypeJsonConverter<BacktestResult>() },
59  FloatParseHandling = FloatParseHandling.Decimal
60  };
61 
62  var backtest = JsonConvert.DeserializeObject<BacktestResult>(File.ReadAllText(backtestDataFile), backtestSettings);
63  LiveResult live = null;
64 
65  if (!string.IsNullOrEmpty(liveDataFile))
66  {
67  var settings = new JsonSerializerSettings
68  {
69  NullValueHandling = NullValueHandling.Ignore,
70  Converters = new List<JsonConverter> { new NullResultValueTypeJsonConverter<LiveResult>() }
71  };
72 
73  live = JsonConvert.DeserializeObject<LiveResult>(File.ReadAllText(liveDataFile), settings);
74  }
75 
76  string cssOverrideContent = null;
77  if (!string.IsNullOrEmpty(cssOverrideFile))
78  {
79  if (File.Exists(cssOverrideFile))
80  {
81  cssOverrideContent = File.ReadAllText(cssOverrideFile);
82  }
83  else
84  {
85  Log.Trace($"QuantConnect.Report.Main(): CSS override file {cssOverrideFile} was not found");
86  }
87  }
88 
89  string htmlCustomContent = null;
90  if (!string.IsNullOrEmpty(htmlCustomFile))
91  {
92  if (File.Exists(htmlCustomFile))
93  {
94  htmlCustomContent = File.ReadAllText(htmlCustomFile);
95  }
96  else
97  {
98  Log.Trace($"QuantConnect.Report.Main(): HTML custom file {htmlCustomFile} was not found");
99  }
100  }
101 
102  //Create a new report
103  Log.Trace("QuantConnect.Report.Main(): Instantiating report...");
104  var report = new Report(name, description, version, backtest, live, cssOverride: cssOverrideContent, htmlCustom: htmlCustomContent);
105 
106  // Generate the html content
107  Log.Trace("QuantConnect.Report.Main(): Starting content compile...");
108  string html;
109  string _;
110 
111  report.Compile(out html, out _);
112 
113  //Write it to target destination.
114  if (!string.IsNullOrEmpty(destination))
115  {
116  Log.Trace($"QuantConnect.Report.Main(): Writing content to file {destination}");
117  File.WriteAllText(destination, html);
118 
119  if (!String.IsNullOrEmpty(reportFormat) && reportFormat.ToUpperInvariant() == "PDF")
120  {
121  try
122  {
123  Log.Trace("QuantConnect.Report.Main(): Starting conversion to PDF");
124  // Ensure wkhtmltopdf and xvfb are installed and accessible from the $PATH
125  var pdfDestination = destination.Replace(".html", ".pdf");
126  Process process = new();
127  process.StartInfo.FileName = "xvfb-run";
128  process.StartInfo.Arguments = $"--server-args=\"-screen 0, 1600x1200x24+32\" wkhtmltopdf {destination} {pdfDestination}";
129  process.StartInfo.UseShellExecute = false;
130  process.StartInfo.RedirectStandardOutput = true;
131  process.StartInfo.RedirectStandardError = true;
132  process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
133 
134  process.OutputDataReceived += (sender, e) => Log.Trace($"QuantConnect.Report.Main(): {e.Data}");
135  process.ErrorDataReceived += (sender, e) => Log.Error($"QuantConnect.Report.Main(): {e.Data}");
136 
137  process.Start();
138 
139  process.BeginOutputReadLine();
140  process.BeginErrorReadLine();
141 
142  var processExited = process.WaitForExit(1*60*1000); // wait for up to 1 minutes
143 
144  if (processExited)
145  {
146  Log.Trace("QuantConnect.Report.Main(): Convert to PDF process exited with code " + process.ExitCode);
147  }
148  else
149  {
150  Log.Error("QuantConnect.Report.Main(): Process did not exit within the timeout period.");
151  process.Kill(); // kill the process if it's still running
152  }
153  }
154  catch (Exception ex)
155  {
156  Log.Error($"QuantConnect.Report.Main(): {ex.Message}");
157  }
158  }
159  }
160  else
161  {
162  Console.Write(html);
163  }
164 
165  Log.Trace("QuantConnect.Report.Main(): Completed.");
166 
167  if (!Console.IsInputRedirected && !Config.GetBool("close-automatically"))
168  {
169  Console.ReadKey();
170  }
171  }
172  }
173 }