Source code for fynance.plot.equity

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

""" Equity and drawdown figures. """

from __future__ import annotations

# Built-in packages
from typing import Any

# Third-party packages
import numpy as np
from numpy.typing import NDArray

# Local packages
from fynance.plot._helpers import as_equity, drawdown_curve

__all__ = ['plot_equity', 'plot_drawdown']

# Auto log-scale kicks in once the equity spans more than this multiplicative
# range (max / min): a linear axis then crushes the early trajectory and makes
# drawdowns visually incomparable across time.
_LOG_Y_RATIO = 5.0


def _use_log_y(equity: NDArray, logy: bool | str) -> bool:
    """ Resolve the ``logy`` policy into a concrete log/linear choice. """
    positive = bool(equity.size) and bool(np.all(equity > 0.0))

    if logy == "auto":
        if not positive:

            return False

        return float(equity.max() / equity.min()) > _LOG_Y_RATIO

    return bool(logy) and positive


[docs] def plot_equity(result: Any, ax: Any = None, *, base: float | None = None, logy: bool | str = "auto") -> Any: """ Plot an equity curve. Returns the matplotlib ``Axes``. Parameters ---------- result : BacktestResult, PriceSeries or array-like The strategy result (or a raw equity curve). ax : matplotlib.axes.Axes, optional Axis to draw on; a new one is created when omitted. base : float, optional Rescale the curve to start at ``base`` (display only). The default equity starts at the capital (``1.0``); ``base=100`` gives the familiar base-100 reading without touching the underlying numbers. logy : bool or {"auto"}, default "auto" Use a logarithmic y-axis. ``"auto"`` switches to log only when the curve spans more than a 5x range (and stays strictly positive), so a x3-x30 trajectory stays readable while a flat curve is left linear. Returns ------- matplotlib.axes.Axes """ import matplotlib.pyplot as plt equity, index = as_equity(result) if (base is not None and equity.size and np.isfinite(equity[0]) and equity[0] != 0.0): equity = equity * (base / equity[0]) x = range(len(equity)) if index is None else index if ax is None: _, ax = plt.subplots() ax.plot(x, equity, color="#2c7fb8", lw=1.5) ax.set_title("Equity curve") ax.set_ylabel("Equity" if base is None else f"Equity (base {base:g})") ax.grid(alpha=0.3) if _use_log_y(equity, logy): ax.set_yscale("log") return ax
[docs] def plot_drawdown(result: Any, ax: Any = None) -> Any: """ Plot the underwater drawdown curve. Returns the ``Axes``. """ import matplotlib.pyplot as plt equity, index = as_equity(result) dd = drawdown_curve(equity) x = range(len(dd)) if index is None else index if ax is None: _, ax = plt.subplots() ax.fill_between(x, dd, 0.0, color="#d7301f", alpha=0.4) ax.set_title("Drawdown") ax.set_ylabel("Drawdown") ax.grid(alpha=0.3) return ax