Skip to content

summary

One-shot aggregate summaries built out of the other metric modules.

summary

One-shot aggregate summaries built out of the other metric modules.

tearsheet_summary

tearsheet_summary(
    returns: Series,
    prices: Series | None = None,
    rf: float = DEFAULT_RISK_FREE_RATE,
    periods_per_year: int = TRADING_DAYS_PER_YEAR,
) -> dict

Compute the key headline metrics in one call, each as a self-explaining MetricResult.

Parameters:

Name Type Description Default
returns Series

Per-period returns.

required
prices Series | None

Price series to use for price-based metrics (cagr, max_drawdown). Defaults to a synthetic series reconstructed from returns.

None
rf float

Risk-free rate, as an annual rate. Defaults to DEFAULT_RISK_FREE_RATE.

DEFAULT_RISK_FREE_RATE
periods_per_year int

Number of periods in a year, used to annualize.

TRADING_DAYS_PER_YEAR

Returns:

Type Description
dict

A dict mapping metric name to its MetricResult.

Source code in src/portpy/metrics/summary.py
def tearsheet_summary(
    returns: pd.Series,
    prices: pd.Series | None = None,
    rf: float = DEFAULT_RISK_FREE_RATE,
    periods_per_year: int = TRADING_DAYS_PER_YEAR,
) -> dict:
    """
    Compute the key headline metrics in one call, each as a self-explaining MetricResult.

    Args:
        returns: Per-period returns.
        prices: Price series to use for price-based metrics (cagr,
            max_drawdown). Defaults to a synthetic series reconstructed from
            `returns`.
        rf: Risk-free rate, as an annual rate. Defaults to DEFAULT_RISK_FREE_RATE.
        periods_per_year: Number of periods in a year, used to annualize.

    Returns:
        A dict mapping metric name to its MetricResult.
    """
    r = returns.dropna()
    price_series = prices if prices is not None else _returns.prices_from_returns(r)

    return {
        "total_return": _returns.total_return(r, as_result=True),
        "annualized_return": _returns.annualized_return(r, periods_per_year=periods_per_year, as_result=True),
        "cagr": _returns.cagr(price_series, periods_per_year=periods_per_year, as_result=True),
        "volatility": _risk.volatility(r, periods_per_year=periods_per_year, as_result=True),
        "sharpe_ratio": _performance.sharpe_ratio(r, rf=rf, periods_per_year=periods_per_year, as_result=True),
        "sortino_ratio": _performance.sortino_ratio(r, periods_per_year=periods_per_year, as_result=True),
        "calmar_ratio": _performance.calmar_ratio(r, periods_per_year=periods_per_year, as_result=True),
        "max_drawdown": _drawdowns.max_drawdown(price_series, as_result=True),
        "value_at_risk_95": _risk.value_at_risk(r, confidence=DEFAULT_CONFIDENCE_LEVEL, as_result=True),
        "conditional_var_95": _risk.conditional_var(r, confidence=DEFAULT_CONFIDENCE_LEVEL, as_result=True),
        "skewness": _risk.skewness(r, as_result=True),
        "kurtosis": _risk.kurtosis(r, as_result=True),
        "win_rate": _distributions.win_rate(r, as_result=True),
    }

compare_to_benchmark

compare_to_benchmark(
    returns: Series,
    benchmark: Series,
    rf: float = DEFAULT_RISK_FREE_RATE,
    periods_per_year: int = TRADING_DAYS_PER_YEAR,
) -> pd.DataFrame

Side-by-side portfolio-vs-benchmark table: absolute metrics for both, plus relative metrics.

Parameters:

Name Type Description Default
returns Series

Per-period portfolio returns.

required
benchmark Series

Per-period benchmark returns.

required
rf float

Risk-free rate, as an annual rate. Defaults to DEFAULT_RISK_FREE_RATE.

DEFAULT_RISK_FREE_RATE
periods_per_year int

Number of periods in a year, used to annualize.

TRADING_DAYS_PER_YEAR

Returns:

Type Description
DataFrame

A DataFrame indexed by metric name, with "portfolio", "benchmark", and

DataFrame

"difference" columns. "benchmark" is NaN for metrics that are

DataFrame

inherently relative (e.g. beta, alpha, information_ratio).

Source code in src/portpy/metrics/summary.py
def compare_to_benchmark(
    returns: pd.Series,
    benchmark: pd.Series,
    rf: float = DEFAULT_RISK_FREE_RATE,
    periods_per_year: int = TRADING_DAYS_PER_YEAR,
) -> pd.DataFrame:
    """
    Side-by-side portfolio-vs-benchmark table: absolute metrics for both, plus relative metrics.

    Args:
        returns: Per-period portfolio returns.
        benchmark: Per-period benchmark returns.
        rf: Risk-free rate, as an annual rate. Defaults to DEFAULT_RISK_FREE_RATE.
        periods_per_year: Number of periods in a year, used to annualize.

    Returns:
        A DataFrame indexed by metric name, with "portfolio", "benchmark", and
        "difference" columns. "benchmark" is NaN for metrics that are
        inherently relative (e.g. beta, alpha, information_ratio).
    """
    r, b = align_pair(returns, benchmark)
    synthetic_r = _returns.prices_from_returns(r)
    synthetic_b = _returns.prices_from_returns(b)

    rows: dict[str, dict[str, float]] = {
        # "benchmark" is intentionally NaN below for metrics that are inherently
        # relative/one-sided (beta, alpha, correlation, information_ratio,
        # up_capture_ratio, down_capture_ratio, batting_average) - there is no
        # separate "benchmark's own value" to report for these, this is not
        # missing data.
        "annualized_return": {
            "portfolio": _returns.annualized_return(r, periods_per_year=periods_per_year),
            "benchmark": _returns.annualized_return(b, periods_per_year=periods_per_year),
        },
        "volatility": {
            "portfolio": _risk.volatility(r, periods_per_year=periods_per_year),
            "benchmark": _risk.volatility(b, periods_per_year=periods_per_year),
        },
        "sharpe_ratio": {
            "portfolio": _performance.sharpe_ratio(r, rf=rf, periods_per_year=periods_per_year),
            "benchmark": _performance.sharpe_ratio(b, rf=rf, periods_per_year=periods_per_year),
        },
        "max_drawdown": {
            "portfolio": _drawdowns.max_drawdown(synthetic_r),
            "benchmark": _drawdowns.max_drawdown(synthetic_b),
        },
        "beta": {"portfolio": _risk.beta(r, b), "benchmark": np.nan},
        "alpha": {"portfolio": _benchmarks.alpha(r, b, rf=rf, periods_per_year=periods_per_year), "benchmark": np.nan},
        "correlation": {"portfolio": _benchmarks.correlation(r, b), "benchmark": np.nan},
        "information_ratio": {
            "portfolio": _performance.information_ratio(r, b, periods_per_year=periods_per_year),
            "benchmark": np.nan,
        },
        "up_capture_ratio": {
            "portfolio": _benchmarks.up_capture_ratio(r, b, periods_per_year=periods_per_year),
            "benchmark": np.nan,
        },
        "down_capture_ratio": {
            "portfolio": _benchmarks.down_capture_ratio(r, b, periods_per_year=periods_per_year),
            "benchmark": np.nan,
        },
        "batting_average": {"portfolio": _benchmarks.batting_average(r, b), "benchmark": np.nan},
    }

    df = pd.DataFrame(rows).T
    df["difference"] = df["portfolio"] - df["benchmark"]
    df.attrs["portpy_explanation"] = "compare_to_benchmark"
    return df