Lean  $LEAN_TAG$
Statistics.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.Collections.Generic;
18 using System.IO;
19 using System.Linq;
20 using System.Net;
21 using MathNet.Numerics.Distributions;
22 using MathNet.Numerics.Statistics;
23 using QuantConnect.Logging;
24 
26 {
27  /// <summary>
28  /// Calculate all the statistics required from the backtest, based on the equity curve and the profit loss statement.
29  /// </summary>
30  /// <remarks>This is a particularly ugly class and one of the first ones written. It should be thrown out and re-written.</remarks>
31  public class Statistics
32  {
33  /// <summary>
34  /// Drawdown maximum percentage.
35  /// </summary>
36  /// <param name="equityOverTime"></param>
37  /// <param name="rounding"></param>
38  /// <returns></returns>
39  public static decimal DrawdownPercent(SortedDictionary<DateTime, decimal> equityOverTime, int rounding = 2)
40  {
41  var dd = 0m;
42  try
43  {
44  var lPrices = equityOverTime.Values.ToList();
45  var lDrawdowns = new List<decimal>();
46  var high = lPrices[0];
47  foreach (var price in lPrices)
48  {
49  if (price >= high) high = price;
50  lDrawdowns.Add((price/high) - 1);
51  }
52  dd = Math.Round(Math.Abs(lDrawdowns.Min()), rounding);
53  }
54  catch (Exception err)
55  {
56  Log.Error(err);
57  }
58  return dd;
59  }
60 
61  /// <summary>
62  /// Annual compounded returns statistic based on the final-starting capital and years.
63  /// </summary>
64  /// <param name="startingCapital">Algorithm starting capital</param>
65  /// <param name="finalCapital">Algorithm final capital</param>
66  /// <param name="years">Years trading</param>
67  /// <returns>Decimal fraction for annual compounding performance</returns>
68  public static decimal CompoundingAnnualPerformance(decimal startingCapital, decimal finalCapital, decimal years)
69  {
70  if (years == 0 || startingCapital == 0)
71  {
72  return 0;
73  }
74  var power = 1 / (double)years;
75  var baseNumber = (double)finalCapital / (double)startingCapital;
76  var result = Math.Pow(baseNumber, power) - 1;
77  return result.IsNaNOrInfinity() ? 0 : result.SafeDecimalCast();
78  }
79 
80  /// <summary>
81  /// Annualized return statistic calculated as an average of daily trading performance multiplied by the number of trading days per year.
82  /// </summary>
83  /// <param name="performance">Dictionary collection of double performance values</param>
84  /// <param name="tradingDaysPerYear">Trading days per year for the assets in portfolio</param>
85  /// <remarks>May be unaccurate for forex algorithms with more trading days in a year</remarks>
86  /// <returns>Double annual performance percentage</returns>
87  public static double AnnualPerformance(List<double> performance, double tradingDaysPerYear)
88  {
89  return Math.Pow((performance.Average() + 1), tradingDaysPerYear) - 1;
90  }
91 
92  /// <summary>
93  /// Annualized variance statistic calculation using the daily performance variance and trading days per year.
94  /// </summary>
95  /// <param name="performance"></param>
96  /// <param name="tradingDaysPerYear"></param>
97  /// <remarks>Invokes the variance extension in the MathNet Statistics class</remarks>
98  /// <returns>Annual variance value</returns>
99  public static double AnnualVariance(List<double> performance, double tradingDaysPerYear)
100  {
101  var variance = performance.Variance();
102  return variance.IsNaNOrZero() ? 0 : variance * tradingDaysPerYear;
103  }
104 
105  /// <summary>
106  /// Annualized standard deviation
107  /// </summary>
108  /// <param name="performance">Collection of double values for daily performance</param>
109  /// <param name="tradingDaysPerYear">Number of trading days for the assets in portfolio to get annualize standard deviation.</param>
110  /// <remarks>
111  /// Invokes the variance extension in the MathNet Statistics class.
112  /// Feasibly the trading days per year can be fetched from the dictionary of performance which includes the date-times to get the range; if is more than 1 year data.
113  /// </remarks>
114  /// <returns>Value for annual standard deviation</returns>
115  public static double AnnualStandardDeviation(List<double> performance, double tradingDaysPerYear)
116  {
117  return Math.Sqrt(AnnualVariance(performance, tradingDaysPerYear));
118  }
119 
120  /// <summary>
121  /// Annualized variance statistic calculation using the daily performance variance and trading days per year.
122  /// </summary>
123  /// <param name="performance"></param>
124  /// <param name="tradingDaysPerYear"></param>
125  /// <param name="minimumAcceptableReturn">Minimum acceptable return</param>
126  /// <remarks>Invokes the variance extension in the MathNet Statistics class</remarks>
127  /// <returns>Annual variance value</returns>
128  public static double AnnualDownsideVariance(List<double> performance, double tradingDaysPerYear, double minimumAcceptableReturn = 0)
129  {
130  return AnnualVariance(performance.Where(ret => ret < minimumAcceptableReturn).ToList(), tradingDaysPerYear);
131  }
132 
133  /// <summary>
134  /// Annualized downside standard deviation
135  /// </summary>
136  /// <param name="performance">Collection of double values for daily performance</param>
137  /// <param name="tradingDaysPerYear">Number of trading days for the assets in portfolio to get annualize standard deviation.</param>
138  /// <param name="minimumAcceptableReturn">Minimum acceptable return</param>
139  /// <returns>Value for annual downside standard deviation</returns>
140  public static double AnnualDownsideStandardDeviation(List<double> performance, double tradingDaysPerYear, double minimumAcceptableReturn = 0)
141  {
142  return Math.Sqrt(AnnualDownsideVariance(performance, tradingDaysPerYear, minimumAcceptableReturn));
143  }
144 
145  /// <summary>
146  /// Tracking error volatility (TEV) statistic - a measure of how closely a portfolio follows the index to which it is benchmarked
147  /// </summary>
148  /// <remarks>If algo = benchmark, TEV = 0</remarks>
149  /// <param name="algoPerformance">Double collection of algorithm daily performance values</param>
150  /// <param name="benchmarkPerformance">Double collection of benchmark daily performance values</param>
151  /// <param name="tradingDaysPerYear">Number of trading days per year</param>
152  /// <returns>Value for tracking error</returns>
153  public static double TrackingError(List<double> algoPerformance, List<double> benchmarkPerformance, double tradingDaysPerYear)
154  {
155  // Un-equal lengths will blow up other statistics, but this will handle the case here
156  if (algoPerformance.Count != benchmarkPerformance.Count)
157  {
158  return 0.0;
159  }
160 
161  var performanceDifference = new List<double>();
162  for (var i = 0; i < algoPerformance.Count; i++)
163  {
164  performanceDifference.Add(algoPerformance[i] - benchmarkPerformance[i]);
165  }
166 
167  return Math.Sqrt(AnnualVariance(performanceDifference, tradingDaysPerYear));
168  }
169 
170  /// <summary>
171  /// Sharpe ratio with respect to risk free rate: measures excess of return per unit of risk.
172  /// </summary>
173  /// <remarks>With risk defined as the algorithm's volatility</remarks>
174  /// <param name="averagePerformance">Average daily performance</param>
175  /// <param name="standardDeviation">Standard deviation of the daily performance</param>
176  /// <param name="riskFreeRate">The risk free rate</param>
177  /// <returns>Value for sharpe ratio</returns>
178  public static double SharpeRatio(double averagePerformance, double standardDeviation, double riskFreeRate)
179  {
180  return standardDeviation == 0 ? 0 : (averagePerformance - riskFreeRate) / standardDeviation;
181  }
182 
183  /// <summary>
184  /// Sharpe ratio with respect to risk free rate: measures excess of return per unit of risk.
185  /// </summary>
186  /// <remarks>With risk defined as the algorithm's volatility</remarks>
187  /// <param name="averagePerformance">Average daily performance</param>
188  /// <param name="standardDeviation">Standard deviation of the daily performance</param>
189  /// <param name="riskFreeRate">The risk free rate</param>
190  /// <returns>Value for sharpe ratio</returns>
191  public static decimal SharpeRatio(decimal averagePerformance, decimal standardDeviation, decimal riskFreeRate)
192  {
193  return SharpeRatio((double)averagePerformance, (double)standardDeviation, (double)riskFreeRate).SafeDecimalCast();
194  }
195 
196  /// <summary>
197  /// Sharpe ratio with respect to risk free rate: measures excess of return per unit of risk.
198  /// </summary>
199  /// <remarks>With risk defined as the algorithm's volatility</remarks>
200  /// <param name="algoPerformance">Collection of double values for the algorithm daily performance</param>
201  /// <param name="riskFreeRate">The risk free rate</param>
202  /// <param name="tradingDaysPerYear">Trading days per year for the assets in portfolio</param>
203  /// <returns>Value for sharpe ratio</returns>
204  public static double SharpeRatio(List<double> algoPerformance, double riskFreeRate, double tradingDaysPerYear)
205  {
206  return SharpeRatio(AnnualPerformance(algoPerformance, tradingDaysPerYear), AnnualStandardDeviation(algoPerformance, tradingDaysPerYear), riskFreeRate);
207  }
208 
209  /// <summary>
210  /// Sortino ratio with respect to risk free rate: measures excess of return per unit of downside risk.
211  /// </summary>
212  /// <remarks>With risk defined as the algorithm's volatility</remarks>
213  /// <param name="algoPerformance">Collection of double values for the algorithm daily performance</param>
214  /// <param name="riskFreeRate">The risk free rate</param>
215  /// <param name="tradingDaysPerYear">Trading days per year for the assets in portfolio</param>
216  /// <param name="minimumAcceptableReturn">Minimum acceptable return for Sortino ratio calculation</param>
217  /// <returns>Value for Sortino ratio</returns>
218  public static double SortinoRatio(List<double> algoPerformance, double riskFreeRate, double tradingDaysPerYear, double minimumAcceptableReturn = 0)
219  {
220  return SharpeRatio(AnnualPerformance(algoPerformance, tradingDaysPerYear), AnnualDownsideStandardDeviation(algoPerformance, tradingDaysPerYear, minimumAcceptableReturn), riskFreeRate);
221  }
222 
223  /// <summary>
224  /// Helper method to calculate the probabilistic sharpe ratio
225  /// </summary>
226  /// <param name="listPerformance">The list of algorithm performance values</param>
227  /// <param name="benchmarkSharpeRatio">The benchmark sharpe ratio to use</param>
228  /// <returns>Probabilistic Sharpe Ratio</returns>
229  public static double ProbabilisticSharpeRatio(List<double> listPerformance,
230  double benchmarkSharpeRatio)
231  {
232  var observedSharpeRatio = ObservedSharpeRatio(listPerformance);
233 
234  var skewness = listPerformance.Skewness();
235  var kurtosis = listPerformance.Kurtosis();
236 
237  var operandA = skewness * observedSharpeRatio;
238  var operandB = ((kurtosis - 1) / 4) * (Math.Pow(observedSharpeRatio, 2));
239 
240  // Calculated standard deviation of point estimate
241  var estimateStandardDeviation = Math.Pow((1 - operandA + operandB) / (listPerformance.Count - 1), 0.5);
242 
243  if (double.IsNaN(estimateStandardDeviation))
244  {
245  return 0;
246  }
247 
248  // Calculate PSR(benchmark)
249  var value = estimateStandardDeviation.IsNaNOrZero() ? 0 : (observedSharpeRatio - benchmarkSharpeRatio) / estimateStandardDeviation;
250  return (new Normal()).CumulativeDistribution(value);
251  }
252 
253  /// <summary>
254  /// Calculates the observed sharpe ratio
255  /// </summary>
256  /// <param name="listPerformance">The performance samples to use</param>
257  /// <returns>The observed sharpe ratio</returns>
258  public static double ObservedSharpeRatio(List<double> listPerformance)
259  {
260  var performanceAverage = listPerformance.Average();
261  var standardDeviation = listPerformance.StandardDeviation();
262  // we don't annualize it
263  return standardDeviation.IsNaNOrZero() ? 0 : performanceAverage / standardDeviation;
264  }
265 
266  /// <summary>
267  /// Calculate the drawdown between a high and current value
268  /// </summary>
269  /// <param name="current">Current value</param>
270  /// <param name="high">Latest maximum</param>
271  /// <param name="roundingDecimals">Digits to round the result too</param>
272  /// <returns>Drawdown percentage</returns>
273  public static decimal DrawdownPercent(decimal current, decimal high, int roundingDecimals = 2)
274  {
275  if (high == 0)
276  {
277  throw new ArgumentException("High value must not be 0");
278  }
279 
280  var drawdownPercentage = ((current / high) - 1) * 100;
281  return Math.Round(drawdownPercentage, roundingDecimals);
282  }
283 
284  } // End of Statistics
285 
286 } // End of Namespace