mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-27 15:37:44 +00:00
cbe1c52e00
Rename all source files, scripts, tests, documentation, and configuration from Predix/predix to NexQuant/nexquant across the entire codebase.
133 lines
4.4 KiB
Python
133 lines
4.4 KiB
Python
#!/usr/bin/env python
|
|
"""
|
|
NexQuant Auto-Pilot — vollautomatischer Strategie-Generator.
|
|
|
|
Läuft unbegrenzt, kein menschlicher Eingriff nötig.
|
|
Jede Runde: Factors laden → LLM Code → Pre-Flight → Backtest → Optuna → Ensemble
|
|
Bei Crash: auto-restart nach 30s.
|
|
|
|
Usage:
|
|
python scripts/nexquant_autopilot.py
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json, logging, os, sys, time, traceback
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
import numpy as np, pandas as pd
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
|
# Load .env before any rdagent imports (required for pydantic-settings)
|
|
try:
|
|
from dotenv import load_dotenv
|
|
_env_path = Path(__file__).resolve().parent.parent / ".env"
|
|
load_dotenv(_env_path)
|
|
except ImportError:
|
|
pass
|
|
|
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
|
logger = logging.getLogger("autopilot")
|
|
|
|
LOG_FILE = Path(__file__).resolve().parent.parent / "git_ignore_folder" / "logs" / f"autopilot_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
|
|
LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
fh = logging.FileHandler(str(LOG_FILE))
|
|
fh.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
|
|
logger.addHandler(fh)
|
|
|
|
BATCH_SIZE = 2
|
|
OPTUNA_TRIALS = 10
|
|
COOLDOWN = 30
|
|
MAX_CONSECUTIVE_FAILS = 5
|
|
|
|
def main_round(style: str, round_num: int) -> int:
|
|
"""Run one round. Returns number of accepted strategies."""
|
|
from rdagent.scenarios.qlib.local.strategy_orchestrator import StrategyOrchestrator
|
|
|
|
accepted_count = 0
|
|
try:
|
|
orch = StrategyOrchestrator(
|
|
top_factors=20, trading_style=style,
|
|
min_sharpe=0.1, use_optuna=True, optuna_trials=OPTUNA_TRIALS,
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Orchestrator init failed: {e}")
|
|
return 0
|
|
|
|
try:
|
|
results = orch.generate_strategies(count=BATCH_SIZE, workers=1)
|
|
except Exception as e:
|
|
logger.error(f"generate_strategies failed: {e}")
|
|
return 0
|
|
|
|
for r in results:
|
|
status = r.get("status", "?")
|
|
if status == "accepted":
|
|
accepted_count += 1
|
|
logger.info(f" ✓ {r.get('strategy_name','?')[:40]:40s} S={r.get('sharpe_ratio',0):.1f} OOS={r.get('oos_sharpe',0):.1f}")
|
|
else:
|
|
reason = r.get("reason", "?")[:80]
|
|
logger.debug(f" ✗ {r.get('strategy_name','?')[:40]:40s} {reason}")
|
|
|
|
if accepted_count >= 2:
|
|
try:
|
|
ensemble = orch.build_ensemble(results)
|
|
if ensemble and ensemble.get("status") == "success":
|
|
logger.info(f" Ensemble: S={ensemble['sharpe_ratio']:.1f} OOS={ensemble['oos_sharpe']:.1f} ({len(ensemble['members'])} members)")
|
|
except Exception:
|
|
pass
|
|
|
|
return accepted_count
|
|
|
|
|
|
def main():
|
|
print(f"\n{'='*50}")
|
|
print(f" NexQuant Auto-Pilot")
|
|
print(f" Log: {LOG_FILE}")
|
|
print(f" Batch: {BATCH_SIZE} | Optuna: {OPTUNA_TRIALS} trials")
|
|
print(f"{'='*50}\n")
|
|
|
|
round_num = 0
|
|
total_accepted = 0
|
|
consecutive_fails = 0
|
|
start_time = datetime.now()
|
|
styles = ["swing", "daytrading"]
|
|
|
|
while True:
|
|
round_num += 1
|
|
style = styles[round_num % 2]
|
|
print(f"\n[Round {round_num}] {style} | {datetime.now().strftime('%H:%M:%S')}", flush=True)
|
|
|
|
try:
|
|
accepted = main_round(style, round_num)
|
|
total_accepted += accepted
|
|
|
|
if accepted == 0:
|
|
consecutive_fails += 1
|
|
else:
|
|
consecutive_fails = 0
|
|
|
|
elapsed = (datetime.now() - start_time).total_seconds()
|
|
rate = total_accepted / (elapsed / 3600) if elapsed > 0 else 0
|
|
print(f" Accepted: {accepted} | Total: {total_accepted} | Rate: {rate:.1f}/h | Fails: {consecutive_fails}", flush=True)
|
|
|
|
if consecutive_fails >= MAX_CONSECUTIVE_FAILS:
|
|
logger.warning(f"{consecutive_fails} consecutive failures — cooling down {COOLDOWN*2}s")
|
|
time.sleep(COOLDOWN * 2)
|
|
consecutive_fails = 0
|
|
|
|
except KeyboardInterrupt:
|
|
print(f"\n\nStopped after {round_num} rounds. Total accepted: {total_accepted}")
|
|
break
|
|
except Exception as e:
|
|
logger.error(f"Round {round_num} crashed: {e}\n{traceback.format_exc()[-500:]}")
|
|
consecutive_fails += 1
|
|
time.sleep(COOLDOWN)
|
|
|
|
time.sleep(COOLDOWN)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|