feat: saxo
This commit is contained in:
@@ -145,7 +145,15 @@ def price_combo(
|
||||
net_pnl = scenario_exec - entry_ref
|
||||
broker_cost = (entry_ref - entry_ref_mid) + (scenario_mid - scenario_exec)
|
||||
|
||||
bounded = check_bounded_risk(legs, entry_ref, surface_now, spot_now)
|
||||
# Uses surface_scenario (not surface_now): for a single-expiry combo (condor,
|
||||
# butterfly, straddle...) all legs are simultaneously intrinsic at eval_days, so the
|
||||
# surface is never actually consulted there and this changes nothing. For a
|
||||
# calendar/diagonal spread, the far leg is still alive at the near leg's expiry and
|
||||
# DOES need a vol assumption to be priced — using surface_now there silently mixed
|
||||
# "today's vol" into a number sitting next to net_pnl (which uses the scenario's
|
||||
# shocked vol), producing a max_gain that could be below net_pnl. Pricing both with
|
||||
# the same scenario vol view keeps them consistent.
|
||||
bounded = check_bounded_risk(legs, entry_ref, surface_scenario, spot_now, r)
|
||||
delta_now = greeks_at(legs, spot_now, 0, surface_now, r)["delta"]
|
||||
delta_scenario = greeks_at(legs, spot_scenario, horizon_days, surface_scenario, r)["delta"]
|
||||
|
||||
@@ -166,7 +174,7 @@ def price_combo(
|
||||
})
|
||||
|
||||
|
||||
def check_bounded_risk(legs: List[Dict[str, Any]], entry_ref: float, surface: Any, spot: float) -> Dict[str, Any]:
|
||||
def check_bounded_risk(legs: List[Dict[str, Any]], entry_ref: float, surface: Any, spot: float, r: float = 0.05) -> Dict[str, Any]:
|
||||
"""
|
||||
Scan a wide log-spaced spot range at expiry and inspect both tails independently for LOSS
|
||||
vs GAIN direction. "Bounded risk" only requires the loss side to be capped — a long
|
||||
@@ -175,10 +183,15 @@ def check_bounded_risk(legs: List[Dict[str, Any]], entry_ref: float, surface: An
|
||||
|
||||
A tail is loss-bounded if moving further to that extreme does not make the P&L any worse
|
||||
than a point already well into that tail; symmetrically for gain-bounded.
|
||||
|
||||
"At expiry" means the earliest expiry among the legs. For a single-expiry combo that's
|
||||
every leg simultaneously (pure intrinsic, no vol assumption involved at all). For a
|
||||
calendar/diagonal spread it's only the near leg — the far leg is still alive and needs
|
||||
`surface` to be priced, so max_gain/max_loss there is only as good as that vol input.
|
||||
"""
|
||||
eval_days = min(l["days_to_expiry"] for l in legs)
|
||||
grid = np.geomspace(spot * 0.05, spot * 20, 300)
|
||||
values = [value_at(legs, float(p), eval_days, surface, 0.05) - entry_ref for p in grid]
|
||||
values = [value_at(legs, float(p), eval_days, surface, r) - entry_ref for p in grid]
|
||||
|
||||
tail_n = max(3, len(values) // 30)
|
||||
tol = max(abs(entry_ref), 1.0) * 0.01
|
||||
|
||||
Reference in New Issue
Block a user