Files
NexQuant/scripts/nexquant_live_strategy.py
T
TPTBusiness 6975f77b77 feat: auto-mode live strategy — factors when fresh, SMA fallback
- LiveSignal auto-detects factor data freshness (<7 days old)
- Falls back to 1h SMA10/30 (+0.40%/month) when factors are stale
- 30min full factor scan script for discovering new signals
- Ready for 30min factor upgrade when data available
2026-05-11 20:37:30 +02:00

115 lines
3.7 KiB
Python

#!/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()