/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ using System; using System.Collections.Generic; using System.Linq; using QuantConnect.Algorithm; using QuantConnect.AlgorithmFactory; using QuantConnect.Brokerages.Backtesting; using QuantConnect.Interfaces; using QuantConnect.Lean.Engine.Results; using QuantConnect.Logging; using QuantConnect.Packets; namespace QuantConnect.Lean.Engine.Setup { /// /// Backtesting setup handler processes the algorithm initialize method and sets up the internal state of the algorithm class. /// public class BacktestingSetupHandler : ISetupHandler { private TimeSpan _maxRuntime = TimeSpan.FromSeconds(300); private decimal _startingCaptial = 0; private int _maxOrders = 0; private DateTime _startingDate = new DateTime(1998, 01, 01); /// /// Internal errors list from running the setup proceedures. /// public List Errors { get; set; } /// /// Maximum runtime of the algorithm in seconds. /// /// Maximum runtime is a formula based on the number and resolution of symbols requested, and the days backtesting public TimeSpan MaximumRuntime { get { return _maxRuntime; } } /// /// Starting capital according to the users initialize routine. /// /// Set from the user code. /// public decimal StartingPortfolioValue { get { return _startingCaptial; } } /// /// Start date for analysis loops to search for data. /// /// public DateTime StartingDate { get { return _startingDate; } } /// /// Maximum number of orders for this backtest. /// /// To stop algorithm flooding the backtesting system with hundreds of megabytes of order data we limit it to 100 per day public int MaxOrders { get { return _maxOrders; } } /// /// Initialize the backtest setup handler. /// public BacktestingSetupHandler() { Errors = new List(); } /// /// Creates a new algorithm instance. Verified there's only one defined in the assembly and requires /// instantiation to take less than 10 seconds /// /// Physical location of the assembly. /// Algorithm instance. public IAlgorithm CreateAlgorithmInstance(string assemblyPath) { string error; IAlgorithm algorithm; // limit load times to 10 seconds and force the assembly to have exactly one derived type var loader = new Loader(TimeSpan.FromSeconds(10), names => names.SingleOrDefault()); bool complete = loader.TryCreateAlgorithmInstanceWithIsolator(assemblyPath, out algorithm, out error); if (!complete) throw new Exception(error + " Try re-building algorithm."); return algorithm; } /// /// Setup the algorithm cash, dates and data subscriptions as desired. /// /// Algorithm instance /// Brokerage instance /// Algorithm job /// /// Boolean true on successfully initializing the algorithm public bool Setup(IAlgorithm algorithm, out IBrokerage brokerage, AlgorithmNodePacket baseJob, IResultHandler resultHandler) { var job = baseJob as BacktestNodePacket; if (job == null) { throw new ArgumentException("Expected BacktestNodePacket but received " + baseJob.GetType().Name); } Log.Trace(string.Format("BacktestingSetupHandler.Setup(): Setting up job: Plan: {0}, UID: {1}, PID: {2}, Version: {3}, Source: {4}", job.UserPlan, job.UserId, job.ProjectId, job.Version, job.RequestSource)); brokerage = null; if (algorithm == null) { Errors.Add("Could not create instance of algorithm"); return false; } //Make sure the algorithm start date ok. if (job.PeriodStart == default(DateTime)) { Errors.Add("Algorithm start date was never set"); return false; } //Execute the initialize code: var isolator = new Isolator(); var initializeComplete = isolator.ExecuteWithTimeLimit(TimeSpan.FromSeconds(10), () => { try { //Algorithm is backtesting, not live: algorithm.SetLiveMode(false); //Set the backtest level asset ram allocation limits algorithm.SetAssetLimits(500, 100, 30); //Set the algorithm time before we even initialize: algorithm.SetDateTime(job.PeriodStart); //Initialise the algorithm, get the required data: algorithm.Initialize(); //Add currency data feeds that weren't explicity added in Initialize algorithm.Portfolio.CashBook.EnsureCurrencyDataFeeds(algorithm.Securities, algorithm.SubscriptionManager); } catch (Exception err) { Errors.Add("Failed to initialize algorithm: Initialize(): " + err.Message); } }); //Before continuing, detect if this is ready: if (!initializeComplete) return false; // this needs to be done after algorithm initialization brokerage = new BacktestingBrokerage(algorithm); SetupHandler.UpdateTransactionModels(algorithm, algorithm.BrokerageModel); //Calculate the max runtime for the strategy _maxRuntime = GetMaximumRuntime(job.PeriodStart, job.PeriodFinish, algorithm.SubscriptionManager.Count); //Get starting capital: _startingCaptial = algorithm.Portfolio.Cash; //Max Orders: 10k per backtest: if (job.UserPlan == UserPlan.Free) { _maxOrders = 10000; } else { _maxOrders = int.MaxValue; _maxRuntime += _maxRuntime; } //Set back to the algorithm, algorithm.SetMaximumOrders(_maxOrders); //Starting date of the algorithm: _startingDate = job.PeriodStart; //Put into log for debugging: Log.Trace("SetUp Backtesting: User: " + job.UserId + " ProjectId: " + job.ProjectId + " AlgoId: " + job.AlgorithmId); Log.Trace("Dates: Start: " + job.PeriodStart.ToShortDateString() + " End: " + job.PeriodFinish.ToShortDateString() + " Cash: " + _startingCaptial.ToString("C")); if (Errors.Count > 0) { initializeComplete = false; } return initializeComplete; } /// /// Calculate the maximum runtime for this algorithm job. /// /// State date of the algorithm /// End date of the algorithm /// Number of data feeds the user has requested /// Timespan maximum run period private TimeSpan GetMaximumRuntime(DateTime start, DateTime finish, int subscriptionCount) { double maxRunTime = 0; var jobDays = (finish - start).TotalDays; maxRunTime = 10 * subscriptionCount * jobDays; //Rationalize: if ((maxRunTime / 3600) > 12) { //12 hours maximum maxRunTime = 3600 * 12; } else if (maxRunTime < 60) { //If less than 60 seconds. maxRunTime = 60; } Log.Trace("BacktestingSetupHandler.GetMaxRunTime(): Job Days: " + jobDays + " Max Runtime: " + Math.Round(maxRunTime / 60) + " min"); //Override for windows: if (OS.IsWindows) { maxRunTime = 24 * 60 * 60; } return TimeSpan.FromSeconds(maxRunTime); } /// /// Setup error handlers for the backtest. /// /// Result handler /// Brokerage interface /// Boolean true on successful setup /// Not used in a backtesting setup handler. This is primarily for setting up brokerage error handler functions public bool SetupErrorHandler(IResultHandler results, IBrokerage brokerage) { return true; } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// /// 2 public void Dispose() { // nothing to clean up } } // End Result Handler Thread: } // End Namespace