/* * 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.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using Newtonsoft.Json; using QuantConnect.Algorithm.Framework.Alphas; using QuantConnect.Algorithm.Framework.Alphas.Analysis; using QuantConnect.Algorithm.Framework.Alphas.Analysis.Providers; using QuantConnect.Configuration; using QuantConnect.Interfaces; using QuantConnect.Lean.Engine.Alpha; using QuantConnect.Lean.Engine.TransactionHandlers; using QuantConnect.Logging; using QuantConnect.Packets; using QuantConnect.Statistics; using QuantConnect.Util; namespace QuantConnect.Lean.Engine.Alphas { /// /// Default alpha handler that supports sending insights to the messaging handler, analyzing insights online /// public class DefaultAlphaHandler : IAlphaHandler { private DateTime _lastStepTime; private List _insights; private ISecurityValuesProvider _securityValuesProvider; private FitnessScoreManager _fitnessScore; private DateTime _lastFitnessScoreCalculation; private Timer _storeTimer; private readonly object _lock = new object(); private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private string _alphaResultsPath; /// /// The cancellation token that will be cancelled when requested to exit /// protected CancellationToken CancellationToken => _cancellationTokenSource.Token; /// /// Gets a flag indicating if this handler's thread is still running and processing messages /// public virtual bool IsActive { get; private set; } /// /// Gets the current alpha runtime statistics /// public AlphaRuntimeStatistics RuntimeStatistics { get; private set; } /// /// Gets the algorithm's unique identifier /// protected virtual string AlgorithmId => Job.AlgorithmId; /// /// Gets whether or not the job is a live job /// protected bool LiveMode => Job is LiveNodePacket; /// /// Gets the algorithm job packet /// protected AlgorithmNodePacket Job { get; private set; } /// /// Gets the algorithm instance /// protected IAlgorithm Algorithm { get; private set; } /// /// Gets the confgured messaging handler for sending packets /// protected IMessagingHandler MessagingHandler { get; private set; } /// /// Gets the insight manager instance used to manage the analysis of algorithm insights /// protected virtual IInsightManager InsightManager { get; private set; } /// /// Initializes this alpha handler to accept insights from the specified algorithm /// /// The algorithm job /// The algorithm instance /// Handler used for sending insights /// Api instance /// Algorithms transaction handler public virtual void Initialize(AlgorithmNodePacket job, IAlgorithm algorithm, IMessagingHandler messagingHandler, IApi api, ITransactionHandler transactionHandler) { // initializing these properties just in case, doesn't hurt to have them populated Job = job; Algorithm = algorithm; MessagingHandler = messagingHandler; _fitnessScore = new FitnessScoreManager(); _insights = new List(); _securityValuesProvider = new AlgorithmSecurityValuesProvider(algorithm); InsightManager = CreateInsightManager(); var statistics = new StatisticsInsightManagerExtension(algorithm); RuntimeStatistics = statistics.Statistics; InsightManager.AddExtension(statistics); AddInsightManagerCustomExtensions(statistics); var baseDirectory = Config.Get("results-destination-folder", Directory.GetCurrentDirectory()); var directory = Path.Combine(baseDirectory, AlgorithmId); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } _alphaResultsPath = Path.Combine(directory, "alpha-results.json"); // when insight is generated, take snapshot of securities and place in queue for insight manager to process on alpha thread algorithm.InsightsGenerated += (algo, collection) => { lock (_insights) { _insights.AddRange(collection.Insights); } }; } /// /// Allows each alpha handler implementation to add there own optional extensions /// protected virtual void AddInsightManagerCustomExtensions(StatisticsInsightManagerExtension statistics) { // send scored insights to messaging handler InsightManager.AddExtension(new AlphaResultPacketSender(Job, MessagingHandler, TimeSpan.FromSeconds(3), 50)); InsightManager.AddExtension(new ChartingInsightManagerExtension(Algorithm, statistics)); } /// /// Invoked after the algorithm's Initialize method was called allowing the alpha handler to check /// other things, such as sampling period for backtests /// /// The algorithm instance public void OnAfterAlgorithmInitialized(IAlgorithm algorithm) { _fitnessScore.Initialize(algorithm); // send date ranges to extensions for initialization -- this data wasn't available when the handler was // initialzied, so we need to invoke it here InsightManager.InitializeExtensionsForRange(algorithm.StartDate, algorithm.EndDate, algorithm.UtcTime); if (LiveMode) { _storeTimer = new Timer(_ => StoreInsights(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10)); } IsActive = true; } /// /// Performs processing in sync with the algorithm's time loop to provide consisten reading of data /// public virtual void ProcessSynchronousEvents() { // check the last snap shot time, we may have already produced a snapshot via OnInsightsGenerated if (_lastStepTime != Algorithm.UtcTime) { _lastStepTime = Algorithm.UtcTime; lock (_insights) { InsightManager.Step(_lastStepTime, _securityValuesProvider.GetAllValues(), new GeneratedInsightsCollection(_lastStepTime, _insights.Count == 0 ? Enumerable.Empty() : _insights, clone: false)); _insights.Clear(); } } if (_lastFitnessScoreCalculation.Date != Algorithm.UtcTime.Date) { _lastFitnessScoreCalculation = Algorithm.UtcTime.Date; _fitnessScore.UpdateScores(); RuntimeStatistics.FitnessScore = _fitnessScore.FitnessScore; RuntimeStatistics.PortfolioTurnover = _fitnessScore.PortfolioTurnover; RuntimeStatistics.SortinoRatio = _fitnessScore.SortinoRatio; RuntimeStatistics.ReturnOverMaxDrawdown = _fitnessScore.ReturnOverMaxDrawdown; } } /// /// Stops processing and stores insights /// public void Exit() { Log.Trace("DefaultAlphaHandler.Exit(): Exiting..."); _storeTimer.DisposeSafely(); _storeTimer = null; // persist insights at exit StoreInsights(); InsightManager?.DisposeSafely(); IsActive = false; Log.Trace("DefaultAlphaHandler.Exit(): Ended"); } /// /// Save insight results to persistent storage /// /// Method called by the storing timer and on exit protected virtual void StoreInsights() { // avoid reentrancy if (Monitor.TryEnter(_lock)) { try { if (InsightManager == null) { // could be null if we are not initialized and exit is called return; } // default save all results to disk and don't remove any from memory // this will result in one file with all of the insights/results in it var insights = InsightManager.AllInsights.OrderBy(insight => insight.GeneratedTimeUtc).ToList(); if (insights.Count > 0) { var directory = Directory.GetParent(_alphaResultsPath); if (!directory.Exists) { directory.Create(); } File.WriteAllText(_alphaResultsPath, JsonConvert.SerializeObject(insights, Formatting.Indented)); } } finally { Monitor.Exit(_lock); } } } /// /// Creates the to manage the analysis of generated insights /// /// A new insight manager instance protected virtual IInsightManager CreateInsightManager() { var scoreFunctionProvider = new DefaultInsightScoreFunctionProvider(); return new InsightManager(scoreFunctionProvider, 0); } /// /// Encapsulates routing finalized insights to the messaging handler /// protected class AlphaResultPacketSender : IInsightManagerExtension, IDisposable { private readonly Timer _timer; private readonly TimeSpan _interval; private readonly int _maximumQueueLength; private readonly AlgorithmNodePacket _job; private readonly ConcurrentQueue _insights; private readonly IMessagingHandler _messagingHandler; private readonly int _maximumNumberOfInsightsPerPacket; public AlphaResultPacketSender(AlgorithmNodePacket job, IMessagingHandler messagingHandler, TimeSpan interval, int maximumNumberOfInsightsPerPacket) { _job = job; _interval = interval; _messagingHandler = messagingHandler; _insights = new ConcurrentQueue(); _maximumNumberOfInsightsPerPacket = maximumNumberOfInsightsPerPacket; _timer = new Timer(MessagingUpdateIntervalElapsed); _timer.Change(interval, interval); // don't bother holding on more than makes sense. this makes the maximum // number of insights we'll hold in the queue equal to one hour's worth of // processing. For 50 insights/message @ 1message/sec this is 90K _maximumQueueLength = (int) (TimeSpan.FromMinutes(30).Ticks / interval.Ticks * maximumNumberOfInsightsPerPacket); } private void MessagingUpdateIntervalElapsed(object state) { try { _timer.Change(Timeout.Infinite, Timeout.Infinite); try { Insight insight; var insights = new List(); while (insights.Count < _maximumNumberOfInsightsPerPacket && _insights.TryDequeue(out insight)) { insights.Add(insight); } if (insights.Count > 0) { _messagingHandler.Send(new AlphaResultPacket(_job.AlgorithmId, _job.UserId, insights)); } } catch (Exception err) { Log.Error(err); } _timer.Change(_interval, _interval); } catch (ObjectDisposedException) { // pass. The timer callback can be called even after disposed } } /// /// Enqueue finalized insights to be sent via the messaging handler /// public void OnInsightAnalysisCompleted(InsightAnalysisContext context) { if (_insights.Count < _maximumQueueLength) { _insights.Enqueue(context.Insight); } } public void Step(DateTime frontierTimeUtc) { //NOP } public void InitializeForRange(DateTime algorithmStartDate, DateTime algorithmEndDate, DateTime algorithmUtcTime) { //NOP } public void OnInsightGenerated(InsightAnalysisContext context) { //NOP } public void OnInsightClosed(InsightAnalysisContext context) { //NOP } /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// 2 public void Dispose() { _timer?.DisposeSafely(); } } } }