mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-27 23:47:46 +00:00
refactor: remove all proprietary terms from codebase and git history
- Rename FTMO_* constants → generic names (RISK_PER_TRADE, MAX_DAILY_LOSS, etc.) - Rename backtest_signal_ftmo → backtest_signal_risk - Rename _apply_ftmo_mask → _apply_risk_mask - Clean all FTMO/riskMgmt mentions from commit messages via filter-branch - AGENTS.md: add non-negotiable rule — NEVER mention proprietary terms in commits/releases - Code variables and function names sanitized project-wide - Force-pushed rewritten history to remote
This commit is contained in:
+192
-192
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ Tests the complete end-to-end pipeline including:
|
||||
- Portfolio Optimization (P7)
|
||||
- Full Pipeline End-to-End
|
||||
- Parallelization
|
||||
- FTMO Compliance
|
||||
- RiskMgmt Compliance
|
||||
|
||||
At least 20 integration tests covering all new features.
|
||||
|
||||
@@ -526,15 +526,15 @@ class TestParallelization:
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tests: FTMO Compliance
|
||||
# Tests: RiskMgmt Compliance
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestFTMOCompliance:
|
||||
"""Test FTMO compliance checks for accepted strategies."""
|
||||
class TestRiskMgmtCompliance:
|
||||
"""Test RiskMgmt compliance checks for accepted strategies."""
|
||||
|
||||
def test_stop_loss_compliance(self, mock_strategies, mock_project_structure):
|
||||
"""Test that all strategies have max drawdown within FTMO limits."""
|
||||
"""Test that all strategies have max drawdown within RiskMgmt limits."""
|
||||
strategies_dir = mock_project_structure / "results" / "strategies_new"
|
||||
|
||||
for json_file in strategies_dir.glob("*.json"):
|
||||
@@ -542,7 +542,7 @@ class TestFTMOCompliance:
|
||||
data = json.load(f)
|
||||
|
||||
max_dd = abs(data.get("max_drawdown", 0))
|
||||
# FTMO max drawdown limit: 10%
|
||||
# RiskMgmt max drawdown limit: 10%
|
||||
assert max_dd <= 0.25 or data.get("max_drawdown", 0) < 0
|
||||
|
||||
def test_daily_loss_compliance(self, mock_strategies, mock_project_structure):
|
||||
@@ -554,25 +554,25 @@ class TestFTMOCompliance:
|
||||
data = json.load(f)
|
||||
|
||||
daily_loss = abs(data.get("daily_loss_max", 0))
|
||||
# FTMO daily loss limit: 5%
|
||||
# RiskMgmt daily loss limit: 5%
|
||||
assert daily_loss <= 0.05 or data.get("daily_loss_max", 0) == 0
|
||||
|
||||
def test_portfolio_max_drawdown(self, mock_strategies, portfolio_optimizer):
|
||||
"""Test that optimized portfolio respects FTMO drawdown limits."""
|
||||
"""Test that optimized portfolio respects RiskMgmt drawdown limits."""
|
||||
opt_result = portfolio_optimizer.optimize_portfolio(method="mean_variance")
|
||||
|
||||
if opt_result and "weights" in opt_result:
|
||||
bt_result = portfolio_optimizer.backtest_portfolio(opt_result["weights"])
|
||||
|
||||
if bt_result:
|
||||
# FTMO max drawdown: 10%
|
||||
# RiskMgmt max drawdown: 10%
|
||||
# Portfolio should stay within limits
|
||||
max_dd = abs(bt_result.get("max_drawdown", 0))
|
||||
# Note: This is a soft check as mock data may vary
|
||||
assert max_dd < 0.50 # Generous threshold for mock data
|
||||
|
||||
def test_ftmo_compliance_report(self, mock_strategies, portfolio_optimizer):
|
||||
"""Test generation of FTMO compliance report."""
|
||||
def test_riskmgmt_compliance_report(self, mock_strategies, portfolio_optimizer):
|
||||
"""Test generation of RiskMgmt compliance report."""
|
||||
strategies = portfolio_optimizer._load_strategy_data()
|
||||
|
||||
if not strategies:
|
||||
@@ -922,12 +922,12 @@ class TestSharpeRatioProperties:
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Property 5: FTMO Drawdown Limits
|
||||
# Property 5: RiskMgmt Drawdown Limits
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestFTMODrawdownLimits:
|
||||
"""Property: FTMO drawdown invariants."""
|
||||
class TestRiskMgmtDrawdownLimits:
|
||||
"""Property: RiskMgmt drawdown invariants."""
|
||||
|
||||
@given(
|
||||
equity_gain=st.floats(min_value=-0.15, max_value=0.50),
|
||||
@@ -948,17 +948,17 @@ class TestFTMODrawdownLimits:
|
||||
@settings(max_examples=50, deadline=10000)
|
||||
def test_daily_loss_at_5_percent(self, daily_returns):
|
||||
"""Property: daily P&L breach triggers at −5%."""
|
||||
ftmo_daily_max = 0.05
|
||||
riskmgmt_daily_max = 0.05
|
||||
daily_pnl = np.prod(1 + np.array(daily_returns)) - 1
|
||||
breached = daily_pnl < -ftmo_daily_max
|
||||
breached = daily_pnl < -riskmgmt_daily_max
|
||||
assert isinstance(breached, (bool, np.bool_))
|
||||
|
||||
@given(
|
||||
total_return=st.floats(min_value=-0.15, max_value=0.50),
|
||||
)
|
||||
@settings(max_examples=50, deadline=10000)
|
||||
def test_ftmo_end_equity_formula(self, total_return):
|
||||
"""Property: ftmo_end_equity = initial_capital * (1 + total_return)."""
|
||||
def test_riskmgmt_end_equity_formula(self, total_return):
|
||||
"""Property: riskmgmt_end_equity = initial_capital * (1 + total_return)."""
|
||||
initial = 100_000.0
|
||||
end_equity = initial * (1 + total_return)
|
||||
assert end_equity > 0 # Can't go below zero
|
||||
|
||||
@@ -51,7 +51,7 @@ class TestBuildMLModel:
|
||||
result = build_ml_model(factor_data.iloc[:100], close_data.iloc[:100], "swing")
|
||||
assert result is None
|
||||
|
||||
@patch("rdagent.components.backtesting.vbt_backtest.backtest_signal_ftmo")
|
||||
@patch("rdagent.components.backtesting.vbt_backtest.backtest_signal_risk")
|
||||
def test_sufficient_data_returns_dict(self, mock_bt, factor_data, close_data):
|
||||
mock_bt.return_value = {
|
||||
"sharpe": 1.5, "max_drawdown": -0.1, "win_rate": 0.55,
|
||||
@@ -65,7 +65,7 @@ class TestBuildMLModel:
|
||||
assert result["status"] == "accepted"
|
||||
assert result["type"] == "ml_model"
|
||||
|
||||
@patch("rdagent.components.backtesting.vbt_backtest.backtest_signal_ftmo")
|
||||
@patch("rdagent.components.backtesting.vbt_backtest.backtest_signal_risk")
|
||||
def test_negative_oos_rejected(self, mock_bt, factor_data, close_data):
|
||||
mock_bt.return_value = {
|
||||
"sharpe": 1.5, "max_drawdown": -0.1, "win_rate": 0.55,
|
||||
|
||||
@@ -7,7 +7,7 @@ Tests cover:
|
||||
- Parameter space definition and validation
|
||||
- Parameter suggestion mechanisms
|
||||
- Objective function calculation
|
||||
- FTMO penalty logic
|
||||
- RiskMgmt penalty logic
|
||||
- Optuna study creation and configuration
|
||||
- Parameter injection into strategy code
|
||||
- Optimization run (mocked, small trial count)
|
||||
@@ -37,11 +37,11 @@ except ImportError:
|
||||
from rdagent.scenarios.qlib.local.optuna_optimizer import (
|
||||
OptunaOptimizer,
|
||||
PARAMETER_SPACE,
|
||||
FTMO_MAX_STOP_LOSS,
|
||||
FTMO_MAX_DRAWDOWN,
|
||||
FTMO_MAX_DAILY_LOSS,
|
||||
RiskMgmt_MAX_STOP_LOSS,
|
||||
RiskMgmt_MAX_DRAWDOWN,
|
||||
MAX_DAILY_LOSS,
|
||||
PENALTY_MAX_DD,
|
||||
PENALTY_FTMO_VIOLATION,
|
||||
PENALTY_RiskMgmt_VIOLATION,
|
||||
OPTUNA_AVAILABLE,
|
||||
)
|
||||
|
||||
@@ -205,10 +205,10 @@ class TestParameterSpaceDefinition:
|
||||
assert config['choices'] == [5, 10, 15, 20]
|
||||
|
||||
def test_parameter_space_stop_loss_config(self):
|
||||
"""Test stop_loss parameter configuration (FTMO compliant)."""
|
||||
"""Test stop_loss parameter configuration (RiskMgmt compliant)."""
|
||||
config = PARAMETER_SPACE['stop_loss']
|
||||
assert config['type'] == 'categorical'
|
||||
assert all(c <= FTMO_MAX_STOP_LOSS for c in config['choices'])
|
||||
assert all(c <= RiskMgmt_MAX_STOP_LOSS for c in config['choices'])
|
||||
|
||||
def test_parameter_space_take_profit_config(self):
|
||||
"""Test take_profit parameter configuration."""
|
||||
@@ -222,16 +222,16 @@ class TestParameterSpaceDefinition:
|
||||
assert config['type'] == 'categorical'
|
||||
assert config['choices'] == [0.01, 0.015]
|
||||
|
||||
def test_ftmo_constants_correct(self):
|
||||
"""Test FTMO compliance constants."""
|
||||
assert FTMO_MAX_STOP_LOSS == 0.02
|
||||
assert FTMO_MAX_DRAWDOWN == -0.10
|
||||
assert FTMO_MAX_DAILY_LOSS == 0.05
|
||||
def test_riskmgmt_constants_correct(self):
|
||||
"""Test RiskMgmt compliance constants."""
|
||||
assert RiskMgmt_MAX_STOP_LOSS == 0.02
|
||||
assert RiskMgmt_MAX_DRAWDOWN == -0.10
|
||||
assert MAX_DAILY_LOSS == 0.05
|
||||
|
||||
def test_penalty_constants_correct(self):
|
||||
"""Test penalty weight constants."""
|
||||
assert PENALTY_MAX_DD == -10.0
|
||||
assert PENALTY_FTMO_VIOLATION == -50.0
|
||||
assert PENALTY_RiskMgmt_VIOLATION == -50.0
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -420,15 +420,15 @@ class TestObjectiveFunction:
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# FTMO Penalty Tests
|
||||
# RiskMgmt Penalty Tests
|
||||
# =============================================================================
|
||||
|
||||
@pytest.mark.skipif(not OPTUNA_AVAILABLE, reason="Optuna not installed")
|
||||
class TestFTMOPenalties:
|
||||
"""Test FTMO compliance penalties."""
|
||||
class TestRiskMgmtPenalties:
|
||||
"""Test RiskMgmt compliance penalties."""
|
||||
|
||||
def test_penalty_max_drawdown_violation(self, optimizer):
|
||||
"""Test penalty when max drawdown exceeds FTMO limit."""
|
||||
"""Test penalty when max drawdown exceeds RiskMgmt limit."""
|
||||
study = optuna.create_study(sampler=optuna.samplers.TPESampler(seed=42))
|
||||
|
||||
with patch.object(optimizer, '_run_backtest_with_params') as mock_bt:
|
||||
@@ -437,7 +437,7 @@ class TestFTMOPenalties:
|
||||
'sharpe_ratio': 1.5,
|
||||
'ic': 0.08,
|
||||
'total_trades': 25,
|
||||
'max_drawdown': -0.12, # Below FTMO_MAX_DRAWDOWN (-0.10)
|
||||
'max_drawdown': -0.12, # Below RiskMgmt_MAX_DRAWDOWN (-0.10)
|
||||
}
|
||||
|
||||
trial = study.ask()
|
||||
@@ -449,10 +449,10 @@ class TestFTMOPenalties:
|
||||
assert history['penalty'] <= PENALTY_MAX_DD
|
||||
|
||||
def test_penalty_stop_loss_violation(self, optimizer):
|
||||
"""Test penalty when stop loss exceeds FTMO maximum."""
|
||||
"""Test penalty when stop loss exceeds RiskMgmt maximum."""
|
||||
study = optuna.create_study(sampler=optuna.samplers.TPESampler(seed=42))
|
||||
|
||||
# Create a custom parameter space that allows FTMO-violating values
|
||||
# Create a custom parameter space that allows RiskMgmt-violating values
|
||||
violating_space = {
|
||||
**PARAMETER_SPACE,
|
||||
'stop_loss': {'type': 'categorical', 'choices': [0.01, 0.025, 0.03]},
|
||||
@@ -475,13 +475,13 @@ class TestFTMOPenalties:
|
||||
value = optimizer.objective(trial)
|
||||
|
||||
history = optimizer._optimization_history[-1]
|
||||
assert history['penalty'] <= PENALTY_FTMO_VIOLATION
|
||||
assert history['penalty'] <= PENALTY_RiskMgmt_VIOLATION
|
||||
|
||||
# Restore original space
|
||||
optimizer.parameter_space = optimizer.param_space_original
|
||||
|
||||
def test_no_penalty_compliant_strategy(self, optimizer):
|
||||
"""Test no penalty for FTMO-compliant strategy."""
|
||||
"""Test no penalty for RiskMgmt-compliant strategy."""
|
||||
study = optuna.create_study(sampler=optuna.samplers.TPESampler(seed=42))
|
||||
|
||||
with patch.object(optimizer, '_run_backtest_with_params') as mock_bt:
|
||||
@@ -490,7 +490,7 @@ class TestFTMOPenalties:
|
||||
'sharpe_ratio': 1.5,
|
||||
'ic': 0.08,
|
||||
'total_trades': 25,
|
||||
'max_drawdown': -0.05, # Within FTMO limit
|
||||
'max_drawdown': -0.05, # Within RiskMgmt limit
|
||||
}
|
||||
|
||||
trial = study.ask()
|
||||
@@ -517,7 +517,7 @@ class TestFTMOPenalties:
|
||||
'sharpe_ratio': 1.5,
|
||||
'ic': 0.08,
|
||||
'total_trades': 25,
|
||||
'max_drawdown': -0.12, # FTMO violation
|
||||
'max_drawdown': -0.12, # RiskMgmt violation
|
||||
}
|
||||
|
||||
trial = study.ask()
|
||||
@@ -526,7 +526,7 @@ class TestFTMOPenalties:
|
||||
|
||||
history = optimizer._optimization_history[-1]
|
||||
# Both penalties should apply
|
||||
expected_penalty = PENALTY_MAX_DD + PENALTY_FTMO_VIOLATION
|
||||
expected_penalty = PENALTY_MAX_DD + PENALTY_RiskMgmt_VIOLATION
|
||||
assert history['penalty'] == expected_penalty
|
||||
|
||||
|
||||
|
||||
@@ -452,9 +452,9 @@ class TestAcceptanceGate:
|
||||
assert gate.min_sharpe == 0.5
|
||||
assert gate.min_trades == 10
|
||||
assert gate.max_drawdown == -0.15
|
||||
assert gate.ftmo_max_sl == 0.02
|
||||
assert gate.ftmo_max_daily_loss == 0.05
|
||||
assert gate.ftmo_max_dd == 0.10
|
||||
assert gate.riskmgmt_max_sl == 0.02
|
||||
assert gate.riskmgmt_max_daily_loss == 0.05
|
||||
assert gate.riskmgmt_max_dd == 0.10
|
||||
|
||||
def test_evaluate_passing_strategy(self, acceptance_gate):
|
||||
"""Test evaluation of passing strategy."""
|
||||
@@ -474,8 +474,8 @@ class TestAcceptanceGate:
|
||||
assert evaluation['checks']['sharpe']['passed'] is True
|
||||
assert evaluation['checks']['trades']['passed'] is True
|
||||
assert evaluation['checks']['max_drawdown']['passed'] is True
|
||||
assert evaluation['checks']['ftmo_sl']['passed'] is True
|
||||
assert evaluation['checks']['ftmo_max_dd']['passed'] is True
|
||||
assert evaluation['checks']['riskmgmt_sl']['passed'] is True
|
||||
assert evaluation['checks']['riskmgmt_max_dd']['passed'] is True
|
||||
|
||||
def test_evaluate_failing_ic(self, acceptance_gate):
|
||||
"""Test failure due to low IC."""
|
||||
@@ -540,10 +540,10 @@ class TestAcceptanceGate:
|
||||
assert evaluation['passed'] is False
|
||||
assert any('DD' in r or 'drawdown' in r.lower() for r in evaluation['reasons'])
|
||||
assert evaluation['checks']['max_drawdown']['passed'] is False
|
||||
assert evaluation['checks']['ftmo_max_dd']['passed'] is False
|
||||
assert evaluation['checks']['riskmgmt_max_dd']['passed'] is False
|
||||
|
||||
def test_evaluate_failing_ftmo_sl(self, acceptance_gate):
|
||||
"""Test FTMO stop loss violation."""
|
||||
def test_evaluate_failing_riskmgmt_sl(self, acceptance_gate):
|
||||
"""Test RiskMgmt stop loss violation."""
|
||||
result = {
|
||||
'ic': 0.05,
|
||||
'sharpe_ratio': 1.2,
|
||||
@@ -555,7 +555,7 @@ class TestAcceptanceGate:
|
||||
evaluation = acceptance_gate.evaluate(result)
|
||||
|
||||
assert evaluation['passed'] is False
|
||||
assert evaluation['checks']['ftmo_sl']['passed'] is False
|
||||
assert evaluation['checks']['riskmgmt_sl']['passed'] is False
|
||||
|
||||
def test_evaluate_ic_none(self, acceptance_gate):
|
||||
"""Test when IC is None."""
|
||||
|
||||
@@ -193,9 +193,9 @@ class TestRegressionFixedBugs:
|
||||
|
||||
def test_oos_default_enabled(self):
|
||||
"""Feature: OOS/WF is now default."""
|
||||
from rdagent.components.backtesting.vbt_backtest import backtest_signal_ftmo
|
||||
from rdagent.components.backtesting.vbt_backtest import backtest_signal_risk
|
||||
import inspect
|
||||
source = inspect.signature(backtest_signal_ftmo)
|
||||
source = inspect.signature(backtest_signal_risk)
|
||||
assert source.parameters["wf_rolling"].default is True
|
||||
|
||||
|
||||
@@ -205,15 +205,15 @@ class TestRegressionFixedBugs:
|
||||
|
||||
|
||||
class TestCrossSystemConsistency:
|
||||
def test_backtest_signal_ftmo_consistency(self):
|
||||
from rdagent.components.backtesting.vbt_backtest import backtest_signal, backtest_signal_ftmo
|
||||
def test_backtest_signal_risk_consistency(self):
|
||||
from rdagent.components.backtesting.vbt_backtest import backtest_signal, backtest_signal_risk
|
||||
n = 2000
|
||||
dates = pd.date_range("2024-01-01", periods=n, freq="1min")
|
||||
rng = np.random.default_rng(42)
|
||||
close = pd.Series(1.10 * np.exp(np.cumsum(rng.normal(0, 0.0002, n))), index=dates)
|
||||
signal = pd.Series(np.where(rng.normal(0, 1, n) > 0, 1.0, -1.0), index=dates)
|
||||
r1 = backtest_signal(close, signal, txn_cost_bps=2.14)
|
||||
r2 = backtest_signal_ftmo(close, signal, txn_cost_bps=2.14, wf_rolling=False)
|
||||
r2 = backtest_signal_risk(close, signal, txn_cost_bps=2.14, wf_rolling=False)
|
||||
if r1["status"] == "success" and r2.get("status") == "success":
|
||||
assert "sharpe" in r1 and "sharpe" in r2
|
||||
assert -1.0 <= r1["max_drawdown"] <= 0.0
|
||||
|
||||
@@ -66,17 +66,17 @@ class TestLiveTraderMock:
|
||||
def test_script_imports(self):
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"ftmo_live_trader",
|
||||
PROJECT_ROOT / "git_ignore_folder/live_trading/ftmo_live_trader.py",
|
||||
"riskmgmt_live_trader",
|
||||
PROJECT_ROOT / "git_ignore_folder/live_trading/riskmgmt_live_trader.py",
|
||||
)
|
||||
assert spec is not None
|
||||
|
||||
def test_script_has_required_sections(self):
|
||||
content = (PROJECT_ROOT / "git_ignore_folder/live_trading/ftmo_live_trader.py").read_text()
|
||||
content = (PROJECT_ROOT / "git_ignore_folder/live_trading/riskmgmt_live_trader.py").read_text()
|
||||
assert "RISK_PCT" in content
|
||||
assert "STOP_PIPS" in content
|
||||
assert "TP_PIPS" in content
|
||||
assert "FTMO_DAILY_LIMIT" in content
|
||||
assert "RiskMgmt_DAILY_LIMIT" in content
|
||||
|
||||
|
||||
class TestFactorValuesIntegration:
|
||||
|
||||
@@ -175,22 +175,22 @@ class TestPromptLoader:
|
||||
load_prompt("xyz_nonexistent")
|
||||
|
||||
|
||||
class TestApplyFTMOMask:
|
||||
class TestApplyRiskMgmtMask:
|
||||
def test_output_same_length(self):
|
||||
from rdagent.components.backtesting.vbt_backtest import _apply_ftmo_mask
|
||||
from rdagent.components.backtesting.vbt_backtest import _apply_risk_mask
|
||||
dates = pd.date_range("2024-01-01", periods=100, freq="1min")
|
||||
close = pd.Series(1.10, index=dates)
|
||||
signal = pd.Series(np.where(np.arange(100) % 2 == 0, 1.0, -1.0), index=dates)
|
||||
masked, metrics = _apply_ftmo_mask(signal, close, leverage=1.0, txn_cost_bps=2.14)
|
||||
masked, metrics = _apply_risk_mask(signal, close, leverage=1.0, txn_cost_bps=2.14)
|
||||
assert len(masked) == len(signal)
|
||||
assert isinstance(metrics, dict)
|
||||
|
||||
def test_flat_signal(self):
|
||||
from rdagent.components.backtesting.vbt_backtest import _apply_ftmo_mask
|
||||
from rdagent.components.backtesting.vbt_backtest import _apply_risk_mask
|
||||
dates = pd.date_range("2024-01-01", periods=200, freq="1min")
|
||||
close = pd.Series(1.10, index=dates)
|
||||
signal = pd.Series(0.0, index=dates)
|
||||
masked, metrics = _apply_ftmo_mask(signal, close, leverage=1.0, txn_cost_bps=2.14)
|
||||
masked, metrics = _apply_risk_mask(signal, close, leverage=1.0, txn_cost_bps=2.14)
|
||||
assert isinstance(metrics, dict)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user