refactor: rename project from Predix to NexQuant

Rename all source files, scripts, tests, documentation, and configuration
from Predix/predix to NexQuant/nexquant across the entire codebase.
This commit is contained in:
TPTBusiness
2026-05-09 17:48:22 +02:00
parent 85b56b8179
commit cbe1c52e00
92 changed files with 3690 additions and 1015 deletions
+2 -2
View File
@@ -5,8 +5,8 @@ import numpy as np
import pandas as pd
from pathlib import Path
OHLCV_PATH = Path('/home/nico/Predix/git_ignore_folder/factor_implementation_source_data/intraday_pv.h5')
FACTORS_DIR = Path('/home/nico/Predix/results/factors')
OHLCV_PATH = Path('/home/nico/NexQuant/git_ignore_folder/factor_implementation_source_data/intraday_pv.h5')
FACTORS_DIR = Path('/home/nico/NexQuant/results/factors')
VALUES_DIR = FACTORS_DIR / 'values'
print("=" * 70)
+3 -3
View File
@@ -3,10 +3,10 @@
Option A: Generate Kronos predicted-return factor from EUR/USD 1-min data.
Runs Kronos-mini inference in daily strides (96 bars/day) over all available
OHLCV data and saves the resulting factor for use in Predix's factor pipeline.
OHLCV data and saves the resulting factor for use in NexQuant's factor pipeline.
Usage:
conda activate predix
conda activate nexquant
python scripts/kronos_factor_gen.py
python scripts/kronos_factor_gen.py --context 512 --pred 96 --device cuda
python scripts/kronos_factor_gen.py --device cpu # slower but no GPU needed
@@ -71,7 +71,7 @@ def main():
print(f"\nSample (first 5):")
print(factor_df.head())
# Save metadata for predix.py top / best integration
# Save metadata for nexquant.py top / best integration
meta = {
"factor_name": f"KronosPredReturn_p{args.pred}",
"description": f"Kronos-mini predicted return, {args.pred}-bar horizon",
+1 -1
View File
@@ -6,7 +6,7 @@ Computes IC (Information Coefficient) and hit rate for Kronos predictions
vs actual realized returns. Results are printed for comparison with LightGBM.
Usage:
conda activate predix
conda activate nexquant
python scripts/kronos_model_eval.py
python scripts/kronos_model_eval.py --pred 30 --context 512 --device cuda
"""
@@ -10,8 +10,8 @@ For each accepted strategy, add:
- Generate Live Trading report
Usage:
python predix_add_risk_management.py
python predix_add_risk_management.py --live # Mark as live-ready
python nexquant_add_risk_management.py
python nexquant_add_risk_management.py --live # Mark as live-ready
"""
import os, sys, json, time
from pathlib import Path
+132
View File
@@ -0,0 +1,132 @@
#!/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()
@@ -1,14 +1,14 @@
"""
Predix Batch Backtest Script - Extract and backtest existing factors.
NexQuant Batch Backtest Script - Extract and backtest existing factors.
Scans generated factor code from workspaces, runs Qlib backtests directly
(bypassing CoSTEER), and saves results to JSON + SQLite.
Usage:
python predix_batch_backtest.py --factors 100 # Backtest top 100 factors
python predix_batch_backtest.py --all # Backtest all discovered factors
python predix_batch_backtest.py --parallel 5 # 5 parallel backtests
python predix_batch_backtest.py --scan-only # Only scan, don't run backtests
python nexquant_batch_backtest.py --factors 100 # Backtest top 100 factors
python nexquant_batch_backtest.py --all # Backtest all discovered factors
python nexquant_batch_backtest.py --parallel 5 # 5 parallel backtests
python nexquant_batch_backtest.py --scan-only # Only scan, don't run backtests
"""
import json
@@ -660,7 +660,7 @@ def _run_factor_directly(factor_info: FactorInfo) -> Optional[BacktestResult]:
import tempfile
import subprocess
with tempfile.TemporaryDirectory(prefix="predix_factor_") as tmp_dir:
with tempfile.TemporaryDirectory(prefix="nexquant_factor_") as tmp_dir:
ws = Path(tmp_dir)
# Write factor code
@@ -742,7 +742,7 @@ def _run_qlib_single(factor_info: FactorInfo) -> BacktestResult:
import tempfile
# Create temp workspace
with tempfile.TemporaryDirectory(prefix="predix_bt_") as tmp_dir:
with tempfile.TemporaryDirectory(prefix="nexquant_bt_") as tmp_dir:
ws = Path(tmp_dir)
# Write factor code
@@ -1182,7 +1182,7 @@ def main(
Metric for ranking ('ic' or 'sharpe')
"""
console.print(Panel(
"[bold cyan]Predix Batch Backtest Runner[/bold cyan]\n"
"[bold cyan]NexQuant Batch Backtest Runner[/bold cyan]\n"
f"Scanning workspaces for generated factors...",
border_style="cyan",
))
@@ -1196,7 +1196,7 @@ def main(
if not all_factors_list:
console.print("\n[red]No factors found in workspaces![/red]")
console.print(
"[yellow]Ensure factors have been generated via `predix.py quant` first.[/yellow]"
"[yellow]Ensure factors have been generated via `nexquant.py quant` first.[/yellow]"
)
return
@@ -1407,7 +1407,7 @@ if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description="Predix Batch Backtest - Extract and backtest existing factors"
description="NexQuant Batch Backtest - Extract and backtest existing factors"
)
parser.add_argument(
"--factors", "-n",
@@ -12,9 +12,9 @@ Features:
- Daytrading AND swing style alternating
Usage:
python scripts/predix_continuous_strategies.py
python scripts/predix_continuous_strategies.py --style daytrading --rounds 100
python scripts/predix_continuous_strategies.py --style both --workers 4
python scripts/nexquant_continuous_strategies.py
python scripts/nexquant_continuous_strategies.py --style daytrading --rounds 100
python scripts/nexquant_continuous_strategies.py --style both --workers 4
"""
from __future__ import annotations
@@ -105,7 +105,7 @@ def main():
args = parser.parse_args()
print(f"\n{'='*60}")
print(f" Predix Continuous Strategy Generator")
print(f" NexQuant Continuous Strategy Generator")
print(f" Style: {args.style} | Workers: {args.workers}")
print(f" Min Sharpe: {args.min_sharpe} | Batch: {args.batch_size}")
print(f" ML every {args.ml_rounds} rounds")
+156
View File
@@ -0,0 +1,156 @@
#!/usr/bin/env python
"""Fast rebacktest: only strategies with factor parquets, skip already-done."""
import json, sys, pandas as pd, subprocess, tempfile, numpy as np
from pathlib import Path
from datetime import datetime
sys.path.insert(0, str(Path(__file__).resolve().parent))
from rdagent.components.backtesting.vbt_backtest import backtest_signal
OHLCV = Path("git_ignore_folder/factor_implementation_source_data/intraday_pv.h5")
FACTORS_DIR = Path("results/factors/values")
STRAT_DIR = Path("results/strategies_new")
# Pre-build factor name → path map
fmap = {p.stem: str(p) for p in FACTORS_DIR.glob("*.parquet")}
# Load close once
print("Loading OHLCV...")
ohlcv = pd.read_hdf(str(OHLCV), key="data")
close = ohlcv["$close"].dropna()
if isinstance(close.index, pd.MultiIndex):
close = close.droplevel(-1)
close = close.astype(float).sort_index()
print(f"{len(close):,} bars")
# Build work list
work = []
for f in sorted(STRAT_DIR.glob("*.json")):
try:
d = json.loads(f.read_text())
except Exception:
continue
if d.get("reevaluation_status") == "verified_v2":
continue
names = d.get("factor_names", [])
code = d.get("code", "")
if not names or not code:
continue
paths = []
for n in names:
p = fmap.get(n) or fmap.get(n.replace("/", "_")[:150])
if p:
paths.append((n, p))
if len(paths) >= 2:
work.append((f, d, paths))
print(f"{len(work)} strategies to process")
if not work:
print("All done!")
sys.exit(0)
ok = skip = fail = 0
start = datetime.now()
for i, (f, data, factor_paths) in enumerate(work):
name = data.get("strategy_name", f.stem)[:45]
code = data.get("code", "")
# Load factor series
series = {}
for fn, fp in factor_paths:
try:
s = pd.read_parquet(fp).iloc[:, 0]
series[fn] = s
except Exception:
pass
if len(series) < 2:
skip += 1
continue
df = pd.DataFrame(series).sort_index()
if isinstance(df.index, pd.MultiIndex):
df = df.droplevel(-1)
try:
df_1m = df.reindex(close.index).ffill()
except Exception:
skip += 1
continue
valid = df_1m.notna().any(axis=1)
if valid.sum() < 1000:
skip += 1
continue
ca = close.loc[valid]
fa = df_1m.loc[valid]
# Execute strategy code
try:
with tempfile.TemporaryDirectory() as td:
tdp = Path(td)
fa.to_parquet(str(tdp / "factors.parquet"))
ca.to_pickle(str(tdp / "close.pkl"))
exec_script = (
"import pandas as pd, numpy as np\n"
"factors = pd.read_parquet('factors.parquet')\n"
"close = pd.read_pickle('close.pkl')\n"
"df = factors\n"
+ code +
"\nif 'signal' not in dir():\n"
" raise SystemExit(1)\n"
"pd.Series(signal).fillna(0).to_pickle('signal.pkl')\n"
)
(tdp / "run.py").write_text(exec_script)
r = subprocess.run(
["python", "run.py"],
capture_output=True, text=True, timeout=60, cwd=str(tdp),
)
if r.returncode != 0:
fail += 1
continue
sig = pd.read_pickle(tdp / "signal.pkl")
except Exception:
fail += 1
continue
try:
sig = sig.reindex(ca.index).ffill().fillna(0)
result = backtest_signal(ca, sig, txn_cost_bps=2.14)
except Exception:
fail += 1
continue
# Write back
data["reevaluation_status"] = "verified_v2"
data["sharpe_ratio"] = result.get("sharpe")
data["max_drawdown"] = result.get("max_drawdown")
data["win_rate"] = result.get("win_rate")
data["total_return"] = result.get("total_return")
data["summary"] = {
**data.get("summary", {}),
"sharpe": result.get("sharpe"),
"max_drawdown": result.get("max_drawdown"),
"win_rate": result.get("win_rate"),
"monthly_return_pct": result.get("monthly_return_pct"),
"real_n_trades": result.get("n_trades"),
"total_return": result.get("total_return"),
"annualized_return": result.get("annualized_return"),
"engine": "verified_v2",
"txn_cost_bps": 2.14,
}
f.write_text(json.dumps(data, indent=2, ensure_ascii=False))
ok += 1
elapsed = (datetime.now() - start).total_seconds()
rate = ok / elapsed * 60 if elapsed > 0 else 0
print(f" [{ok:4d}/{len(work)}] {rate:5.0f}/min {name:45s} "
f"S={result['sharpe']:6.1f} DD={result['max_drawdown']:7.2%} "
f"WR={result['win_rate']:5.1%} T={result['n_trades']:4d}")
elapsed = (datetime.now() - start).total_seconds()
print(f"\nDONE: ok={ok} skip={skip} fail={fail} in {elapsed:.0f}s")
@@ -1,13 +1,13 @@
"""
Predix Full Data Factor Evaluator - Evaluate factors with FULL 1min data.
NexQuant Full Data Factor Evaluator - Evaluate factors with FULL 1min data.
Evaluates factors using the complete intraday_pv.h5 dataset (2022-2026, ~2.26M rows)
instead of the debug dataset (2024 only, ~371K rows).
Usage:
python predix_full_eval.py --top 100 # Evaluate top 100 factors with full data
python predix_full_eval.py --all # Evaluate all factors
python predix_full_eval.py --parallel 4 # 4 parallel workers
python nexquant_full_eval.py --top 100 # Evaluate top 100 factors with full data
python nexquant_full_eval.py --all # Evaluate all factors
python nexquant_full_eval.py --parallel 4 # 4 parallel workers
"""
import json
@@ -271,7 +271,7 @@ def evaluate_factor_full(factor: FactorInfo, full_data: pd.DataFrame,
import tempfile
import subprocess
with tempfile.TemporaryDirectory(prefix="predix_full_") as tmp_dir:
with tempfile.TemporaryDirectory(prefix="nexquant_full_") as tmp_dir:
ws = Path(tmp_dir)
try:
@@ -628,7 +628,7 @@ def main(
) -> None:
"""Main entry point."""
console.print(Panel(
"[bold cyan]Predix Full Data Factor Evaluator[/bold cyan]\n"
"[bold cyan]NexQuant Full Data Factor Evaluator[/bold cyan]\n"
f"Using FULL 1min data: {FULL_DATA_FILE}",
border_style="cyan",
))
@@ -679,7 +679,7 @@ if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description="Predix Full Data Factor Evaluator"
description="NexQuant Full Data Factor Evaluator"
)
parser.add_argument(
"--top", "-n",
@@ -7,13 +7,13 @@ each with real backtesting on OHLCV data.
Usage:
# Swing trading (96-bar forward returns)
python predix_gen_strategies_real_bt.py 10
python nexquant_gen_strategies_real_bt.py 10
# Daytrading with FTMO constraints (12-bar forward returns)
TRADING_STYLE=daytrading python predix_gen_strategies_real_bt.py 5
TRADING_STYLE=daytrading python nexquant_gen_strategies_real_bt.py 5
# With parallel workers (default: CPU count)
TRADING_STYLE=daytrading WORKERS=4 python predix_gen_strategies_real_bt.py 20
TRADING_STYLE=daytrading WORKERS=4 python nexquant_gen_strategies_real_bt.py 20
"""
import os, sys, json, time, math, random, logging, warnings, subprocess
from pathlib import Path
@@ -42,9 +42,9 @@ except Exception:
# ============================================================================
# Configuration
# ============================================================================
OHLCV_PATH = Path('/home/nico/Predix/git_ignore_folder/factor_implementation_source_data/intraday_pv.h5')
FACTORS_DIR = Path('/home/nico/Predix/results/factors')
STRATEGIES_DIR = Path('/home/nico/Predix/results/strategies_new')
OHLCV_PATH = Path('/home/nico/NexQuant/git_ignore_folder/factor_implementation_source_data/intraday_pv.h5')
FACTORS_DIR = Path('/home/nico/NexQuant/results/factors')
STRATEGIES_DIR = Path('/home/nico/NexQuant/results/strategies_new')
STRATEGIES_DIR.mkdir(parents=True, exist_ok=True)
# Trading style
@@ -632,7 +632,7 @@ def main(target_count=10):
# Generate PDF report
try:
from predix_strategy_report import StrategyPerformanceReporter
from nexquant_strategy_report import StrategyPerformanceReporter
reporter = StrategyPerformanceReporter(strategy)
reporter.generate_report()
except:
@@ -1,16 +1,16 @@
"""
Predix Parallel Runner - Run multiple factor experiments concurrently.
NexQuant Parallel Runner - Run multiple factor experiments concurrently.
Spawns N subprocesses, each running `predix.py quant` with isolated config:
Spawns N subprocesses, each running `nexquant.py quant` with isolated config:
- Separate log files (fin_quant_run1.log, fin_quant_run2.log, etc.)
- Separate result directories (results/runs/run1/, results/runs/run2/, etc.)
- Separate workspace directories
- API key distribution across multiple keys (round-robin)
Usage:
python predix_parallel.py --runs 5 --api-keys 2
python predix_parallel.py --runs 3 --model openrouter
python predix_parallel.py --runs 5 --model local --api-keys 1
python nexquant_parallel.py --runs 5 --api-keys 2
python nexquant_parallel.py --runs 3 --model openrouter
python nexquant_parallel.py --runs 5 --model local --api-keys 1
"""
import os
import signal
@@ -188,7 +188,7 @@ class ParallelRunner:
def _build_command(self, run_state: RunState) -> list[str]:
"""
Build the subprocess command to run predix quant.
Build the subprocess command to run nexquant quant.
Parameters
----------
@@ -202,7 +202,7 @@ class ParallelRunner:
"""
cmd = [
sys.executable, # Use same Python interpreter
str(self.project_root / "predix.py"),
str(self.project_root / "nexquant.py"),
"quant",
"--model", run_state.model,
"--run-id", str(run_state.run_id),
@@ -327,7 +327,7 @@ class ParallelRunner:
# Build summary table
table = Table(
title="🔀 Predix Parallel Run Dashboard",
title="🔀 NexQuant Parallel Run Dashboard",
show_header=True,
header_style="bold cyan",
expand=True,
@@ -399,7 +399,7 @@ class ParallelRunner:
signal.signal(signal.SIGTERM, self._signal_handler)
console.print(f"\n[bold cyan]{'=' * 60}[/bold cyan]")
console.print("[bold cyan]🔀 Predix Parallel Runner[/bold cyan]")
console.print("[bold cyan]🔀 NexQuant Parallel Runner[/bold cyan]")
console.print(f"[bold cyan]{'=' * 60}[/bold cyan]")
console.print(f" Runs: {self.num_runs}")
console.print(f" API Keys: {self.num_api_keys} ({len(self.api_keys)} available)")
@@ -503,7 +503,7 @@ if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description="Predix Parallel Runner - Run multiple factor experiments concurrently",
description="NexQuant Parallel Runner - Run multiple factor experiments concurrently",
)
parser.add_argument(
"--runs", "-n",
+467
View File
@@ -0,0 +1,467 @@
#!/usr/bin/env python
"""
Quick Daytrading Strategy Generator with CORRECT factor alignment.
Uses forward-fill to align daily factors to 1-min frequency,
then runs fast backtests without LLM calls.
Usage:
python nexquant_quick_daytrading.py 5
python nexquant_quick_daytrading.py 10
"""
import json, time, subprocess, tempfile # nosec
from pathlib import Path
import numpy as np
import pandas as pd
from rich.console import Console
console = Console()
STRATEGIES_DIR = Path('results/strategies_new')
STRATEGIES_DIR.mkdir(parents=True, exist_ok=True)
FACTOR_FILES = Path('results/factors')
VALUE_FILES = FACTOR_FILES / 'values'
OHLCV_PATH = Path('git_ignore_folder/factor_implementation_source_data/intraday_pv.h5')
# Best daytrading strategies (12-min horizon, optimized for FTMO)
DAYTRADING_COMBOS = [
{
'name': 'MomentumDivergence12min',
'factors': ['daily_close_return_96', 'daily_session_momentum_divergence_1d'],
'code': '''mom = factors['daily_close_return_96']
div = factors['daily_session_momentum_divergence_1d']
w = 20
mom_z = (mom - mom.rolling(w).mean()) / (mom.rolling(w).std() + 1e-8)
div_z = (div - div.rolling(w).mean()) / (div.rolling(w).std() + 1e-8)
composite = (mom_z - div_z).fillna(0)
signal = pd.Series(0, index=close.index, name='signal')
signal[composite > 0.3] = 1
signal[composite < -0.3] = -1
signal = signal.fillna(0).astype(int)''',
},
{
'name': 'LondonSessionScalp',
'factors': ['london_mom', 'daily_session_momentum_divergence_1d'],
'code': '''mom = factors['london_mom']
div = factors['daily_session_momentum_divergence_1d']
w = 15
mom_z = (mom - mom.rolling(w).mean()) / (mom.rolling(w).std() + 1e-8)
div_z = (div - div.rolling(w).mean()) / (div.rolling(w).std() + 1e-8)
composite = (mom_z - div_z).fillna(0)
signal = pd.Series(0, index=close.index, name='signal')
signal[composite > 0.25] = 1
signal[composite < -0.25] = -1
signal = signal.fillna(0).astype(int)''',
},
{
'name': 'TrendReversionScalp',
'factors': ['daily_ols_slope_96', 'daily_session_momentum_divergence_1d', 'DailyTrendStrength_Raw'],
'code': '''slope = factors['daily_ols_slope_96']
div = factors['daily_session_momentum_divergence_1d']
trend = factors['DailyTrendStrength_Raw']
w = 20
slope_z = (slope - slope.rolling(w).mean()) / (slope.rolling(w).std() + 1e-8)
div_z = (div - div.rolling(w).mean()) / (div.rolling(w).std() + 1e-8)
trend_z = (trend - trend.rolling(w).mean()) / (trend.rolling(w).std() + 1e-8)
composite = (0.5 * slope_z - 0.3 * div_z + 0.2 * trend_z).fillna(0)
signal = pd.Series(0, index=close.index, name='signal')
signal[composite > 0.3] = 1
signal[composite < -0.3] = -1
signal = signal.fillna(0).astype(int)''',
},
{
'name': 'VolAdjMomentum12',
'factors': ['daily_ret_vol_adj_1d', 'daily_session_momentum_divergence_1d', 'DCP'],
'code': '''vol = factors['daily_ret_vol_adj_1d']
div = factors['daily_session_momentum_divergence_1d']
dcp = factors['DCP']
w = 20
vol_z = (vol - vol.rolling(w).mean()) / (vol.rolling(w).std() + 1e-8)
div_z = (div - div.rolling(w).mean()) / (div.rolling(w).std() + 1e-8)
dcp_z = (dcp - dcp.rolling(w).mean()) / (dcp.rolling(w).std() + 1e-8)
composite = (0.5 * vol_z - 0.3 * div_z + 0.2 * dcp_z).fillna(0)
signal = pd.Series(0, index=close.index, name='signal')
signal[composite > 0.35] = 1
signal[composite < -0.35] = -1
signal = signal.fillna(0).astype(int)''',
},
{
'name': 'SessionMeanReversion',
'factors': ['session_momentum_diff', 'daily_norm_body', 'daily_c2c_return'],
'code': '''session = factors['session_momentum_diff']
body = factors['daily_norm_body']
c2c = factors['daily_c2c_return']
w = 15
sess_z = (session - session.rolling(w).mean()) / (session.rolling(w).std() + 1e-8)
body_z = (body - body.rolling(w).mean()) / (body.rolling(w).std() + 1e-8)
c2c_z = (c2c - c2c.rolling(w).mean()) / (c2c.rolling(w).std() + 1e-8)
composite = (0.5 * sess_z + 0.3 * body_z + 0.2 * c2c_z).fillna(0)
signal = pd.Series(0, index=close.index, name='signal')
signal[composite > 0.4] = 1
signal[composite < -0.4] = -1
signal = signal.fillna(0).astype(int)''',
},
{
'name': 'MomentumContinuation',
'factors': ['daily_mom', 'daily_ret_1d', 'momentum_1d'],
'code': '''mom = factors['daily_mom']
ret = factors['daily_ret_1d']
mom2 = factors['momentum_1d']
w = 12
mom_z = (mom - mom.rolling(w).mean()) / (mom.rolling(w).std() + 1e-8)
ret_z = (ret - ret.rolling(w).mean()) / (ret.rolling(w).std() + 1e-8)
mom2_z = (mom2 - mom2.rolling(w).mean()) / (mom2.rolling(w).std() + 1e-8)
composite = (0.4 * mom_z + 0.3 * ret_z + 0.3 * mom2_z).fillna(0)
signal = pd.Series(0, index=close.index, name='signal')
signal[composite > 0.2] = 1
signal[composite < -0.2] = -1
signal = signal.fillna(0).astype(int)''',
},
{
'name': 'HighFreqScalper',
'factors': ['daily_close_return_96', 'DCP', 'london_mom'],
'code': '''close_ret = factors['daily_close_return_96']
dcp = factors['DCP']
london = factors['london_mom']
w = 10
cr_z = (close_ret - close_ret.rolling(w).mean()) / (close_ret.rolling(w).std() + 1e-8)
dcp_z = (dcp - dcp.rolling(w).mean()) / (dcp.rolling(w).std() + 1e-8)
lon_z = (london - london.rolling(w).mean()) / (london.rolling(w).std() + 1e-8)
composite = (0.4 * cr_z + 0.3 * dcp_z + 0.3 * lon_z).fillna(0)
signal = pd.Series(0, index=close.index, name='signal')
signal[composite > 0.25] = 1
signal[composite < -0.25] = -1
signal = signal.fillna(0).astype(int)''',
},
{
'name': 'AdaptiveMomentumMR',
'factors': ['daily_close_return_96', 'daily_session_momentum_divergence_1d', 'daily_ols_slope_96'],
'code': '''mom = factors['daily_close_return_96']
div = factors['daily_session_momentum_divergence_1d']
slope = factors['daily_ols_slope_96']
w = 20
mom_z = (mom - mom.rolling(w).mean()) / (mom.rolling(w).std() + 1e-8)
div_z = (div - div.rolling(w).mean()) / (div.rolling(w).std() + 1e-8)
slope_z = (slope - slope.rolling(w).mean()) / (slope.rolling(w).std() + 1e-8)
# Regime detection: high momentum = trend, low = mean reversion
regime = (mom_z.abs() > 1.0).astype(float)
composite = (regime * mom_z + (1 - regime) * (-div_z) + 0.3 * slope_z).fillna(0)
signal = pd.Series(0, index=close.index, name='signal')
signal[composite > 0.4] = 1
signal[composite < -0.4] = -1
signal = signal.fillna(0).astype(int)''',
},
{
'name': 'TrendPullbackScalp',
'factors': ['daily_close_return_96', 'daily_session_momentum_divergence_1d', 'daily_norm_body'],
'code': '''mom = factors['daily_close_return_96']
div = factors['daily_session_momentum_divergence_1d']
body = factors['daily_norm_body']
w = 15
mom_z = (mom - mom.rolling(w).mean()) / (mom.rolling(w).std() + 1e-8)
div_z = (div - div.rolling(w).mean()) / (div.rolling(w).std() + 1e-8)
body_z = (body - body.rolling(w).mean()) / (body.rolling(w).std() + 1e-8)
# Enter on pullbacks (divergence against trend)
composite = (mom_z - 0.5 * div_z * mom_z.sign() + 0.2 * body_z).fillna(0)
signal = pd.Series(0, index=close.index, name='signal')
signal[composite > 0.35] = 1
signal[composite < -0.35] = -1
signal = signal.fillna(0).astype(int)''',
},
{
'name': 'IntradayMomentumBlend',
'factors': ['daily_close_return_96', 'london_mom', 'daily_session_momentum_divergence_1d', 'DCP'],
'code': '''mom = factors['daily_close_return_96']
lon = factors['london_mom']
div = factors['daily_session_momentum_divergence_1d']
dcp = factors['DCP']
w = 20
mom_z = (mom - mom.rolling(w).mean()) / (mom.rolling(w).std() + 1e-8)
lon_z = (lon - lon.rolling(w).mean()) / (lon.rolling(w).std() + 1e-8)
div_z = (div - div.rolling(w).mean()) / (div.rolling(w).std() + 1e-8)
dcp_z = (dcp - dcp.rolling(w).mean()) / (dcp.rolling(w).std() + 1e-8)
composite = (0.3 * mom_z + 0.3 * lon_z - 0.2 * div_z + 0.2 * dcp_z).fillna(0)
signal = pd.Series(0, index=close.index, name='signal')
signal[composite > 0.3] = 1
signal[composite < -0.3] = -1
signal = signal.fillna(0).astype(int)''',
},
]
def load_factor_series(name):
"""Load factor parquet and return as Series with correct index."""
safe = name.replace('/','_').replace('\\','_')[:150]
pf = VALUE_FILES / f"{safe}.parquet"
if not pf.exists():
return None
df = pd.read_parquet(str(pf))
# Extract EURUSD
if df.index.names == ['datetime', 'instrument']:
df_reset = df.reset_index()
if 'instrument' in df_reset.columns:
df_eur = df_reset[df_reset['instrument'] == 'EURUSD'].copy()
df_eur = df_eur.set_index('datetime')
series = df_eur.iloc[:, -1] # Last column is the factor value
series.name = name
return series
# If single index, just return first column
series = df.iloc[:, 0]
series.name = name
return series
def main(n_strategies=5):
console.print("[bold cyan]🎯 Daytrading Strategy Generator (Quick Mode)[/bold cyan]\n")
console.print(" Style: 12-minute forward returns")
console.print(" Target: FTMO compliant (IC>0.02, Sharpe>0.5, Trades>20, DD>-10%)\n")
# Load OHLCV data
if not OHLCV_PATH.exists():
console.print(f"[red]✗ OHLCV data not found: {OHLCV_PATH}[/red]")
return
ohlcv = pd.read_hdf(str(OHLCV_PATH), key='data')
# Extract close prices with datetime-only index (not MultiIndex)
if '$close' in ohlcv.columns:
close = ohlcv['$close'].dropna()
elif 'close' in ohlcv.columns:
close = ohlcv['close'].dropna()
else:
close = ohlcv.select_dtypes(include=[np.number]).iloc[:, 0].dropna()
# Extract datetime from MultiIndex if present
if isinstance(close.index, pd.MultiIndex):
close_dt_idx = close.index.get_level_values('datetime')
close_series = pd.Series(close.values, index=close_dt_idx, name='close')
else:
close_series = close
close_series = close_series.dropna()
console.print(f"[green]✓[/green] Loaded {len(close_series):,} OHLCV bars")
# Load all factor series and align to close index
all_factor_series = {}
for combo in DAYTRADING_COMBOS:
for factor_name in combo['factors']:
if factor_name in all_factor_series:
continue
series = load_factor_series(factor_name)
if series is not None:
# Forward fill to match close frequency
series_ff = series.reindex(close_series.index).ffill()
all_factor_series[factor_name] = series_ff
# Create factors DataFrame
df_factors = pd.DataFrame(all_factor_series)
df_factors = df_factors.dropna(how='all')
console.print(f"[green]✓[/green] Loaded {len(df_factors.columns)} factor series")
console.print(f"[green]✓[/green] Aligned to {len(df_factors):,} bars\n")
accepted = []
for i, combo in enumerate(DAYTRADING_COMBOS[:n_strategies]):
console.print(f"[{i+1}/{n_strategies}] Testing {combo['name']}...")
# Build factor dataframe
valid_factors = [f for f in combo['factors'] if f in df_factors.columns]
if len(valid_factors) < 2:
console.print(f" ✗ Not enough valid factors")
continue
strat_factors = df_factors[valid_factors].dropna()
if len(strat_factors) < 1000:
console.print(f" ✗ Not enough data: {len(strat_factors)} bars")
continue
# Build backtest script
forward_bars = 12
strategy_code = combo['code']
script = f"""
import pandas as pd
import numpy as np
import json
close = pd.read_pickle('close.pkl') # nosec
factors = pd.read_pickle('factors.pkl') # nosec
# Execute strategy
try:
{chr(10).join(' ' + l for l in strategy_code.split(chr(10)))}
except Exception as e:
print(f"ERROR: {{e}}")
exit(1)
if 'signal' not in dir():
print("ERROR: No signal generated")
exit(1)
signal = signal.fillna(0)
# Align
common_idx = close.index.intersection(signal.index)
close = close.loc[common_idx]
signal = signal.loc[common_idx]
# Forward returns (12-min horizon for daytrading)
FORWARD_BARS = {forward_bars}
returns_fwd = close.pct_change(FORWARD_BARS).shift(-FORWARD_BARS)
signal_aligned = signal.loc[returns_fwd.dropna().index]
fwd_returns = returns_fwd.loc[signal_aligned.index]
if len(signal_aligned) < 100 or len(fwd_returns) < 100:
print("ERROR: Not enough data")
exit(1)
# Metrics
ic = signal_aligned.corr(fwd_returns)
strategy_returns = signal_aligned * fwd_returns
sharpe = strategy_returns.mean() / strategy_returns.std() * np.sqrt(252 * 1440 / {forward_bars}) if strategy_returns.std() > 0 else 0
cum = (1 + strategy_returns).cumprod()
running_max = cum.expanding().max()
drawdown = (cum - running_max) / running_max.replace(0, np.nan)
max_dd = drawdown.min() if len(drawdown) > 0 else 0
win_rate = (strategy_returns > 0).sum() / len(strategy_returns) if len(strategy_returns) > 0 else 0
n_trades = int((signal_aligned != signal_aligned.shift(1)).sum())
total_return = cum.iloc[-1] - 1
n_bars = len(strategy_returns)
n_months = n_bars / (252 * 1440 / {forward_bars} / 12) if n_bars > 0 else 1
monthly_return = (1 + total_return) ** (1 / n_months) - 1 if n_months > 0 and (1 + total_return) > 0 else total_return
result = {{
"status": "success",
"sharpe": float(sharpe),
"max_drawdown": float(max_dd) if not np.isnan(max_dd) else -0.20,
"win_rate": float(win_rate),
"ic": float(ic) if not np.isnan(ic) else 0,
"n_trades": n_trades,
"total_return": float(total_return),
"monthly_return_pct": float(monthly_return * 100),
"n_bars": int(n_bars),
"n_months": float(n_months),
"signal_long": int((signal_aligned == 1).sum()),
"signal_short": int((signal_aligned == -1).sum()),
"signal_neutral": int((signal_aligned == 0).sum()),
}}
print(json.dumps(result))
"""
# Run backtest
import tempfile
with tempfile.TemporaryDirectory() as td:
tdp = Path(td)
strat_close = close_series.loc[strat_factors.index]
strat_close.to_pickle(str(tdp / 'close.pkl')) # nosec
strat_factors.to_pickle(str(tdp / 'factors.pkl')) # nosec
script_path = tdp / 'run.py'
script_path.write_text(script)
try:
result_proc = subprocess.run( # nosec B603
[sys.executable, str(script_path)],
capture_output=True, text=True, timeout=60,
cwd=str(tdp)
)
if result_proc.returncode != 0:
console.print(f" ✗ Failed: {result_proc.stderr[:200]}")
continue
result = None
for line in result_proc.stdout.strip().split('\n'):
try:
result = json.loads(line)
break
except:
continue
if not result or result.get('status') != 'success':
console.print(f" ✗ Invalid result")
continue
except subprocess.TimeoutExpired: # nosec
console.print(f" ✗ Timeout")
continue
except Exception as e:
console.print(f" ✗ Error: {e}")
continue
ic = result.get('ic', 0)
sharpe = result.get('sharpe', 0)
trades = result.get('n_trades', 0)
dd = result.get('max_drawdown', 0)
# FTMO criteria
if abs(ic) > 0.02 and sharpe > 0.5 and trades > 20 and dd > -0.10:
strategy = {
'strategy_name': combo['name'],
'factor_names': combo['factors'],
'description': f"Daytrading strategy combining {', '.join(combo['factors'])}",
'code': combo['code'],
'real_backtest': result,
'metrics': result,
'summary': {
'sharpe': sharpe,
'max_drawdown': dd,
'win_rate': result.get('win_rate', 0),
'monthly_return_pct': result.get('monthly_return_pct', 0),
'real_ic': ic,
'real_n_trades': trades,
'forward_bars': 12,
'trading_style': 'daytrading',
}
}
fname = f"{int(time.time())}_{combo['name']}.json"
with open(STRATEGIES_DIR / fname, 'w') as f:
json.dump(strategy, f, indent=2, ensure_ascii=False)
accepted.append(strategy)
console.print(f" ✓ [green]ACCEPT[/green]: IC={ic:.4f}, Sharpe={sharpe:.2f}, Trades={trades}, DD={dd:.1%}")
else:
console.print(f" ✗ [red]REJECT[/red]: IC={ic:.4f}, Sharpe={sharpe:.2f}, Trades={trades}, DD={dd:.1%}")
console.print(f"\n[bold green]✓ {len(accepted)}/{n_strategies} strategies accepted[/bold green]\n")
if accepted:
console.print("[bold]Results:[/bold]")
for s in accepted:
bt = s['real_backtest']
console.print(f"{s['strategy_name']:30s} IC={bt['ic']:.4f} Sharpe={bt['sharpe']:.2f} "
f"Monthly={bt['monthly_return_pct']:.2f}% Trades={bt['n_trades']}")
if __name__ == '__main__':
import sys
n = int(sys.argv[1]) if len(sys.argv) > 1 else 5
main(n)
+111
View File
@@ -0,0 +1,111 @@
#!/usr/bin/env python
"""One strategy runner — standalone, called from parent script."""
import json, sys, pandas as pd, subprocess, tempfile, numpy as np
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from rdagent.components.backtesting.vbt_backtest import backtest_signal
if len(sys.argv) < 2:
print("Usage: python nexquant_rebacktest_one.py <strategy_json_path>")
sys.exit(1)
strat_path = Path(sys.argv[1])
data = json.loads(strat_path.read_text())
OHLCV = Path("git_ignore_folder/factor_implementation_source_data/intraday_pv.h5")
FACTORS_DIR = Path("results/factors/values")
fmap = {p.stem: str(p) for p in FACTORS_DIR.glob("*.parquet")}
names = data.get("factor_names", [])
code = data.get("code", "")
name = data.get("strategy_name", strat_path.stem)
if not names or not code:
print(json.dumps({"status": "skipped", "reason": "no factors/code"}))
sys.exit(0)
# Load close
ohlcv = pd.read_hdf(str(OHLCV), key="data")
close = ohlcv["$close"].dropna()
if isinstance(close.index, pd.MultiIndex):
close = close.droplevel(-1)
close = close.astype(float).sort_index()
# Load factors
series = {}
for fn in names:
fp = fmap.get(fn) or fmap.get(fn.replace("/", "_")[:150])
if fp:
try:
s = pd.read_parquet(fp).iloc[:, 0]
series[fn] = s
except Exception:
pass
if len(series) < 2:
print(json.dumps({"status": "skipped", "reason": f"only {len(series)} factors loaded"}))
sys.exit(0)
df = pd.DataFrame(series).sort_index()
if isinstance(df.index, pd.MultiIndex):
df = df.droplevel(-1)
df_1m = df.reindex(close.index).ffill()
valid = df_1m.notna().any(axis=1)
if valid.sum() < 1000:
print(json.dumps({"status": "skipped", "reason": f"only {valid.sum()} valid bars"}))
sys.exit(0)
ca = close.loc[valid]
fa = df_1m.loc[valid]
# Execute
try:
with tempfile.TemporaryDirectory() as td:
tdp = Path(td)
fa.to_parquet(str(tdp / "factors.parquet"))
ca.to_pickle(str(tdp / "close.pkl"))
exec_script = (
"import sys, os\n"
"sys.stdout = open(os.devnull, 'w')\n"
"sys.stderr = open(os.devnull, 'w')\n"
"import pandas as pd, numpy as np\n"
"factors = pd.read_parquet('factors.parquet')\n"
"close = pd.read_pickle('close.pkl')\n"
"df = factors\n"
+ code +
"\nif 'signal' not in dir():\n"
" raise SystemExit(1)\n"
"pd.Series(signal).fillna(0).to_pickle('signal.pkl')\n"
)
(tdp / "run.py").write_text(exec_script)
r = subprocess.run(
["python", "run.py"],
capture_output=True, text=True, timeout=60, cwd=str(tdp),
stdin=subprocess.DEVNULL,
)
if r.returncode != 0:
print(json.dumps({"status": "code_failed", "stderr": r.stderr[:500]}))
sys.exit(1)
sig = pd.read_pickle(tdp / "signal.pkl")
except Exception as e:
print(json.dumps({"status": "code_failed", "error": str(e)[:500]}))
sys.exit(1)
sig = sig.reindex(ca.index).ffill().fillna(0)
result = backtest_signal(ca, sig, txn_cost_bps=2.14)
# Return result as JSON
output = {
"status": "ok",
"sharpe": result.get("sharpe"),
"max_drawdown": result.get("max_drawdown"),
"win_rate": result.get("win_rate"),
"n_trades": result.get("n_trades"),
"total_return": result.get("total_return"),
"monthly_return_pct": result.get("monthly_return_pct"),
"annualized_return": result.get("annualized_return"),
}
print(json.dumps(output))
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env python
"""Parent orchestrator: calls nexquant_rebacktest_one.py for each strategy."""
import json, subprocess, sys
from pathlib import Path
from datetime import datetime
STRAT_DIR = Path("results/strategies_new")
# Build work list
work = []
for f in sorted(STRAT_DIR.glob("*.json")):
if "verified_v2" in f.read_text():
continue
try:
d = json.loads(f.read_text())
except Exception:
continue
if d.get("factor_names") and d.get("code"):
work.append(f)
print(f"{len(work)} strategies to re-backtest", flush=True)
ok = skip = fail = 0
start = datetime.now()
for i, f in enumerate(work):
name = f.stem[:45]
print(f"[{i+1}/{len(work)}] {name} ...", end=" ", flush=True)
try:
r = subprocess.run(
["timeout", "-s", "KILL", "90", "python", "scripts/nexquant_rebacktest_one.py", str(f)],
capture_output=True, text=True, timeout=120,
stdin=subprocess.DEVNULL,
)
result = json.loads(r.stdout.strip() or "{}")
except subprocess.TimeoutExpired:
print("TIMEOUT", flush=True)
fail += 1
continue
except Exception as e:
print(f"ERROR: {e}", flush=True)
fail += 1
continue
if result.get("status") == "ok":
data = json.loads(f.read_text())
data["reevaluation_status"] = "verified_v2"
data["sharpe_ratio"] = result.get("sharpe")
data["max_drawdown"] = result.get("max_drawdown")
data["win_rate"] = result.get("win_rate")
data["total_return"] = result.get("total_return")
data["summary"] = {
**data.get("summary", {}),
"sharpe": result.get("sharpe"),
"max_drawdown": result.get("max_drawdown"),
"win_rate": result.get("win_rate"),
"monthly_return_pct": result.get("monthly_return_pct"),
"real_n_trades": result.get("n_trades"),
"total_return": result.get("total_return"),
"annualized_return": result.get("annualized_return"),
"engine": "verified_v2",
"txn_cost_bps": 2.14,
}
f.write_text(json.dumps(data, indent=2, ensure_ascii=False))
ok += 1
print(f"S={result['sharpe']:.1f} DD={result['max_drawdown']:.2%} WR={result['win_rate']:.1%} T={result['n_trades']}", flush=True)
elif result.get("status") == "skipped":
skip += 1
print(f"SKIP: {result.get('reason', '?')}", flush=True)
else:
fail += 1
print(f"FAIL: {result.get('stderr', result.get('error', '?'))[:100]}", flush=True)
elapsed = (datetime.now() - start).total_seconds()
print(f"\nDONE: ok={ok} skip={skip} fail={fail} in {elapsed:.0f}s", flush=True)
@@ -116,8 +116,8 @@ except Exception as e:
"n_short":int((sig==-1).sum()), "n_neutral":int((sig==0).sum())}
def main(count=None):
sdir = Path('/home/nico/Predix/results/strategies')
vdir = Path('/home/nico/Predix/results/factors/values')
sdir = Path('/home/nico/NexQuant/results/strategies')
vdir = Path('/home/nico/NexQuant/results/factors/values')
files = []
for f in sorted(sdir.glob('*.json'), reverse=True):
@@ -13,9 +13,9 @@ For every strategy JSON in results/strategies_new (or a user-supplied dir):
Does NOT mutate the strategy JSON files read-only comparison.
Usage:
python scripts/predix_rebacktest_unified.py # all strategies
python scripts/predix_rebacktest_unified.py 50 # first 50
python scripts/predix_rebacktest_unified.py 50 --csv report.csv
python scripts/nexquant_rebacktest_unified.py # all strategies
python scripts/nexquant_rebacktest_unified.py 50 # first 50
python scripts/nexquant_rebacktest_unified.py 50 --csv report.csv
"""
from __future__ import annotations
@@ -38,9 +38,9 @@ from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn, TimeEl
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from rdagent.components.backtesting.vbt_backtest import backtest_signal_ftmo # noqa: E402
OHLCV_PATH = Path("/home/nico/Predix/git_ignore_folder/factor_implementation_source_data/intraday_pv.h5")
FACTORS_VALUES_DIR = Path("/home/nico/Predix/results/factors/values")
STRATEGIES_DIR = Path("/home/nico/Predix/results/strategies_new")
OHLCV_PATH = Path("/home/nico/NexQuant/git_ignore_folder/factor_implementation_source_data/intraday_pv.h5")
FACTORS_VALUES_DIR = Path("/home/nico/NexQuant/results/factors/values")
STRATEGIES_DIR = Path("/home/nico/NexQuant/results/strategies_new")
# ── Logging setup: everything printed goes to log file + stdout ───────────────
_LOG_DIR = Path(__file__).resolve().parent.parent / "git_ignore_folder" / "logs"
@@ -1,13 +1,13 @@
"""
Predix Simple Factor Evaluator - Direct IC/Sharpe computation.
NexQuant Simple Factor Evaluator - Direct IC/Sharpe computation.
Evaluates existing factor results by computing IC and Sharpe directly
from factor values and forward returns, without Qlib infrastructure.
Usage:
python predix_simple_eval.py --top 100 # Evaluate top 100 factors
python predix_simple_eval.py --all # Evaluate all
python predix_simple_eval.py --parallel 4 # 4 parallel workers
python nexquant_simple_eval.py --top 100 # Evaluate top 100 factors
python nexquant_simple_eval.py --all # Evaluate all
python nexquant_simple_eval.py --parallel 4 # 4 parallel workers
"""
import json
@@ -421,7 +421,7 @@ def main(
) -> None:
"""Main entry point."""
console.print(Panel(
"[bold cyan]Predix Simple Factor Evaluator[/bold cyan]\n"
"[bold cyan]NexQuant Simple Factor Evaluator[/bold cyan]\n"
f"Scanning workspaces for generated factors...",
border_style="cyan",
))
@@ -467,7 +467,7 @@ if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description="Predix Simple Factor Evaluator - Direct IC/Sharpe computation"
description="NexQuant Simple Factor Evaluator - Direct IC/Sharpe computation"
)
parser.add_argument(
"--top", "-n",
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
#!/usr/bin/env python
"""
Strategy Performance Report Generator for Predix.
Strategy Performance Report Generator for NexQuant.
Generates detailed PDF reports with charts for each accepted strategy.
@@ -11,8 +11,8 @@ Features:
- Full metrics table and strategy code
Usage:
python predix_strategy_report.py # All strategies
python predix_strategy_report.py results/strategies_new/123.json # Single strategy
python nexquant_strategy_report.py # All strategies
python nexquant_strategy_report.py results/strategies_new/123.json # Single strategy
"""
import os, sys, json, warnings
from pathlib import Path
@@ -39,8 +39,8 @@ from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
warnings.filterwarnings('ignore')
# Config
OHLCV_PATH = Path('/home/nico/Predix/git_ignore_folder/factor_implementation_source_data/intraday_pv.h5')
REPORTS_DIR = Path('/home/nico/Predix/results/strategy_reports')
OHLCV_PATH = Path('/home/nico/NexQuant/git_ignore_folder/factor_implementation_source_data/intraday_pv.h5')
REPORTS_DIR = Path('/home/nico/NexQuant/results/strategy_reports')
REPORTS_DIR.mkdir(parents=True, exist_ok=True)
# Colors
@@ -226,7 +226,7 @@ class StrategyPerformanceReporter:
def _gen_pdf_report(self, pdf_path):
doc = SimpleDocTemplate(str(pdf_path), pagesize=A4,
title=f"Predix: {self.name}", author="Predix AI",
title=f"NexQuant: {self.name}", author="NexQuant AI",
leftMargin=2*cm, rightMargin=2*cm, topMargin=2*cm, bottomMargin=2*cm)
styles = getSampleStyleSheet()
styles.add(ParagraphStyle(name='PTitle', fontName='Helvetica-Bold', fontSize=22, leading=26, alignment=TA_CENTER, textColor=colors.HexColor('#1A237E')))
@@ -324,7 +324,7 @@ def generate_report_for_strategy(path: str) -> dict:
def generate_all_reports():
d = Path('/home/nico/Predix/results/strategies_new')
d = Path('/home/nico/NexQuant/results/strategies_new')
if not d.exists(): print("No strategies."); return
for jf in sorted(d.glob('*.json')):
try:
+1 -1
View File
@@ -14,7 +14,7 @@ FTMO 100k rules enforced:
Out-of-sample window: 2024-01-01 onwards (never seen during factor research).
Usage:
conda activate predix
conda activate nexquant
python scripts/realistic_backtest_all.py
python scripts/realistic_backtest_all.py --target-monthly 4.0 --min-trades 50
python scripts/realistic_backtest_all.py --workers 8
+2 -2
View File
@@ -1,5 +1,5 @@
#!/bin/bash
# Run all Predix integration tests
# Run all NexQuant integration tests
# Usage:
# ./scripts/run_all_tests.sh # Full test suite
# ./scripts/run_all_tests.sh --quick # Skip slow tests
@@ -12,7 +12,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
echo "========================================="
echo "Predix Integration Test Suite"
echo "NexQuant Integration Test Suite"
echo "========================================="
echo "Project: $PROJECT_ROOT"
echo "Date: $(date '+%Y-%m-%d %H:%M:%S')"
+5 -5
View File
@@ -4,11 +4,11 @@
# Restarts automatically on crash, generates strategies continuously.
# ============================================================================
SCRIPT_DIR="/home/nico/Predix"
GENERATOR="python ${SCRIPT_DIR}/predix_smart_strategy_gen.py"
SCRIPT_DIR="/home/nico/NexQuant"
GENERATOR="python ${SCRIPT_DIR}/nexquant_smart_strategy_gen.py"
TARGET_COUNT=3
LOGFILE="${SCRIPT_DIR}/results/logs/generator_loop.log"
PIDFILE="/tmp/predix_loop.pid"
PIDFILE="/tmp/nexquant_loop.pid"
echo $$ > "$PIDFILE"
mkdir -p "${SCRIPT_DIR}/results/logs"
@@ -19,7 +19,7 @@ log() {
cleanup() {
log "Received termination signal. Cleaning up..."
pkill -f "predix_smart_strategy_gen.py" 2>/dev/null
pkill -f "nexquant_smart_strategy_gen.py" 2>/dev/null
rm -f "$PIDFILE"
log "Cleanup complete. Exiting."
exit 0
@@ -53,7 +53,7 @@ while true; do
log "📁 Existing strategies: ${STRAT_COUNT}"
# Kill any stale processes
pkill -9 -f "predix_smart_strategy_gen.py" 2>/dev/null
pkill -9 -f "nexquant_smart_strategy_gen.py" 2>/dev/null
sleep 2
# Start generator
+6 -6
View File
@@ -4,13 +4,13 @@
# Checks every 20min: is the generator running? If not, (re)start it.
# ============================================================================
SCRIPT_DIR="/home/nico/Predix"
GENERATOR="python ${SCRIPT_DIR}/predix_smart_strategy_gen.py"
SCRIPT_DIR="/home/nico/NexQuant"
GENERATOR="python ${SCRIPT_DIR}/nexquant_smart_strategy_gen.py"
TARGET_COUNT=3
LOGFILE="${SCRIPT_DIR}/results/logs/watchdog.log"
LOCKFILE="/tmp/predix_generator.lock"
LOCKFILE="/tmp/nexquant_generator.lock"
MAX_ATTEMPTS=50 # Stop after this many attempts
PIDFILE="/tmp/predix_generator_attempt.pid"
PIDFILE="/tmp/nexquant_generator_attempt.pid"
mkdir -p "${SCRIPT_DIR}/results/logs"
@@ -51,7 +51,7 @@ check_progress() {
# Kill any existing generator processes
cleanup() {
pkill -9 -f "predix_smart_strategy_gen.py" 2>/dev/null
pkill -9 -f "nexquant_smart_strategy_gen.py" 2>/dev/null
rm -f "$LOCKFILE"
log "Cleaned up old processes"
}
@@ -63,7 +63,7 @@ if [ "$(get_attempt_count)" -ge "$MAX_ATTEMPTS" ]; then
fi
# Check if generator is running
if pgrep -f "predix_smart_strategy_gen.py" > /dev/null 2>&1; then
if pgrep -f "nexquant_smart_strategy_gen.py" > /dev/null 2>&1; then
# Check if it's making progress
if check_progress; then
log "Generator is running and making progress. Exiting."