Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| from datetime import datetime | |
| import numpy as np | |
| import pandas as pd | |
| from .density import DensityEstimate | |
| from .payoff import payoff_collar, payoff_protective_put, payoff_stock, risk_metrics | |
| from .types import ScoredStrategy, StrategyCandidate | |
| def _nearest_price( | |
| options: pd.DataFrame, expiry: datetime, option_type: str, strike: float | |
| ) -> float: | |
| o = options[ | |
| (options["expiry"] == pd.to_datetime(expiry)) | |
| & (options["option_type"] == option_type) | |
| ].copy() | |
| if o.empty: | |
| raise ValueError(f"No {option_type} options for expiry={expiry}") | |
| o["dist"] = (o["strike"] - strike).abs() | |
| row = o.sort_values("dist").iloc[0] | |
| return float(row["mid"]) | |
| def generate_candidates( | |
| spot: float, density: DensityEstimate, options: pd.DataFrame | |
| ) -> list[StrategyCandidate]: | |
| expiry = density.expiry | |
| k_put = 0.95 * spot | |
| k_call = 1.05 * spot | |
| put_premium = _nearest_price(options, expiry, "put", k_put) | |
| call_premium = _nearest_price(options, expiry, "call", k_call) | |
| return [ | |
| StrategyCandidate(name="long_stock", params={"units": 1.0}), | |
| StrategyCandidate( | |
| name="protective_put", | |
| params={"units": 1.0, "strike_put": k_put, "put_premium": put_premium}, | |
| ), | |
| StrategyCandidate( | |
| name="collar", | |
| params={ | |
| "units": 1.0, | |
| "strike_put": k_put, | |
| "put_premium": put_premium, | |
| "strike_call": k_call, | |
| "call_premium": call_premium, | |
| }, | |
| ), | |
| ] | |
| def _strategy_payoff( | |
| name: str, params: dict[str, float], s_t: np.ndarray, spot0: float | |
| ) -> np.ndarray: | |
| if name == "long_stock": | |
| return payoff_stock(s_t, spot0, units=params.get("units", 1.0)) | |
| if name == "protective_put": | |
| return payoff_protective_put( | |
| s_t, | |
| spot0, | |
| strike_put=params["strike_put"], | |
| put_premium=params["put_premium"], | |
| units=params.get("units", 1.0), | |
| ) | |
| if name == "collar": | |
| return payoff_collar( | |
| s_t, | |
| spot0, | |
| strike_put=params["strike_put"], | |
| put_premium=params["put_premium"], | |
| strike_call=params["strike_call"], | |
| call_premium=params["call_premium"], | |
| units=params.get("units", 1.0), | |
| ) | |
| raise ValueError(f"Unknown strategy {name}") | |
| def score_candidates( | |
| candidates: list[StrategyCandidate], | |
| density: DensityEstimate, | |
| spot0: float, | |
| risk_lambda: float = 0.5, | |
| ) -> list[ScoredStrategy]: | |
| s_t = density.strikes.to_numpy(dtype=float) | |
| probs = density.density.to_numpy(dtype=float) | |
| out: list[ScoredStrategy] = [] | |
| for c in candidates: | |
| pay = _strategy_payoff(c.name, c.params, s_t, spot0) | |
| metrics = risk_metrics(pay, probs=probs) | |
| objective = metrics["expected_payoff"] - risk_lambda * abs( | |
| min(metrics["q05"], 0.0) | |
| ) | |
| out.append( | |
| ScoredStrategy( | |
| candidate=c, | |
| expected_payoff=metrics["expected_payoff"], | |
| downside_q05=metrics["q05"], | |
| objective=float(objective), | |
| ) | |
| ) | |
| return sorted(out, key=lambda x: x.objective, reverse=True) | |
| def select_best(scored: list[ScoredStrategy]) -> ScoredStrategy: | |
| if not scored: | |
| raise ValueError("No scored candidates") | |
| return scored[0] | |