feat: Specialist Desks v2 — COT, Forward Curves, Surprise Index, Hawk/Dove scorer
- COT Positioning: CFTC disaggregated + financial futures (19 markets) via Socrata free API net MM position % OI + weekly change stored in cot_data table - Forward Curves: yfinance front-month vs +3M slope (8 commodities) contango/backwardation/flat stored in forward_curve_data table - Surprise Index: consensus_estimate + actual_value on specialist_reports auto-computes surprise_score = actual - consensus on save - Hawk/Dove Text Scorer: GPT-4o-mini endpoint for CB statements score -1..+1, label, summary, key_phrases (forex/bonds: hawk/dove; commodities: bull/bear) - AI context injection: COT net positioning, forward curve structure, surprise scores, upcoming consensus estimates injected into all desk blocks - Frontend: COT panel (net% bars), Forward Curves panel, SurpriseInput on report cards, Hawk/Dove scorer in forex/bonds config tab - auto_cycle.py: non-blocking COT + curve refresh before each cycle Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -108,6 +108,32 @@ def init_db():
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Specialist Reports — surprise index + text sentiment columns
|
||||
try:
|
||||
c.execute("ALTER TABLE specialist_reports ADD COLUMN consensus_estimate REAL")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
c.execute("ALTER TABLE specialist_reports ADD COLUMN actual_value REAL")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
c.execute("ALTER TABLE specialist_reports ADD COLUMN surprise_score REAL")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
c.execute("ALTER TABLE specialist_reports ADD COLUMN text_sentiment_score REAL")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
c.execute("ALTER TABLE specialist_reports ADD COLUMN text_sentiment_label TEXT")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
c.execute("ALTER TABLE specialist_reports ADD COLUMN text_sentiment_summary TEXT")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Pattern Lab — historical backtest runs
|
||||
c.execute("""CREATE TABLE IF NOT EXISTS backtest_lab_runs (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -677,6 +703,36 @@ def init_db():
|
||||
PRIMARY KEY (report_id, asset_class)
|
||||
)""")
|
||||
|
||||
# ── COT & Forward Curve data ──────────────────────────────────────────────
|
||||
c.execute("""CREATE TABLE IF NOT EXISTS cot_data (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
market_name TEXT NOT NULL,
|
||||
commodity TEXT NOT NULL,
|
||||
asset_class TEXT NOT NULL,
|
||||
report_date TEXT NOT NULL,
|
||||
mm_long INTEGER DEFAULT 0,
|
||||
mm_short INTEGER DEFAULT 0,
|
||||
open_interest INTEGER DEFAULT 0,
|
||||
net_position INTEGER DEFAULT 0,
|
||||
net_pct_oi REAL DEFAULT 0.0,
|
||||
change_net INTEGER DEFAULT 0,
|
||||
fetched_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
UNIQUE(commodity, report_date)
|
||||
)""")
|
||||
|
||||
c.execute("""CREATE TABLE IF NOT EXISTS forward_curve_data (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
asset TEXT NOT NULL,
|
||||
asset_class TEXT NOT NULL,
|
||||
front_price REAL,
|
||||
far_price REAL,
|
||||
slope_pct REAL,
|
||||
structure TEXT NOT NULL DEFAULT 'unknown',
|
||||
months_spread INTEGER DEFAULT 3,
|
||||
fetched_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
UNIQUE(asset, DATE(fetched_at))
|
||||
)""")
|
||||
|
||||
# Seed desk defaults (idempotent)
|
||||
_desk_count = c.execute("SELECT COUNT(*) FROM asset_class_configs").fetchone()[0]
|
||||
if _desk_count == 0:
|
||||
@@ -4268,3 +4324,109 @@ def upsert_asset_class_config(
|
||||
return True
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
# ── COT Data ──────────────────────────────────────────────────────────────────
|
||||
|
||||
def save_cot_data(entries: List[Dict[str, Any]]) -> int:
|
||||
conn = get_conn()
|
||||
try:
|
||||
saved = 0
|
||||
for e in entries:
|
||||
conn.execute("""INSERT INTO cot_data
|
||||
(market_name, commodity, asset_class, report_date, mm_long, mm_short,
|
||||
open_interest, net_position, net_pct_oi, change_net)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?)
|
||||
ON CONFLICT(commodity, report_date) DO UPDATE SET
|
||||
mm_long=excluded.mm_long, mm_short=excluded.mm_short,
|
||||
open_interest=excluded.open_interest, net_position=excluded.net_position,
|
||||
net_pct_oi=excluded.net_pct_oi, change_net=excluded.change_net,
|
||||
fetched_at=datetime('now')""",
|
||||
(e["market_name"], e["commodity"], e["asset_class"], e["report_date"],
|
||||
e.get("mm_long", 0), e.get("mm_short", 0), e.get("open_interest", 0),
|
||||
e.get("net_position", 0), e.get("net_pct_oi", 0.0), e.get("change_net", 0)))
|
||||
saved += 1
|
||||
conn.commit()
|
||||
return saved
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_latest_cot_data() -> List[Dict[str, Any]]:
|
||||
conn = get_conn()
|
||||
try:
|
||||
rows = conn.execute("""
|
||||
SELECT * FROM cot_data
|
||||
WHERE (commodity, report_date) IN (
|
||||
SELECT commodity, MAX(report_date) FROM cot_data GROUP BY commodity
|
||||
)
|
||||
ORDER BY asset_class, net_pct_oi DESC""").fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
# ── Forward Curve Data ────────────────────────────────────────────────────────
|
||||
|
||||
def save_forward_curves(entries: List[Dict[str, Any]]) -> int:
|
||||
conn = get_conn()
|
||||
try:
|
||||
saved = 0
|
||||
for e in entries:
|
||||
conn.execute("""INSERT INTO forward_curve_data
|
||||
(asset, asset_class, front_price, far_price, slope_pct, structure, months_spread)
|
||||
VALUES (?,?,?,?,?,?,?)
|
||||
ON CONFLICT(asset, DATE(fetched_at)) DO UPDATE SET
|
||||
front_price=excluded.front_price, far_price=excluded.far_price,
|
||||
slope_pct=excluded.slope_pct, structure=excluded.structure,
|
||||
fetched_at=datetime('now')""",
|
||||
(e["asset"], e["asset_class"], e.get("front_price"), e.get("far_price"),
|
||||
e.get("slope_pct"), e.get("structure", "unknown"), e.get("months_spread", 3)))
|
||||
saved += 1
|
||||
conn.commit()
|
||||
return saved
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_latest_forward_curves() -> List[Dict[str, Any]]:
|
||||
conn = get_conn()
|
||||
try:
|
||||
rows = conn.execute("""
|
||||
SELECT * FROM forward_curve_data
|
||||
WHERE (asset, DATE(fetched_at)) IN (
|
||||
SELECT asset, MAX(DATE(fetched_at)) FROM forward_curve_data GROUP BY asset
|
||||
)
|
||||
ORDER BY asset_class, asset""").fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
# ── Surprise Index ────────────────────────────────────────────────────────────
|
||||
|
||||
def update_report_result(
|
||||
report_id: str,
|
||||
actual_value: Optional[float],
|
||||
consensus_estimate: Optional[float],
|
||||
text_sentiment_score: Optional[float] = None,
|
||||
text_sentiment_label: Optional[str] = None,
|
||||
text_sentiment_summary: Optional[str] = None,
|
||||
) -> bool:
|
||||
conn = get_conn()
|
||||
try:
|
||||
surprise = None
|
||||
if actual_value is not None and consensus_estimate is not None:
|
||||
surprise = round(actual_value - consensus_estimate, 4)
|
||||
conn.execute("""UPDATE specialist_reports SET
|
||||
actual_value=?, consensus_estimate=?, surprise_score=?,
|
||||
text_sentiment_score=?, text_sentiment_label=?, text_sentiment_summary=?,
|
||||
updated_at=datetime('now')
|
||||
WHERE id=?""",
|
||||
(actual_value, consensus_estimate, surprise,
|
||||
text_sentiment_score, text_sentiment_label, text_sentiment_summary,
|
||||
report_id))
|
||||
conn.commit()
|
||||
return True
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
Reference in New Issue
Block a user