Skip to content

Benchmark analytics

Pass a benchmark series (same frequency as your strategy returns) to unlock the full benchmark-relative analytical surface in one shot.

Metrics

import pandas as pd
import fundcloud  # registers the .fc accessor

m = strategy.fc.metrics(benchmark=spy)
m.loc[["alpha", "beta", "correlation", "r_squared",
       "information_ratio", "tracking_error",
       "up_capture", "down_capture", "capture_ratio", "treynor_ratio"]]

String benchmarks

When your strategies sit in a wide DataFrame, you can pass the benchmark's column name instead of slicing the Series manually:

panel = pd.DataFrame({"s1": s1, "s2": s2, "SPY": spy})
panel.fc.render_html("out.html", benchmark="SPY")
panel.fc.plot_summary(benchmark="SPY").show()

Fundcloud pulls SPY out of the frame and drops it from the per-asset rendering so you never get "SPY vs SPY" as a tab.

Key Meaning Formula
alpha Jensen's annualised alpha ann(r − rf) − β · ann(bench − rf)
beta Market sensitivity cov(r, bench) / var(bench)
correlation Pearson correlation cov / (σ_r · σ_b)
r_squared Share of variance explained correlation²
information_ratio Active return / active σ mean(r − bench) / σ(r − bench)
tracking_error Annualised active σ σ(r − bench) · √periods_per_year
up_capture Participation on up days mean(r ∣ bench > 0) / mean(bench ∣ bench > 0)
down_capture Participation on down days mean(r ∣ bench < 0) / mean(bench ∣ bench < 0)
capture_ratio Morningstar single number up_capture / down_capture
treynor_ratio Excess return per unit of beta ann(r − rf) / β

Rolling variants: fundcloud.metrics.rolling_alpha(r, bench, window=63) and rolling_beta. Both inner-align strategy and benchmark on the shared trading calendar before the rolling covariance — essential when a 7-day/week series (crypto) is compared against a 5-day/week series (futures, equities), otherwise NaNs from the calendar mismatch propagate across the rolling window and empty the output.

Summary figure

from fundcloud import plots

plots.summary(strategy, benchmark=spy).show()

With benchmark= supplied, two extra full-width rows appear between the rolling-Sharpe and the return-distribution panels:

  • Rolling alpha (annualised) — positive = out-performing what beta alone predicts.
  • Rolling beta — drift in market sensitivity over time.

The cumulative panel already overlays the benchmark as a dashed line, and the stats pill automatically gains a benchmark row.

HTML / PDF / Excel tear sheets

from fundcloud.portfolio import Portfolio
from fundcloud.reports import Tearsheet

ts = Tearsheet(Portfolio(returns=strategy, name="my"),
               benchmark=spy, title="Strategy vs SPY")

ts.render_html("out.html")       # sidebar grows a "Benchmark" section
ts.render_pdf("out.pdf")         # extra rolling α/β page
ts.render_excel("out.xlsx")      # adds a "Benchmark" sheet

HTML. The right-hand sidebar renders a Benchmark accordion with every benchmark-relative metric; each row shows the strategy value in bold and the benchmark's own value as a muted second column so readers can compare directly. Tooltips on every ? badge explain the definition and formula.

PDF. An extra A4-portrait page titled "Benchmark dynamics (rolling 63-bar)" renders rolling alpha on top and rolling beta on the bottom.

Excel. A new Benchmark sheet ships the aligned strategy + benchmark return series in columns A–C, plus a metric-by-side table in columns F–H that puts strategy values next to the benchmark's own readings.

Runnable example

See examples/27_benchmark_comparison.py for the full end-to-end walkthrough.