/* * 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 Deedle; using System.Linq; using Python.Runtime; using QuantConnect.Packets; using System; using QuantConnect.Util; using System.Collections.Generic; namespace QuantConnect.Report.ReportElements { internal sealed class CumulativeReturnsReportElement : ChartReportElement { private LiveResult _live; private BacktestResult _backtest; /// /// Create a new array of cumulative percentage return of strategy and benchmark /// /// Name of the widget /// Location of injection /// Backtest result object /// Live result object public CumulativeReturnsReportElement(string name, string key, BacktestResult backtest, LiveResult live) { _live = live; _backtest = backtest; Name = name; Key = key; } /// /// Generate the cumulative return of the backtest, benchmark, and live /// strategy using the ReportCharts.py python library /// public override string Render() { var backtestReturns = ResultsUtil.EquityPoints(_backtest); var benchmark = ResultsUtil.BenchmarkPoints(_backtest); var liveReturns = ResultsUtil.EquityPoints(_live); var liveBenchmark = ResultsUtil.BenchmarkPoints(_live); var backtestTime = backtestReturns.Keys.ToList(); var backtestStrategy = backtestReturns.Values.ToList(); var benchmarkTime = benchmark.Keys.ToList(); var benchmarkPoints = benchmark.Values.ToList(); var liveTime = liveReturns.Keys.ToList(); var liveStrategy = liveReturns.Values.ToList(); var liveBenchmarkTime = liveBenchmark.Keys.ToList(); var liveBenchmarkStrategy = liveBenchmark.Values.ToList(); var base64 = ""; using (Py.GIL()) { var backtestList = new PyList(); var liveList = new PyList(); var backtestSeries = new Series(backtestTime, backtestStrategy); var liveSeries = new Series(liveTime, liveStrategy); var backtestBenchmarkSeries = new Series(benchmarkTime, benchmarkPoints); var liveBenchmarkSeries = new Series(liveBenchmarkTime, liveBenchmarkStrategy); // Equivalent in python using pandas for the following operations is: // -------------------------------------------------- // >>> # note: [...] denotes the data we're passing in // >>> df = pd.Series([...], index=time) // >>> df_live = pd.Series([...], index=live_time) // >>> df_live = df_live.mul(df.iloc[-1] / df_live.iloc[0]).fillna(method='ffill').dropna() // >>> df_final = pd.concat([df, df_live], axis=0) // >>> df_cumulative_returns = ((df_final.pct_change().dropna() + 1).cumprod() - 1) // -------------------------------------------------- // // We multiply the final value of the backtest and benchmark to have a continuous graph showing the performance out of sample // as a continuation of the cumulative returns graph. Otherwise, we start plotting from 0% and not the last value of the backtest data var backtestLastValue = backtestSeries.ValueCount == 0 ? 0 : backtestSeries.LastValue(); var backtestBenchmarkLastValue = backtestBenchmarkSeries.ValueCount == 0 ? 0 : backtestBenchmarkSeries.LastValue(); var liveContinuousEquity = liveSeries; var liveBenchContinuousEquity = liveBenchmarkSeries; if (liveSeries.ValueCount != 0) { liveContinuousEquity = (liveSeries * (backtestLastValue / liveSeries.FirstValue())) .FillMissing(Direction.Forward) .DropMissing(); } if (liveBenchmarkSeries.ValueCount != 0) { liveBenchContinuousEquity = (liveBenchmarkSeries * (backtestBenchmarkLastValue / liveBenchmarkSeries.FirstValue())) .FillMissing(Direction.Forward) .DropMissing(); } var liveStart = liveContinuousEquity.ValueCount == 0 ? DateTime.MaxValue : liveContinuousEquity.DropMissing().FirstKey(); var liveBenchStart = liveBenchContinuousEquity.ValueCount == 0 ? DateTime.MaxValue : liveBenchContinuousEquity.DropMissing().FirstKey(); var finalEquity = backtestSeries.Where(kvp => kvp.Key < liveStart).Observations.ToList(); var finalBenchEquity = backtestBenchmarkSeries.Where(kvp => kvp.Key < liveBenchStart).Observations.ToList(); finalEquity.AddRange(liveContinuousEquity.Observations); finalBenchEquity.AddRange(liveBenchContinuousEquity.Observations); var finalSeries = (new Series(finalEquity).CumulativeReturns() * 100) .FillMissing(Direction.Forward) .DropMissing(); var finalBenchSeries = (new Series(finalBenchEquity).CumulativeReturns() * 100) .FillMissing(Direction.Forward) .DropMissing(); var backtestCumulativePercent = finalSeries.Where(kvp => kvp.Key < liveStart); var backtestBenchmarkCumulativePercent = finalBenchSeries.Where(kvp => kvp.Key < liveBenchStart); var liveCumulativePercent = finalSeries.Where(kvp => kvp.Key >= liveStart); var liveBenchmarkCumulativePercent = finalBenchSeries.Where(kvp => kvp.Key >= liveBenchStart); backtestList.Append(backtestCumulativePercent.Keys.ToList().ToPython()); backtestList.Append(backtestCumulativePercent.Values.ToList().ToPython()); backtestList.Append(backtestBenchmarkCumulativePercent.Keys.ToList().ToPython()); backtestList.Append(backtestBenchmarkCumulativePercent.Values.ToList().ToPython()); liveList.Append(liveCumulativePercent.Keys.ToList().ToPython()); liveList.Append(liveCumulativePercent.Values.ToList().ToPython()); liveList.Append(liveBenchmarkCumulativePercent.Keys.ToList().ToPython()); liveList.Append(liveBenchmarkCumulativePercent.Values.ToList().ToPython()); base64 = Charting.GetCumulativeReturns(backtestList, liveList); } return base64; } } }