Source code for tensorquantlib.tt.pricing

"""
TT-accelerated pricing — build compressed surrogates for MC-based pricers.

Provides factory functions that build TT-surrogate models by evaluating
Monte-Carlo pricers on structured grids and compressing via TT-SVD.

Supported pricers:
    - Heston stochastic volatility (``heston_surrogate``)
    - American options via LSM (``american_surrogate``)
    - Exotic options — asian, barrier, lookback (``exotic_surrogate``)
    - Jump-diffusion — Merton analytic (``jump_diffusion_surrogate``)

Usage::

    from tensorquantlib.tt.pricing import heston_surrogate

    surr = heston_surrogate(
        S_range=(80, 120), K_range=(90, 110),
        T_range=(0.25, 2.0), n_points=15, eps=1e-4,
    )
    price = surr.evaluate([100, 105, 1.0])
"""

from __future__ import annotations

import itertools
import time

import numpy as np

from .decompose import tt_svd
from .surrogate import TTSurrogate


def _build_grid(
    axes: list[np.ndarray],
    pricer_fn,
) -> np.ndarray:
    """Evaluate *pricer_fn* on every point of the Cartesian product of *axes*.

    Args:
        axes: List of 1-D arrays — grid ticks per dimension.
        pricer_fn: Callable accepting ``*args`` where len(args) == len(axes).
                   Must return a scalar float.

    Returns:
        np.ndarray of shape ``(len(axes[0]), ..., len(axes[-1]))``.
    """
    shape = tuple(len(a) for a in axes)
    grid = np.empty(shape, dtype=np.float64)
    for idx in itertools.product(*(range(n) for n in shape)):
        args = tuple(axes[k][idx[k]] for k in range(len(axes)))
        grid[idx] = pricer_fn(*args)
    return grid


def _make_surrogate(
    axes: list[np.ndarray],
    pricer_fn,
    eps: float,
    max_rank: int | None,
) -> TTSurrogate:
    t0 = time.perf_counter()
    grid = _build_grid(axes, pricer_fn)
    build_time = time.perf_counter() - t0

    t1 = time.perf_counter()
    cores = tt_svd(grid, eps=eps, max_rank=max_rank)
    compress_time = time.perf_counter() - t1

    return TTSurrogate(
        cores=cores,
        axes=axes,
        eps=eps,
        build_time=build_time,
        compress_time=compress_time,
        original_shape=grid.shape,
        original_nbytes=grid.nbytes,
    )


# ── Heston surrogate ───────────────────────────────────────────────────


[docs] def heston_surrogate( S_range: tuple[float, float] = (80, 120), K_range: tuple[float, float] = (90, 110), T_range: tuple[float, float] = (0.25, 2.0), r: float = 0.05, v0: float = 0.04, kappa: float = 2.0, theta: float = 0.04, xi: float = 0.3, rho: float = -0.7, n_points: int = 15, eps: float = 1e-4, max_rank: int | None = None, n_mc_paths: int = 50_000, option_type: str = "call", ) -> TTSurrogate: """Build a TT-surrogate for Heston MC prices over (S, K, T). Args: S_range: Spot price range. K_range: Strike range. T_range: Maturity range. r: Risk-free rate. v0, kappa, theta, xi, rho: Heston parameters. n_points: Grid points per axis. eps: TT-SVD tolerance. max_rank: Maximum TT-rank. n_mc_paths: Paths for Heston MC. option_type: "call" or "put". Returns: TTSurrogate with axes [S, K, T]. """ from ..finance.heston import HestonParams, heston_price_mc params = HestonParams(v0=v0, kappa=kappa, theta=theta, xi=xi, rho=rho) axes = [ np.linspace(*S_range, n_points), np.linspace(*K_range, n_points), np.linspace(*T_range, n_points), ] def pricer(S, K, T): return heston_price_mc(S, K, T, r, params, n_paths=n_mc_paths, option_type=option_type) return _make_surrogate(axes, pricer, eps, max_rank)
# ── American option surrogate ───────────────────────────────────────────
[docs] def american_surrogate( S_range: tuple[float, float] = (80, 120), K_range: tuple[float, float] = (90, 110), T_range: tuple[float, float] = (0.25, 2.0), r: float = 0.05, sigma: float = 0.2, n_points: int = 15, eps: float = 1e-4, max_rank: int | None = None, n_paths: int = 50_000, n_steps: int = 100, option_type: str = "put", ) -> TTSurrogate: """Build a TT-surrogate for American option (LSM) prices over (S, K, T). Args: S_range: Spot price range. K_range: Strike range. T_range: Maturity range. r: Risk-free rate. sigma: Volatility. n_points: Grid points per axis. eps: TT-SVD tolerance. max_rank: Maximum TT-rank. n_paths: MC paths for LSM. n_steps: Time steps for LSM. option_type: "call" or "put". Returns: TTSurrogate with axes [S, K, T]. """ from ..finance.american import american_option_lsm axes = [ np.linspace(*S_range, n_points), np.linspace(*K_range, n_points), np.linspace(*T_range, n_points), ] def pricer(S, K, T): return american_option_lsm( S, K, T, r, sigma, n_paths=n_paths, n_steps=n_steps, option_type=option_type ) return _make_surrogate(axes, pricer, eps, max_rank)
# ── Exotic option surrogate ─────────────────────────────────────────────
[docs] def exotic_surrogate( exotic_type: str = "asian", S_range: tuple[float, float] = (80, 120), K_range: tuple[float, float] = (90, 110), T_range: tuple[float, float] = (0.25, 2.0), r: float = 0.05, sigma: float = 0.2, n_points: int = 15, eps: float = 1e-4, max_rank: int | None = None, n_paths: int = 50_000, **pricer_kwargs, ) -> TTSurrogate: """Build a TT-surrogate for exotic option MC prices over (S, K, T). Args: exotic_type: One of "asian", "barrier_up_out", "barrier_down_out", "lookback_fixed", "lookback_floating". S_range: Spot price range. K_range: Strike range. T_range: Maturity range. r: Risk-free rate. sigma: Volatility. n_points: Grid points per axis. eps: TT-SVD tolerance. max_rank: Maximum TT-rank. n_paths: MC paths. **pricer_kwargs: Extra keyword arguments passed to the underlying pricer. Returns: TTSurrogate with axes [S, K, T]. """ from ..finance.exotics import ( asian_price_mc, barrier_price_mc, lookback_price_mc, ) axes = [ np.linspace(*S_range, n_points), np.linspace(*K_range, n_points), np.linspace(*T_range, n_points), ] if exotic_type == "asian": def pricer(S, K, T): return asian_price_mc(S, K, T, r, sigma, n_paths=n_paths, **pricer_kwargs) elif exotic_type.startswith("barrier"): barrier = pricer_kwargs.pop("barrier", 130.0) # Convert "barrier_up_out" → "up-and-out" bt = exotic_type.replace("barrier_", "").replace("_", "-and-") def pricer(S, K, T): return barrier_price_mc( S, K, T, r, sigma, barrier=barrier, barrier_type=bt, n_paths=n_paths, **pricer_kwargs, ) elif exotic_type.startswith("lookback"): strike_type = "fixed" if "fixed" in exotic_type else "floating" def pricer(S, K, T): result = lookback_price_mc( S, K, T, r, sigma, strike_type=strike_type, n_paths=n_paths, **pricer_kwargs ) return result[0] if isinstance(result, tuple) else result else: raise ValueError(f"Unknown exotic_type: {exotic_type!r}") return _make_surrogate(axes, pricer, eps, max_rank)
# ── Jump-diffusion surrogate ────────────────────────────────────────────
[docs] def jump_diffusion_surrogate( S_range: tuple[float, float] = (80, 120), K_range: tuple[float, float] = (90, 110), T_range: tuple[float, float] = (0.25, 2.0), r: float = 0.05, sigma: float = 0.2, lam: float = 1.0, mu_j: float = -0.05, sigma_j: float = 0.1, n_points: int = 15, eps: float = 1e-4, max_rank: int | None = None, option_type: str = "call", ) -> TTSurrogate: """Build a TT-surrogate for Merton jump-diffusion prices over (S, K, T). Uses the analytic series expansion (fast) rather than MC. Args: S_range: Spot price range. K_range: Strike range. T_range: Maturity range. r: Risk-free rate. sigma: Diffusion volatility. lam: Jump intensity. mu_j: Mean jump size. sigma_j: Jump size volatility. n_points: Grid points per axis. eps: TT-SVD tolerance. max_rank: Maximum TT-rank. option_type: "call" or "put". Returns: TTSurrogate with axes [S, K, T]. """ from ..finance.jump_diffusion import merton_jump_price axes = [ np.linspace(*S_range, n_points), np.linspace(*K_range, n_points), np.linspace(*T_range, n_points), ] def pricer(S, K, T): return merton_jump_price(S, K, T, r, sigma, lam, mu_j, sigma_j, option_type=option_type) return _make_surrogate(axes, pricer, eps, max_rank)