Skip to content

Reports

Tearsheet is the single report object — one class, three renderers (render_html, render_pdf, render_excel) — fed a Portfolio and an optional benchmark. The fundcloud.reports.formatting module exposes the shared building blocks (StatCard, StatRow, stat_cards, stats_rows, format_stat) so custom templates and bespoke reports can reuse the exact formatting the default tear sheet uses. See the Tear sheets guide for output structure and the PDF/Excel extras.

fundcloud.reports

Tear-sheet output — HTML, PDF, Excel.

The primary public object is :class:Tearsheet. Renderer modules are lazy-loaded via :class:Tearsheet.render_* methods, so installing fundcloud without [reports] stays cheap.

Tearsheet dataclass

Tearsheet(
    portfolio: Portfolio,
    benchmark: Series | str | None = None,
    template: Literal["strategy"] = "strategy",
    title: str | None = None,
    meta: dict[str, str] = dict(),
)

A renderable tear sheet.

benchmark accepts a :class:pandas.Series or a column name to look up on the portfolio's underlying returns (set at construction). The lookup runs in __post_init__ so every downstream renderer sees a plain pd.Series.

render_html

render_html(path: str | Path | None = None) -> str | Path

Render to HTML.

path is None → returns the HTML string. path is a path → writes the file and returns the :class:Path.

Returning the Path (instead of the 5 MB+ inline-plotly string) avoids flooding REPL / notebook output when a path is provided.

Source code in python/fundcloud/reports/tearsheet.py
def render_html(self, path: str | Path | None = None) -> str | Path:
    """Render to HTML.

    ``path`` is ``None``  → returns the HTML string.
    ``path`` is a path    → writes the file and returns the :class:`Path`.

    Returning the Path (instead of the 5 MB+ inline-plotly string) avoids
    flooding REPL / notebook output when a path is provided.
    """
    from fundcloud.reports import html as _html

    return _html.render(self, path=path)

render_pdf

render_pdf(
    path: str | Path,
    *,
    engine: Literal["matplotlib", "weasyprint"]
    | None = None,
) -> Path

Render a PDF.

engine="matplotlib" (default) uses pure-Python matplotlib PdfPages — no system libraries required. engine="weasyprint" uses HTML + CSS paged media; needs Pango / GLib installed on the host.

Source code in python/fundcloud/reports/tearsheet.py
def render_pdf(
    self, path: str | Path, *, engine: Literal["matplotlib", "weasyprint"] | None = None
) -> Path:
    """Render a PDF.

    ``engine="matplotlib"`` (default) uses pure-Python matplotlib ``PdfPages`` —
    no system libraries required. ``engine="weasyprint"`` uses HTML + CSS
    paged media; needs Pango / GLib installed on the host.
    """
    from fundcloud.reports import pdf as _pdf

    return _pdf.render(self, path=Path(path), engine=engine)

fundcloud.reports.formatting

Tear-sheet formatting helpers — shared by HTML, PDF, and Excel renderers.

StatCard dataclass

StatCard(label: str, value: str, klass: str = '')

StatRow dataclass

StatRow(label: str, value: str)

stat_cards

stat_cards(stats: Series) -> list[StatCard]

Top-of-page stat cards — four high-signal metrics.

Source code in python/fundcloud/reports/formatting.py
def stat_cards(stats: pd.Series) -> list[StatCard]:
    """Top-of-page stat cards — four high-signal metrics."""
    wanted = [
        ("cagr", "CAGR"),
        ("sharpe", "Sharpe"),
        ("max_drawdown", "Max drawdown"),
        ("cvar", "CVaR (95%)"),
    ]
    cards: list[StatCard] = []
    for key, label in wanted:
        if key not in stats.index:
            continue
        val = stats[key]
        formatted = format_stat(key, float(val) if pd.notna(val) else float("nan"))
        klass = ""
        if pd.notna(val) and isinstance(val, (int, float, np.floating)):
            if key in {"max_drawdown", "cvar"}:
                klass = "neg" if float(val) < 0 else ""
            elif float(val) > 0:
                klass = "pos"
            elif float(val) < 0:
                klass = "neg"
        cards.append(StatCard(label=label, value=formatted, klass=klass))
    return cards

stats_rows

stats_rows(stats: Series) -> list[StatRow]

Row-per-metric table for the bottom of the tear sheet.

Source code in python/fundcloud/reports/formatting.py
def stats_rows(stats: pd.Series) -> list[StatRow]:
    """Row-per-metric table for the bottom of the tear sheet."""
    return [
        StatRow(label=_label(k), value=format_stat(k, float(v) if pd.notna(v) else float("nan")))
        for k, v in stats.items()
    ]

format_stat

format_stat(name: str, value: float) -> str
Source code in python/fundcloud/reports/formatting.py
def format_stat(name: str, value: float) -> str:
    if value is None or (isinstance(value, float) and np.isnan(value)):
        return "—"
    info = METRIC_INFO.get(name)
    if info is not None:
        if info.fmt == "pct":
            return f"{value * 100.0:.2f}%"
        if info.fmt == "pct4":
            return f"{value * 100.0:.4f}%"
        if info.fmt == "ratio":
            return f"{value:.2f}"
        if info.fmt == "int":
            return f"{int(value):d}"
    # Fall-through heuristics for keys without METRIC_INFO entries.
    if name in _PCT_METRICS:
        return f"{value * 100.0:.2f}%"
    if name in _RATIO_METRICS:
        return f"{value:.2f}"
    if name == "periods":
        return f"{int(value):d}"
    return f"{value:.4f}"