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