/* * 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.Linq; using Newtonsoft.Json; using QuantConnect.Data; using QuantConnect.Util; using QuantConnect.Logging; using QuantConnect.Packets; using QuantConnect.Interfaces; using QuantConnect.Brokerages; using System.Collections.Generic; using QuantConnect.Configuration; using QuantConnect.AlgorithmFactory; using QuantConnect.Lean.Engine.DataFeeds; using QuantConnect.Data.UniverseSelection; using QuantConnect.Lean.Engine.DataFeeds.WorkScheduling; using HistoryRequest = QuantConnect.Data.HistoryRequest; using QuantConnect.Securities; namespace QuantConnect.Lean.Engine.Setup { /// /// Base class that provides shared code for /// the implementations /// public static class BaseSetupHandler { /// /// Get the maximum time that the creation of an algorithm can take /// public static TimeSpan AlgorithmCreationTimeout { get; } = TimeSpan.FromSeconds(Config.GetDouble("algorithm-creation-timeout", 90)); /// /// Primary entry point to setup a new algorithm /// /// The parameters object to use /// True on successfully setting up the algorithm state, or false on error. public static bool Setup(SetupHandlerParameters parameters) { var algorithm = parameters.Algorithm; var job = parameters.AlgorithmNodePacket; algorithm?.SetDeploymentTarget(job.DeploymentTarget); Log.Trace($"BaseSetupHandler.Setup({job.DeploymentTarget}): UID: {job.UserId.ToStringInvariant()}, " + $"PID: {job.ProjectId.ToStringInvariant()}, Version: {job.Version}, Source: {job.RequestSource}" ); return true; } /// /// Will first check and add all the required conversion rate securities /// and later will seed an initial value to them. /// /// The algorithm instance /// The universe selection instance /// /// If passed, the currencies in the CashBook that are contained in this list will be updated. /// By default, if not passed (null), all currencies in the cashbook without a properly set up currency conversion will be updated. /// This is not intended for actual algorithms but for tests or for this method to be used as a helper. /// public static void SetupCurrencyConversions( IAlgorithm algorithm, UniverseSelection universeSelection, IReadOnlyCollection currenciesToUpdateWhiteList = null) { // this is needed to have non-zero currency conversion rates during warmup // will also set the Cash.ConversionRateSecurity universeSelection.EnsureCurrencyDataFeeds(SecurityChanges.None); // now set conversion rates Func cashToUpdateFilter = currenciesToUpdateWhiteList == null ? (x) => x.CurrencyConversion != null && x.ConversionRate == 0 : (x) => currenciesToUpdateWhiteList.Contains(x.Symbol); var cashToUpdate = algorithm.Portfolio.CashBook.Values.Where(cashToUpdateFilter).ToList(); var securitiesToUpdate = cashToUpdate .SelectMany(x => x.CurrencyConversion.ConversionRateSecurities) .Distinct() .ToList(); var historyRequestFactory = new HistoryRequestFactory(algorithm); var historyRequests = new List(); foreach (var security in securitiesToUpdate) { var configs = algorithm .SubscriptionManager .SubscriptionDataConfigService .GetSubscriptionDataConfigs(security.Symbol, includeInternalConfigs: true); // we need to order and select a specific configuration type // so the conversion rate is deterministic var configToUse = configs.OrderBy(x => x.TickType).First(); var hours = security.Exchange.Hours; var resolution = configs.GetHighestResolution(); var startTime = historyRequestFactory.GetStartTimeAlgoTz( security.Symbol, 60, resolution, hours, configToUse.DataTimeZone, configToUse.Type); var endTime = algorithm.Time; historyRequests.Add(historyRequestFactory.CreateHistoryRequest( configToUse, startTime, endTime, security.Exchange.Hours, resolution)); } // Attempt to get history for these requests and update cash var slices = algorithm.HistoryProvider.GetHistory(historyRequests, algorithm.TimeZone); slices.PushThrough(data => { foreach (var security in securitiesToUpdate.Where(x => x.Symbol == data.Symbol)) { security.SetMarketPrice(data); } }); foreach (var cash in cashToUpdate) { cash.Update(); } // Any remaining unassigned cash will attempt to fall back to a daily resolution history request to resolve var unassignedCash = cashToUpdate.Where(x => x.ConversionRate == 0).ToList(); if (unassignedCash.Any()) { Log.Trace( $"Failed to assign conversion rates for the following cash: {string.Join(",", unassignedCash.Select(x => x.Symbol))}." + $" Attempting to request daily resolution history to resolve conversion rate"); var unassignedCashSymbols = unassignedCash .SelectMany(x => x.SecuritySymbols) .ToHashSet(); var replacementHistoryRequests = new List(); foreach (var request in historyRequests.Where(x => unassignedCashSymbols.Contains(x.Symbol) && x.Resolution < Resolution.Daily)) { var newRequest = new HistoryRequest(request.EndTimeUtc.AddDays(-10), request.EndTimeUtc, request.DataType, request.Symbol, Resolution.Daily, request.ExchangeHours, request.DataTimeZone, request.FillForwardResolution, request.IncludeExtendedMarketHours, request.IsCustomData, request.DataNormalizationMode, request.TickType); replacementHistoryRequests.Add(newRequest); } slices = algorithm.HistoryProvider.GetHistory(replacementHistoryRequests, algorithm.TimeZone); slices.PushThrough(data => { foreach (var security in securitiesToUpdate.Where(x => x.Symbol == data.Symbol)) { security.SetMarketPrice(data); } }); foreach (var cash in unassignedCash) { cash.Update(); } } Log.Trace($"BaseSetupHandler.SetupCurrencyConversions():{Environment.NewLine}" + $"Account Type: {algorithm.BrokerageModel.AccountType}{Environment.NewLine}{Environment.NewLine}{algorithm.Portfolio.CashBook}"); // this is useful for debugging algorithm.Portfolio.LogMarginInformation(); } /// /// Initialize the debugger /// /// The algorithm node packet /// The worker thread instance to use public static bool InitializeDebugging(AlgorithmNodePacket algorithmNodePacket, WorkerThread workerThread) { var isolator = new Isolator(); return isolator.ExecuteWithTimeLimit(TimeSpan.FromMinutes(5), () => { DebuggerHelper.Initialize(algorithmNodePacket.Language, out var workersInitializationCallback); if(workersInitializationCallback != null) { // initialize workers for debugging if required WeightedWorkScheduler.Instance.AddSingleCallForAll(workersInitializationCallback); } }, algorithmNodePacket.RamAllocation, sleepIntervalMillis: 100, workerThread: workerThread); } /// /// Sets the initial cash for the algorithm if set in the job packet. /// /// Should be called after initialize public static void LoadBacktestJobCashAmount(IAlgorithm algorithm, BacktestNodePacket job) { // set initial cash, if present in the job if (job.CashAmount.HasValue) { // Zero the CashBook - we'll populate directly from job foreach (var kvp in algorithm.Portfolio.CashBook) { kvp.Value.SetAmount(0); } algorithm.SetCash(job.CashAmount.Value.Amount); } } /// /// Sets the account currency the algorithm should use if set in the job packet /// /// Should be called before initialize public static void LoadBacktestJobAccountCurrency(IAlgorithm algorithm, BacktestNodePacket job) { // set account currency if present in the job if (job.CashAmount.HasValue) { algorithm.SetAccountCurrency(job.CashAmount.Value.Currency); } } /// /// Get the available data feeds from config.json, /// public static Dictionary> GetConfiguredDataFeeds() { var dataFeedsConfigString = Config.Get("security-data-feeds"); if (!dataFeedsConfigString.IsNullOrEmpty()) { var dataFeeds = JsonConvert.DeserializeObject>>(dataFeedsConfigString); return dataFeeds; } return null; } /// /// Set the number of trading days per year based on the specified brokerage model. /// /// The algorithm instance /// /// The number of trading days per year. For specific brokerages (Coinbase, Binance, Bitfinex, Bybit, FTX, Kraken), /// the value is 365. For other brokerages, the default value is 252. /// public static void SetBrokerageTradingDayPerYear(IAlgorithm algorithm) { if (algorithm == null) { throw new ArgumentNullException(nameof(algorithm)); } algorithm.Settings.TradingDaysPerYear ??= algorithm.BrokerageModel switch { CoinbaseBrokerageModel or BinanceBrokerageModel or BitfinexBrokerageModel or BybitBrokerageModel or FTXBrokerageModel or KrakenBrokerageModel => 365, _ => 252 }; } } }