32 lines
1.6 KiB
Python
32 lines
1.6 KiB
Python
"""
|
|
Time-path scenario support: lets a scenario describe an evolving trajectory (bell, range/
|
|
oscillation, exponential, step, custom points...) for spot shock / IV level / skew tilt /
|
|
term slope across the days between entry and the scenario horizon, instead of only a single
|
|
point-in-time shock. The frontend is responsible for turning a shape+params choice into a
|
|
plain list of {day, value} anchor points (services/lib/scenarioPath.ts) — this module only
|
|
interpolates whatever anchor points it's given, so it has no notion of "bell" or
|
|
"oscillation" itself and stays reusable across spot/IV/skew/term alike.
|
|
|
|
A path is optional everywhere it's accepted: when None/empty, every call site here falls
|
|
back to the scalar shock value it already had (unchanged behavior from before paths existed).
|
|
"""
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
|
|
def interpolate_path(path: Optional[List[Dict[str, Any]]], day: float, default: float) -> float:
|
|
"""Linear interpolation between anchor points {day, value}, clamped flat beyond the
|
|
first/last anchor. Falls back to `default` when no path is given at all."""
|
|
if not path:
|
|
return default
|
|
pts = sorted(path, key=lambda p: p["day"])
|
|
if day <= pts[0]["day"]:
|
|
return pts[0]["value"]
|
|
if day >= pts[-1]["day"]:
|
|
return pts[-1]["value"]
|
|
for p0, p1 in zip(pts, pts[1:]):
|
|
if p0["day"] <= day <= p1["day"]:
|
|
span = p1["day"] - p0["day"]
|
|
w = (day - p0["day"]) / span if span > 1e-9 else 0.0
|
|
return p0["value"] + (p1["value"] - p0["value"]) * w
|
|
return pts[-1]["value"]
|