/*
* 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.Generic;
using System.Linq;
using MathNet.Numerics.Statistics;
using Newtonsoft.Json;
using QuantConnect.Util;
namespace QuantConnect.Statistics
{
///
/// The class represents a set of statistics calculated from equity and benchmark samples
///
public class PortfolioStatistics
{
private const decimal RiskFreeRate = 0;
///
/// The average rate of return for winning trades
///
[JsonConverter(typeof(JsonRoundingConverter))]
public decimal AverageWinRate { get; set; }
///
/// The average rate of return for losing trades
///
[JsonConverter(typeof(JsonRoundingConverter))]
public decimal AverageLossRate { get; set; }
///
/// The ratio of the average win rate to the average loss rate
///
/// If the average loss rate is zero, ProfitLossRatio is set to 0
[JsonConverter(typeof(JsonRoundingConverter))]
public decimal ProfitLossRatio { get; set; }
///
/// The ratio of the number of winning trades to the total number of trades
///
/// If the total number of trades is zero, WinRate is set to zero
[JsonConverter(typeof(JsonRoundingConverter))]
public decimal WinRate { get; set; }
///
/// The ratio of the number of losing trades to the total number of trades
///
/// If the total number of trades is zero, LossRate is set to zero
[JsonConverter(typeof(JsonRoundingConverter))]
public decimal LossRate { get; set; }
///
/// The expected value of the rate of return
///
[JsonConverter(typeof(JsonRoundingConverter))]
public decimal Expectancy { get; set; }
///
/// Annual compounded returns statistic based on the final-starting capital and years.
///
/// Also known as Compound Annual Growth Rate (CAGR)
[JsonConverter(typeof(JsonRoundingConverter))]
public decimal CompoundingAnnualReturn { get; set; }
///
/// Drawdown maximum percentage.
///
[JsonConverter(typeof(JsonRoundingConverter))]
public decimal Drawdown { get; set; }
///
/// The total net profit percentage.
///
[JsonConverter(typeof(JsonRoundingConverter))]
public decimal TotalNetProfit { get; set; }
///
/// Sharpe ratio with respect to risk free rate: measures excess of return per unit of risk.
///
/// With risk defined as the algorithm's volatility
[JsonConverter(typeof(JsonRoundingConverter))]
public decimal SharpeRatio { get; set; }
///
/// Probabilistic Sharpe Ratio is a probability measure associated with the Sharpe ratio.
/// It informs us of the probability that the estimated Sharpe ratio is greater than a chosen benchmark
///
/// See https://www.quantconnect.com/forum/discussion/6483/probabilistic-sharpe-ratio/p1
[JsonConverter(typeof(JsonRoundingConverter))]
public decimal ProbabilisticSharpeRatio { get; set; }
///
/// Algorithm "Alpha" statistic - abnormal returns over the risk free rate and the relationshio (beta) with the benchmark returns.
///
[JsonConverter(typeof(JsonRoundingConverter))]
public decimal Alpha { get; set; }
///
/// Algorithm "beta" statistic - the covariance between the algorithm and benchmark performance, divided by benchmark's variance
///
[JsonConverter(typeof(JsonRoundingConverter))]
public decimal Beta { get; set; }
///
/// Annualized standard deviation
///
[JsonConverter(typeof(JsonRoundingConverter))]
public decimal AnnualStandardDeviation { get; set; }
///
/// Annualized variance statistic calculation using the daily performance variance and trading days per year.
///
[JsonConverter(typeof(JsonRoundingConverter))]
public decimal AnnualVariance { get; set; }
///
/// Information ratio - risk adjusted return
///
/// (risk = tracking error volatility, a volatility measures that considers the volatility of both algo and benchmark)
[JsonConverter(typeof(JsonRoundingConverter))]
public decimal InformationRatio { get; set; }
///
/// Tracking error volatility (TEV) statistic - a measure of how closely a portfolio follows the index to which it is benchmarked
///
/// If algo = benchmark, TEV = 0
[JsonConverter(typeof(JsonRoundingConverter))]
public decimal TrackingError { get; set; }
///
/// Treynor ratio statistic is a measurement of the returns earned in excess of that which could have been earned on an investment that has no diversifiable risk
///
[JsonConverter(typeof(JsonRoundingConverter))]
public decimal TreynorRatio { get; set; }
///
/// Initializes a new instance of the class
///
/// Trade record of profits and losses
/// The list of daily equity values
/// The list of algorithm performance values
/// The list of benchmark values
/// The algorithm starting capital
/// The number of trading days per year
public PortfolioStatistics(
SortedDictionary profitLoss,
SortedDictionary equity,
List listPerformance,
List listBenchmark,
decimal startingCapital,
int tradingDaysPerYear = 252)
{
if (startingCapital == 0
// minimum amount of samples to calculate variance
|| listBenchmark.Count < 2
|| listPerformance.Count < 2)
{
return;
}
var runningCapital = startingCapital;
var totalProfit = 0m;
var totalLoss = 0m;
var totalWins = 0;
var totalLosses = 0;
foreach (var pair in profitLoss)
{
var tradeProfitLoss = pair.Value;
if (tradeProfitLoss > 0)
{
totalProfit += tradeProfitLoss / runningCapital;
totalWins++;
}
else
{
totalLoss += tradeProfitLoss / runningCapital;
totalLosses++;
}
runningCapital += tradeProfitLoss;
}
AverageWinRate = totalWins == 0 ? 0 : totalProfit / totalWins;
AverageLossRate = totalLosses == 0 ? 0 : totalLoss / totalLosses;
ProfitLossRatio = AverageLossRate == 0 ? 0 : AverageWinRate / Math.Abs(AverageLossRate);
WinRate = profitLoss.Count == 0 ? 0 : (decimal) totalWins / profitLoss.Count;
LossRate = profitLoss.Count == 0 ? 0 : (decimal) totalLosses / profitLoss.Count;
Expectancy = WinRate * ProfitLossRatio - LossRate;
if (startingCapital != 0)
{
TotalNetProfit = equity.Values.LastOrDefault() / startingCapital - 1;
}
var fractionOfYears = (decimal) (equity.Keys.LastOrDefault() - equity.Keys.FirstOrDefault()).TotalDays / 365;
CompoundingAnnualReturn = Statistics.CompoundingAnnualPerformance(startingCapital, equity.Values.LastOrDefault(), fractionOfYears);
Drawdown = DrawdownPercent(equity, 3);
AnnualVariance = GetAnnualVariance(listPerformance, tradingDaysPerYear);
AnnualStandardDeviation = (decimal) Math.Sqrt((double) AnnualVariance);
var benchmarkAnnualPerformance = GetAnnualPerformance(listBenchmark, tradingDaysPerYear);
var annualPerformance = GetAnnualPerformance(listPerformance, tradingDaysPerYear);
SharpeRatio = AnnualStandardDeviation == 0 ? 0 : (annualPerformance - RiskFreeRate) / AnnualStandardDeviation;
var benchmarkVariance = listBenchmark.Variance();
Beta = benchmarkVariance.IsNaNOrZero() ? 0 : (decimal) (listPerformance.Covariance(listBenchmark) / benchmarkVariance);
Alpha = Beta == 0 ? 0 : annualPerformance - (RiskFreeRate + Beta * (benchmarkAnnualPerformance - RiskFreeRate));
TrackingError = (decimal)Statistics.TrackingError(listPerformance, listBenchmark, (double)tradingDaysPerYear);
InformationRatio = TrackingError == 0 ? 0 : (annualPerformance - benchmarkAnnualPerformance) / TrackingError;
TreynorRatio = Beta == 0 ? 0 : (annualPerformance - RiskFreeRate) / Beta;
// deannualize a 1 sharpe ratio
var benchmarkSharpeRatio = 1.0d / Math.Sqrt(252);
ProbabilisticSharpeRatio = Statistics.ProbabilisticSharpeRatio(listPerformance, benchmarkSharpeRatio).SafeDecimalCast();
}
///
/// Initializes a new instance of the class
///
public PortfolioStatistics()
{
}
///
/// Gets the current defined risk free annual return rate
///
public static decimal GetRiskFreeRate()
{
return RiskFreeRate;
}
///
/// Drawdown maximum percentage.
///
/// The list of daily equity values
/// The number of decimal places to round the result
/// The drawdown percentage
private static decimal DrawdownPercent(SortedDictionary equityOverTime, int rounding = 2)
{
var prices = equityOverTime.Values.ToList();
if (prices.Count == 0) return 0;
var drawdowns = new List();
var high = prices[0];
foreach (var price in prices)
{
if (price > high) high = price;
if (high > 0) drawdowns.Add(price / high - 1);
}
return Math.Round(Math.Abs(drawdowns.Min()), rounding);
}
///
/// Annualized return statistic calculated as an average of daily trading performance multiplied by the number of trading days per year.
///
/// Dictionary collection of double performance values
/// Trading days per year for the assets in portfolio
/// May be unaccurate for forex algorithms with more trading days in a year
/// Double annual performance percentage
private static decimal GetAnnualPerformance(List performance, int tradingDaysPerYear = 252)
{
return (decimal)Statistics.AnnualPerformance(performance, (double)tradingDaysPerYear);
}
///
/// Annualized variance statistic calculation using the daily performance variance and trading days per year.
///
///
///
/// Invokes the variance extension in the MathNet Statistics class
/// Annual variance value
private static decimal GetAnnualVariance(List performance, int tradingDaysPerYear = 252)
{
var variance = performance.Variance();
return variance.IsNaNOrZero() ? 0 : (decimal)variance * tradingDaysPerYear;
}
}
}