Skip to content

PSR calculation omits the risk-free rate #9533

@DerekMelchin

Description

@DerekMelchin

Expected Behavior

The Probabilistic Sharpe Ratio (PSR) should be statistical inference about the same Sharpe ratio the engine reports. The reported SharpeRatio is computed on excess returns (it subtracts the risk-free rate), so the PSR — defined as the probability that the true Sharpe ratio exceeds a benchmark — should also be built on the risk-free-adjusted Sharpe. A strategy whose reported (risk-free-adjusted) Sharpe ratio is strongly negative should therefore show a low PSR, not a near-100% one. The two statistics should be consistent with each other.

Actual Behavior

The reported Sharpe ratio and the PSR are built from two different Sharpe ratios:

  • SharpeRatio uses excess returns — it subtracts the risk-free rate.
  • The PSR's internal Sharpe (ObservedSharpeRatio) uses gross returns — mean / standard deviation, with no risk-free term — and compares it against a benchmark SR* = 1 / √(tradingDaysPerYear) (an annualized Sharpe of 1.0).

So the PSR actually estimates P(true gross Sharpe > 1.0), while the reported Sharpe is the excess-over-risk-free Sharpe. For a low-volatility holding during a high-rate period the two contradict each other completely. A buy-and-hold of SHV (a 0–1 year Treasury-bill ETF, about as close to the risk-free asset as exists) reports:

  • Sharpe Ratio: −8.88
  • Probabilistic Sharpe Ratio: 100%
  • Compounding Annual Return: 4.60%, Annual Standard Deviation: 0.25%, Max Drawdown: 0.10%

The most cash-like asset available simultaneously looks terrible (Sharpe −8.88) and certain (PSR 100%). Decomposing it: the gross annualized Sharpe is 4.60% / 0.25% ≈ 18, which sits far above the PSR's 1.0 benchmark over ~860 observations, pinning the PSR at 100%. The reported Sharpe subtracts the risk-free rate, and because the volatility is only 0.25%, that subtraction moves the ratio by rf / 0.25% ≈ 27 units, dragging it to −8.88.

Potential Solution

Compute ObservedSharpeRatio on risk-free-adjusted returns (subtract the per-period risk-free rate before forming mean / standardDeviation), so the PSR is inference about the same excess-return Sharpe the engine reports. This also matches Bailey & López de Prado (2012), whose PSR is defined on the standard excess-return Sharpe ratio.

With that change, SHV's Sharpe becomes −8.88 / √252 ≈ −0.56, which is far below the 1/√252 ≈ 0.063 benchmark, so the PSR would fall to ≈ 0% — consistent with the −8.88 Sharpe instead of contradicting it.

Relevant code (Common/Statistics):

  • PortfolioStatistics.cs: SharpeRatio = Statistics.SharpeRatio(annualPerformance, AnnualStandardDeviation, riskFreeRate) and benchmarkSharpeRatio = 1.0d / Math.Sqrt(tradingDaysPerYear); ProbabilisticSharpeRatio = Statistics.ProbabilisticSharpeRatio(listPerformance, benchmarkSharpeRatio).
  • Statistics.cs: SharpeRatio(...) => (averagePerformance - riskFreeRate) / standardDeviation (subtracts rf), but ObservedSharpeRatio(...) => performanceAverage / standardDeviation (no rf).

Also worth confirming the rf = 0 choice inside ObservedSharpeRatio and the SR* = 1/√252 benchmark were intentional and are on the same basis.

Reproducing the Problem

Buy-and-hold SHV from 2023-01-01 to 2026-06-01 reliably produces a strongly negative Sharpe ratio alongside a 100% PSR.

from AlgorithmImports import *

class BuyAndHoldSHV(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2023, 1, 1)
        self.set_end_date(2026, 6, 1)
        self.set_cash(10_000_000)
        self.add_equity("SHV", Resolution.DAILY)

    def on_data(self, data):
        if not self.portfolio.invested:
            self.set_holdings("SHV", 1)

Public backtest: https://www.quantconnect.cloud/backtest/281b5a27fc39d1cd448ef70792fc37c1/?theme=chrome

Result: Sharpe Ratio −8.88 and Probabilistic Sharpe Ratio 100%

System Information

QC Cloud

Checklist

  • I have completely filled out this template
  • I have confirmed that this issue exists on the current master branch
  • I have confirmed that this is not a duplicate issue by searching issues
  • I have provided detailed steps to reproduce the issue

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions