Lean  $LEAN_TAG$
BaseSetupHandler.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 System.Linq;
19 using Newtonsoft.Json;
20 using QuantConnect.Data;
21 using QuantConnect.Util;
22 using QuantConnect.Logging;
23 using QuantConnect.Packets;
26 using System.Collections.Generic;
34 
36 {
37  /// <summary>
38  /// Base class that provides shared code for
39  /// the <see cref="ISetupHandler"/> implementations
40  /// </summary>
41  public static class BaseSetupHandler
42  {
43  /// <summary>
44  /// Get the maximum time that the creation of an algorithm can take
45  /// </summary>
46  public static TimeSpan AlgorithmCreationTimeout { get; } = TimeSpan.FromSeconds(Config.GetDouble("algorithm-creation-timeout", 90));
47 
48  /// <summary>
49  /// Primary entry point to setup a new algorithm
50  /// </summary>
51  /// <param name="parameters">The parameters object to use</param>
52  /// <returns>True on successfully setting up the algorithm state, or false on error.</returns>
53  public static bool Setup(SetupHandlerParameters parameters)
54  {
55  var algorithm = parameters.Algorithm;
56  var job = parameters.AlgorithmNodePacket;
57 
58  algorithm?.SetDeploymentTarget(job.DeploymentTarget);
59 
60  Log.Trace($"BaseSetupHandler.Setup({job.DeploymentTarget}): UID: {job.UserId.ToStringInvariant()}, " +
61  $"PID: {job.ProjectId.ToStringInvariant()}, Version: {job.Version}, Source: {job.RequestSource}"
62  );
63  return true;
64  }
65 
66  /// <summary>
67  /// Will first check and add all the required conversion rate securities
68  /// and later will seed an initial value to them.
69  /// </summary>
70  /// <param name="algorithm">The algorithm instance</param>
71  /// <param name="universeSelection">The universe selection instance</param>
72  /// <param name="currenciesToUpdateWhiteList">
73  /// If passed, the currencies in the CashBook that are contained in this list will be updated.
74  /// By default, if not passed (null), all currencies in the cashbook without a properly set up currency conversion will be updated.
75  /// This is not intended for actual algorithms but for tests or for this method to be used as a helper.
76  /// </param>
77  public static void SetupCurrencyConversions(
78  IAlgorithm algorithm,
79  UniverseSelection universeSelection,
80  IReadOnlyCollection<string> currenciesToUpdateWhiteList = null)
81  {
82  // this is needed to have non-zero currency conversion rates during warmup
83  // will also set the Cash.ConversionRateSecurity
84  universeSelection.EnsureCurrencyDataFeeds(SecurityChanges.None);
85 
86  // now set conversion rates
87  Func<Cash, bool> cashToUpdateFilter = currenciesToUpdateWhiteList == null
88  ? (x) => x.CurrencyConversion != null && x.ConversionRate == 0
89  : (x) => currenciesToUpdateWhiteList.Contains(x.Symbol);
90  var cashToUpdate = algorithm.Portfolio.CashBook.Values.Where(cashToUpdateFilter).ToList();
91 
92  var securitiesToUpdate = cashToUpdate
93  .SelectMany(x => x.CurrencyConversion.ConversionRateSecurities)
94  .Distinct()
95  .ToList();
96 
97  var historyRequestFactory = new HistoryRequestFactory(algorithm);
98  var historyRequests = new List<HistoryRequest>();
99  foreach (var security in securitiesToUpdate)
100  {
101  var configs = algorithm
102  .SubscriptionManager
103  .SubscriptionDataConfigService
104  .GetSubscriptionDataConfigs(security.Symbol,
105  includeInternalConfigs: true);
106 
107  // we need to order and select a specific configuration type
108  // so the conversion rate is deterministic
109  var configToUse = configs.OrderBy(x => x.TickType).First();
110  var hours = security.Exchange.Hours;
111 
112  var resolution = configs.GetHighestResolution();
113  var startTime = historyRequestFactory.GetStartTimeAlgoTz(
114  security.Symbol,
115  60,
116  resolution,
117  hours,
118  configToUse.DataTimeZone,
119  configToUse.Type);
120  var endTime = algorithm.Time;
121 
122  historyRequests.Add(historyRequestFactory.CreateHistoryRequest(
123  configToUse,
124  startTime,
125  endTime,
126  security.Exchange.Hours,
127  resolution));
128  }
129 
130  // Attempt to get history for these requests and update cash
131  var slices = algorithm.HistoryProvider.GetHistory(historyRequests, algorithm.TimeZone);
132  slices.PushThrough(data =>
133  {
134  foreach (var security in securitiesToUpdate.Where(x => x.Symbol == data.Symbol))
135  {
136  security.SetMarketPrice(data);
137  }
138  });
139 
140  foreach (var cash in cashToUpdate)
141  {
142  cash.Update();
143  }
144 
145  // Any remaining unassigned cash will attempt to fall back to a daily resolution history request to resolve
146  var unassignedCash = cashToUpdate.Where(x => x.ConversionRate == 0).ToList();
147  if (unassignedCash.Any())
148  {
149  Log.Trace(
150  $"Failed to assign conversion rates for the following cash: {string.Join(",", unassignedCash.Select(x => x.Symbol))}." +
151  $" Attempting to request daily resolution history to resolve conversion rate");
152 
153  var unassignedCashSymbols = unassignedCash
154  .SelectMany(x => x.SecuritySymbols)
155  .ToHashSet();
156 
157  var replacementHistoryRequests = new List<HistoryRequest>();
158  foreach (var request in historyRequests.Where(x =>
159  unassignedCashSymbols.Contains(x.Symbol) && x.Resolution < Resolution.Daily))
160  {
161  var newRequest = new HistoryRequest(request.EndTimeUtc.AddDays(-10), request.EndTimeUtc,
162  request.DataType,
163  request.Symbol, Resolution.Daily, request.ExchangeHours, request.DataTimeZone,
164  request.FillForwardResolution,
165  request.IncludeExtendedMarketHours, request.IsCustomData, request.DataNormalizationMode,
166  request.TickType);
167 
168  replacementHistoryRequests.Add(newRequest);
169  }
170 
171  slices = algorithm.HistoryProvider.GetHistory(replacementHistoryRequests, algorithm.TimeZone);
172  slices.PushThrough(data =>
173  {
174  foreach (var security in securitiesToUpdate.Where(x => x.Symbol == data.Symbol))
175  {
176  security.SetMarketPrice(data);
177  }
178  });
179 
180  foreach (var cash in unassignedCash)
181  {
182  cash.Update();
183  }
184  }
185 
186  Log.Trace($"BaseSetupHandler.SetupCurrencyConversions():{Environment.NewLine}" +
187  $"Account Type: {algorithm.BrokerageModel.AccountType}{Environment.NewLine}{Environment.NewLine}{algorithm.Portfolio.CashBook}");
188  // this is useful for debugging
189  algorithm.Portfolio.LogMarginInformation();
190  }
191 
192  /// <summary>
193  /// Initialize the debugger
194  /// </summary>
195  /// <param name="algorithmNodePacket">The algorithm node packet</param>
196  /// <param name="workerThread">The worker thread instance to use</param>
197  public static bool InitializeDebugging(AlgorithmNodePacket algorithmNodePacket, WorkerThread workerThread)
198  {
199  var isolator = new Isolator();
200  return isolator.ExecuteWithTimeLimit(TimeSpan.FromMinutes(5),
201  () => {
202  DebuggerHelper.Initialize(algorithmNodePacket.Language, out var workersInitializationCallback);
203 
204  if(workersInitializationCallback != null)
205  {
206  // initialize workers for debugging if required
207  WeightedWorkScheduler.Instance.AddSingleCallForAll(workersInitializationCallback);
208  }
209  },
210  algorithmNodePacket.RamAllocation,
211  sleepIntervalMillis: 100,
212  workerThread: workerThread);
213  }
214 
215  /// <summary>
216  /// Sets the initial cash for the algorithm if set in the job packet.
217  /// </summary>
218  /// <remarks>Should be called after initialize <see cref="LoadBacktestJobAccountCurrency"/></remarks>
219  public static void LoadBacktestJobCashAmount(IAlgorithm algorithm, BacktestNodePacket job)
220  {
221  // set initial cash, if present in the job
222  if (job.CashAmount.HasValue)
223  {
224  // Zero the CashBook - we'll populate directly from job
225  foreach (var kvp in algorithm.Portfolio.CashBook)
226  {
227  kvp.Value.SetAmount(0);
228  }
229 
230  algorithm.SetCash(job.CashAmount.Value.Amount);
231  }
232  }
233 
234  /// <summary>
235  /// Sets the account currency the algorithm should use if set in the job packet
236  /// </summary>
237  /// <remarks>Should be called before initialize <see cref="LoadBacktestJobCashAmount"/></remarks>
239  {
240  // set account currency if present in the job
241  if (job.CashAmount.HasValue)
242  {
243  algorithm.SetAccountCurrency(job.CashAmount.Value.Currency);
244  }
245  }
246 
247  /// <summary>
248  /// Get the available data feeds from config.json,
249  /// </summary>
250  public static Dictionary<SecurityType, List<TickType>> GetConfiguredDataFeeds()
251  {
252  var dataFeedsConfigString = Config.Get("security-data-feeds");
253 
254  if (!dataFeedsConfigString.IsNullOrEmpty())
255  {
256  var dataFeeds = JsonConvert.DeserializeObject<Dictionary<SecurityType, List<TickType>>>(dataFeedsConfigString);
257  return dataFeeds;
258  }
259 
260  return null;
261  }
262 
263  /// <summary>
264  /// Set the number of trading days per year based on the specified brokerage model.
265  /// </summary>
266  /// <param name="algorithm">The algorithm instance</param>
267  /// <returns>
268  /// The number of trading days per year. For specific brokerages (Coinbase, Binance, Bitfinex, Bybit, FTX, Kraken),
269  /// the value is 365. For other brokerages, the default value is 252.
270  /// </returns>
271  public static void SetBrokerageTradingDayPerYear(IAlgorithm algorithm)
272  {
273  if (algorithm == null)
274  {
275  throw new ArgumentNullException(nameof(algorithm));
276  }
277 
278  algorithm.Settings.TradingDaysPerYear ??= algorithm.BrokerageModel switch
279  {
285  or KrakenBrokerageModel => 365,
286  _ => 252
287  };
288  }
289  }
290 }