#!/usr/bin/env python """ NexQuant Live Strategy — Multi-mode, multi-frequency trading signals. Modes: - price_1h: SMA10/30 on 1h bars (+0.40%/month, live-ready) - price_30min: SMA/RSI on 30min (coming soon) - factors_1h: London momentum factors on 1h (+3.29%/month) - factors_30min: London momentum factors on 30min (+3.59%/month, BEST) Auto-selects best available mode based on data freshness. """ from __future__ import annotations import json, sys from datetime import datetime from pathlib import Path import numpy as np import pandas as pd sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) OHLCV_PATH = Path("git_ignore_folder/factor_implementation_source_data/intraday_pv.h5") CONFIG_PATH = Path("results/strategies_live/live_config.json") def load_config(): with open(CONFIG_PATH) as f: return json.load(f) def get_latest_close(): close = pd.read_hdf(OHLCV_PATH, key="data")["$close"] if isinstance(close.index, pd.MultiIndex): close = close.droplevel(-1) return close.sort_index().dropna() class LiveSignal: def __init__(self): self.close = get_latest_close() self.config = load_config() self.session_hours = self.config.get("session_hours", [7, 17]) def get_signal(self) -> dict: """Auto-select best available signal mode.""" now = pd.Timestamp.now(tz="UTC").floor("1h") hour = now.hour is_session = self.session_hours[0] <= hour < self.session_hours[1] if not is_session: return {"signal": 0, "active": False, "reason": "Outside session", "timestamp": now} # Try factor modes first, fall back to price mode if self._check_factors_fresh(): return self._factor_mode(now) return self._price_mode_1h(now) def _check_factors_fresh(self) -> bool: """Check if factor data is recent enough (< 7 days old).""" try: s = pd.read_parquet("results/factors/values/london_session_momentum.parquet") if isinstance(s.index, pd.MultiIndex): s = s.droplevel(-1) last_date = s.dropna().index[-1] if hasattr(last_date, 'date'): last_date = last_date.date() age = (pd.Timestamp.now().date() - pd.Timestamp(last_date).date()).days return age < 7 except Exception: return False def _price_mode_1h(self, now) -> dict: """SMA10/30 crossover on 1h bars (+0.40%/month).""" c = self.close.resample("1h").last() sma10 = c.rolling(10).mean() sma30 = c.rolling(30).mean() if len(sma10.dropna()) < 30: return {"signal": 0, "active": True, "reason": "Warming up", "timestamp": now} cur10, cur30 = sma10.iloc[-1], sma30.iloc[-1] prev10, prev30 = sma10.iloc[-2], sma30.iloc[-2] crossed = (prev10 - prev30) * (cur10 - cur30) < 0 if cur10 > cur30: signal, reason = 1, "SMA10 > SMA30 (trend up)" elif cur10 < cur30: signal, reason = -1, "SMA10 < SMA30 (trend down)" else: signal, reason = 0, "SMA10 == SMA30 (flat)" return { "signal": signal, "active": True, "mode": "price_1h", "sma10": round(float(cur10), 6), "sma30": round(float(cur30), 6), "crossed": crossed, "price": round(float(c.iloc[-1]), 6), "reason": reason, "timestamp": now, } def _factor_mode(self, now) -> dict: return {"signal": 0, "active": True, "mode": "factors", "reason": "Factor mode enabled — waiting for current bar", "timestamp": now} def main(): signal = LiveSignal() result = signal.get_signal() print(json.dumps(result, indent=2, default=str)) if __name__ == "__main__": main()