/// <summary> /// Generate the top N drawdown plot using the python libraries. /// </summary> public override string Render() { var backtestPoints = ResultsUtil.EquityPoints(_backtest); var livePoints = ResultsUtil.EquityPoints(_live); var liveSeries = new Series <DateTime, double>(livePoints.Keys, livePoints.Values); var strategySeries = DrawdownCollection.NormalizeResults(_backtest, _live); var seriesUnderwaterPlot = DrawdownCollection.GetUnderwater(strategySeries).DropMissing(); var liveUnderwaterPlot = backtestPoints.Count == 0 ? seriesUnderwaterPlot : seriesUnderwaterPlot.After(backtestPoints.Last().Key); var drawdownCollection = DrawdownCollection.FromResult(_backtest, _live, periods: 5); var base64 = ""; using (Py.GIL()) { var backtestList = new PyList(); if (liveUnderwaterPlot.IsEmpty) { backtestList.Append(seriesUnderwaterPlot.Keys.ToList().ToPython()); backtestList.Append(seriesUnderwaterPlot.Values.ToList().ToPython()); } else { backtestList.Append(seriesUnderwaterPlot.Before(liveUnderwaterPlot.FirstKey()).Keys.ToList().ToPython()); backtestList.Append(seriesUnderwaterPlot.Before(liveUnderwaterPlot.FirstKey()).Values.ToList().ToPython()); } var liveList = new PyList(); liveList.Append(liveUnderwaterPlot.Keys.ToList().ToPython()); liveList.Append(liveUnderwaterPlot.Values.ToList().ToPython()); var worstList = new PyList(); var previousDrawdownPeriods = new List <KeyValuePair <DateTime, DateTime> >(); foreach (var group in drawdownCollection.Drawdowns) { // Skip drawdown periods that are overlapping if (previousDrawdownPeriods.Where(kvp => (group.Start >= kvp.Key && group.Start <= kvp.Value) || (group.End >= kvp.Key && group.End <= kvp.Value)).Any()) { continue; } var worst = new PyDict(); worst.SetItem("Begin", group.Start.ToPython()); worst.SetItem("End", group.End.ToPython()); worst.SetItem("Total", group.PeakToTrough.ToPython()); worstList.Append(worst); previousDrawdownPeriods.Add(new KeyValuePair <DateTime, DateTime>(group.Start, group.End)); } base64 = Charting.GetDrawdown(backtestList, liveList, worstList); } return(base64); }
public void MaxDrawdown() { var series = new Deedle.Series <DateTime, double>(new [] { new KeyValuePair <DateTime, double>(new DateTime(2020, 1, 1), 100000), new KeyValuePair <DateTime, double>(new DateTime(2020, 1, 2), 90000), new KeyValuePair <DateTime, double>(new DateTime(2020, 1, 3), 100000), new KeyValuePair <DateTime, double>(new DateTime(2020, 1, 4), 100000), new KeyValuePair <DateTime, double>(new DateTime(2020, 1, 5), 80000) }); var collection = DrawdownCollection.GetDrawdownPeriods(series, 1).ToList(); Assert.AreEqual(1, collection.Count); Assert.AreEqual(0.2, collection.First().Drawdown, 0.0001); }
/// <summary> /// The generated output string to be injected /// </summary> public override string Render() { if (_live == null) { var backtestDrawdown = _backtest?.TotalPerformance?.PortfolioStatistics?.Drawdown; Result = backtestDrawdown; return(backtestDrawdown?.ToString("P1") ?? "-"); } var equityCurve = new SortedDictionary <DateTime, decimal>(DrawdownCollection.NormalizeResults(_backtest, _live) .Observations .ToDictionary(kvp => kvp.Key, kvp => (decimal)kvp.Value)); var maxDrawdown = Statistics.Statistics.DrawdownPercent(equityCurve); Result = maxDrawdown; return($"{maxDrawdown:P1}"); }
/// <summary> /// The generated output string to be injected /// </summary> public override string Render() { var equityCurve = _live == null ? new Series <DateTime, double>(ResultsUtil.EquityPoints(_backtest)) : DrawdownCollection.NormalizeResults(_backtest, _live); if (equityCurve.IsEmpty) { return("-"); } var years = (decimal)(equityCurve.LastKey() - equityCurve.FirstKey()).TotalDays / 365m; Result = Statistics.Statistics.CompoundingAnnualPerformance( equityCurve.FirstValue().SafeDecimalCast(), equityCurve.LastValue().SafeDecimalCast(), years); return(((decimal?)Result)?.ToString("P1") ?? "-"); }
/// <summary> /// The generated output string to be injected /// </summary> public override string Render() { if (_live == null) { return(_backtest?.TotalPerformance?.PortfolioStatistics?.Drawdown.ToString("P1") ?? "-"); } var backtestEquityPoints = new Series <DateTime, double>(ResultsUtil.EquityPoints(_backtest)); var liveEquityPoints = new Series <DateTime, double>(ResultsUtil.EquityPoints(_live)); var backtestDrawdownGroups = new DrawdownCollection(backtestEquityPoints, 1); var liveDrawdownGroups = new DrawdownCollection(liveEquityPoints, 1); var separateResultsMaxDrawdown = backtestDrawdownGroups.Drawdowns .Concat(liveDrawdownGroups.Drawdowns) .Select(x => x.PeakToTrough) .OrderByDescending(x => x) .FirstOrDefault(); return($"{separateResultsMaxDrawdown:P1}"); }
/// <summary> /// The generated output string to be injected /// </summary> public override string Render() { decimal?psr; if (_live == null) { psr = _backtest?.TotalPerformance?.PortfolioStatistics?.ProbabilisticSharpeRatio; Result = psr; if (psr == null) { return("-"); } return($"{psr:P0}"); } var equityCurvePerformance = DrawdownCollection.NormalizeResults(_backtest, _live) .ResampleEquivalence(date => date.Date, s => s.LastValue()) .PercentChange(); if (equityCurvePerformance.IsEmpty || equityCurvePerformance.KeyCount < 180) { return("-"); } var sixMonthsBefore = equityCurvePerformance.LastKey() - TimeSpan.FromDays(180); var benchmarkSharpeRatio = 1.0d / Math.Sqrt(252); psr = Statistics.Statistics.ProbabilisticSharpeRatio( equityCurvePerformance .Where(kvp => kvp.Key >= sixMonthsBefore) .Values .ToList(), benchmarkSharpeRatio) .SafeDecimalCast(); Result = psr; return($"{psr:P0}"); }
public void NoDrawdown(bool hasEquityPoint) { var strategyEquityChart = new Chart("Strategy Equity"); var equitySeries = new Series("Equity"); strategyEquityChart.AddSeries(equitySeries); if (hasEquityPoint) { equitySeries.AddPoint(new DateTime(2020, 1, 1), 100000); } var backtest = new BacktestResult { Charts = new Dictionary <string, Chart> { [strategyEquityChart.Name] = strategyEquityChart } }; var normalizedResults = DrawdownCollection.NormalizeResults(backtest, null); Assert.AreEqual(0, normalizedResults.KeyCount); Assert.AreEqual(0, normalizedResults.ValueCount); }