Lean  $LEAN_TAG$
BacktestingSetupHandler.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 
17 using System;
18 using QuantConnect.Util;
19 using QuantConnect.Logging;
20 using QuantConnect.Packets;
24 using System.Collections.Generic;
28 
30 {
31  /// <summary>
32  /// Backtesting setup handler processes the algorithm initialize method and sets up the internal state of the algorithm class.
33  /// </summary>
35  {
36  /// <summary>
37  /// Get the maximum time that the initialization of an algorithm can take
38  /// </summary>
39  protected TimeSpan InitializationTimeOut { get; set; } = TimeSpan.FromMinutes(5);
40 
41  /// <summary>
42  /// Get the maximum time that the creation of an algorithm can take
43  /// </summary>
45 
46  /// <summary>
47  /// The worker thread instance the setup handler should use
48  /// </summary>
49  public WorkerThread WorkerThread { get; set; }
50 
51  /// <summary>
52  /// Internal errors list from running the setup procedures.
53  /// </summary>
54  public List<Exception> Errors { get; set; }
55 
56  /// <summary>
57  /// Maximum runtime of the algorithm in seconds.
58  /// </summary>
59  /// <remarks>Maximum runtime is a formula based on the number and resolution of symbols requested, and the days backtesting</remarks>
60  public TimeSpan MaximumRuntime { get; protected set; }
61 
62  /// <summary>
63  /// Starting capital according to the users initialize routine.
64  /// </summary>
65  /// <remarks>Set from the user code.</remarks>
66  /// <seealso cref="QCAlgorithm.SetCash(decimal)"/>
67  public decimal StartingPortfolioValue { get; protected set; }
68 
69  /// <summary>
70  /// Start date for analysis loops to search for data.
71  /// </summary>
72  /// <seealso cref="QCAlgorithm.SetStartDate(DateTime)"/>
73  public DateTime StartingDate { get; protected set; }
74 
75  /// <summary>
76  /// Maximum number of orders for this backtest.
77  /// </summary>
78  /// <remarks>To stop algorithm flooding the backtesting system with hundreds of megabytes of order data we limit it to 100 per day</remarks>
79  public int MaxOrders { get; protected set; }
80 
81  /// <summary>
82  /// Initialize the backtest setup handler.
83  /// </summary>
85  {
86  MaximumRuntime = TimeSpan.FromSeconds(300);
87  Errors = new List<Exception>();
88  StartingDate = new DateTime(1998, 01, 01);
89  }
90 
91  /// <summary>
92  /// Create a new instance of an algorithm from a physical dll path.
93  /// </summary>
94  /// <param name="assemblyPath">The path to the assembly's location</param>
95  /// <param name="algorithmNodePacket">Details of the task required</param>
96  /// <returns>A new instance of IAlgorithm, or throws an exception if there was an error</returns>
97  public virtual IAlgorithm CreateAlgorithmInstance(AlgorithmNodePacket algorithmNodePacket, string assemblyPath)
98  {
99  string error;
100  IAlgorithm algorithm;
101 
102  var debugNode = algorithmNodePacket as BacktestNodePacket;
103  var debugging = debugNode != null && debugNode.Debugging || Config.GetBool("debugging", false);
104 
105  if (debugging && !BaseSetupHandler.InitializeDebugging(algorithmNodePacket, WorkerThread))
106  {
107  throw new AlgorithmSetupException("Failed to initialize debugging");
108  }
109 
110  // Limit load times to 90 seconds and force the assembly to have exactly one derived type
111  var loader = new Loader(debugging, algorithmNodePacket.Language, AlgorithmCreationTimeout, names => names.SingleOrAlgorithmTypeName(Config.Get("algorithm-type-name", algorithmNodePacket.AlgorithmId)), WorkerThread);
112  var complete = loader.TryCreateAlgorithmInstanceWithIsolator(assemblyPath, algorithmNodePacket.RamAllocation, out algorithm, out error);
113  if (!complete) throw new AlgorithmSetupException($"During the algorithm initialization, the following exception has occurred: {error}");
114 
115  return algorithm;
116  }
117 
118  /// <summary>
119  /// Creates a new <see cref="BacktestingBrokerage"/> instance
120  /// </summary>
121  /// <param name="algorithmNodePacket">Job packet</param>
122  /// <param name="uninitializedAlgorithm">The algorithm instance before Initialize has been called</param>
123  /// <param name="factory">The brokerage factory</param>
124  /// <returns>The brokerage instance, or throws if error creating instance</returns>
125  public virtual IBrokerage CreateBrokerage(AlgorithmNodePacket algorithmNodePacket, IAlgorithm uninitializedAlgorithm, out IBrokerageFactory factory)
126  {
127  factory = new BacktestingBrokerageFactory();
128  return new BacktestingBrokerage(uninitializedAlgorithm);
129  }
130 
131  /// <summary>
132  /// Setup the algorithm cash, dates and data subscriptions as desired.
133  /// </summary>
134  /// <param name="parameters">The parameters object to use</param>
135  /// <returns>Boolean true on successfully initializing the algorithm</returns>
136  public virtual bool Setup(SetupHandlerParameters parameters)
137  {
138  var algorithm = parameters.Algorithm;
139  var job = parameters.AlgorithmNodePacket as BacktestNodePacket;
140  if (job == null)
141  {
142  throw new ArgumentException("Expected BacktestNodePacket but received " + parameters.AlgorithmNodePacket.GetType().Name);
143  }
144 
145  BaseSetupHandler.Setup(parameters);
146 
147  if (algorithm == null)
148  {
149  Errors.Add(new AlgorithmSetupException("Could not create instance of algorithm"));
150  return false;
151  }
152 
153  algorithm.Name = job.Name;
154 
155  //Make sure the algorithm start date ok.
156  if (job.PeriodStart == default(DateTime))
157  {
158  Errors.Add(new AlgorithmSetupException("Algorithm start date was never set"));
159  return false;
160  }
161 
162  var controls = job.Controls;
163  var isolator = new Isolator();
164  var initializeComplete = isolator.ExecuteWithTimeLimit(InitializationTimeOut, () =>
165  {
166  try
167  {
168  parameters.ResultHandler.SendStatusUpdate(AlgorithmStatus.Initializing, "Initializing algorithm...");
169  //Set our parameters
170  algorithm.SetParameters(job.Parameters);
171  algorithm.SetAvailableDataTypes(BaseSetupHandler.GetConfiguredDataFeeds());
172 
173  //Algorithm is backtesting, not live:
174  algorithm.SetAlgorithmMode(job.AlgorithmMode);
175 
176  //Set the source impl for the event scheduling
177  algorithm.Schedule.SetEventSchedule(parameters.RealTimeHandler);
178 
179  // set the option chain provider
180  var optionChainProvider = new BacktestingOptionChainProvider();
181  var initParameters = new ChainProviderInitializeParameters(parameters.MapFileProvider, algorithm.HistoryProvider);
182  optionChainProvider.Initialize(initParameters);
183  algorithm.SetOptionChainProvider(new CachingOptionChainProvider(optionChainProvider));
184 
185  // set the future chain provider
186  var futureChainProvider = new BacktestingFutureChainProvider();
187  futureChainProvider.Initialize(initParameters);
188  algorithm.SetFutureChainProvider(new CachingFutureChainProvider(futureChainProvider));
189 
190  // before we call initialize
192 
193  //Initialise the algorithm, get the required data:
194  algorithm.Initialize();
195 
196  // set start and end date if present in the job
197  if (job.PeriodStart.HasValue)
198  {
199  algorithm.SetStartDate(job.PeriodStart.Value);
200  }
201  if (job.PeriodFinish.HasValue)
202  {
203  algorithm.SetEndDate(job.PeriodFinish.Value);
204  }
205 
206  if(job.OutOfSampleMaxEndDate.HasValue)
207  {
208  if(algorithm.EndDate > job.OutOfSampleMaxEndDate.Value)
209  {
210  Log.Trace($"BacktestingSetupHandler.Setup(): setting end date to {job.OutOfSampleMaxEndDate.Value:yyyyMMdd}");
211  algorithm.SetEndDate(job.OutOfSampleMaxEndDate.Value);
212 
213  if (algorithm.StartDate > algorithm.EndDate)
214  {
215  algorithm.SetStartDate(algorithm.EndDate);
216  }
217  }
218  }
219 
220  // after we call initialize
222 
223  // after algorithm was initialized, should set trading days per year for our great portfolio statistics
225 
226  // finalize initialization
227  algorithm.PostInitialize();
228  }
229  catch (Exception err)
230  {
231  Errors.Add(new AlgorithmSetupException("During the algorithm initialization, the following exception has occurred: ", err));
232  }
233  }, controls.RamAllocation,
234  sleepIntervalMillis: 100, // entire system is waiting on this, so be as fast as possible
235  workerThread: WorkerThread);
236 
237  if (Errors.Count > 0)
238  {
239  // if we already got an error just exit right away
240  return false;
241  }
242 
243  //Before continuing, detect if this is ready:
244  if (!initializeComplete) return false;
245 
246  MaximumRuntime = TimeSpan.FromMinutes(job.Controls.MaximumRuntimeMinutes);
247 
249  StartingPortfolioValue = algorithm.Portfolio.Cash;
250 
251  // Get and set maximum orders for this job
252  MaxOrders = job.Controls.BacktestingMaxOrders;
253  algorithm.SetMaximumOrders(MaxOrders);
254 
255  //Starting date of the algorithm:
256  StartingDate = algorithm.StartDate;
257 
258  //Put into log for debugging:
259  Log.Trace("SetUp Backtesting: User: " + job.UserId + " ProjectId: " + job.ProjectId + " AlgoId: " + job.AlgorithmId);
260  Log.Trace($"Dates: Start: {algorithm.StartDate.ToStringInvariant("d")} " +
261  $"End: {algorithm.EndDate.ToStringInvariant("d")} " +
262  $"Cash: {StartingPortfolioValue.ToStringInvariant("C")} " +
263  $"MaximumRuntime: {MaximumRuntime} " +
264  $"MaxOrders: {MaxOrders}");
265 
266  return initializeComplete;
267  }
268 
269  /// <summary>
270  /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
271  /// </summary>
272  /// <filterpriority>2</filterpriority>
273  public void Dispose()
274  {
275  }
276  } // End Result Handler Thread:
277 
278 } // End Namespace