From 91b7eaa340acd06dde04bd68713d1eb3c5b8aa90 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 22:05:11 +0000 Subject: [PATCH 1/3] Initial plan From f2f1e5c23144fa44d449968cdd4583b81a4d4eb8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 22:09:24 +0000 Subject: [PATCH 2/3] feat: unify direction threshold/lookahead defaults and fix InferenceConfig - Create src/core/constants.py with DIRECTION_DEFAULTS and INFERENCE_DEFAULTS - Update 4 locations to use DIRECTION_DEFAULTS (training.py, modular_data_loaders.py x2, commands.py) - Fix InferenceConfig.min_tcn_probability from 0.55 to 0.60 (matches YAML config) - Update 2 fallback locations in modular_inference.py to use INFERENCE_DEFAULTS - All defaults now match config/config_improved_H1.yaml authoritative values Co-authored-by: Raynergy-svg <82431565+Raynergy-svg@users.noreply.github.com> --- cli/commands.py | 5 +++-- cli/training.py | 5 +++-- src/core/constants.py | 36 ++++++++++++++++++++++++++++++++ src/core/modular_data_loaders.py | 10 +++++---- src/core/modular_inference.py | 12 ++++++----- 5 files changed, 55 insertions(+), 13 deletions(-) create mode 100644 src/core/constants.py diff --git a/cli/commands.py b/cli/commands.py index 884e603..631c57a 100644 --- a/cli/commands.py +++ b/cli/commands.py @@ -21,6 +21,7 @@ from cli.tf_config import _configure_tf_metal from src.utils import load_config +from src.core.constants import DIRECTION_DEFAULTS logger = logging.getLogger(__name__) @@ -2688,8 +2689,8 @@ def _buddy_test_modular_ensemble( model_meta = json.loads(model_meta_path.read_text()) if model_meta_path.exists() else {} model_cfg = model_meta.get("config", {}) - threshold_pct = model_cfg.get("direction_threshold", 0.0015) # Default matches training - lookahead = model_cfg.get("direction_lookahead", 24) + threshold_pct = model_cfg.get("direction_threshold", DIRECTION_DEFAULTS['threshold']) + lookahead = model_cfg.get("direction_lookahead", DIRECTION_DEFAULTS['lookahead']) console.print(f"[dim]Using MODEL training settings: threshold={threshold_pct*100:.2f}%, lookahead={lookahead}[/dim]") diff --git a/cli/training.py b/cli/training.py index a5bcde2..2702503 100644 --- a/cli/training.py +++ b/cli/training.py @@ -29,6 +29,7 @@ from src.utils import load_config from src.utils.seed_manager import set_global_seed, get_global_seed +from src.core.constants import DIRECTION_DEFAULTS # ── String constants (SonarQube duplicate-literal fix) ────────────── _STYLE_HEADER = "bold cyan" @@ -817,8 +818,8 @@ def _train_ensemble_models( transformer_cfg = cfg.get("transformer", {}) use_transformer = transformer_cfg.get("use_transformer", True) use_regime = transformer_cfg.get("use_regime", False) - direction_threshold = cfg.get("direction_threshold", 0.005) - direction_lookahead = cfg.get("direction_lookahead", 12) + direction_threshold = cfg.get("direction_threshold", DIRECTION_DEFAULTS['threshold']) + direction_lookahead = cfg.get("direction_lookahead", DIRECTION_DEFAULTS['lookahead']) regime_lookback = transformer_cfg.get("regime_lookback", 20) regime_lookahead = transformer_cfg.get("regime_lookahead", 12) diff --git a/src/core/constants.py b/src/core/constants.py new file mode 100644 index 0000000..c3cca74 --- /dev/null +++ b/src/core/constants.py @@ -0,0 +1,36 @@ +""" +Shared constants for ML Engine configuration. + +This module defines authoritative defaults that match config/config_improved_H1.yaml +to prevent configuration drift across the codebase. +""" + +# ============================================================================= +# DIRECTION LABELING DEFAULTS (H1 TIMEFRAME) +# ============================================================================= +# These values match config/config_improved_H1.yaml: +# direction_threshold: 0.003 # Min 0.3% move for clear signal +# direction_lookahead: 24 # 24 hours lookahead +# +# Used by: +# - cli/training.py (training orchestration) +# - src/core/modular_data_loaders.py (data loading functions) +# - cli/commands.py (backtest evaluation) + +DIRECTION_DEFAULTS = { + 'threshold': 0.003, # 0.3% minimum price move for clear signal + 'lookahead': 24, # 24 H1 bars = 24 hours lookahead +} + + +# ============================================================================= +# INFERENCE GATE DEFAULTS +# ============================================================================= +# These values match config/config_improved_H1.yaml inference section + +INFERENCE_DEFAULTS = { + 'min_tcn_probability': 0.60, # 60% minimum confidence (not coin-flip) + 'min_confidence': 50.0, # ADX-based trend strength threshold + 'min_momentum': 0.20, # Momentum percentile threshold + 'max_drawdown_pct': 0.025, # 2.5% max expected drawdown +} diff --git a/src/core/modular_data_loaders.py b/src/core/modular_data_loaders.py index e1a84d0..6967b32 100644 --- a/src/core/modular_data_loaders.py +++ b/src/core/modular_data_loaders.py @@ -30,6 +30,8 @@ import numpy as np import pandas as pd +from src.core.constants import DIRECTION_DEFAULTS + logger = logging.getLogger(__name__) @@ -1678,8 +1680,8 @@ def load_regime_data( def load_direction_data( df: pd.DataFrame, split: Tuple[float, float, float] = (0.7, 0.2, 0.1), - lookahead: int = 6, - threshold: float = 0.001, # 0.1% minimum move (reduced from 0.5% to include more samples) + lookahead: int = DIRECTION_DEFAULTS['lookahead'], + threshold: float = DIRECTION_DEFAULTS['threshold'], locked_feature_names: Optional[List[str]] = None, ) -> Dict[str, np.ndarray]: """ @@ -3663,8 +3665,8 @@ def validate_no_leakage( def load_all_modular_data( df: pd.DataFrame, split: Tuple[float, float, float] = (0.7, 0.2, 0.1), - direction_threshold: float = 0.005, - direction_lookahead: int = 6, + direction_threshold: float = DIRECTION_DEFAULTS['threshold'], + direction_lookahead: int = DIRECTION_DEFAULTS['lookahead'], use_regime: bool = False, regime_lookback: int = 20, regime_lookahead: int = 12, diff --git a/src/core/modular_inference.py b/src/core/modular_inference.py index 70a7d23..9751222 100644 --- a/src/core/modular_inference.py +++ b/src/core/modular_inference.py @@ -45,6 +45,8 @@ import numpy as np import pandas as pd +from .constants import INFERENCE_DEFAULTS + # Import normalized feature computation from data loaders from .modular_data_loaders import ( compute_normalized_features, @@ -409,8 +411,8 @@ class InferenceConfig: min_confidence: float = 50.0 # 0-100 scale (tightened from 45) # Direction-confidence gate (Transformer/primary direction model) - # 0.55 means require >=55% (or <=45%) to consider the direction actionable. - min_tcn_probability: float = 0.55 + # 0.60 means require >=60% (or <=40%) to consider the direction actionable. + min_tcn_probability: float = INFERENCE_DEFAULTS['min_tcn_probability'] # Momentum gate - median momentum is 0.3, so 0.20 catches bottom 40% min_momentum: float = 0.20 # 0-1 scale (tightened from 0.15) @@ -3750,8 +3752,8 @@ def predict( # === TRANSFORMER CONFIDENCE GATE (direction confidence) === # Require reasonable confidence in direction prediction. # Prefer config-driven threshold so this can be tuned without code changes. - # Example: min_tcn_probability=0.55 means require >=55% (or <=45%). - min_dir_prob = float(getattr(self.config, 'min_tcn_probability', 0.55)) + # Example: min_tcn_probability=0.60 means require >=60% (or <=40%). + min_dir_prob = float(getattr(self.config, 'min_tcn_probability', INFERENCE_DEFAULTS['min_tcn_probability'])) min_dir_prob = max(0.5, min(0.99, min_dir_prob)) direction_confidence_gate_passed = ( tcn_probability >= min_dir_prob or tcn_probability <= (1.0 - min_dir_prob) @@ -3940,7 +3942,7 @@ def predict( if not volatility_gate_passed: reasons.append(f"low_volatility_regime({volatility_regime_name or 'N/A'})") if not direction_confidence_gate_passed: - min_dir_prob = float(getattr(self.config, 'min_tcn_probability', 0.55)) + min_dir_prob = float(getattr(self.config, 'min_tcn_probability', INFERENCE_DEFAULTS['min_tcn_probability'])) min_dir_prob = max(0.5, min(0.99, min_dir_prob)) reasons.append(f"weak_direction_conf({tcn_probability:.2f}<{min_dir_prob:.2f})") if self.use_regime: From fb066694e696fb1515913d2ad588a968ecdcc684 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 22:10:11 +0000 Subject: [PATCH 3/3] test: add comprehensive tests for unified configuration defaults - Test DIRECTION_DEFAULTS and INFERENCE_DEFAULTS are correctly defined - Verify constants match config/config_improved_H1.yaml - Test InferenceConfig uses INFERENCE_DEFAULTS - Test function signatures use DIRECTION_DEFAULTS for default parameters - All tests verify consistency between code and YAML config Co-authored-by: Raynergy-svg <82431565+Raynergy-svg@users.noreply.github.com> --- tests/test_unified_defaults.py | 97 ++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 tests/test_unified_defaults.py diff --git a/tests/test_unified_defaults.py b/tests/test_unified_defaults.py new file mode 100644 index 0000000..fc07256 --- /dev/null +++ b/tests/test_unified_defaults.py @@ -0,0 +1,97 @@ +""" +Test suite for unified configuration defaults. + +Verifies that: +1. Constants module defines correct values +2. All code locations use the shared constants +3. Values match config/config_improved_H1.yaml +""" + +import pytest +import yaml +from pathlib import Path + + +def test_direction_defaults_exist(): + """Test that DIRECTION_DEFAULTS constant is defined correctly.""" + from src.core.constants import DIRECTION_DEFAULTS + + assert 'threshold' in DIRECTION_DEFAULTS + assert 'lookahead' in DIRECTION_DEFAULTS + assert DIRECTION_DEFAULTS['threshold'] == 0.003 + assert DIRECTION_DEFAULTS['lookahead'] == 24 + + +def test_inference_defaults_exist(): + """Test that INFERENCE_DEFAULTS constant is defined correctly.""" + from src.core.constants import INFERENCE_DEFAULTS + + assert 'min_tcn_probability' in INFERENCE_DEFAULTS + assert 'min_confidence' in INFERENCE_DEFAULTS + assert 'min_momentum' in INFERENCE_DEFAULTS + assert 'max_drawdown_pct' in INFERENCE_DEFAULTS + + assert INFERENCE_DEFAULTS['min_tcn_probability'] == 0.60 + assert INFERENCE_DEFAULTS['min_confidence'] == 50.0 + assert INFERENCE_DEFAULTS['min_momentum'] == 0.20 + assert INFERENCE_DEFAULTS['max_drawdown_pct'] == 0.025 + + +def test_constants_match_yaml_config(): + """Test that constants match config/config_improved_H1.yaml.""" + from src.core.constants import DIRECTION_DEFAULTS, INFERENCE_DEFAULTS + + config_path = Path(__file__).parent.parent / 'config' / 'config_improved_H1.yaml' + with open(config_path, 'r') as f: + config = yaml.safe_load(f) + + # Check direction defaults + assert DIRECTION_DEFAULTS['threshold'] == config['direction_threshold'] + assert DIRECTION_DEFAULTS['lookahead'] == config['direction_lookahead'] + + # Check inference defaults + inference_config = config.get('inference', {}) + assert INFERENCE_DEFAULTS['min_tcn_probability'] == inference_config.get('min_tcn_probability') + assert INFERENCE_DEFAULTS['min_confidence'] == inference_config.get('min_confidence') + assert INFERENCE_DEFAULTS['min_momentum'] == inference_config.get('min_momentum') + assert INFERENCE_DEFAULTS['max_drawdown_pct'] == inference_config.get('max_drawdown_pct') + + +def test_inference_config_uses_constant(): + """Test that InferenceConfig uses INFERENCE_DEFAULTS for min_tcn_probability.""" + from src.core.modular_inference import InferenceConfig + from src.core.constants import INFERENCE_DEFAULTS + + config = InferenceConfig() + assert config.min_tcn_probability == INFERENCE_DEFAULTS['min_tcn_probability'] + assert config.min_tcn_probability == 0.60 + + +def test_load_direction_data_signature(): + """Test that load_direction_data function signature uses constants.""" + from src.core.modular_data_loaders import load_direction_data + from src.core.constants import DIRECTION_DEFAULTS + import inspect + + sig = inspect.signature(load_direction_data) + + # Check default values in signature + assert sig.parameters['lookahead'].default == DIRECTION_DEFAULTS['lookahead'] + assert sig.parameters['threshold'].default == DIRECTION_DEFAULTS['threshold'] + + +def test_load_all_modular_data_signature(): + """Test that load_all_modular_data function signature uses constants.""" + from src.core.modular_data_loaders import load_all_modular_data + from src.core.constants import DIRECTION_DEFAULTS + import inspect + + sig = inspect.signature(load_all_modular_data) + + # Check default values in signature + assert sig.parameters['direction_threshold'].default == DIRECTION_DEFAULTS['threshold'] + assert sig.parameters['direction_lookahead'].default == DIRECTION_DEFAULTS['lookahead'] + + +if __name__ == '__main__': + pytest.main([__file__, '-v'])