feat: option lab
This commit is contained in:
@@ -132,6 +132,60 @@ def get_quote(symbol: str) -> Optional[Dict[str, Any]]:
|
||||
return {"symbol": symbol, "price": None, "error": "no data"}
|
||||
|
||||
|
||||
def get_quote_with_volatility(symbol: str, vol_window: int = 20) -> Optional[Dict[str, Any]]:
|
||||
"""Like get_quote(), plus a realized volatility overlay: annualized %, rolling
|
||||
`vol_window`-day stddev of log returns — same formula as the Instrument Analysis
|
||||
chart's volatility overlay (services.instrument_service). Needs more history than
|
||||
get_quote()'s 5d/1mo window, so it's kept as a separate function rather than slowing
|
||||
down get_quote()'s many other callers that don't need volatility."""
|
||||
import numpy as np
|
||||
|
||||
for period in ("3mo", "6mo"):
|
||||
try:
|
||||
ticker = yf.Ticker(symbol)
|
||||
hist = ticker.history(period=period, interval="1d", auto_adjust=True)
|
||||
if hist.empty:
|
||||
continue
|
||||
hist = hist.dropna(subset=["Close"])
|
||||
if len(hist) < vol_window + 2:
|
||||
continue
|
||||
|
||||
close = hist["Close"]
|
||||
price = float(close.iloc[-1])
|
||||
last_date = hist.index[-1].date()
|
||||
prior_rows = hist[hist.index.date < last_date]
|
||||
prev = float(prior_rows["Close"].iloc[-1]) if not prior_rows.empty else price
|
||||
change = price - prev
|
||||
change_pct = (change / prev * 100) if prev else 0
|
||||
|
||||
log_ret = np.log(close / close.shift(1))
|
||||
vol_series = (log_ret.rolling(vol_window).std() * np.sqrt(252) * 100).dropna()
|
||||
if vol_series.empty:
|
||||
continue
|
||||
volatility_pct = float(vol_series.iloc[-1])
|
||||
|
||||
# D-1 vol: same explicit date-comparison approach as the price above, not
|
||||
# just "the point before last" (guards the same near-24h double-row case).
|
||||
volatility_change_pct = None
|
||||
prior_vol = vol_series[vol_series.index.date < last_date]
|
||||
if not prior_vol.empty:
|
||||
volatility_change_pct = round(volatility_pct - float(prior_vol.iloc[-1]), 2)
|
||||
|
||||
return {
|
||||
"symbol": symbol,
|
||||
"price": round(price, 4),
|
||||
"change": round(change, 4),
|
||||
"change_pct": round(change_pct, 2),
|
||||
"volatility_pct": round(volatility_pct, 2),
|
||||
"volatility_change_pct": volatility_change_pct,
|
||||
"volume": int(hist["Volume"].iloc[-1]) if "Volume" in hist.columns else 0,
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
}
|
||||
except Exception:
|
||||
continue
|
||||
return {"symbol": symbol, "price": None, "error": "no data"}
|
||||
|
||||
|
||||
def get_all_quotes() -> Dict[str, List[Dict[str, Any]]]:
|
||||
result = {}
|
||||
for asset_class, assets in WATCHLIST.items():
|
||||
|
||||
Reference in New Issue
Block a user