17 using System.Collections.Generic;
19 using MathNet.Numerics.Distributions;
20 using MathNet.Numerics.Statistics;
21 using Newtonsoft.Json;
134 public decimal
Beta {
get;
set; }
205 SortedDictionary<DateTime, decimal> profitLoss,
206 SortedDictionary<DateTime, decimal> equity,
207 SortedDictionary<DateTime, decimal> portfolioTurnover,
208 List<double> listPerformance,
209 List<double> listBenchmark,
210 decimal startingCapital,
212 int tradingDaysPerYear,
213 int? winCount =
null,
214 int? lossCount =
null)
217 EndEquity = equity.LastOrDefault().Value;
219 if (portfolioTurnover.Count > 0)
224 if (startingCapital == 0
226 || listBenchmark.Count < 2
227 || listPerformance.Count < 2)
232 var runningCapital = startingCapital;
233 var totalProfit = 0m;
237 foreach (var pair
in profitLoss)
239 var tradeProfitLoss = pair.Value;
241 if (tradeProfitLoss > 0)
243 totalProfit += tradeProfitLoss / runningCapital;
248 totalLoss += tradeProfitLoss / runningCapital;
252 runningCapital += tradeProfitLoss;
261 if (winCount.HasValue && lossCount.HasValue)
263 totalWins = winCount.Value;
264 totalLosses = lossCount.Value;
267 var totalTrades = totalWins + totalLosses;
268 WinRate = totalTrades == 0 ? 0 : (decimal) totalWins / totalTrades;
269 LossRate = totalTrades == 0 ? 0 : (decimal) totalLosses / totalTrades;
272 if (startingCapital != 0)
274 TotalNetProfit = equity.Values.LastOrDefault() / startingCapital - 1;
277 var fractionOfYears = (decimal) (equity.Keys.LastOrDefault() - equity.Keys.FirstOrDefault()).TotalDays / 365;
280 Drawdown = DrawdownPercent(equity, 3);
285 var benchmarkAnnualPerformance = GetAnnualPerformance(listBenchmark, tradingDaysPerYear);
286 var annualPerformance = GetAnnualPerformance(listPerformance, tradingDaysPerYear);
288 var riskFreeRate = riskFreeInterestRateModel.GetAverageRiskFreeRate(equity.Select(x => x.Key));
294 var benchmarkVariance = listBenchmark.Variance();
295 Beta = benchmarkVariance.IsNaNOrZero() ? 0 : (decimal) (listPerformance.Covariance(listBenchmark) / benchmarkVariance);
297 Alpha =
Beta == 0 ? 0 : annualPerformance - (riskFreeRate +
Beta * (benchmarkAnnualPerformance - riskFreeRate));
306 var benchmarkSharpeRatio = 1.0d / Math.Sqrt(tradingDaysPerYear);
309 ValueAtRisk99 = GetValueAtRisk(listPerformance, tradingDaysPerYear, 0.99d);
310 ValueAtRisk95 = GetValueAtRisk(listPerformance, tradingDaysPerYear, 0.95d);
326 private static decimal DrawdownPercent(SortedDictionary<DateTime, decimal> equityOverTime,
int rounding = 2)
328 var prices = equityOverTime.Values.ToList();
329 if (prices.Count == 0)
return 0;
331 var drawdowns =
new List<decimal>();
332 var high = prices[0];
333 foreach (var price
in prices)
335 if (price > high) high = price;
336 if (high > 0) drawdowns.Add(price / high - 1);
339 return Math.Round(Math.Abs(drawdowns.Min()), rounding);
349 private static decimal GetAnnualPerformance(List<double> performance,
int tradingDaysPerYear)
353 return Statistics.AnnualPerformance(performance, tradingDaysPerYear).SafeDecimalCast();
355 catch (ArgumentException ex)
357 var partialSums = 0.0;
359 double troublePoint =
default;
360 foreach(var point
in performance)
363 partialSums += point;
364 if (Math.Pow(partialSums / points, tradingDaysPerYear).IsNaNOrInfinity())
366 troublePoint = point;
371 throw new ArgumentException($
"PortfolioStatistics.GetAnnualPerformance(): An exception was thrown when trying to cast the annual performance value due to the following performance point: {troublePoint}. " +
372 $
"The exception thrown was the following: {ex.Message}.");
376 private static decimal GetValueAtRisk(
377 List<double> performance,
378 int lookbackPeriodDays,
379 double confidenceLevel,
382 var periodPerformance = performance.TakeLast(lookbackPeriodDays);
383 var mean = periodPerformance.Mean();
384 var standardDeviation = periodPerformance.StandardDeviation();
385 var valueAtRisk = (decimal)Normal.InvCDF(mean, standardDeviation, 1 - confidenceLevel);
386 return Math.Round(valueAtRisk, rounding);