/* * 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 QuantConnect.Algorithm.Framework.Alphas; using QuantConnect.Algorithm.Framework.Alphas.Analysis; using QuantConnect.Interfaces; namespace QuantConnect.Lean.Engine.Alphas { /// /// Manages alpha statistics responsbilities /// public class StatisticsInsightManagerExtension : IInsightManagerExtension { private readonly double _smoothingFactor; private readonly int _rollingAverageIsReadyCount; private readonly bool _requireRollingAverageWarmup; private readonly decimal _tradablePercentOfVolume; /// /// Gets the current statistics. The values are current as of the time specified /// in and /// public AlphaRuntimeStatistics Statistics { get; } /// /// Gets whether or not the rolling average statistics is ready /// public bool RollingAverageIsReady => !_requireRollingAverageWarmup || Statistics.TotalInsightsAnalysisCompleted >= _rollingAverageIsReadyCount; /// /// Initializes a new instance of the class /// /// The account currency provider /// Percent of volume of first bar used to estimate the maximum number of tradable shares. Defaults to 1% /// The period used for exponential smoothing of scores - this is a number of insights. Defaults to 100 insight predictions. /// Specify true to force the population average scoring to warmup before plotting. public StatisticsInsightManagerExtension( IAccountCurrencyProvider accountCurrencyProvider, decimal tradablePercentOfVolume = 0.01m, int period = 100, bool requireRollingAverageWarmup = false) { Statistics = new AlphaRuntimeStatistics(accountCurrencyProvider); _tradablePercentOfVolume = tradablePercentOfVolume; _smoothingFactor = 2.0 / (period + 1.0); // use normal ema warmup period _rollingAverageIsReadyCount = period; _requireRollingAverageWarmup = requireRollingAverageWarmup; } /// /// Handles the event /// Increments total, long and short counters. Updates long/short ratio /// /// The newly generated insight context public void OnInsightGenerated(InsightAnalysisContext context) { // incremement total insight counter Statistics.TotalInsightsGenerated++; // update long/short ratio statistics if (context.Insight.Direction == InsightDirection.Up) { Statistics.LongCount++; } else if (context.Insight.Direction == InsightDirection.Down) { Statistics.ShortCount++; } } /// /// Computes an estimated value for the insight. This is intended to be invoked at the end of the /// insight period, i.e, when now == insight.GeneratedTimeUtc + insight.Period; /// /// Context whose insight has just closed public void OnInsightClosed(InsightAnalysisContext context) { // increment closed insight counter Statistics.TotalInsightsClosed += 1; // tradable volume (purposefully includes fractional shares) var volume = _tradablePercentOfVolume * context.InitialValues.Volume; // value of the entering the trade in the account currency var enterValue = volume * context.InitialValues.Price * context.InitialValues.QuoteCurrencyConversionRate; // value of exiting the trade in the account currency var exitValue = volume * context.CurrentValues.Price * context.CurrentValues.QuoteCurrencyConversionRate; // total value delta between enter and exit values var insightValue = (int)context.Insight.Direction * (exitValue - enterValue); context.Insight.EstimatedValue = insightValue; Statistics.TotalAccumulatedEstimatedAlphaValue += insightValue; } /// /// Updates the specified statistics with the new scores /// /// Context whose insight has just completed analysis public void OnInsightAnalysisCompleted(InsightAnalysisContext context) { // increment analysis completed counter Statistics.TotalInsightsAnalysisCompleted += 1; foreach (var scoreType in InsightManager.ScoreTypes) { if (!context.ShouldAnalyze(scoreType)) { continue; } var score = context.Score.GetScore(scoreType); var currentTime = context.CurrentValues.TimeUtc; // online population average var mean = Statistics.MeanPopulationScore.GetScore(scoreType); var newMean = mean + (score - mean) / Statistics.TotalInsightsAnalysisCompleted; Statistics.MeanPopulationScore.SetScore(scoreType, newMean, currentTime); var newEma = newMean; if (Statistics.TotalInsightsAnalysisCompleted > 4) { // compute the traditional ema var ema = Statistics.RollingAveragedPopulationScore.GetScore(scoreType); newEma = score * _smoothingFactor + ema * (1 - _smoothingFactor); } Statistics.RollingAveragedPopulationScore.SetScore(scoreType, newEma, currentTime); } } /// /// Invokes the manager at the end of the time step. /// /// The current frontier time utc public void Step(DateTime frontierTimeUtc) { Statistics.SetDate(frontierTimeUtc); } /// /// Allows the extension to initialize itself over the expected range /// /// The start date of the algorithm /// The end date of the algorithm /// The algorithm's current utc time public void InitializeForRange(DateTime algorithmStartDate, DateTime algorithmEndDate, DateTime algorithmUtcTime) { Statistics.SetStartDate(algorithmStartDate); } } }