Junaid Hasan
Initialize option-implied strategy lab v1 with clean repo artifacts
b6d53e2
Raw
History Blame Contribute Delete
3.47 kB
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]