Skip to content

explain

See Explainability for how the pieces below fit together, and where the explanation text itself actually lives (hint: not in this module).

Note

from portpy import Portfolio (or any import of the portpy package) binds the package attribute portpy.explain to the function below, not this module. Import the other names (available, get, register, Explanation, MetricResult) directly from portpy.explain as shown in each signature.

Explanation dataclass

Explanation(
    name: str,
    category: str,
    summary: str,
    how_to_read: str,
    good_vs_bad: str,
    caveats: str = "",
    formula: str = "",
    interpret: Callable[[Any], str] | None = None,
)

A structured, human-readable knowledge card attached to a PortPy object.

Attributes:

Name Type Description
name str

Registry key, matching the function/chart/model/strategy name (e.g. "sharpe_ratio", "drawdown_chart", "capm").

category str

One of "metric", "chart", "model", "strategy".

summary str

One or two sentences on what this is.

how_to_read str

How to read the number/axis/output in practice.

good_vs_bad str

Rules of thumb for judging whether a value is good or bad.

caveats str

Known limitations, edge cases, or ways this can mislead.

formula str

Optional plain-text formula for reference.

interpret Callable[[Any], str] | None

Optional function mapping a concrete value to a one-line verdict, used to render a "this result" line when a value is available.

render

render(value: Any = None) -> str

Render this card as plain text.

Parameters:

Name Type Description Default
value Any

Optional concrete value to include a one-line, value-specific verdict for, via interpret.

None

Returns:

Type Description
str

The rendered explanation text.

Source code in src/portpy/explain.py
def render(self, value: Any = None) -> str:
    """
    Render this card as plain text.

    Args:
        value: Optional concrete value to include a one-line, value-specific
            verdict for, via `interpret`.

    Returns:
        The rendered explanation text.
    """
    lines = [f"{self.name} ({self.category})", "=" * len(f"{self.name} ({self.category})")]
    lines += ["", "What it is:", f"  {self.summary}"]
    if self.formula:
        lines += ["", "Formula:", f"  {self.formula}"]
    lines += ["", "How to read it:", f"  {self.how_to_read}"]
    lines += ["", "Good vs. bad:", f"  {self.good_vs_bad}"]
    if self.caveats:
        lines += ["", "Caveats:", f"  {self.caveats}"]
    if value is not None and self.interpret is not None:
        try:
            lines += ["", "This result:", f"  {self.interpret(value)}"]
        except Exception:
            pass
    return "\n".join(lines)

MetricResult

Bases: float

A float that also knows what it means.

Arithmetic, comparisons, round(), string formatting, and numpy/pandas interop all work exactly as they would on a plain float (this is a float subclass). On top of that, it carries the metric's registered name and can render a full explanation on demand.

Note

str(result) prints the plain number (so print(sharpe) stays clean); repr(result) - what you see when a bare expression is evaluated in a REPL/notebook - includes the name and a one-line interpretation.

__new__

__new__(
    value: float,
    name: str,
    unit: str | None = None,
    meta: dict | None = None,
) -> MetricResult

Parameters:

Name Type Description Default
value float

The numeric result.

required
name str

The registry key this result explains itself with (e.g. "sharpe_ratio").

required
unit str | None

Optional display unit (e.g. "%/yr").

None
meta dict | None

Optional extra metadata carried alongside the value.

None

Returns:

Type Description
MetricResult

A new MetricResult.

Source code in src/portpy/explain.py
def __new__(
    cls,
    value: float,
    name: str,
    unit: str | None = None,
    meta: dict | None = None,
) -> MetricResult:
    """
    Args:
        value: The numeric result.
        name: The registry key this result explains itself with (e.g. "sharpe_ratio").
        unit: Optional display unit (e.g. "%/yr").
        meta: Optional extra metadata carried alongside the value.

    Returns:
        A new MetricResult.
    """
    obj = super().__new__(cls, value)
    obj.name = name
    obj.unit = unit
    obj.meta = meta or {}
    return obj

interpretation property

interpretation: str

A one-line, value-specific verdict (empty string if none is registered).

explain

explain(print_it: bool = True) -> str

Print (by default) and return the full explanation for this result.

Parameters:

Name Type Description Default
print_it bool

If True (default), also print() the rendered text.

True

Returns:

Type Description
str

The rendered explanation text.

Source code in src/portpy/explain.py
def explain(self, print_it: bool = True) -> str:
    """
    Print (by default) and return the full explanation for this result.

    Args:
        print_it: If True (default), also print() the rendered text.

    Returns:
        The rendered explanation text.
    """
    return explain(self, print_it=print_it)

explain

explain(
    obj: Any, *, value: Any = None, print_it: bool = True
) -> str

Explain a metric name, :class:MetricResult, chart, model, or strategy.

Parameters:

Name Type Description Default
obj Any

A registered name (str), a :class:MetricResult, or any PortPy object that exposes a _portpy_explain_name attribute (charts, fitted models, strategies) or a pandas .attrs["portpy_explanation"] entry.

required
value Any

Optional concrete value to generate a one-line verdict for. Inferred automatically for :class:MetricResult and objects carrying _portpy_explain_value.

None
print_it bool

If True (default), also print() the rendered text.

True

Returns:

Type Description
str

The rendered explanation text.

Source code in src/portpy/explain.py
def explain(obj: Any, *, value: Any = None, print_it: bool = True) -> str:
    """
    Explain a metric name, :class:`MetricResult`, chart, model, or strategy.

    Args:
        obj: A registered name (str), a :class:`MetricResult`, or any PortPy
            object that exposes a _portpy_explain_name attribute (charts, fitted
            models, strategies) or a pandas .attrs["portpy_explanation"] entry.
        value: Optional concrete value to generate a one-line verdict for. Inferred
            automatically for :class:`MetricResult` and objects carrying
            _portpy_explain_value.
        print_it: If True (default), also print() the rendered text.

    Returns:
        The rendered explanation text.
    """
    name: str | None = None

    if isinstance(obj, str):
        name = obj
    elif isinstance(obj, MetricResult):
        name = obj.name
        if value is None:
            value = float(obj)
    elif hasattr(obj, "_portpy_explain_name"):
        name = obj._portpy_explain_name
        if value is None:
            value = getattr(obj, "_portpy_explain_value", None)
    else:
        attrs = getattr(obj, "attrs", None)
        if isinstance(attrs, dict) and "portpy_explanation" in attrs:
            name = attrs["portpy_explanation"]

    if name is None:
        raise TypeError(
            f"Don't know how to explain an object of type {type(obj).__name__!r}. "
            "Pass a registered name (str), a MetricResult, or a PortPy chart/model/strategy."
        )

    text = get(name).render(value)
    if print_it:
        print(text)
    return text

register

register(explanation: Explanation) -> Explanation

Register (or overwrite) an Explanation in the global registry.

Parameters:

Name Type Description Default
explanation Explanation

The Explanation to register, keyed by its name.

required

Returns:

Type Description
Explanation

The same explanation, for convenient use at module load time.

Source code in src/portpy/explain.py
def register(explanation: Explanation) -> Explanation:
    """
    Register (or overwrite) an Explanation in the global registry.

    Args:
        explanation: The Explanation to register, keyed by its `name`.

    Returns:
        The same `explanation`, for convenient use at module load time.
    """
    _REGISTRY[explanation.name] = explanation
    return explanation

get

get(name: str) -> Explanation

Look up a registered Explanation by name.

Parameters:

Name Type Description Default
name str

The registry key to look up (e.g. "sharpe_ratio").

required

Returns:

Type Description
Explanation

The registered Explanation.

Raises:

Type Description
KeyError

If no Explanation is registered under name.

Source code in src/portpy/explain.py
def get(name: str) -> Explanation:
    """
    Look up a registered Explanation by name.

    Args:
        name: The registry key to look up (e.g. "sharpe_ratio").

    Returns:
        The registered Explanation.

    Raises:
        KeyError: If no Explanation is registered under `name`.
    """
    try:
        return _REGISTRY[name]
    except KeyError as exc:
        raise KeyError(
            f"No explanation registered for {name!r}. "
            f"Call portpy.explain.available() to list all {len(_REGISTRY)} registered names."
        ) from exc

available

available(category: str | None = None) -> list[str]

List registered explanation names, optionally filtered by category.

Parameters:

Name Type Description Default
category str | None

If given, restrict to this category (e.g. "metric").

None

Returns:

Type Description
list[str]

A sorted list of registered names.

Source code in src/portpy/explain.py
def available(category: str | None = None) -> list[str]:
    """
    List registered explanation names, optionally filtered by category.

    Args:
        category: If given, restrict to this category (e.g. "metric").

    Returns:
        A sorted list of registered names.
    """
    if category is None:
        return sorted(_REGISTRY)
    return sorted(k for k, v in _REGISTRY.items() if v.category == category)