feat: strategy builder

This commit is contained in:
OpenSquared
2026-07-21 10:18:15 +02:00
parent 52890f4d1d
commit 6c22b3f552
11 changed files with 129 additions and 16 deletions

View File

@@ -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),

View File

@@ -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]:

View File

@@ -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),

View File

@@ -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,
}