Files
quantconnect--lean/Engine/Alphas/DefaultAlphaHandler.cs
T
Martin Molinero 88d4719327 Fixes for future market users
- Update consumers using Market.USA for future symbols
- Fix for SymbolPropertiesDatabase changes
2020-04-28 16:29:14 -03:00

373 lines
15 KiB
C#

/*
* 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
{
/// <summary>
/// Default alpha handler that supports sending insights to the messaging handler, analyzing insights online
/// </summary>
public class DefaultAlphaHandler : IAlphaHandler
{
private DateTime _lastStepTime;
private List<Insight> _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;
/// <summary>
/// The cancellation token that will be cancelled when requested to exit
/// </summary>
protected CancellationToken CancellationToken => _cancellationTokenSource.Token;
/// <summary>
/// Gets a flag indicating if this handler's thread is still running and processing messages
/// </summary>
public virtual bool IsActive { get; private set; }
/// <summary>
/// Gets the current alpha runtime statistics
/// </summary>
public AlphaRuntimeStatistics RuntimeStatistics { get; private set; }
/// <summary>
/// Gets the algorithm's unique identifier
/// </summary>
protected virtual string AlgorithmId => Job.AlgorithmId;
/// <summary>
/// Gets whether or not the job is a live job
/// </summary>
protected bool LiveMode => Job is LiveNodePacket;
/// <summary>
/// Gets the algorithm job packet
/// </summary>
protected AlgorithmNodePacket Job { get; private set; }
/// <summary>
/// Gets the algorithm instance
/// </summary>
protected IAlgorithm Algorithm { get; private set; }
/// <summary>
/// Gets the confgured messaging handler for sending packets
/// </summary>
protected IMessagingHandler MessagingHandler { get; private set; }
/// <summary>
/// Gets the insight manager instance used to manage the analysis of algorithm insights
/// </summary>
protected virtual IInsightManager InsightManager { get; private set; }
/// <summary>
/// Initializes this alpha handler to accept insights from the specified algorithm
/// </summary>
/// <param name="job">The algorithm job</param>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="messagingHandler">Handler used for sending insights</param>
/// <param name="api">Api instance</param>
/// <param name="transactionHandler">Algorithms transaction handler</param>
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<Insight>();
_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);
}
};
}
/// <summary>
/// Allows each alpha handler implementation to add there own optional extensions
/// </summary>
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));
}
/// <summary>
/// Invoked after the algorithm's Initialize method was called allowing the alpha handler to check
/// other things, such as sampling period for backtests
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
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;
}
/// <summary>
/// Performs processing in sync with the algorithm's time loop to provide consisten reading of data
/// </summary>
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<Insight>() : _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;
}
}
/// <summary>
/// Stops processing and stores insights
/// </summary>
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");
}
/// <summary>
/// Save insight results to persistent storage
/// </summary>
/// <remarks>Method called by the storing timer and on exit</remarks>
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);
}
}
}
/// <summary>
/// Creates the <see cref="InsightManager"/> to manage the analysis of generated insights
/// </summary>
/// <returns>A new insight manager instance</returns>
protected virtual IInsightManager CreateInsightManager()
{
var scoreFunctionProvider = new DefaultInsightScoreFunctionProvider();
return new InsightManager(scoreFunctionProvider, 0);
}
/// <summary>
/// Encapsulates routing finalized insights to the messaging handler
/// </summary>
protected class AlphaResultPacketSender : IInsightManagerExtension, IDisposable
{
private readonly Timer _timer;
private readonly TimeSpan _interval;
private readonly int _maximumQueueLength;
private readonly AlgorithmNodePacket _job;
private readonly ConcurrentQueue<Insight> _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<Insight>();
_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<Insight>();
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
}
}
/// <summary>
/// Enqueue finalized insights to be sent via the messaging handler
/// </summary>
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
}
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
_timer?.DisposeSafely();
}
}
}
}