feat: strategy builder

This commit is contained in:
OpenSquared
2026-08-03 09:36:13 +02:00
parent 7e640a09d0
commit 663e7eaa74
6 changed files with 440 additions and 14 deletions

View File

@@ -0,0 +1,31 @@
"""
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"]