Files
foxhunt/crates/ml-features/src/config.rs
jgrusewski 0c9f368d24 cleanup: remove ALL 32 enable_* feature flags — all features unconditional
Remove 8 enable_* from FeatureConfig (ml-features) and 24 from
DQNHyperparameters (ml). All features are always active — no boolean
toggles, no dead conditional branches, no false impression of optionality.

FeatureConfig reduced to single `phase: FeaturePhase` field.
DQNHyperparameters loses 24 fields, downstream conditionals collapsed.
TOML configs cleaned of all enable_* lines.

16 files changed, -461/+181 lines.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 10:56:20 +02:00

565 lines
19 KiB
Rust

//! Feature Configuration for Progressive ML Feature Engineering
//!
//! This module defines the feature set configuration across Wave 19 phases:
//! - Wave A: 26 features (real-time inference baseline)
//! - Wave B: 36 features (adds alternative bars + volume features)
//! - Wave C: 201 features (adds fractional diff + meta-labeling)
//! - Wave D: 225 features (adds regime detection + adaptive strategies)
//!
//! ## Architecture
//!
//! `FeatureConfig` provides a single source of truth for feature extraction
//! across both training (`DbnSequenceLoader`) and inference (`ProductionFeatureExtractorAdapter`).
//! This eliminates the previous padding bug (256 features via 25x repetition).
//!
//! ## Usage
//!
//! ```rust
//! use ml::features::config::{FeatureConfig, FeaturePhase};
//!
//! // Wave A: 26 features (baseline)
//! let config = FeatureConfig::wave_a();
//! assert_eq!(config.feature_count(), 26);
//!
//! // Wave B: 36 features (alternative bars)
//! let config = FeatureConfig::wave_b();
//! assert_eq!(config.feature_count(), 36);
//!
//! // Wave C: 201 features (advanced)
//! let config = FeatureConfig::wave_c();
//! assert_eq!(config.feature_count(), 201);
//!
//! // Wave D: 225 features (regime detection)
//! let config = FeatureConfig::wave_d();
//! assert_eq!(config.feature_count(), 225);
//! ```
use serde::{Deserialize, Serialize};
/// Feature category classification for Wave D features
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FeatureCategory {
/// OHLCV baseline features
OHLCV,
/// Technical indicators
TechnicalIndicators,
/// Microstructure features
Microstructure,
/// Regime detection features
RegimeDetection,
/// Adaptive strategy features
AdaptiveStrategy,
}
/// Individual feature definition with index, name, and category
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Feature {
/// Feature index (0-224 for Wave D)
pub index: usize,
/// Feature name
pub name: String,
/// Feature category
pub category: FeatureCategory,
}
impl Feature {
/// Create a new feature definition
pub const fn new(index: usize, _name: &'static str, category: FeatureCategory) -> Self {
Self {
index,
name: String::new(), // Will be set via constructor
category,
}
}
}
/// Wave D feature definitions (indices 201-224)
///
/// This provides the detailed specification for all 24 Wave D features
/// that extend Wave C's 201 features to reach 225 total features.
pub fn wave_d_features() -> Vec<Feature> {
vec![
// CUSUM Statistics (indices 201-210, 10 features)
Feature {
index: 201,
name: "cusum_s_plus_normalized".to_owned(),
category: FeatureCategory::RegimeDetection,
},
Feature {
index: 202,
name: "cusum_s_minus_normalized".to_owned(),
category: FeatureCategory::RegimeDetection,
},
Feature {
index: 203,
name: "cusum_break_indicator".to_owned(),
category: FeatureCategory::RegimeDetection,
},
Feature {
index: 204,
name: "cusum_direction".to_owned(),
category: FeatureCategory::RegimeDetection,
},
Feature {
index: 205,
name: "cusum_time_since_break".to_owned(),
category: FeatureCategory::RegimeDetection,
},
Feature {
index: 206,
name: "cusum_frequency".to_owned(),
category: FeatureCategory::RegimeDetection,
},
Feature {
index: 207,
name: "cusum_positive_count".to_owned(),
category: FeatureCategory::RegimeDetection,
},
Feature {
index: 208,
name: "cusum_negative_count".to_owned(),
category: FeatureCategory::RegimeDetection,
},
Feature {
index: 209,
name: "cusum_intensity".to_owned(),
category: FeatureCategory::RegimeDetection,
},
Feature {
index: 210,
name: "cusum_drift_ratio".to_owned(),
category: FeatureCategory::RegimeDetection,
},
// ADX & Directional Indicators (indices 211-215, 5 features)
Feature {
index: 211,
name: "adx".to_owned(),
category: FeatureCategory::RegimeDetection,
},
Feature {
index: 212,
name: "plus_di".to_owned(),
category: FeatureCategory::RegimeDetection,
},
Feature {
index: 213,
name: "minus_di".to_owned(),
category: FeatureCategory::RegimeDetection,
},
Feature {
index: 214,
name: "dx".to_owned(),
category: FeatureCategory::RegimeDetection,
},
Feature {
index: 215,
name: "trend_classification".to_owned(),
category: FeatureCategory::RegimeDetection,
},
// Regime Transition Probabilities (indices 216-220, 5 features)
Feature {
index: 216,
name: "regime_stability".to_owned(),
category: FeatureCategory::RegimeDetection,
},
Feature {
index: 217,
name: "most_likely_next_regime".to_owned(),
category: FeatureCategory::RegimeDetection,
},
Feature {
index: 218,
name: "regime_entropy".to_owned(),
category: FeatureCategory::RegimeDetection,
},
Feature {
index: 219,
name: "regime_expected_duration".to_owned(),
category: FeatureCategory::RegimeDetection,
},
Feature {
index: 220,
name: "regime_change_probability".to_owned(),
category: FeatureCategory::RegimeDetection,
},
// Adaptive Strategy Metrics (indices 221-224, 4 features)
Feature {
index: 221,
name: "position_multiplier".to_owned(),
category: FeatureCategory::AdaptiveStrategy,
},
Feature {
index: 222,
name: "stop_loss_multiplier".to_owned(),
category: FeatureCategory::AdaptiveStrategy,
},
Feature {
index: 223,
name: "regime_conditioned_sharpe".to_owned(),
category: FeatureCategory::AdaptiveStrategy,
},
Feature {
index: 224,
name: "risk_budget_utilization".to_owned(),
category: FeatureCategory::AdaptiveStrategy,
},
]
}
/// Feature extraction configuration for Wave 19 progressive engineering
///
/// All feature groups are always active. The `phase` field determines
/// the total feature count (Wave A=26, B=36, C=201, D=225).
/// Used by both training (`DbnSequenceLoader`) and inference (`ProductionFeatureExtractorAdapter`).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureConfig {
/// Feature engineering phase (Wave A/B/C/D)
pub phase: FeaturePhase,
}
/// Feature engineering phase (Wave 19 progressive implementation)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FeaturePhase {
/// Wave A: 26 features (baseline technical indicators + microstructure)
WaveA,
/// Wave B: 36 features (adds alternative bars + barrier optimization)
WaveB,
/// Wave C: 201 features (adds fractional diff + regime detection)
WaveC,
/// Wave D: 225 features (adds Wave D regime detection + adaptive strategies)
WaveD,
}
impl Default for FeatureConfig {
/// Default configuration: Wave A (26 features)
fn default() -> Self {
Self::wave_a()
}
}
impl FeatureConfig {
/// Wave A configuration: 26 features (baseline)
///
/// Feature breakdown:
/// - OHLCV: 5 features (open, high, low, close, volume)
/// - Technical indicators: 21 features (RSI, MACD, Bollinger, ATR, ADX, CCI, Stochastic, EMAs, etc.)
///
/// Total: 26 features
pub const fn wave_a() -> Self {
Self {
phase: FeaturePhase::WaveA,
}
}
/// Wave B configuration: 36 features (alternative bars)
///
/// Feature breakdown:
/// - Wave A: 26 features (OHLCV + technical indicators)
/// - Alternative bars: 10 features (dollar, volume, tick, run, imbalance bars)
///
/// Total: 36 features
pub const fn wave_b() -> Self {
Self {
phase: FeaturePhase::WaveB,
}
}
/// Wave C configuration: 201 features (advanced)
///
/// Feature breakdown:
/// - Base: 39 features (OHLCV 5 + Technical 21 + Microstructure 3 + Alternative bars 10)
/// - Wave C additions: 162 features (fractional differentiation, regime detection, statistical features)
///
/// Total: 201 features (indices 0-200)
pub const fn wave_c() -> Self {
Self {
phase: FeaturePhase::WaveC,
}
}
/// Wave D configuration: 225 features (regime detection + adaptive strategies)
///
/// Feature breakdown:
/// - Wave C: 201 features (indices 0-200)
/// - Wave D additions: 24 features (indices 201-224)
/// - CUSUM Statistics: 10 features (indices 201-210)
/// - ADX & Directional Indicators: 5 features (indices 211-215)
/// - Regime Transition Probabilities: 5 features (indices 216-220)
/// - Adaptive Strategy Metrics: 4 features (indices 221-224)
///
/// Total: 225 features (indices 0-224)
pub const fn wave_d() -> Self {
Self {
phase: FeaturePhase::WaveD,
}
}
/// Calculate total feature count based on the feature phase.
///
/// All feature groups within a phase are always active.
/// - Wave A: 26 (OHLCV 5 + Technical 21)
/// - Wave B: 36 (+ Alternative bars 10)
/// - Wave C: 201 (+ Microstructure 3 + Fractional diff 162)
/// - Wave D: 225 (+ Wave D regime 24)
pub const fn feature_count(&self) -> usize {
match self.phase {
FeaturePhase::WaveA => 26, // OHLCV (5) + Technical (21)
FeaturePhase::WaveB => 36, // + Alternative bars (10)
FeaturePhase::WaveC => 201, // + Microstructure (3) + Fractional diff (162)
FeaturePhase::WaveD => 225, // + Wave D regime (24)
}
}
/// Get feature indices for each group based on the phase.
///
/// Returns (`start_idx`, `end_idx`) for each feature group active in the current phase.
/// This allows data loaders to know which indices correspond to which features.
pub fn feature_indices(&self) -> FeatureIndices {
let mut indices = FeatureIndices::default();
let mut current_idx = 0;
// OHLCV: always present (all phases)
indices.ohlcv = Some((current_idx, current_idx + 5));
current_idx += 5;
// Technical indicators: always present (all phases)
indices.technical_indicators = Some((current_idx, current_idx + 21));
current_idx += 21;
if matches!(self.phase, FeaturePhase::WaveB | FeaturePhase::WaveC | FeaturePhase::WaveD) {
// Alternative bars: Wave B+
indices.alternative_bars = Some((current_idx, current_idx + 10));
current_idx += 10;
}
if matches!(self.phase, FeaturePhase::WaveC | FeaturePhase::WaveD) {
// Microstructure: Wave C+
indices.microstructure = Some((current_idx, current_idx + 3));
current_idx += 3;
// Fractional diff: Wave C+ (162 features to reach 201 total)
indices.fractional_diff = Some((current_idx, current_idx + 162));
current_idx += 162;
}
if matches!(self.phase, FeaturePhase::WaveD) {
// Wave D regime features (24)
indices.wave_d_regime = Some((current_idx, current_idx + 24));
}
indices
}
/// Check if a specific feature group is enabled.
/// All feature groups within the current phase are always active.
pub const fn is_enabled(&self, group: FeatureGroup) -> bool {
match group {
// OHLCV and Technical Indicators are always active in all phases
FeatureGroup::OHLCV | FeatureGroup::TechnicalIndicators => true,
// Alternative bars and barrier optimization: Wave B+
FeatureGroup::AlternativeBars | FeatureGroup::BarrierOptimization => {
matches!(self.phase, FeaturePhase::WaveB | FeaturePhase::WaveC | FeaturePhase::WaveD)
}
// Microstructure, fractional diff, regime detection: Wave C+
FeatureGroup::Microstructure | FeatureGroup::FractionalDiff | FeatureGroup::RegimeDetection => {
matches!(self.phase, FeaturePhase::WaveC | FeaturePhase::WaveD)
}
// Wave D regime: only Wave D
FeatureGroup::WaveDRegime => matches!(self.phase, FeaturePhase::WaveD),
}
}
/// Get Wave D feature definitions (indices 201-224)
///
/// Returns a vector of all 24 Wave D features with their indices,
/// names, and categories when the phase is Wave D. Returns empty
/// for earlier phases.
pub fn get_wave_d_features(&self) -> Vec<Feature> {
if matches!(self.phase, FeaturePhase::WaveD) {
wave_d_features()
} else {
vec![]
}
}
}
/// Feature group classification
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FeatureGroup {
/// OHLCV features (5)
OHLCV,
/// Technical indicators (21)
TechnicalIndicators,
/// Microstructure features (3)
Microstructure,
/// Alternative bar features (10)
AlternativeBars,
/// Barrier optimization (labels, not features)
BarrierOptimization,
/// Fractional differentiation (20)
FractionalDiff,
/// Regime detection (10)
RegimeDetection,
/// Wave D regime detection (24)
WaveDRegime,
}
/// Feature index ranges for each group
///
/// Provides (`start_idx`, `end_idx`) for each feature group to allow
/// data loaders and models to identify which indices correspond to which features.
#[derive(Debug, Clone, Default)]
pub struct FeatureIndices {
/// OHLCV indices (5 features)
pub ohlcv: Option<(usize, usize)>,
/// Technical indicator indices (21 features)
pub technical_indicators: Option<(usize, usize)>,
/// Microstructure indices (3 features)
pub microstructure: Option<(usize, usize)>,
/// Alternative bar indices (10 features)
pub alternative_bars: Option<(usize, usize)>,
/// Fractional differentiation indices (20 features)
pub fractional_diff: Option<(usize, usize)>,
/// Regime detection indices (10 features)
pub regime_detection: Option<(usize, usize)>,
/// Wave D regime detection indices (24 features, indices 201-224)
pub wave_d_regime: Option<(usize, usize)>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wave_a_config() {
let config = FeatureConfig::wave_a();
assert_eq!(config.phase, FeaturePhase::WaveA);
assert_eq!(config.feature_count(), 26);
}
#[test]
fn test_wave_b_config() {
let config = FeatureConfig::wave_b();
assert_eq!(config.phase, FeaturePhase::WaveB);
assert_eq!(config.feature_count(), 36);
}
#[test]
fn test_wave_c_config() {
let config = FeatureConfig::wave_c();
assert_eq!(config.phase, FeaturePhase::WaveC);
assert_eq!(config.feature_count(), 201); // Wave C has exactly 201 features
}
#[test]
fn test_wave_d_config() {
let config = FeatureConfig::wave_d();
assert_eq!(config.phase, FeaturePhase::WaveD);
assert_eq!(config.feature_count(), 225); // Wave D has exactly 225 features
}
#[test]
fn test_wave_d_features() {
let features = wave_d_features();
assert_eq!(features.len(), 24);
// Verify indices are correct (201-224)
assert_eq!(features[0].index, 201);
assert_eq!(features[23].index, 224);
// Verify CUSUM features (10)
let cusum_features: Vec<_> = features
.iter()
.filter(|f| f.index >= 201 && f.index <= 210)
.collect();
assert_eq!(cusum_features.len(), 10);
// Verify ADX features (5)
let adx_features: Vec<_> = features
.iter()
.filter(|f| f.index >= 211 && f.index <= 215)
.collect();
assert_eq!(adx_features.len(), 5);
// Verify transition features (5)
let transition_features: Vec<_> = features
.iter()
.filter(|f| f.index >= 216 && f.index <= 220)
.collect();
assert_eq!(transition_features.len(), 5);
// Verify adaptive features (4)
let adaptive_features: Vec<_> = features
.iter()
.filter(|f| f.index >= 221 && f.index <= 224)
.collect();
assert_eq!(adaptive_features.len(), 4);
}
#[test]
fn test_feature_indices_wave_a() {
let config = FeatureConfig::wave_a();
let indices = config.feature_indices();
assert_eq!(indices.ohlcv, Some((0, 5)));
assert_eq!(indices.technical_indicators, Some((5, 26)));
assert_eq!(indices.microstructure, None); // Not in Wave A phase
assert_eq!(indices.alternative_bars, None); // Not in Wave A phase
}
#[test]
fn test_feature_indices_wave_b() {
let config = FeatureConfig::wave_b();
let indices = config.feature_indices();
assert_eq!(indices.ohlcv, Some((0, 5)));
assert_eq!(indices.technical_indicators, Some((5, 26)));
assert_eq!(indices.alternative_bars, Some((26, 36)));
}
#[test]
fn test_feature_indices_wave_d() {
let config = FeatureConfig::wave_d();
let indices = config.feature_indices();
// Verify Wave D indices exist and are at the end
assert!(indices.wave_d_regime.is_some());
let (start, end) = indices.wave_d_regime.unwrap();
assert_eq!(end - start, 24); // 24 Wave D features
// Wave D features should start after Wave C features (201+)
assert!(start >= 201);
}
#[test]
fn test_is_enabled() {
let config = FeatureConfig::wave_a();
assert!(config.is_enabled(FeatureGroup::OHLCV));
assert!(config.is_enabled(FeatureGroup::TechnicalIndicators));
assert!(!config.is_enabled(FeatureGroup::AlternativeBars)); // Not in Wave A
assert!(!config.is_enabled(FeatureGroup::WaveDRegime)); // Not in Wave A
let config = FeatureConfig::wave_d();
assert!(config.is_enabled(FeatureGroup::WaveDRegime));
assert!(config.is_enabled(FeatureGroup::AlternativeBars));
assert!(config.is_enabled(FeatureGroup::FractionalDiff));
}
#[test]
fn test_get_wave_d_features() {
let config = FeatureConfig::wave_d();
let features = config.get_wave_d_features();
assert_eq!(features.len(), 24);
let config = FeatureConfig::wave_c();
let features = config.get_wave_d_features();
assert_eq!(features.len(), 0); // Wave C phase doesn't include Wave D features
}
#[test]
fn test_default_is_wave_a() {
let config = FeatureConfig::default();
assert_eq!(config.phase, FeaturePhase::WaveA);
assert_eq!(config.feature_count(), 26);
}
}