Source code for merton

"""merton: production-grade Merton structural credit-risk model.

The cold import path stays light: the only eager dependencies are the
core single-firm math (``core``, ``calibration``, ``extensions``,
``greeks``) plus the type system and the Numba JIT pre-warm hook.
Heavier surfaces — ``backtest`` (scipy.stats), ``portfolio``
(MC + copulas), ``reports`` (jinja2), ``excel`` (FastAPI + xlwings),
``scenarios``, ``obs``, ``cli`` — are imported lazily on first access
through :func:`__getattr__`.

Examples
--------
>>> from merton import Firm, fit
>>> firm = Firm(equity=100, debt_short=20, debt_long=30, equity_vol=0.30)
>>> result = fit(firm)
>>> result.pd >= 0 and result.pd <= 1
True
"""

from __future__ import annotations

from importlib import import_module
from typing import TYPE_CHECKING

try:
    from ._version import __version__
except ImportError:  # pragma: no cover - first install before hatch-vcs runs
    __version__ = "0.0.0+unknown"

from . import _config as config
from . import (
    calibration,
    exceptions,
    extensions,
    greeks,
)
from ._backend._numba import warm_cache as _warm_numba_cache
from .core.default_point import DefaultPoint
from .core.distance import distance_to_default, prob_of_default
from .core.firm import Firm
from .core.model import MertonModel, fit
from .core.panel import FirmPanel
from .core.physical import physical_pd
from .core.pricing import equity_value
from .core.result import MertonResult
from .core.spread import implied_credit_spread
from .core.term_structure import term_structure_pd

if TYPE_CHECKING:
    from types import ModuleType

# Submodules surfaced lazily through __getattr__ to keep cold-import light.
# - `backtest` pulls in scipy.stats + pandas (~500 ms eager).
# - `portfolio` pulls in the MC engine + copulas.
# - `reports` pulls in jinja2 + weasyprint.
# - `excel` pulls in FastAPI + xlwings.
# - `cli`, `obs`, `scenarios` are conditional surfaces.
#
# `io`, `diagnostics`, and `viz` are roadmapped for 1.x; their helpers
# currently live on `FirmPanel`, `MertonResult.summary()`, and
# `merton.reports` respectively.
_LAZY_SUBMODULES = frozenset(
    {
        "backtest",
        "cli",
        "excel",
        "obs",
        "portfolio",
        "reports",
        "scenarios",
    }
)


def __getattr__(name: str) -> ModuleType:
    if name in _LAZY_SUBMODULES:
        module = import_module(f"{__name__}.{name}")
        globals()[name] = module
        return module
    if name == "batch_fit":
        # `batch_fit` pulls pandas into the import path. Lazy-import it so
        # users of the single-firm `fit()` API don't pay for it.
        from .batch import batch_fit as _batch_fit

        globals()["batch_fit"] = _batch_fit
        return _batch_fit
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


def __dir__() -> list[str]:
    return sorted(set(globals().keys()) | _LAZY_SUBMODULES)


[docs] def warm_cache() -> None: """Pre-compile Numba kernels so the first user-facing call is fast. Called automatically by the wheel build (via :func:`_warm_numba_cache`) and exposed here so users can force the cache to warm explicitly. """ _warm_numba_cache()
__all__ = [ "DefaultPoint", "Firm", "FirmPanel", "MertonModel", "MertonResult", "__version__", "backtest", "batch_fit", "calibration", "config", "distance_to_default", "equity_value", "excel", "exceptions", "extensions", "fit", "greeks", "implied_credit_spread", "obs", "physical_pd", "portfolio", "prob_of_default", "reports", "scenarios", "term_structure_pd", "warm_cache", ]