/* * 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.Globalization; using System.Linq; using QuantConnect.Data; using QuantConnect.Indicators; namespace QuantConnect.Algorithm { public partial class QCAlgorithm { private readonly ConcurrentDictionary _charts = new ConcurrentDictionary(); private static readonly Dictionary> ReservedChartSeriesNames = new Dictionary> { { "Strategy Equity", new List { "Equity", "Daily Performance" } }, { "Meta", new List() }, { "Alpha", new List { "Direction Score", "Magnitude Score" } }, { "Alpha Count", new List { "Count" } }, { "Alpha Assets", new List() }, { "Alpha Asset Breakdown", new List() } }; /// /// Access to the runtime statistics property. User provided statistics. /// /// RuntimeStatistics are displayed in the head banner in live trading public ConcurrentDictionary RuntimeStatistics { get; } = new ConcurrentDictionary(); /// /// Add a Chart object to algorithm collection /// /// Chart object to add to collection. /// public void AddChart(Chart chart) { _charts.TryAdd(chart.Name, chart); } /// /// Plot a chart using string series name, with value. /// /// Name of the plot series /// Value to plot /// public void Plot(string series, decimal value) { //By default plot to the primary chart: Plot("Strategy Equity", series, value); } /// /// Plot a chart using string series name, with int value. Alias of Plot(); /// /// Record(string series, int value) /// public void Record(string series, int value) { Plot(series, value); } /// /// Plot a chart using string series name, with double value. Alias of Plot(); /// /// public void Record(string series, double value) { Plot(series, value); } /// /// Plot a chart using string series name, with decimal value. Alias of Plot(); /// /// /// /// public void Record(string series, decimal value) { //By default plot to the primary chart: Plot(series, value); } /// /// Plot a chart using string series name, with double value. /// /// public void Plot(string series, double value) { Plot(series, value.SafeDecimalCast()); } /// /// Plot a chart using string series name, with int value. /// /// public void Plot(string series, int value) { Plot(series, (decimal)value); } /// ///Plot a chart using string series name, with float value. /// /// public void Plot(string series, float value) { Plot(series, (decimal)value); } /// /// Plot a chart to string chart name, using string series name, with double value. /// /// public void Plot(string chart, string series, double value) { Plot(chart, series, value.SafeDecimalCast()); } /// /// Plot a chart to string chart name, using string series name, with int value /// /// public void Plot(string chart, string series, int value) { Plot(chart, series, (decimal)value); } /// /// Plot a chart to string chart name, using string series name, with float value /// /// public void Plot(string chart, string series, float value) { Plot(chart, series, (decimal)value); } /// /// Plot a value to a chart of string-chart name, with string series name, and decimal value. If chart does not exist, create it. /// /// Chart name /// Series name /// Value of the point public void Plot(string chart, string series, decimal value) { // Check if chart/series names are reserved List reservedSeriesNames; if (ReservedChartSeriesNames.TryGetValue(chart, out reservedSeriesNames)) { if (reservedSeriesNames.Count == 0) { throw new Exception($"Algorithm.Plot(): '{chart}' is a reserved chart name."); } if (reservedSeriesNames.Contains(series)) { throw new Exception($"Algorithm.Plot(): '{series}' is a reserved series name for chart '{chart}'."); } } // If we don't have the chart, create it: _charts.TryAdd(chart, new Chart(chart)); var thisChart = _charts[chart]; if (!thisChart.Series.ContainsKey(series)) { //Number of series in total, excluding reserved charts var seriesCount = _charts.Select(x => x.Value) .Aggregate(0, (i, c) => ReservedChartSeriesNames.TryGetValue(c.Name, out reservedSeriesNames) ? i + c.Series.Values.Count(s => reservedSeriesNames.Count > 0 && !reservedSeriesNames.Contains(s.Name)) : i + c.Series.Count); if (seriesCount > 10) { Error("Exceeded maximum series count: Each backtest can have up to 10 series in total."); return; } //If we don't have the series, create it: thisChart.AddSeries(new Series(series, SeriesType.Line, 0, "$")); } thisChart.Series[series].AddPoint(UtcTime, value); } /// /// Add a series object for charting. This is useful when initializing charts with /// series other than type = line. If a series exists in the chart with the same name, /// then it is replaced. /// /// The chart name /// The series name /// The type of series, i.e, Scatter /// The unit of the y axis, usually $ public void AddSeries(string chart, string series, SeriesType seriesType, string unit = "$") { Chart c; if (!_charts.TryGetValue(chart, out c)) { _charts[chart] = c = new Chart(chart); } c.Series[series] = new Series(series, seriesType, unit); } /// /// Plots the value of each indicator on the chart /// /// The chart's name /// The indicatorsto plot /// public void Plot(string chart, params IndicatorBase[] indicators) where T : IBaseData { foreach (var indicator in indicators) { Plot(chart, indicator.Name, indicator.Current.Value); } } /// /// Automatically plots each indicator when a new value is available /// public void PlotIndicator(string chart, params IndicatorBase[] indicators) where T : IBaseData { foreach (var i in indicators) { if (i == null) continue; // copy loop variable for usage in closure var ilocal = i; i.Updated += (sender, args) => { Plot(chart, ilocal); }; } } /// /// Automatically plots each indicator when a new value is available, optionally waiting for indicator.IsReady to return true /// public void PlotIndicator(string chart, bool waitForReady, params IndicatorBase[] indicators) where T : IBaseData { foreach (var i in indicators) { if (i == null) continue; // copy loop variable for usage in closure var ilocal = i; i.Updated += (sender, args) => { if (!waitForReady || ilocal.IsReady) { Plot(chart, ilocal); } }; } } /// /// Set a runtime statistic for the algorithm. Runtime statistics are shown in the top banner of a live algorithm GUI. /// /// Name of your runtime statistic /// String value of your runtime statistic /// public void SetRuntimeStatistic(string name, string value) { RuntimeStatistics.AddOrUpdate(name, value); } /// /// Set a runtime statistic for the algorithm. Runtime statistics are shown in the top banner of a live algorithm GUI. /// /// Name of your runtime statistic /// Decimal value of your runtime statistic public void SetRuntimeStatistic(string name, decimal value) { SetRuntimeStatistic(name, value.ToString(CultureInfo.InvariantCulture)); } /// /// Set a runtime statistic for the algorithm. Runtime statistics are shown in the top banner of a live algorithm GUI. /// /// Name of your runtime statistic /// Int value of your runtime statistic public void SetRuntimeStatistic(string name, int value) { SetRuntimeStatistic(name, value.ToStringInvariant()); } /// /// Set a runtime statistic for the algorithm. Runtime statistics are shown in the top banner of a live algorithm GUI. /// /// Name of your runtime statistic /// Double value of your runtime statistic public void SetRuntimeStatistic(string name, double value) { SetRuntimeStatistic(name, value.ToString(CultureInfo.InvariantCulture)); } /// /// Get the chart updates by fetch the recent points added and return for dynamic plotting. /// /// /// List of chart updates since the last request /// GetChartUpdates returns the latest updates since previous request. public List GetChartUpdates(bool clearChartData = false) { var updates = _charts.Select(x => x.Value).Select(chart => chart.GetUpdates()).ToList(); if (clearChartData) { // we can clear this data out after getting updates to prevent unnecessary memory usage foreach (var chart in _charts) { foreach (var series in chart.Value.Series) { series.Value.Purge(); } } } return updates; } } }