Source code for fynance.plot.tearsheet

#!/usr/bin/env python3
# coding: utf-8

""" One-call performance report (the API the notebook and UI both use). """

from __future__ import annotations

# Built-in packages
from typing import Any

# Third-party packages
import numpy as np

# Local packages
from fynance.plot._helpers import as_equity
from fynance.plot.attribution import plot_contribution, plot_turnover
from fynance.plot.costs import plot_cost_decomposition
from fynance.plot.equity import plot_drawdown, plot_equity
from fynance.plot.returns import plot_rolling_sharpe

__all__ = ['tearsheet', 'tearsheet_text']


def _summary(result: Any, period: int) -> dict[str, float]:
    """ Resolve a metrics summary from a result/series/array. """
    if hasattr(result, "summary"):

        return result.summary(period=period)

    from fynance.metrics import summary

    equity, _ = as_equity(result)

    return summary(equity, period=period)


[docs] def tearsheet(result: Any, period: int = 252, figsize: tuple = (11, 7), *, base: float | None = None, logy: bool | str = "auto") -> Any: """ Build a full performance report figure. Composes the equity curve, drawdown, rolling Sharpe, return distribution and a metrics table into one matplotlib ``Figure`` (works headless, embeds in a notebook or a Streamlit app). Parameters ---------- result : BacktestResult, PriceSeries or array-like The strategy result (or an equity curve). period : int Annualization factor. figsize : tuple Figure size. base : float, optional Rescale the equity panel to start at ``base`` (e.g. ``100`` for the familiar base-100 reading); display only, see :func:`plot_equity`. logy : bool or {"auto"}, default "auto" Log y-axis policy for the equity panel; ``"auto"`` switches to log on wide-amplitude curves, see :func:`plot_equity`. Returns ------- matplotlib.figure.Figure """ import matplotlib.pyplot as plt # Optional extra panels grow the core 2x2 report by one row each: # - a multi-asset book adds per-asset contribution + turnover; # - a cost breakdown adds a full-width cumulative-fees panel. asset_gross = getattr(result, "asset_gross_returns", None) positions = getattr(result, "positions", None) index = getattr(result, "index", None) cost_components: dict[str, Any] | None = getattr( result, "cost_components", None) is_book = ( asset_gross is not None and np.asarray(asset_gross).ndim == 2 and np.asarray(asset_gross).shape[1] > 1 ) has_costs = cost_components is not None and any( np.nansum(np.asarray(v, dtype=float)) != 0.0 for v in cost_components.values() ) n_rows = 2 + int(is_book) + int(has_costs) fig = plt.figure(figsize=(figsize[0], figsize[1] * n_rows / 2.0)) gs = fig.add_gridspec(n_rows, 2) plot_equity(result, ax=fig.add_subplot(gs[0, 0]), base=base, logy=logy) plot_drawdown(result, ax=fig.add_subplot(gs[0, 1])) plot_rolling_sharpe(result, window=period, ax=fig.add_subplot(gs[1, 0])) ax_table = fig.add_subplot(gs[1, 1]) ax_table.axis("off") stats = _summary(result, period) rows = [[k, f"{v:.4f}"] for k, v in stats.items()] table = ax_table.table(cellText=rows, colLabels=["metric", "value"], loc="center", cellLoc="left") table.auto_set_font_size(False) table.set_fontsize(9) table.scale(1.0, 1.3) ax_table.set_title("Summary") row = 2 if is_book: plot_contribution(asset_gross, index=index, ax=fig.add_subplot(gs[row, 0])) if positions is not None and np.asarray(positions).ndim == 2: plot_turnover(positions, index=index, ax=fig.add_subplot(gs[row, 1])) row += 1 if has_costs and cost_components is not None: plot_cost_decomposition(cost_components, index=index, ax=fig.add_subplot(gs[row, :])) row += 1 fig.tight_layout() return fig
[docs] def tearsheet_text(result: Any, period: int = 252) -> str: """ Plain-text performance summary (for notebooks / CLI). """ stats = _summary(result, period) return "\n".join(f"{k:<20s} {v:>12.4f}" for k, v in stats.items())