feat: strategy builder
This commit is contained in:
@@ -69,7 +69,7 @@ def get_iv_watchlist():
|
||||
Returns a summary list sorted by IV Rank descending.
|
||||
"""
|
||||
from services.iv_engine import get_atm_iv, _resolve_ticker
|
||||
from services.database import get_iv_rank_percentile, save_iv_snapshot, get_iv_history, get_watchlist_tickers
|
||||
from services.database import get_iv_rank_percentile, save_iv_snapshot, get_iv_history, get_watchlist_tickers, get_iv_change_1d
|
||||
from datetime import date
|
||||
|
||||
results = []
|
||||
@@ -97,10 +97,12 @@ def get_iv_watchlist():
|
||||
if live_iv:
|
||||
save_iv_snapshot(proxy, today, iv)
|
||||
rank_data = get_iv_rank_percentile(proxy, iv)
|
||||
iv_change_1d = get_iv_change_1d(proxy, iv)
|
||||
|
||||
item = {
|
||||
"ticker": ticker,
|
||||
"iv_current_pct": round(iv * 100, 1),
|
||||
"iv_change_1d_pct": round(iv_change_1d * 100, 1) if iv_change_1d is not None else None,
|
||||
"iv_rank": rank_data.get("iv_rank"),
|
||||
"iv_percentile": rank_data.get("iv_percentile"),
|
||||
"history_days": rank_data.get("history_days", 0),
|
||||
|
||||
@@ -3159,6 +3159,22 @@ def get_iv_history(ticker: str, days: int = 90) -> List[Dict]:
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
def get_iv_change_1d(ticker: str, current_iv: float) -> Optional[float]:
|
||||
"""Day-over-day IV delta (decimal, e.g. 0.012 = +1.2 vol points) — current_iv minus
|
||||
the most recent prior trading day's recorded iv_current. None if no prior day exists."""
|
||||
conn = get_conn()
|
||||
row = conn.execute(
|
||||
"""SELECT iv_current FROM iv_history
|
||||
WHERE ticker=? AND iv_current IS NOT NULL AND recorded_date < date('now')
|
||||
ORDER BY recorded_date DESC LIMIT 1""",
|
||||
(ticker.upper(),),
|
||||
).fetchone()
|
||||
conn.close()
|
||||
if not row or row["iv_current"] is None:
|
||||
return None
|
||||
return current_iv - row["iv_current"]
|
||||
|
||||
|
||||
# ── IV Watchlist ──────────────────────────────────────────────────────────────
|
||||
|
||||
def get_watchlist_tickers() -> List[str]:
|
||||
|
||||
@@ -404,7 +404,7 @@ def get_full_iv_snapshot(ticker: str) -> Dict[str, Any]:
|
||||
Full IV snapshot for a ticker: current IV, rank/percentile, term structure, skew, flow.
|
||||
Calls DB for historical rank/percentile.
|
||||
"""
|
||||
from services.database import get_iv_rank_percentile, save_iv_snapshot
|
||||
from services.database import get_iv_rank_percentile, save_iv_snapshot, get_iv_change_1d
|
||||
|
||||
proxy = _resolve_ticker(ticker)
|
||||
today = date.today().isoformat()
|
||||
@@ -423,16 +423,19 @@ def get_full_iv_snapshot(ticker: str) -> Dict[str, Any]:
|
||||
iv_current = recent[0]["iv_current"]
|
||||
|
||||
rank_data: Dict[str, Any] = {}
|
||||
iv_change_1d = None
|
||||
if iv_current:
|
||||
if live_iv and date.today().weekday() < 5:
|
||||
# Only persist weekday IV — weekend premium inflates IV and corrupts history
|
||||
save_iv_snapshot(proxy, today, iv_current, term.get("iv_30d"), term.get("iv_60d"), term.get("iv_90d"))
|
||||
rank_data = get_iv_rank_percentile(proxy, iv_current)
|
||||
iv_change_1d = get_iv_change_1d(proxy, iv_current)
|
||||
|
||||
return {
|
||||
"ticker": ticker,
|
||||
"proxy": proxy,
|
||||
"iv_current_pct": round(iv_current * 100, 1) if iv_current else None,
|
||||
"iv_change_1d_pct": round(iv_change_1d * 100, 1) if iv_change_1d is not None else None,
|
||||
"iv_rank": rank_data.get("iv_rank"),
|
||||
"iv_percentile": rank_data.get("iv_percentile"),
|
||||
"history_days": rank_data.get("history_days", 0),
|
||||
|
||||
@@ -28,6 +28,7 @@ def get_chain_slice(symbol: str, target_days: int = 8, n_expiries: int = 3) -> D
|
||||
)
|
||||
|
||||
spot = next((r["spot"] for r in flat_rows if r.get("spot") is not None), None)
|
||||
as_of = max((r["created_at"] for r in flat_rows if r.get("created_at")), default=None)
|
||||
today = date.today()
|
||||
|
||||
by_expiry: Dict[str, List[Dict[str, Any]]] = {}
|
||||
@@ -77,6 +78,7 @@ def get_chain_slice(symbol: str, target_days: int = 8, n_expiries: int = 3) -> D
|
||||
"symbol": symbol.upper(),
|
||||
"proxy": symbol.upper(),
|
||||
"spot": round(float(spot), 6) if spot is not None else None,
|
||||
"as_of": as_of,
|
||||
"expiries": expiries_out,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user