Source code for merton.extensions.base

"""Structural-model ABC and shared result dataclass."""

from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any

from .._typing import FloatArray

if TYPE_CHECKING:
    from ..core.firm import Firm


@dataclass(slots=True, frozen=True)
[docs] class StructuralResult: """Output of any :class:`StructuralModel`. Mirrors the shape of :class:`merton.MertonResult` so downstream tools can treat any structural fit uniformly. """
[docs] firm: Firm
[docs] asset_value: float | FloatArray
[docs] asset_vol: float
[docs] default_point: float | FloatArray
[docs] dd: float
[docs] pd: float
[docs] method: str
[docs] diagnostics: dict[str, Any] = field(default_factory=dict)
[docs] def summary(self) -> str: ticker = self.firm.ticker or "<firm>" lines = [ f"StructuralResult ({ticker}, method={self.method})", f" Distance-to-default : {self.dd:.4f}", f" Probability of default : {self.pd:.6f}", f" Asset value : {self.asset_value:,.2f}", f" Asset volatility (σ_A) : {self.asset_vol:.4f}", f" Default point : {self.default_point:,.2f}", ] return "\n".join(lines)
def __repr__(self) -> str: return self.summary()
[docs] class StructuralModel(ABC): """ABC for structural credit-risk models beyond the flat Merton setup."""
[docs] method: str = ""
@abstractmethod
[docs] def fit(self, firm: Firm) -> StructuralResult: """Return a :class:`StructuralResult` for ``firm``."""
__all__ = ["StructuralModel", "StructuralResult"]