feat: instrument model
This commit is contained in:
@@ -229,6 +229,95 @@ def timeline_whatif(
|
||||
conn.close()
|
||||
|
||||
|
||||
# ── Magnitude estimation ──────────────────────────────────────────────────────
|
||||
|
||||
def _parse_num(s: Optional[str]) -> Optional[float]:
|
||||
if not s:
|
||||
return None
|
||||
t = str(s).strip().upper()
|
||||
try:
|
||||
if t.endswith('K'): return float(t[:-1]) * 1_000
|
||||
if t.endswith('M'): return float(t[:-1]) * 1_000_000
|
||||
if t.endswith('B'): return float(t[:-1]) * 1_000_000_000
|
||||
return float(s.replace('%', '').replace(',', ''))
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _event_scale(event_name: str) -> tuple[float, float]:
|
||||
"""(pips_per_unit, surprise_threshold) for rule-based estimation."""
|
||||
n = event_name.lower()
|
||||
if any(k in n for k in ['non-farm employment change', 'nfp']):
|
||||
return 0.0007, 20_000 # per job
|
||||
if 'adp non-farm' in n:
|
||||
return 0.0005, 15_000
|
||||
if 'unemployment claims' in n:
|
||||
return -0.0003, 8_000 # more claims = worse = negative
|
||||
if any(k in n for k in ['cpi', 'pce', 'ppi', 'inflation', 'hicp']):
|
||||
return 80.0, 0.1 # per %
|
||||
if any(k in n for k in ['interest rate', 'rate decision', 'fed fund']):
|
||||
return 300.0, 0.1
|
||||
if any(k in n for k in ['pmi', 'ism']):
|
||||
return 6.0, 0.5
|
||||
if 'gdp' in n:
|
||||
return 70.0, 0.1
|
||||
if any(k in n for k in ['retail sales', 'consumer spending']):
|
||||
return 55.0, 0.2
|
||||
if 'unemployment rate' in n:
|
||||
return -100.0, 0.1 # higher rate = worse
|
||||
if 'employment change' in n:
|
||||
return 0.0005, 15_000
|
||||
return 25.0, 0.5 # default
|
||||
|
||||
|
||||
_INST_DIR: Dict[tuple, int] = {
|
||||
("EURUSD", "USD"): -1, ("EURUSD", "EUR"): +1,
|
||||
("USDJPY", "USD"): +1, ("USDJPY", "JPY"): -1,
|
||||
("GBPUSD", "USD"): -1, ("GBPUSD", "GBP"): +1,
|
||||
("XAUUSD", "USD"): -1,
|
||||
("SP500", "USD"): +1,
|
||||
("TLT", "USD"): -1,
|
||||
("EEM", "USD"): -1,
|
||||
("QQQ", "USD"): +1,
|
||||
}
|
||||
_INST_DIR_INFL: Dict[tuple, int] = {
|
||||
("SP500", "USD"): -1, ("QQQ", "USD"): -1, ("TLT", "USD"): -1,
|
||||
}
|
||||
|
||||
|
||||
def _estimate_pip_magnitude(
|
||||
event_name: str, currency: str, instrument: str,
|
||||
actual: Optional[str], forecast: Optional[str], previous: Optional[str],
|
||||
) -> tuple[float, bool, Optional[float]]:
|
||||
"""Returns (estimated_pips, has_surprise, surprise_delta). No AI."""
|
||||
a = _parse_num(actual)
|
||||
f = _parse_num(forecast)
|
||||
p = _parse_num(previous)
|
||||
|
||||
if a is None:
|
||||
return 0.0, False, None # future event
|
||||
|
||||
ref = f if f is not None else p
|
||||
if ref is None:
|
||||
return 0.0, False, None
|
||||
|
||||
delta = a - ref
|
||||
scale, threshold = _event_scale(event_name)
|
||||
has_surprise = abs(delta) >= threshold
|
||||
raw = max(-100.0, min(100.0, delta * scale))
|
||||
|
||||
n = event_name.lower()
|
||||
is_inflation = any(k in n for k in ['cpi', 'pce', 'ppi', 'inflation', 'hicp'])
|
||||
inst = instrument.upper()
|
||||
cur = currency.upper()
|
||||
direction = _INST_DIR_INFL.get((inst, cur), _INST_DIR.get((inst, cur), 0)) if is_inflation \
|
||||
else _INST_DIR.get((inst, cur), 0)
|
||||
|
||||
if direction == 0:
|
||||
return round(abs(raw), 1), has_surprise, round(delta, 4)
|
||||
return round(raw * direction, 1), has_surprise, round(delta, 4)
|
||||
|
||||
|
||||
_INSTRUMENT_CURRENCIES: Dict[str, List[str]] = {
|
||||
"EURUSD": ["EUR", "USD"],
|
||||
"USDJPY": ["USD", "JPY"],
|
||||
@@ -315,22 +404,29 @@ def get_calendar_events(
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
r = dict(row)
|
||||
ev_date = r["event_date"]
|
||||
r = dict(row)
|
||||
ev_date = r["event_date"]
|
||||
is_future = ev_date > str(ref)
|
||||
category = _ff_event_to_category(r["event_name"])
|
||||
est_pips, has_surprise, surp_delta = _estimate_pip_magnitude(
|
||||
r["event_name"], r["currency"], inst_upper,
|
||||
r.get("actual_value"), r.get("forecast_value"), r.get("previous_value"),
|
||||
)
|
||||
result.append({
|
||||
"date": ev_date,
|
||||
"event_time": r.get("event_time", ""),
|
||||
"currency": r["currency"],
|
||||
"impact": r["impact"],
|
||||
"event_name": r["event_name"],
|
||||
"label": f"[{r['currency']}] {r['event_name']}",
|
||||
"actual_value": r.get("actual_value"),
|
||||
"forecast_value": r.get("forecast_value"),
|
||||
"previous_value": r.get("previous_value"),
|
||||
"category": category,
|
||||
"is_future": is_future,
|
||||
"date": ev_date,
|
||||
"event_time": r.get("event_time", ""),
|
||||
"currency": r["currency"],
|
||||
"impact": r["impact"],
|
||||
"event_name": r["event_name"],
|
||||
"label": f"[{r['currency']}] {r['event_name']}",
|
||||
"actual_value": r.get("actual_value"),
|
||||
"forecast_value": r.get("forecast_value"),
|
||||
"previous_value": r.get("previous_value"),
|
||||
"category": category,
|
||||
"is_future": is_future,
|
||||
"estimated_pips": est_pips,
|
||||
"has_surprise": has_surprise,
|
||||
"surprise_delta": surp_delta,
|
||||
})
|
||||
return result
|
||||
finally:
|
||||
|
||||
Reference in New Issue
Block a user