mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-27 15:37:44 +00:00
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:
@@ -26,7 +26,7 @@ Die Pakete sind in `requirements.txt` enthalten.
|
||||
### Alle Tests ausführen
|
||||
|
||||
```bash
|
||||
cd /home/nico/Predix
|
||||
cd /home/nico/NexQuant
|
||||
pytest test/backtesting/
|
||||
```
|
||||
|
||||
@@ -226,8 +226,8 @@ Für GitHub Actions oder andere CI/CD-Systeme:
|
||||
|
||||
```bash
|
||||
# Stelle sicher dass du im Projekt-Verzeichnis bist
|
||||
cd /home/nico/Predix
|
||||
export PYTHONPATH=/home/nico/Predix:$PYTHONPATH
|
||||
cd /home/nico/NexQuant
|
||||
export PYTHONPATH=/home/nico/NexQuant:$PYTHONPATH
|
||||
pytest test/backtesting/
|
||||
```
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
"""Predix Backtesting Test Package"""
|
||||
"""NexQuant Backtesting Test Package"""
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Predix Backtesting Test Fixtures
|
||||
NexQuant Backtesting Test Fixtures
|
||||
Wiederverwendbare Test-Daten und Fixtures für alle Backtesting-Tests
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@@ -25,8 +25,8 @@ def _make_ohlcv(n: int = 600, freq: str = "1min") -> pd.DataFrame:
|
||||
}, index=idx)
|
||||
|
||||
|
||||
def _make_predix_hdf5(tmp_path: Path, n: int = 300) -> Path:
|
||||
"""Write a minimal Predix-format HDF5 file and return its path."""
|
||||
def _make_nexquant_hdf5(tmp_path: Path, n: int = 300) -> Path:
|
||||
"""Write a minimal NexQuant-format HDF5 file and return its path."""
|
||||
idx = pd.MultiIndex.from_arrays(
|
||||
[pd.date_range("2024-01-01", periods=n, freq="1min"), ["EURUSD"] * n],
|
||||
names=["datetime", "instrument"],
|
||||
@@ -63,12 +63,12 @@ def _make_mock_adapter():
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Unit tests: _ohlcv_from_predix
|
||||
# Unit tests: _ohlcv_from_nexquant
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestOhlcvConversion:
|
||||
def test_renames_dollar_columns(self):
|
||||
from rdagent.components.coder.kronos_adapter import _ohlcv_from_predix
|
||||
from rdagent.components.coder.kronos_adapter import _ohlcv_from_nexquant
|
||||
idx = pd.MultiIndex.from_arrays(
|
||||
[pd.date_range("2024-01-01", periods=3, freq="1min"), ["EURUSD"] * 3],
|
||||
names=["datetime", "instrument"],
|
||||
@@ -78,17 +78,17 @@ class TestOhlcvConversion:
|
||||
"$low": [1.05, 1.15, 1.25], "$close": [1.12, 1.22, 1.32],
|
||||
"$volume": [100.0, 200.0, 300.0],
|
||||
}, index=idx)
|
||||
result = _ohlcv_from_predix(df)
|
||||
result = _ohlcv_from_nexquant(df)
|
||||
assert list(result.columns) == ["open", "high", "low", "close", "volume"]
|
||||
|
||||
def test_no_dollar_columns_passthrough(self):
|
||||
from rdagent.components.coder.kronos_adapter import _ohlcv_from_predix
|
||||
from rdagent.components.coder.kronos_adapter import _ohlcv_from_nexquant
|
||||
df = pd.DataFrame({"open": [1.0], "close": [1.1], "high": [1.2], "low": [0.9], "volume": [100.0]})
|
||||
result = _ohlcv_from_predix(df)
|
||||
result = _ohlcv_from_nexquant(df)
|
||||
assert "close" in result.columns
|
||||
|
||||
def test_output_is_float64(self):
|
||||
from rdagent.components.coder.kronos_adapter import _ohlcv_from_predix
|
||||
from rdagent.components.coder.kronos_adapter import _ohlcv_from_nexquant
|
||||
df = pd.DataFrame({
|
||||
"$open": np.array([1.1], dtype="float32"),
|
||||
"$close": np.array([1.1], dtype="float32"),
|
||||
@@ -96,7 +96,7 @@ class TestOhlcvConversion:
|
||||
"$low": np.array([1.1], dtype="float32"),
|
||||
"$volume": np.array([100.0], dtype="float32"),
|
||||
})
|
||||
result = _ohlcv_from_predix(df)
|
||||
result = _ohlcv_from_nexquant(df)
|
||||
assert result["close"].dtype == np.float64
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ class TestBuildKronosFactor:
|
||||
def test_output_has_correct_multiindex(self, tmp_path, monkeypatch):
|
||||
import rdagent.components.coder.kronos_adapter as mod
|
||||
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: _make_mock_adapter())
|
||||
h5 = _make_predix_hdf5(tmp_path)
|
||||
h5 = _make_nexquant_hdf5(tmp_path)
|
||||
result = mod.build_kronos_factor(h5, context_bars=100, pred_bars=20, stride_bars=20, device="cpu")
|
||||
assert result.index.names == ["datetime", "instrument"]
|
||||
assert result.index.nlevels == 2
|
||||
@@ -142,14 +142,14 @@ class TestBuildKronosFactor:
|
||||
def test_output_column_name(self, tmp_path, monkeypatch):
|
||||
import rdagent.components.coder.kronos_adapter as mod
|
||||
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: _make_mock_adapter())
|
||||
h5 = _make_predix_hdf5(tmp_path)
|
||||
h5 = _make_nexquant_hdf5(tmp_path)
|
||||
result = mod.build_kronos_factor(h5, context_bars=100, pred_bars=20, stride_bars=20, device="cpu")
|
||||
assert "KronosPredReturn" in result.columns
|
||||
|
||||
def test_output_has_non_nan_values(self, tmp_path, monkeypatch):
|
||||
import rdagent.components.coder.kronos_adapter as mod
|
||||
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: _make_mock_adapter())
|
||||
h5 = _make_predix_hdf5(tmp_path)
|
||||
h5 = _make_nexquant_hdf5(tmp_path)
|
||||
result = mod.build_kronos_factor(h5, context_bars=100, pred_bars=20, stride_bars=20, device="cpu")
|
||||
assert result["KronosPredReturn"].notna().sum() > 0
|
||||
|
||||
@@ -157,7 +157,7 @@ class TestBuildKronosFactor:
|
||||
import rdagent.components.coder.kronos_adapter as mod
|
||||
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: _make_mock_adapter())
|
||||
n = 300
|
||||
h5 = _make_predix_hdf5(tmp_path, n=n)
|
||||
h5 = _make_nexquant_hdf5(tmp_path, n=n)
|
||||
result = mod.build_kronos_factor(h5, context_bars=100, pred_bars=20, stride_bars=20, device="cpu")
|
||||
assert len(result) == n
|
||||
|
||||
@@ -165,7 +165,7 @@ class TestBuildKronosFactor:
|
||||
"""Values within a predicted window should be forward-filled, not NaN."""
|
||||
import rdagent.components.coder.kronos_adapter as mod
|
||||
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: _make_mock_adapter())
|
||||
h5 = _make_predix_hdf5(tmp_path, n=300)
|
||||
h5 = _make_nexquant_hdf5(tmp_path, n=300)
|
||||
result = mod.build_kronos_factor(h5, context_bars=100, pred_bars=20, stride_bars=20, device="cpu")
|
||||
non_nan_ratio = result["KronosPredReturn"].notna().mean()
|
||||
assert non_nan_ratio > 0.5, f"Expected >50% non-NaN, got {non_nan_ratio:.2%}"
|
||||
@@ -185,7 +185,7 @@ class TestEvaluateKronosModel:
|
||||
def test_returns_required_keys(self, tmp_path, monkeypatch):
|
||||
import rdagent.components.coder.kronos_adapter as mod
|
||||
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: _make_mock_adapter())
|
||||
h5 = _make_predix_hdf5(tmp_path, n=400)
|
||||
h5 = _make_nexquant_hdf5(tmp_path, n=400)
|
||||
metrics = mod.evaluate_kronos_model(h5, context_bars=100, pred_bars=20, stride_bars=20, device="cpu")
|
||||
for key in ["IC_mean", "IC_std", "IC_IR", "hit_rate", "n_predictions"]:
|
||||
assert key in metrics, f"Missing key: {key}"
|
||||
@@ -193,14 +193,14 @@ class TestEvaluateKronosModel:
|
||||
def test_hit_rate_in_valid_range(self, tmp_path, monkeypatch):
|
||||
import rdagent.components.coder.kronos_adapter as mod
|
||||
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: _make_mock_adapter())
|
||||
h5 = _make_predix_hdf5(tmp_path, n=400)
|
||||
h5 = _make_nexquant_hdf5(tmp_path, n=400)
|
||||
metrics = mod.evaluate_kronos_model(h5, context_bars=100, pred_bars=20, stride_bars=20, device="cpu")
|
||||
assert 0.0 <= metrics["hit_rate"] <= 1.0
|
||||
|
||||
def test_n_predictions_positive(self, tmp_path, monkeypatch):
|
||||
import rdagent.components.coder.kronos_adapter as mod
|
||||
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: _make_mock_adapter())
|
||||
h5 = _make_predix_hdf5(tmp_path, n=400)
|
||||
h5 = _make_nexquant_hdf5(tmp_path, n=400)
|
||||
metrics = mod.evaluate_kronos_model(h5, context_bars=100, pred_bars=20, stride_bars=20, device="cpu")
|
||||
assert metrics["n_predictions"] > 0
|
||||
|
||||
@@ -213,41 +213,41 @@ class TestCLICommands:
|
||||
def test_kronos_factor_missing_data_exits(self, tmp_path, monkeypatch):
|
||||
"""kronos-factor exits with code 1 when HDF5 data is missing."""
|
||||
from typer.testing import CliRunner
|
||||
import predix as predix_mod
|
||||
import nexquant as nexquant_mod
|
||||
monkeypatch.chdir(tmp_path)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(predix_mod.app, ["kronos-factor"])
|
||||
result = runner.invoke(nexquant_mod.app, ["kronos-factor"])
|
||||
assert result.exit_code == 1
|
||||
|
||||
def test_kronos_eval_missing_data_exits(self, tmp_path, monkeypatch):
|
||||
"""kronos-eval exits with code 1 when HDF5 data is missing."""
|
||||
from typer.testing import CliRunner
|
||||
import predix as predix_mod
|
||||
import nexquant as nexquant_mod
|
||||
monkeypatch.chdir(tmp_path)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(predix_mod.app, ["kronos-eval"])
|
||||
result = runner.invoke(nexquant_mod.app, ["kronos-eval"])
|
||||
assert result.exit_code == 1
|
||||
|
||||
def test_kronos_factor_runs_with_mock(self, tmp_path, monkeypatch):
|
||||
"""kronos-factor completes and saves parquet + json when adapter is mocked."""
|
||||
from typer.testing import CliRunner
|
||||
import rdagent.components.coder.kronos_adapter as mod
|
||||
import predix as predix_mod
|
||||
import nexquant as nexquant_mod
|
||||
|
||||
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: _make_mock_adapter())
|
||||
|
||||
data_dir = tmp_path / "git_ignore_folder" / "factor_implementation_source_data"
|
||||
data_dir.mkdir(parents=True)
|
||||
_make_predix_hdf5(data_dir.parent.parent, n=300)
|
||||
_make_nexquant_hdf5(data_dir.parent.parent, n=300)
|
||||
h5_src = tmp_path / "intraday_pv.h5"
|
||||
# Put HDF5 where the CLI expects it
|
||||
import shutil
|
||||
src = _make_predix_hdf5(tmp_path, n=300)
|
||||
src = _make_nexquant_hdf5(tmp_path, n=300)
|
||||
shutil.copy(src, data_dir / "intraday_pv.h5")
|
||||
|
||||
monkeypatch.chdir(tmp_path)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(predix_mod.app, [
|
||||
result = runner.invoke(nexquant_mod.app, [
|
||||
"kronos-factor", "--context", "100", "--pred", "20", "--device", "cpu"
|
||||
])
|
||||
assert result.exit_code == 0, result.output
|
||||
@@ -257,20 +257,20 @@ class TestCLICommands:
|
||||
"""kronos-eval completes and prints IC metrics when adapter is mocked."""
|
||||
from typer.testing import CliRunner
|
||||
import rdagent.components.coder.kronos_adapter as mod
|
||||
import predix as predix_mod
|
||||
import nexquant as nexquant_mod
|
||||
|
||||
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: _make_mock_adapter())
|
||||
|
||||
data_dir = tmp_path / "git_ignore_folder" / "factor_implementation_source_data"
|
||||
data_dir.mkdir(parents=True)
|
||||
_make_predix_hdf5(data_dir.parent.parent, n=400)
|
||||
src = _make_predix_hdf5(tmp_path, n=400)
|
||||
_make_nexquant_hdf5(data_dir.parent.parent, n=400)
|
||||
src = _make_nexquant_hdf5(tmp_path, n=400)
|
||||
import shutil
|
||||
shutil.copy(src, data_dir / "intraday_pv.h5")
|
||||
|
||||
monkeypatch.chdir(tmp_path)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(predix_mod.app, [
|
||||
result = runner.invoke(nexquant_mod.app, [
|
||||
"kronos-eval", "--context", "100", "--pred", "20", "--device", "cpu"
|
||||
])
|
||||
assert result.exit_code == 0, result.output
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Shared fixtures for Predix integration tests.
|
||||
Shared fixtures for NexQuant integration tests.
|
||||
Provides common test data, mock objects, and utilities.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Comprehensive Integration Test Suite for Predix
|
||||
Comprehensive Integration Test Suite for NexQuant
|
||||
Tests all 13 implemented features to ensure they work correctly.
|
||||
|
||||
Usage:
|
||||
@@ -1457,22 +1457,22 @@ class TestFinQuantCriticalIntegrations:
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CLI Model Selection Tests (predix.py, cli.py)
|
||||
# CLI Model Selection Tests (nexquant.py, cli.py)
|
||||
# =============================================================================
|
||||
|
||||
class TestCLIModelSelection:
|
||||
"""Test CLI model selection (--model/-m flag) for local vs OpenRouter."""
|
||||
|
||||
def test_predix_cli_imports(self):
|
||||
"""Test that predix.py CLI module can be imported."""
|
||||
def test_nexquant_cli_imports(self):
|
||||
"""Test that nexquant.py CLI module can be imported."""
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"predix", Path(__file__).parent.parent.parent / "predix.py"
|
||||
"nexquant", Path(__file__).parent.parent.parent / "nexquant.py"
|
||||
)
|
||||
predix = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(predix)
|
||||
assert hasattr(predix, "app")
|
||||
assert hasattr(predix, "quant")
|
||||
nexquant = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(nexquant)
|
||||
assert hasattr(nexquant, "app")
|
||||
assert hasattr(nexquant, "quant")
|
||||
|
||||
def test_fin_quant_cli_has_model_option(self):
|
||||
"""Test that fin_quant CLI has --model option."""
|
||||
@@ -1488,36 +1488,36 @@ class TestCLIModelSelection:
|
||||
# (Typer auto-generates help from function signatures)
|
||||
assert isinstance(result.output, str)
|
||||
|
||||
def test_predix_quant_has_model_option(self):
|
||||
"""Test that predix quant CLI has --model option."""
|
||||
def test_nexquant_quant_has_model_option(self):
|
||||
"""Test that nexquant quant CLI has --model option."""
|
||||
from typer.testing import CliRunner
|
||||
import importlib.util
|
||||
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"predix", Path(__file__).parent.parent.parent / "predix.py"
|
||||
"nexquant", Path(__file__).parent.parent.parent / "nexquant.py"
|
||||
)
|
||||
predix = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(predix)
|
||||
nexquant = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(nexquant)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(predix.app, ["quant", "--help"])
|
||||
result = runner.invoke(nexquant.app, ["quant", "--help"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "--model" in result.output or "-m" in result.output
|
||||
|
||||
def test_predix_quant_has_log_file_option(self):
|
||||
"""Test that predix quant CLI has --log-file option."""
|
||||
def test_nexquant_quant_has_log_file_option(self):
|
||||
"""Test that nexquant quant CLI has --log-file option."""
|
||||
from typer.testing import CliRunner
|
||||
import importlib.util
|
||||
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"predix", Path(__file__).parent.parent.parent / "predix.py"
|
||||
"nexquant", Path(__file__).parent.parent.parent / "nexquant.py"
|
||||
)
|
||||
predix = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(predix)
|
||||
nexquant = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(nexquant)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(predix.app, ["quant", "--help"])
|
||||
result = runner.invoke(nexquant.app, ["quant", "--help"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "--log-file" in result.output
|
||||
@@ -1541,12 +1541,12 @@ class TestCLIModelSelection:
|
||||
import inspect
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"predix", Path(__file__).parent.parent.parent / "predix.py"
|
||||
"nexquant", Path(__file__).parent.parent.parent / "nexquant.py"
|
||||
)
|
||||
predix = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(predix)
|
||||
nexquant = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(nexquant)
|
||||
|
||||
source = inspect.getsource(predix.quant)
|
||||
source = inspect.getsource(nexquant.quant)
|
||||
assert "OPENROUTER_API_KEY" in source
|
||||
assert "not set" in source or "not set in" in source
|
||||
finally:
|
||||
@@ -1554,50 +1554,50 @@ class TestCLIModelSelection:
|
||||
os.environ["OPENROUTER_API_KEY"] = original_key
|
||||
|
||||
def test_tee_writer_class_exists(self):
|
||||
"""Test that TeeWriter class is defined in predix.py."""
|
||||
"""Test that TeeWriter class is defined in nexquant.py."""
|
||||
import importlib.util
|
||||
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"predix", Path(__file__).parent.parent.parent / "predix.py"
|
||||
"nexquant", Path(__file__).parent.parent.parent / "nexquant.py"
|
||||
)
|
||||
predix = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(predix)
|
||||
nexquant = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(nexquant)
|
||||
|
||||
# TeeWriter is defined inside the quant function
|
||||
# Verify the function source contains TeeWriter
|
||||
import inspect
|
||||
source = inspect.getsource(predix.quant)
|
||||
source = inspect.getsource(nexquant.quant)
|
||||
assert "TeeWriter" in source
|
||||
|
||||
def test_predix_health_command(self):
|
||||
"""Test that predix health command exists."""
|
||||
def test_nexquant_health_command(self):
|
||||
"""Test that nexquant health command exists."""
|
||||
from typer.testing import CliRunner
|
||||
import importlib.util
|
||||
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"predix", Path(__file__).parent.parent.parent / "predix.py"
|
||||
"nexquant", Path(__file__).parent.parent.parent / "nexquant.py"
|
||||
)
|
||||
predix = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(predix)
|
||||
nexquant = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(nexquant)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(predix.app, ["health", "--help"])
|
||||
result = runner.invoke(nexquant.app, ["health", "--help"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
def test_predix_status_command(self):
|
||||
"""Test that predix status command exists."""
|
||||
def test_nexquant_status_command(self):
|
||||
"""Test that nexquant status command exists."""
|
||||
from typer.testing import CliRunner
|
||||
import importlib.util
|
||||
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"predix", Path(__file__).parent.parent.parent / "predix.py"
|
||||
"nexquant", Path(__file__).parent.parent.parent / "nexquant.py"
|
||||
)
|
||||
predix = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(predix)
|
||||
nexquant = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(nexquant)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(predix.app, ["status", "--help"])
|
||||
result = runner.invoke(nexquant.app, ["status", "--help"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Integration Tests for Full Predix Pipeline (P6-P9)
|
||||
Integration Tests for Full NexQuant Pipeline (P6-P9)
|
||||
|
||||
Tests the complete end-to-end pipeline including:
|
||||
- Feedback Loop Integration (P6)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Deep tests for predix_autopilot.py — property-based, mocks, edge cases.
|
||||
"""Deep tests for nexquant_autopilot.py — property-based, mocks, edge cases.
|
||||
|
||||
Tests the core logic of the 24/7 strategy generator by mocking
|
||||
the StrategyOrchestrator at the correct import path.
|
||||
@@ -39,14 +39,14 @@ class TestMainRound:
|
||||
"rdagent.scenarios.qlib.local.strategy_orchestrator.StrategyOrchestrator",
|
||||
side_effect=RuntimeError("no data"),
|
||||
):
|
||||
from scripts.predix_autopilot import main_round
|
||||
from scripts.nexquant_autopilot import main_round
|
||||
result = main_round("daytrading", 1)
|
||||
assert result == 0
|
||||
|
||||
def test_returns_zero_on_generate_failure(self, mock_orch):
|
||||
mock_cls, instance = mock_orch
|
||||
instance.generate_strategies.side_effect = RuntimeError("crash")
|
||||
from scripts.predix_autopilot import main_round
|
||||
from scripts.nexquant_autopilot import main_round
|
||||
result = main_round("daytrading", 1)
|
||||
assert result == 0
|
||||
|
||||
@@ -56,7 +56,7 @@ class TestMainRound:
|
||||
{"status": "accepted", "strategy_name": "s1", "sharpe_ratio": 0.5, "oos_sharpe": 0.3},
|
||||
{"status": "rejected", "strategy_name": "s2", "reason": "low"},
|
||||
]
|
||||
from scripts.predix_autopilot import main_round
|
||||
from scripts.nexquant_autopilot import main_round
|
||||
result = main_round("daytrading", 1)
|
||||
assert result == 1
|
||||
|
||||
@@ -70,7 +70,7 @@ class TestMainRound:
|
||||
"status": "success", "sharpe_ratio": 0.95, "oos_sharpe": 0.65,
|
||||
"members": ["a", "b"],
|
||||
}
|
||||
from scripts.predix_autopilot import main_round
|
||||
from scripts.nexquant_autopilot import main_round
|
||||
main_round("daytrading", 1)
|
||||
instance.build_ensemble.assert_called_once()
|
||||
|
||||
@@ -80,7 +80,7 @@ class TestMainRound:
|
||||
{"status": "accepted", "strategy_name": "a", "sharpe_ratio": 0.5, "oos_sharpe": 0.3},
|
||||
{"status": "rejected", "strategy_name": "b", "reason": "no"},
|
||||
]
|
||||
from scripts.predix_autopilot import main_round
|
||||
from scripts.nexquant_autopilot import main_round
|
||||
main_round("daytrading", 1)
|
||||
instance.build_ensemble.assert_not_called()
|
||||
|
||||
@@ -91,14 +91,14 @@ class TestMainRound:
|
||||
{"status": "accepted", "strategy_name": "b", "sharpe_ratio": 0.6, "oos_sharpe": 0.4},
|
||||
]
|
||||
instance.build_ensemble.side_effect = RuntimeError("boom")
|
||||
from scripts.predix_autopilot import main_round
|
||||
from scripts.nexquant_autopilot import main_round
|
||||
result = main_round("daytrading", 1)
|
||||
assert result == 2 # Still counts accepted
|
||||
|
||||
def test_empty_results_returns_zero(self, mock_orch):
|
||||
mock_cls, instance = mock_orch
|
||||
instance.generate_strategies.return_value = []
|
||||
from scripts.predix_autopilot import main_round
|
||||
from scripts.nexquant_autopilot import main_round
|
||||
result = main_round("daytrading", 1)
|
||||
assert result == 0
|
||||
|
||||
@@ -109,7 +109,7 @@ class TestMainRound:
|
||||
{"status": "accepted", "strategy_name": "b", "sharpe_ratio": 0.6, "oos_sharpe": 0.4},
|
||||
]
|
||||
instance.build_ensemble.return_value = None
|
||||
from scripts.predix_autopilot import main_round
|
||||
from scripts.nexquant_autopilot import main_round
|
||||
result = main_round("daytrading", 1)
|
||||
assert result == 2
|
||||
|
||||
@@ -130,27 +130,27 @@ class TestMainRound:
|
||||
"reason": "test"},
|
||||
]
|
||||
mock_cls.return_value = instance
|
||||
from scripts.predix_autopilot import main_round
|
||||
from scripts.nexquant_autopilot import main_round
|
||||
result = main_round("daytrading", 1)
|
||||
assert isinstance(result, int) and result >= 0
|
||||
|
||||
|
||||
class TestConfig:
|
||||
def test_batch_size_positive(self):
|
||||
from scripts import predix_autopilot
|
||||
assert predix_autopilot.BATCH_SIZE > 0
|
||||
from scripts import nexquant_autopilot
|
||||
assert nexquant_autopilot.BATCH_SIZE > 0
|
||||
|
||||
def test_optuna_trials_positive(self):
|
||||
from scripts import predix_autopilot
|
||||
assert predix_autopilot.OPTUNA_TRIALS > 0
|
||||
from scripts import nexquant_autopilot
|
||||
assert nexquant_autopilot.OPTUNA_TRIALS > 0
|
||||
|
||||
def test_cooldown_positive(self):
|
||||
from scripts import predix_autopilot
|
||||
assert predix_autopilot.COOLDOWN > 0
|
||||
from scripts import nexquant_autopilot
|
||||
assert nexquant_autopilot.COOLDOWN > 0
|
||||
|
||||
def test_max_consecutive_fails_positive(self):
|
||||
from scripts import predix_autopilot
|
||||
assert predix_autopilot.MAX_CONSECUTIVE_FAILS > 0
|
||||
from scripts import nexquant_autopilot
|
||||
assert nexquant_autopilot.MAX_CONSECUTIVE_FAILS > 0
|
||||
|
||||
|
||||
class TestStyleCycling:
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
Tests for background task infrastructure (parallel runner, CLI paths, env loading).
|
||||
|
||||
Verifies bugs that were previously present:
|
||||
- predix_parallel.py: project_root pointing to scripts/ instead of repo root
|
||||
- predix_parallel.py: .env loaded from scripts/ instead of repo root
|
||||
- predix_parallel.py: API key round-robin overwritten by comma-separated list
|
||||
- nexquant_parallel.py: project_root pointing to scripts/ instead of repo root
|
||||
- nexquant_parallel.py: .env loaded from scripts/ instead of repo root
|
||||
- nexquant_parallel.py: API key round-robin overwritten by comma-separated list
|
||||
- cli.py: project_root depth wrong (4 .parent hops instead of 3)
|
||||
- cli.py start_loop: hardcoded "python" instead of sys.executable
|
||||
- cli.py parallel: hardcoded model=local
|
||||
@@ -18,7 +18,7 @@ from unittest.mock import Mock, patch
|
||||
import pytest
|
||||
|
||||
|
||||
# ── predix_parallel.py ──────────────────────────────────────────────────
|
||||
# ── nexquant_parallel.py ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestParallelRunnerProjectRoot:
|
||||
@@ -26,36 +26,36 @@ class TestParallelRunnerProjectRoot:
|
||||
|
||||
def test_project_root_is_repo_root(self):
|
||||
"""Bug: project_root was Path(__file__).parent (= scripts/)."""
|
||||
from scripts.predix_parallel import ParallelRunner
|
||||
from scripts.nexquant_parallel import ParallelRunner
|
||||
|
||||
runner = ParallelRunner(num_runs=1, num_api_keys=1, model="local")
|
||||
root = runner.project_root
|
||||
|
||||
# Must contain predix.py (repo root), NOT be the scripts/ dir
|
||||
assert (root / "predix.py").exists(), (
|
||||
f"project_root={root} does not contain predix.py — "
|
||||
# Must contain nexquant.py (repo root), NOT be the scripts/ dir
|
||||
assert (root / "nexquant.py").exists(), (
|
||||
f"project_root={root} does not contain nexquant.py — "
|
||||
f"likely still pointing to scripts/ instead of repo root"
|
||||
)
|
||||
assert root.name != "scripts", (
|
||||
f"project_root={root} ends with 'scripts/' — should be repo root"
|
||||
)
|
||||
|
||||
def test_build_command_points_to_predix_py(self):
|
||||
"""Bug: command pointed to scripts/predix.py which doesn't exist."""
|
||||
from scripts.predix_parallel import ParallelRunner, RunState
|
||||
def test_build_command_points_to_nexquant_py(self):
|
||||
"""Bug: command pointed to scripts/nexquant.py which doesn't exist."""
|
||||
from scripts.nexquant_parallel import ParallelRunner, RunState
|
||||
|
||||
runner = ParallelRunner(num_runs=1, num_api_keys=1, model="local")
|
||||
run = RunState(run_id=1, api_key_idx=0, model="local")
|
||||
cmd = runner._build_command(run)
|
||||
|
||||
predix_path = Path(cmd[1])
|
||||
assert predix_path.exists(), (
|
||||
f"Command references {predix_path} which does not exist — "
|
||||
nexquant_path = Path(cmd[1])
|
||||
assert nexquant_path.exists(), (
|
||||
f"Command references {nexquant_path} which does not exist — "
|
||||
f"project_root likely still wrong"
|
||||
)
|
||||
assert predix_path.name == "predix.py"
|
||||
assert predix_path.parent.name != "scripts", (
|
||||
"predix.py should be in repo root, not scripts/"
|
||||
assert nexquant_path.name == "nexquant.py"
|
||||
assert nexquant_path.parent.name != "scripts", (
|
||||
"nexquant.py should be in repo root, not scripts/"
|
||||
)
|
||||
|
||||
def test_env_loading_from_repo_root(self):
|
||||
@@ -75,7 +75,7 @@ class TestParallelRunnerAPIKeys:
|
||||
|
||||
def test_single_api_key_no_overwrite(self):
|
||||
"""Bug: with num_api_keys=1, individual key was set then overwritten."""
|
||||
from scripts.predix_parallel import ParallelRunner, RunState
|
||||
from scripts.nexquant_parallel import ParallelRunner, RunState
|
||||
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
os.environ["OPENROUTER_API_KEY"] = "sk-test-key-1"
|
||||
@@ -95,7 +95,7 @@ class TestParallelRunnerAPIKeys:
|
||||
|
||||
def test_multi_api_key_comma_separated(self):
|
||||
"""With 2+ keys, all runs get comma-separated list for load balancing."""
|
||||
from scripts.predix_parallel import ParallelRunner, RunState
|
||||
from scripts.nexquant_parallel import ParallelRunner, RunState
|
||||
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
os.environ["OPENROUTER_API_KEY"] = "sk-key-a"
|
||||
@@ -111,7 +111,7 @@ class TestParallelRunnerAPIKeys:
|
||||
|
||||
def test_round_robin_api_key_index(self):
|
||||
"""Verify round-robin API key index assignment is computed correctly."""
|
||||
from scripts.predix_parallel import ParallelRunner
|
||||
from scripts.nexquant_parallel import ParallelRunner
|
||||
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
os.environ["OPENROUTER_API_KEY"] = "a"
|
||||
@@ -129,7 +129,7 @@ class TestParallelRunnerLogFileHandling:
|
||||
|
||||
def test_log_file_paths_in_repo_root(self):
|
||||
"""Bug: logs went to scripts/fin_quant_runN.log."""
|
||||
from scripts.predix_parallel import ParallelRunner
|
||||
from scripts.nexquant_parallel import ParallelRunner
|
||||
|
||||
runner = ParallelRunner(num_runs=2, num_api_keys=1, model="local")
|
||||
|
||||
@@ -170,7 +170,7 @@ class TestCLIProjectRoot:
|
||||
"4 .parent hops should NOT yield repo root "
|
||||
f"(got {buggy}, expected {self.REPO_ROOT.parent})"
|
||||
)
|
||||
assert (buggy / "Predix").exists() or buggy == self.REPO_ROOT.parent, (
|
||||
assert (buggy / "NexQuant").exists() or buggy == self.REPO_ROOT.parent, (
|
||||
f"4 .parent hops overshoots repo root: {buggy}"
|
||||
)
|
||||
|
||||
@@ -210,12 +210,12 @@ class TestCLIProjectRoot:
|
||||
|
||||
# All these commands use Path(__file__).parent.parent.parent as project_root
|
||||
commands = {
|
||||
"eval_all": "scripts/predix_full_eval.py",
|
||||
"batch_backtest": "scripts/predix_batch_backtest.py",
|
||||
"simple_eval": "scripts/predix_simple_eval.py",
|
||||
"rebacktest": "scripts/predix_rebacktest_strategies.py",
|
||||
"report": "scripts/predix_strategy_report.py",
|
||||
"parallel": "scripts/predix_parallel.py",
|
||||
"eval_all": "scripts/nexquant_full_eval.py",
|
||||
"batch_backtest": "scripts/nexquant_batch_backtest.py",
|
||||
"simple_eval": "scripts/nexquant_simple_eval.py",
|
||||
"rebacktest": "scripts/nexquant_rebacktest_strategies.py",
|
||||
"report": "scripts/nexquant_strategy_report.py",
|
||||
"parallel": "scripts/nexquant_parallel.py",
|
||||
}
|
||||
|
||||
for cmd_name, script_path in commands.items():
|
||||
@@ -231,12 +231,12 @@ class TestCLIProjectRoot:
|
||||
import inspect
|
||||
|
||||
source = inspect.getsource(start_loop_cli)
|
||||
# The generator should reference scripts/predix_smart_strategy_gen.py
|
||||
assert "predix_smart_strategy_gen.py" in source, (
|
||||
"start_loop_cli should reference predix_smart_strategy_gen.py"
|
||||
# The generator should reference scripts/nexquant_smart_strategy_gen.py
|
||||
assert "nexquant_smart_strategy_gen.py" in source, (
|
||||
"start_loop_cli should reference nexquant_smart_strategy_gen.py"
|
||||
)
|
||||
|
||||
script = self.REPO_ROOT / "scripts" / "predix_smart_strategy_gen.py"
|
||||
script = self.REPO_ROOT / "scripts" / "nexquant_smart_strategy_gen.py"
|
||||
assert script.exists(), (
|
||||
f"Generator script not found at {script}"
|
||||
)
|
||||
@@ -266,7 +266,7 @@ class TestImportsDontCrash:
|
||||
|
||||
def test_import_parallel_runner(self):
|
||||
"""ParallelRunner should import without errors."""
|
||||
from scripts.predix_parallel import ParallelRunner, RunState
|
||||
from scripts.nexquant_parallel import ParallelRunner, RunState
|
||||
runner = ParallelRunner(num_runs=1, num_api_keys=1, model="local")
|
||||
assert runner.num_runs == 1
|
||||
assert len(runner.runs) == 1
|
||||
|
||||
@@ -15,8 +15,8 @@ Verifies:
|
||||
- strategy_orchestrator.py exec() exception logged at ERROR level
|
||||
- strategy_orchestrator.py template validation warns on unreplaced {{...}}
|
||||
- factor_runner.py IC_max guard against scalar (AttributeError)
|
||||
- predix_parallel.py handle leak on Popen failure
|
||||
- predix_rebacktest_strategies.py bare except replaced with except Exception
|
||||
- nexquant_parallel.py handle leak on Popen failure
|
||||
- nexquant_rebacktest_strategies.py bare except replaced with except Exception
|
||||
"""
|
||||
|
||||
import ast
|
||||
@@ -228,32 +228,32 @@ class TestFactorRunnerICMaxGuard:
|
||||
)
|
||||
|
||||
|
||||
# ── Fix 13: predix_parallel.py handle leak ───────────────────────────────
|
||||
# ── Fix 13: nexquant_parallel.py handle leak ───────────────────────────────
|
||||
|
||||
|
||||
class TestParallelRunnerHandleLeak:
|
||||
def test_log_f_close_on_popen_failure(self):
|
||||
"""Bug: open() file handle leaked if Popen failed."""
|
||||
source = (REPO_ROOT / "scripts/predix_parallel.py").read_text()
|
||||
source = (REPO_ROOT / "scripts/nexquant_parallel.py").read_text()
|
||||
|
||||
# After fix, log_f.close() is called before re-raise
|
||||
assert "log_f.close()" in source, (
|
||||
"predix_parallel.py should close log file handle on Popen failure"
|
||||
"nexquant_parallel.py should close log file handle on Popen failure"
|
||||
)
|
||||
|
||||
|
||||
# ── Fix 14: predix_rebacktest_strategies.py bare except ──────────────────
|
||||
# ── Fix 14: nexquant_rebacktest_strategies.py bare except ──────────────────
|
||||
|
||||
|
||||
class TestRebacktestBareExcept:
|
||||
def test_not_bare_except(self):
|
||||
"""Bug: bare except: pass swallowed all errors including SystemExit."""
|
||||
source = (REPO_ROOT / "scripts/predix_rebacktest_strategies.py").read_text()
|
||||
source = (REPO_ROOT / "scripts/nexquant_rebacktest_strategies.py").read_text()
|
||||
|
||||
# After fix, should use except Exception, not bare except
|
||||
assert "except Exception:" in source
|
||||
assert "except:" not in source, (
|
||||
"predix_rebacktest_strategies.py should not use bare except:"
|
||||
"nexquant_rebacktest_strategies.py should not use bare except:"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Deep tests for predix_continuous_strategies.py — ML model building, style cycling.
|
||||
"""Deep tests for nexquant_continuous_strategies.py — ML model building, style cycling.
|
||||
|
||||
Tests the build_ml_model function and the round/style alternation logic
|
||||
without requiring real StrategyOrchestrator connections.
|
||||
@@ -45,7 +45,7 @@ def close_data():
|
||||
class TestBuildMLModel:
|
||||
def test_insufficient_data_returns_none(self, factor_data, close_data):
|
||||
"""<5000 rows should return None."""
|
||||
from scripts.predix_continuous_strategies import build_ml_model
|
||||
from scripts.nexquant_continuous_strategies import build_ml_model
|
||||
result = build_ml_model(factor_data.iloc[:100], close_data.iloc[:100], "swing")
|
||||
assert result is None
|
||||
|
||||
@@ -55,7 +55,7 @@ class TestBuildMLModel:
|
||||
"sharpe": 1.5, "max_drawdown": -0.1, "win_rate": 0.55,
|
||||
"n_trades": 200, "wf_oos_sharpe_mean": 0.8,
|
||||
}
|
||||
from scripts.predix_continuous_strategies import build_ml_model
|
||||
from scripts.nexquant_continuous_strategies import build_ml_model
|
||||
result = build_ml_model(factor_data, close_data, "daytrading")
|
||||
assert result is not None
|
||||
assert "strategy_name" in result
|
||||
@@ -69,7 +69,7 @@ class TestBuildMLModel:
|
||||
"sharpe": 1.5, "max_drawdown": -0.1, "win_rate": 0.55,
|
||||
"n_trades": 200, "wf_oos_sharpe_mean": -0.3,
|
||||
}
|
||||
from scripts.predix_continuous_strategies import build_ml_model
|
||||
from scripts.nexquant_continuous_strategies import build_ml_model
|
||||
result = build_ml_model(factor_data, close_data, "swing")
|
||||
assert result is None
|
||||
|
||||
@@ -90,7 +90,7 @@ class TestBuildMLModel:
|
||||
}, index=factor_data.index[:n])
|
||||
c = pd.Series(1.10 + rng.normal(0, 0.001, n).cumsum(), index=f.index)
|
||||
try:
|
||||
from scripts.predix_continuous_strategies import build_ml_model
|
||||
from scripts.nexquant_continuous_strategies import build_ml_model
|
||||
result = build_ml_model(f, c, "swing")
|
||||
assert result is None or isinstance(result, dict)
|
||||
except Exception as e:
|
||||
@@ -102,12 +102,12 @@ class TestBuildMLModel:
|
||||
|
||||
class TestConfig:
|
||||
def test_batch_size_is_positive(self):
|
||||
from scripts import predix_continuous_strategies
|
||||
assert predix_continuous_strategies.BATCH_SIZE > 0
|
||||
from scripts import nexquant_continuous_strategies
|
||||
assert nexquant_continuous_strategies.BATCH_SIZE > 0
|
||||
|
||||
def test_cooldown_is_positive(self):
|
||||
from scripts import predix_continuous_strategies
|
||||
assert predix_continuous_strategies.COOLDOWN_SECONDS > 0
|
||||
from scripts import nexquant_continuous_strategies
|
||||
assert nexquant_continuous_strategies.COOLDOWN_SECONDS > 0
|
||||
|
||||
|
||||
class TestStyleCycling:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Deep tests for predix_gen_strategies_real_bt.py — property-based, edge cases.
|
||||
"""Deep tests for nexquant_gen_strategies_real_bt.py — property-based, edge cases.
|
||||
|
||||
Tests factor loading, threshold rescaling, backtest runner, acceptance
|
||||
criteria, and the TeeFile logger — without requiring real OHLCV data or LLM.
|
||||
@@ -27,7 +27,7 @@ from hypothesis import strategies as st
|
||||
@pytest.fixture
|
||||
def gen_module():
|
||||
import importlib
|
||||
import scripts.predix_gen_strategies_real_bt as m
|
||||
import scripts.nexquant_gen_strategies_real_bt as m
|
||||
return m
|
||||
|
||||
|
||||
@@ -280,7 +280,7 @@ class TestConfiguration:
|
||||
"""Daytrading config uses tighter risk limits."""
|
||||
os.environ["TRADING_STYLE"] = "daytrading"
|
||||
import importlib
|
||||
import scripts.predix_gen_strategies_real_bt as m
|
||||
import scripts.nexquant_gen_strategies_real_bt as m
|
||||
importlib.reload(m)
|
||||
assert m.MIN_IC == 0.02
|
||||
assert m.MIN_SHARPE == 0.5
|
||||
@@ -289,7 +289,7 @@ class TestConfiguration:
|
||||
def test_swing_defaults(self):
|
||||
os.environ["TRADING_STYLE"] = "swing"
|
||||
import importlib
|
||||
import scripts.predix_gen_strategies_real_bt as m
|
||||
import scripts.nexquant_gen_strategies_real_bt as m
|
||||
importlib.reload(m)
|
||||
assert m.MIN_TRADES == 10
|
||||
assert m.MAX_DRAWDOWN == -0.30
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Tests for MLTrainer - ML Training Pipeline for Predix quant trading system.
|
||||
Tests for MLTrainer - ML Training Pipeline for NexQuant quant trading system.
|
||||
|
||||
Tests cover:
|
||||
- Feature matrix building
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Deep tests for predix_parallel.py — property-based, state transitions, edge cases.
|
||||
"""Deep tests for nexquant_parallel.py — property-based, state transitions, edge cases.
|
||||
|
||||
Tests RunState, ParallelRunner configuration, environment building,
|
||||
command building, and API key loading logic.
|
||||
@@ -25,7 +25,7 @@ from hypothesis import strategies as st
|
||||
|
||||
@pytest.fixture
|
||||
def runstate():
|
||||
from scripts.predix_parallel import RunState
|
||||
from scripts.nexquant_parallel import RunState
|
||||
return RunState(run_id=1, api_key_idx=0, model="local")
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ class TestRunState:
|
||||
|
||||
def test_elapsed_running(self, runstate):
|
||||
runstate.start_time = datetime(2024, 1, 1, 12, 0, 0)
|
||||
with patch("scripts.predix_parallel.datetime") as mock_dt:
|
||||
with patch("scripts.nexquant_parallel.datetime") as mock_dt:
|
||||
mock_dt.now.return_value = datetime(2024, 1, 1, 13, 30, 45)
|
||||
assert runstate.elapsed == "01:30:45"
|
||||
|
||||
@@ -94,7 +94,7 @@ class TestRunState:
|
||||
class TestParallelRunnerConfig:
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_load_api_keys_openrouter(self):
|
||||
from scripts.predix_parallel import ParallelRunner
|
||||
from scripts.nexquant_parallel import ParallelRunner
|
||||
with patch.dict(os.environ, {
|
||||
"OPENROUTER_API_KEY": "sk-key1",
|
||||
"OPENROUTER_API_KEY_2": "sk-key2",
|
||||
@@ -106,13 +106,13 @@ class TestParallelRunnerConfig:
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_load_api_keys_local(self):
|
||||
from scripts.predix_parallel import ParallelRunner
|
||||
from scripts.nexquant_parallel import ParallelRunner
|
||||
runner = ParallelRunner(num_runs=1, num_api_keys=1, model="local")
|
||||
assert runner.api_keys == ["local"]
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_load_api_keys_round_robin(self):
|
||||
from scripts.predix_parallel import ParallelRunner
|
||||
from scripts.nexquant_parallel import ParallelRunner
|
||||
runner = ParallelRunner(num_runs=5, num_api_keys=2, model="local")
|
||||
assert len(runner.runs) == 5
|
||||
idxs = [r.api_key_idx for r in runner.runs]
|
||||
@@ -120,7 +120,7 @@ class TestParallelRunnerConfig:
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_build_env_local_model(self):
|
||||
from scripts.predix_parallel import ParallelRunner, RunState
|
||||
from scripts.nexquant_parallel import ParallelRunner, RunState
|
||||
with patch.dict(os.environ, {
|
||||
"OPENAI_API_KEY": "local",
|
||||
"OPENAI_API_BASE": "http://localhost:8081/v1",
|
||||
@@ -135,7 +135,7 @@ class TestParallelRunnerConfig:
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_build_env_openrouter(self):
|
||||
from scripts.predix_parallel import ParallelRunner, RunState
|
||||
from scripts.nexquant_parallel import ParallelRunner, RunState
|
||||
with patch.dict(os.environ, {
|
||||
"OPENROUTER_API_KEY": "sk-test",
|
||||
"OPENROUTER_API_KEY_2": "sk-test2",
|
||||
@@ -147,7 +147,7 @@ class TestParallelRunnerConfig:
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_build_env_sets_workspace(self):
|
||||
from scripts.predix_parallel import ParallelRunner, RunState
|
||||
from scripts.nexquant_parallel import ParallelRunner, RunState
|
||||
runner = ParallelRunner(num_runs=1, num_api_keys=1, model="local")
|
||||
rs = RunState(run_id=42, api_key_idx=0, model="local")
|
||||
env = runner._build_env(rs)
|
||||
@@ -156,11 +156,11 @@ class TestParallelRunnerConfig:
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_build_command(self):
|
||||
from scripts.predix_parallel import ParallelRunner, RunState
|
||||
from scripts.nexquant_parallel import ParallelRunner, RunState
|
||||
runner = ParallelRunner(num_runs=1, num_api_keys=1, model="local")
|
||||
rs = RunState(run_id=7, api_key_idx=0, model="local")
|
||||
cmd = runner._build_command(rs)
|
||||
assert "predix.py" in cmd[1] or "predix" in cmd[1]
|
||||
assert "nexquant.py" in cmd[1] or "nexquant" in cmd[1]
|
||||
assert "quant" in cmd
|
||||
assert "--model" in cmd
|
||||
assert "local" in cmd
|
||||
@@ -168,7 +168,7 @@ class TestParallelRunnerConfig:
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_parallel_runner_init_counts(self):
|
||||
from scripts.predix_parallel import ParallelRunner
|
||||
from scripts.nexquant_parallel import ParallelRunner
|
||||
for n in [1, 3, 10]:
|
||||
runner = ParallelRunner(num_runs=n, num_api_keys=2, model="local")
|
||||
assert len(runner.runs) == n
|
||||
@@ -178,20 +178,20 @@ class TestParallelRunnerConfig:
|
||||
class TestParallelRunnerEdgeCases:
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_max_runs_limit(self):
|
||||
from scripts.predix_parallel import ParallelRunner
|
||||
from scripts.nexquant_parallel import ParallelRunner
|
||||
runner = ParallelRunner(num_runs=100, num_api_keys=1, model="local")
|
||||
assert len(runner.runs) == 100
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_api_keys_empty_uses_local(self):
|
||||
from scripts.predix_parallel import ParallelRunner
|
||||
from scripts.nexquant_parallel import ParallelRunner
|
||||
runner = ParallelRunner(num_runs=1, num_api_keys=2, model="openrouter")
|
||||
assert len(runner.api_keys) >= 1
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_build_env_preserves_existing_env(self, monkeypatch):
|
||||
monkeypatch.setenv("MY_CUSTOM_VAR", "custom_value")
|
||||
from scripts.predix_parallel import ParallelRunner, RunState
|
||||
from scripts.nexquant_parallel import ParallelRunner, RunState
|
||||
runner = ParallelRunner(num_runs=1, num_api_keys=1, model="local")
|
||||
rs = RunState(run_id=1, api_key_idx=0, model="local")
|
||||
env = runner._build_env(rs)
|
||||
@@ -643,14 +643,14 @@ class TestStrategySaver:
|
||||
'passed': True,
|
||||
'checks': {},
|
||||
},
|
||||
metadata={'version': '1.0', 'author': 'Predix'},
|
||||
metadata={'version': '1.0', 'author': 'NexQuant'},
|
||||
)
|
||||
|
||||
with open(filepath) as f:
|
||||
data = json.load(f)
|
||||
|
||||
assert data['metadata']['version'] == '1.0'
|
||||
assert data['metadata']['author'] == 'Predix'
|
||||
assert data['metadata']['author'] == 'NexQuant'
|
||||
|
||||
def test_save_strategy_with_llm_response(self, strategy_saver):
|
||||
"""Test saving strategy with LLM response preview."""
|
||||
|
||||
@@ -123,9 +123,9 @@ class TestWebDashboard:
|
||||
|
||||
|
||||
class TestScriptsImportable:
|
||||
def test_predix_full_eval(self):
|
||||
def test_nexquant_full_eval(self):
|
||||
import importlib
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/predix_full_eval.py")
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/nexquant_full_eval.py")
|
||||
assert spec is not None
|
||||
|
||||
def test_extract_results(self):
|
||||
@@ -153,24 +153,24 @@ class TestScriptsImportable:
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/kronos_model_eval.py")
|
||||
assert spec is not None
|
||||
|
||||
def test_predix_add_risk_management(self):
|
||||
def test_nexquant_add_risk_management(self):
|
||||
import importlib
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/predix_add_risk_management.py")
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/nexquant_add_risk_management.py")
|
||||
assert spec is not None
|
||||
|
||||
def test_predix_gen_strategies(self):
|
||||
def test_nexquant_gen_strategies(self):
|
||||
import importlib
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/predix_gen_strategies_real_bt.py")
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/nexquant_gen_strategies_real_bt.py")
|
||||
assert spec is not None
|
||||
|
||||
def test_predix_quick_daytrading(self):
|
||||
def test_nexquant_quick_daytrading(self):
|
||||
import importlib
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/predix_quick_daytrading.py")
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/nexquant_quick_daytrading.py")
|
||||
assert spec is not None
|
||||
|
||||
def test_predix_rebacktest_unified(self):
|
||||
def test_nexquant_rebacktest_unified(self):
|
||||
import importlib
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/predix_rebacktest_unified.py")
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/nexquant_rebacktest_unified.py")
|
||||
assert spec is not None
|
||||
|
||||
def test_realistic_backtest_all(self):
|
||||
|
||||
@@ -196,7 +196,7 @@ class TestInfNanHandlingInsertion:
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Bug 5: scan_factors reads factor_code twice (predix_full_eval.py:174 + 195)
|
||||
# Bug 5: scan_factors reads factor_code twice (nexquant_full_eval.py:174 + 195)
|
||||
# =============================================================================
|
||||
|
||||
class TestScanFactorsDoubleRead:
|
||||
@@ -205,9 +205,9 @@ class TestScanFactorsDoubleRead:
|
||||
def test_factor_code_read_only_when_needed(self):
|
||||
"""Confirm the scan_factors double-read behavior (line 174+195)."""
|
||||
import inspect
|
||||
from scripts import predix_full_eval
|
||||
from scripts import nexquant_full_eval
|
||||
|
||||
source = inspect.getsource(predix_full_eval.scan_factors)
|
||||
source = inspect.getsource(nexquant_full_eval.scan_factors)
|
||||
|
||||
# Count occurrences of `.read_text()`
|
||||
count = source.count(".read_text()")
|
||||
|
||||
@@ -18,8 +18,8 @@ class TestContinuousGenerator:
|
||||
def test_module_imports(self):
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"predix_autopilot",
|
||||
PROJECT_ROOT / "scripts/predix_autopilot.py",
|
||||
"nexquant_autopilot",
|
||||
PROJECT_ROOT / "scripts/nexquant_autopilot.py",
|
||||
)
|
||||
assert spec is not None
|
||||
|
||||
@@ -107,6 +107,6 @@ class TestAutopilotIntegration:
|
||||
|
||||
def test_autopilot_pid_running(self):
|
||||
import os
|
||||
result = os.system("pgrep -f predix_autopilot > /dev/null 2>&1")
|
||||
result = os.system("pgrep -f nexquant_autopilot > /dev/null 2>&1")
|
||||
# 0 = running, 1 = not running — both are valid states
|
||||
assert result in (0, 1)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Tests for scripts/predix_full_eval.py pure functions and dataclasses."""
|
||||
"""Tests for scripts/nexquant_full_eval.py pure functions and dataclasses."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -13,7 +13,7 @@ sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
class TestFactorInfo:
|
||||
def test_construction(self):
|
||||
from scripts.predix_full_eval import FactorInfo
|
||||
from scripts.nexquant_full_eval import FactorInfo
|
||||
fi = FactorInfo(
|
||||
workspace_hash="abc123",
|
||||
factor_name="test_factor",
|
||||
@@ -26,7 +26,7 @@ class TestFactorInfo:
|
||||
|
||||
class TestEvalResult:
|
||||
def test_defaults(self):
|
||||
from scripts.predix_full_eval import EvalResult
|
||||
from scripts.nexquant_full_eval import EvalResult
|
||||
er = EvalResult(factor_name="f1", workspace_hash="h1")
|
||||
assert er.status == ""
|
||||
assert er.ic is None
|
||||
@@ -34,7 +34,7 @@ class TestEvalResult:
|
||||
assert er.non_null_count == 0
|
||||
|
||||
def test_failed_result(self):
|
||||
from scripts.predix_full_eval import EvalResult
|
||||
from scripts.nexquant_full_eval import EvalResult
|
||||
er = EvalResult(
|
||||
factor_name="f1", workspace_hash="h1",
|
||||
status="failed", error_message="timeout",
|
||||
@@ -43,7 +43,7 @@ class TestEvalResult:
|
||||
assert er.error_message == "timeout"
|
||||
|
||||
def test_to_dict(self):
|
||||
from scripts.predix_full_eval import EvalResult
|
||||
from scripts.nexquant_full_eval import EvalResult
|
||||
er = EvalResult(factor_name="f1", workspace_hash="h1", status="success", ic=0.05)
|
||||
d = er.to_dict()
|
||||
assert d["factor_name"] == "f1"
|
||||
@@ -53,26 +53,26 @@ class TestEvalResult:
|
||||
|
||||
class TestExtractFactorDescription:
|
||||
def test_docstring_extracted(self):
|
||||
from scripts.predix_full_eval import _extract_factor_description
|
||||
from scripts.nexquant_full_eval import _extract_factor_description
|
||||
code = '"""This is a test factor.\nComputes momentum."""\nx=1'
|
||||
desc = _extract_factor_description(code)
|
||||
assert "test factor" in desc
|
||||
|
||||
def test_comment_extraction(self):
|
||||
from scripts.predix_full_eval import _extract_factor_description
|
||||
from scripts.nexquant_full_eval import _extract_factor_description
|
||||
code = "# Momentum factor\n# Uses 20-bar window\nx=1"
|
||||
desc = _extract_factor_description(code)
|
||||
assert "Momentum factor" in desc
|
||||
assert "20-bar window" in desc
|
||||
|
||||
def test_no_docstring_or_comments(self):
|
||||
from scripts.predix_full_eval import _extract_factor_description
|
||||
from scripts.nexquant_full_eval import _extract_factor_description
|
||||
code = "x = 1\ny = 2\n"
|
||||
desc = _extract_factor_description(code)
|
||||
assert desc == "No description available"
|
||||
|
||||
def test_shebang_skipped(self):
|
||||
from scripts.predix_full_eval import _extract_factor_description
|
||||
from scripts.nexquant_full_eval import _extract_factor_description
|
||||
code = "#!/usr/bin/env python\n# Real comment\nx=1"
|
||||
desc = _extract_factor_description(code)
|
||||
assert "Real comment" in desc
|
||||
@@ -16,10 +16,10 @@ PROJECT_ROOT = Path(__file__).parent.parent.parent
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
|
||||
class TestPredixCLI:
|
||||
class TestNexQuantCLI:
|
||||
def test_cli_commands_available(self):
|
||||
import subprocess
|
||||
r = subprocess.run([sys.executable, "predix.py", "--help"], capture_output=True, text=True, timeout=10)
|
||||
r = subprocess.run([sys.executable, "nexquant.py", "--help"], capture_output=True, text=True, timeout=10)
|
||||
assert r.returncode == 0
|
||||
for cmd in ["evaluate", "top", "best", "portfolio", "build-strategies", "generate-strategies", "health"]:
|
||||
assert cmd in r.stdout.lower(), f"Missing command: {cmd}"
|
||||
|
||||
Reference in New Issue
Block a user