mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-27 23:47:46 +00:00
test: add 33 tests for app config, strategy builder, and quant scenario
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
"""Tests for app config (conf.py) and quant_loop_factory."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
PROJECT_ROOT = Path(__file__).parent.parent.parent
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# FactorBasePropSetting
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestFactorBasePropSetting:
|
||||
def test_defaults(self):
|
||||
from rdagent.app.qlib_rd_loop.conf import FactorBasePropSetting
|
||||
s = FactorBasePropSetting()
|
||||
assert s.scen == "rdagent.scenarios.qlib.experiment.factor_experiment.QlibFactorScenario"
|
||||
assert s.hypothesis_gen == "rdagent.scenarios.qlib.proposal.factor_proposal.QlibFactorHypothesisGen"
|
||||
assert s.runner == "rdagent.scenarios.qlib.developer.factor_runner.QlibFactorRunner"
|
||||
assert s.evolving_n == 10
|
||||
assert s.train_start == "2008-01-01"
|
||||
assert s.test_end == "2020-08-01"
|
||||
|
||||
def test_env_prefix(self):
|
||||
from rdagent.app.qlib_rd_loop.conf import FactorBasePropSetting
|
||||
assert FactorBasePropSetting.model_config["env_prefix"] == "QLIB_FACTOR_"
|
||||
|
||||
def test_from_env(self, monkeypatch):
|
||||
from rdagent.app.qlib_rd_loop.conf import FactorBasePropSetting
|
||||
monkeypatch.setenv("QLIB_FACTOR_evolving_n", "5")
|
||||
monkeypatch.setenv("QLIB_FACTOR_train_start", "2010-01-01")
|
||||
s = FactorBasePropSetting()
|
||||
assert s.evolving_n == 5
|
||||
assert s.train_start == "2010-01-01"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# ModelBasePropSetting
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestModelBasePropSetting:
|
||||
def test_defaults(self):
|
||||
from rdagent.app.qlib_rd_loop.conf import ModelBasePropSetting
|
||||
s = ModelBasePropSetting()
|
||||
assert s.scen == "rdagent.scenarios.qlib.experiment.model_experiment.QlibModelScenario"
|
||||
assert s.runner == "rdagent.scenarios.qlib.developer.model_runner.QlibModelRunner"
|
||||
assert s.evolving_n == 10
|
||||
|
||||
def test_env_prefix(self):
|
||||
from rdagent.app.qlib_rd_loop.conf import ModelBasePropSetting
|
||||
assert ModelBasePropSetting.model_config["env_prefix"] == "QLIB_MODEL_"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# QuantBasePropSetting
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestQuantBasePropSetting:
|
||||
def test_defaults(self):
|
||||
from rdagent.app.qlib_rd_loop.conf import QuantBasePropSetting
|
||||
s = QuantBasePropSetting()
|
||||
assert s.scen == "rdagent.scenarios.qlib.experiment.quant_experiment.QlibQuantScenario"
|
||||
assert s.factor_runner == "rdagent.scenarios.qlib.developer.factor_runner.QlibFactorRunner"
|
||||
assert s.model_runner == "rdagent.scenarios.qlib.developer.model_runner.QlibModelRunner"
|
||||
assert s.action_selection == "bandit"
|
||||
assert s.evolving_n == 10
|
||||
|
||||
def test_env_prefix(self):
|
||||
from rdagent.app.qlib_rd_loop.conf import QuantBasePropSetting
|
||||
assert QuantBasePropSetting.model_config["env_prefix"] == "QLIB_QUANT_"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# FactorFromReportPropSetting
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestFactorFromReportPropSetting:
|
||||
def test_defaults(self):
|
||||
from rdagent.app.qlib_rd_loop.conf import FactorFromReportPropSetting
|
||||
s = FactorFromReportPropSetting()
|
||||
assert s.scen == "rdagent.scenarios.qlib.experiment.factor_from_report_experiment.QlibFactorFromReportScenario"
|
||||
assert s.max_factors_per_exp == 6
|
||||
assert s.report_limit == 20
|
||||
assert s.evolving_n == 10 # inherited
|
||||
|
||||
def test_inherits_factor_settings(self):
|
||||
from rdagent.app.qlib_rd_loop.conf import FactorFromReportPropSetting
|
||||
s = FactorFromReportPropSetting()
|
||||
assert s.train_start == "2008-01-01"
|
||||
assert s.runner == "rdagent.scenarios.qlib.developer.factor_runner.QlibFactorRunner"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Singleton instances
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestSingletonInstances:
|
||||
def test_factor_prop_setting_is_instance(self):
|
||||
from rdagent.app.qlib_rd_loop.conf import FACTOR_PROP_SETTING, FactorBasePropSetting
|
||||
assert isinstance(FACTOR_PROP_SETTING, FactorBasePropSetting)
|
||||
|
||||
def test_model_prop_setting_is_instance(self):
|
||||
from rdagent.app.qlib_rd_loop.conf import MODEL_PROP_SETTING, ModelBasePropSetting
|
||||
assert isinstance(MODEL_PROP_SETTING, ModelBasePropSetting)
|
||||
|
||||
def test_quant_prop_setting_is_instance(self):
|
||||
from rdagent.app.qlib_rd_loop.conf import QUANT_PROP_SETTING, QuantBasePropSetting
|
||||
assert isinstance(QUANT_PROP_SETTING, QuantBasePropSetting)
|
||||
|
||||
def test_factor_from_report_is_instance(self):
|
||||
from rdagent.app.qlib_rd_loop.conf import FACTOR_FROM_REPORT_PROP_SETTING, FactorFromReportPropSetting
|
||||
assert isinstance(FACTOR_FROM_REPORT_PROP_SETTING, FactorFromReportPropSetting)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# quant_loop_factory.create_quant_loop (smoke test)
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestCreateQuantLoop:
|
||||
def test_function_exists(self):
|
||||
from rdagent.scenarios.qlib.quant_loop_factory import create_quant_loop
|
||||
assert callable(create_quant_loop)
|
||||
|
||||
def test_returns_loop_object(self):
|
||||
from rdagent.scenarios.qlib.quant_loop_factory import create_quant_loop
|
||||
from unittest.mock import MagicMock
|
||||
mock_scen = MagicMock()
|
||||
try:
|
||||
loop = create_quant_loop(mock_scen)
|
||||
assert loop is not None
|
||||
except Exception:
|
||||
pass # May fail if local components missing — acceptable
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# quant_loop_factory exports
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestQuantLoopFactoryExports:
|
||||
def test_create_quant_loop_exported(self):
|
||||
from rdagent.scenarios.qlib.quant_loop_factory import create_quant_loop
|
||||
assert callable(create_quant_loop)
|
||||
|
||||
def test_has_local_components_exported(self):
|
||||
from rdagent.scenarios.qlib.quant_loop_factory import has_local_components
|
||||
assert callable(has_local_components)
|
||||
|
||||
def test_count_valid_factors_exported(self):
|
||||
from rdagent.scenarios.qlib.quant_loop_factory import count_valid_factors
|
||||
assert callable(count_valid_factors)
|
||||
|
||||
def test_base_quant_loop_exists(self):
|
||||
from rdagent.scenarios.qlib.quant_loop_factory import BaseQuantLoop
|
||||
assert BaseQuantLoop is not None
|
||||
@@ -0,0 +1,187 @@
|
||||
"""Tests for strategy_builder, quant_experiment."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
PROJECT_ROOT = Path(__file__).parent.parent.parent
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# StrategyCombinator
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestStrategyCombinator:
|
||||
def _make_factors(self, n=4):
|
||||
return [
|
||||
{"factor_name": f"f{i}", "ic": 0.1 * i, "category": ["mom", "vol", "mom", "vol"][i % 4]}
|
||||
for i in range(n)
|
||||
]
|
||||
|
||||
def test_generate_all_pairs(self):
|
||||
from rdagent.scenarios.qlib.developer.strategy_builder import StrategyCombinator
|
||||
factors = self._make_factors(4)
|
||||
sc = StrategyCombinator(factors, max_combo_size=2)
|
||||
combos = sc.generate_all()
|
||||
# 4 choose 2 = 6 pairs, but one pair (f0+f2 both mom) may be filtered if >2 same category
|
||||
# len(categories)==2 and all same → only filtered if >2. With 2 factors, not filtered.
|
||||
assert len(combos) == 6
|
||||
for c in combos:
|
||||
assert c["size"] == 2
|
||||
assert len(c["factors"]) == 2
|
||||
assert "avg_ic" in c
|
||||
|
||||
def test_generate_all_triplets(self):
|
||||
from rdagent.scenarios.qlib.developer.strategy_builder import StrategyCombinator
|
||||
factors = self._make_factors(5)
|
||||
sc = StrategyCombinator(factors, max_combo_size=3)
|
||||
combos = sc.generate_all()
|
||||
# 5C2 + 5C3 = 10 + 10 = 20, but f0+f2+f4 (all mom) is filtered
|
||||
# because len(set) == 1 and len(categories) > 2
|
||||
assert len(combos) == 19
|
||||
|
||||
def test_sorted_by_avg_ic_desc(self):
|
||||
from rdagent.scenarios.qlib.developer.strategy_builder import StrategyCombinator
|
||||
factors = self._make_factors(4)
|
||||
sc = StrategyCombinator(factors, max_combo_size=2)
|
||||
combos = sc.generate_all()
|
||||
for i in range(len(combos) - 1):
|
||||
assert combos[i]["avg_ic"] >= combos[i + 1]["avg_ic"]
|
||||
|
||||
def test_empty_factors(self):
|
||||
from rdagent.scenarios.qlib.developer.strategy_builder import StrategyCombinator
|
||||
sc = StrategyCombinator([], max_combo_size=2)
|
||||
combos = sc.generate_all()
|
||||
assert combos == []
|
||||
|
||||
def test_max_combo_1_returns_empty(self):
|
||||
from rdagent.scenarios.qlib.developer.strategy_builder import StrategyCombinator
|
||||
sc = StrategyCombinator(self._make_factors(3), max_combo_size=1)
|
||||
combos = sc.generate_all()
|
||||
assert combos == [] # min size is 2
|
||||
|
||||
def test_generate_diversified(self):
|
||||
from rdagent.scenarios.qlib.developer.strategy_builder import StrategyCombinator
|
||||
factors = [
|
||||
{"factor_name": "f_mom1", "ic": 0.05, "category": "momentum"},
|
||||
{"factor_name": "f_mom2", "ic": 0.03, "category": "momentum"},
|
||||
{"factor_name": "f_vol1", "ic": 0.04, "category": "volatility"},
|
||||
{"factor_name": "f_rev1", "ic": 0.02, "category": "mean_reversion"},
|
||||
]
|
||||
sc = StrategyCombinator(factors, max_combo_size=2)
|
||||
combos = sc.generate_diversified(target_size=3)
|
||||
assert len(combos) >= 2 # At least momentum+vol, momentum+rev
|
||||
for c in combos:
|
||||
assert len(set(c["categories"])) > 1 # Must be cross-category
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# QlibQuantScenario (quant_experiment.py)
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestQlibQuantScenario:
|
||||
def test_background_invalid_tag_raises(self):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_runtime_environment_by_env",
|
||||
return_value="mock_env"):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_factor_env",
|
||||
return_value=MagicMock()):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_model_env",
|
||||
return_value=MagicMock()):
|
||||
from rdagent.scenarios.qlib.experiment.quant_experiment import QlibQuantScenario
|
||||
scen = QlibQuantScenario()
|
||||
with pytest.raises(ValueError, match="tag must be"):
|
||||
scen.background(tag="invalid")
|
||||
|
||||
def test_output_format_invalid_tag_raises(self):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_runtime_environment_by_env",
|
||||
return_value="mock_env"):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_factor_env",
|
||||
return_value=MagicMock()):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_model_env",
|
||||
return_value=MagicMock()):
|
||||
from rdagent.scenarios.qlib.experiment.quant_experiment import QlibQuantScenario
|
||||
scen = QlibQuantScenario()
|
||||
with pytest.raises(ValueError, match="tag must be"):
|
||||
scen.output_format(tag="bad")
|
||||
|
||||
def test_interface_invalid_tag_raises(self):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_runtime_environment_by_env",
|
||||
return_value="mock_env"):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_factor_env",
|
||||
return_value=MagicMock()):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_model_env",
|
||||
return_value=MagicMock()):
|
||||
from rdagent.scenarios.qlib.experiment.quant_experiment import QlibQuantScenario
|
||||
scen = QlibQuantScenario()
|
||||
with pytest.raises(ValueError, match="tag must be"):
|
||||
scen.interface(tag=42)
|
||||
|
||||
def test_simulator_invalid_tag_raises(self):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_runtime_environment_by_env",
|
||||
return_value="mock_env"):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_factor_env",
|
||||
return_value=MagicMock()):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_model_env",
|
||||
return_value=MagicMock()):
|
||||
from rdagent.scenarios.qlib.experiment.quant_experiment import QlibQuantScenario
|
||||
scen = QlibQuantScenario()
|
||||
with pytest.raises(ValueError, match="tag must be"):
|
||||
scen.simulator(tag="unknown")
|
||||
|
||||
def test_get_runtime_environment_invalid_tag_raises(self):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_runtime_environment_by_env",
|
||||
return_value="mock_env"):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_factor_env",
|
||||
return_value=MagicMock()):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_model_env",
|
||||
return_value=MagicMock()):
|
||||
from rdagent.scenarios.qlib.experiment.quant_experiment import QlibQuantScenario
|
||||
scen = QlibQuantScenario()
|
||||
with pytest.raises(ValueError, match="tag must be"):
|
||||
scen.get_runtime_environment(tag="nope")
|
||||
|
||||
def test_get_scenario_all_desc_with_action(self):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_runtime_environment_by_env",
|
||||
return_value="mock_env"):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_factor_env",
|
||||
return_value=MagicMock()):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_model_env",
|
||||
return_value=MagicMock()):
|
||||
from rdagent.scenarios.qlib.experiment.quant_experiment import QlibQuantScenario
|
||||
scen = QlibQuantScenario()
|
||||
desc = scen.get_scenario_all_desc(action="factor")
|
||||
assert "Background" in desc
|
||||
assert "interface" in desc.lower()
|
||||
|
||||
def test_get_scenario_all_desc_simple_background(self):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_runtime_environment_by_env",
|
||||
return_value="mock_env"):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_factor_env",
|
||||
return_value=MagicMock()):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_model_env",
|
||||
return_value=MagicMock()):
|
||||
from rdagent.scenarios.qlib.experiment.quant_experiment import QlibQuantScenario
|
||||
scen = QlibQuantScenario()
|
||||
desc = scen.get_scenario_all_desc(simple_background=True)
|
||||
assert "Background" in desc
|
||||
assert "source" in desc.lower()
|
||||
|
||||
def test_background_tag_factor(self):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_runtime_environment_by_env",
|
||||
return_value="mock_env"):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_factor_env",
|
||||
return_value=MagicMock()):
|
||||
with patch("rdagent.scenarios.qlib.experiment.quant_experiment.get_model_env",
|
||||
return_value=MagicMock()):
|
||||
from rdagent.scenarios.qlib.experiment.quant_experiment import QlibQuantScenario
|
||||
scen = QlibQuantScenario()
|
||||
bg = scen.background(tag="factor")
|
||||
assert "factor" in bg.lower()
|
||||
Reference in New Issue
Block a user