# AGENT WIRE-14: Paper Trading Executor Wave D Integration Status **Agent**: WIRE-14 **Mission**: Verify paper trading executor uses Wave D features and adaptive sizing **Status**: ⚠️ PARTIAL INTEGRATION - Missing Wave D Features **Priority**: HIGH - Paper trading must test Wave D before live deployment **Date**: 2025-10-19 --- ## Executive Summary The paper trading executor (`services/trading_service/src/paper_trading_executor.rs`) currently uses `SharedMLStrategy` but is **NOT configured for Wave D features**. Critical gaps identified: 1. ✅ Uses `SharedMLStrategy` (ONE SINGLE SYSTEM architecture) 2. ❌ **NO Wave D feature configuration** - Uses hardcoded defaults (20 lookback, 0.6 confidence) 3. ❌ **NO regime state queries** - Does not check `regime_states` table 4. ❌ **NO adaptive position sizing** - Uses fixed 1.0 contract size 5. ⚠️ **Kelly Criterion mentioned but not implemented** (line 569 comment only) **Risk**: Paper trading will test Wave C baseline (201 features) instead of Wave D (225 features + regime detection). --- ## Code Analysis ### 1. ML Strategy Initialization **File**: `services/trading_service/src/paper_trading_executor.rs` **Lines**: 154-157 ```rust pub fn new(db_pool: PgPool, config: PaperTradingConfig) -> Self { // Initialize with shared ML strategy (default configuration) let ml_strategy = SharedMLStrategy::new(20, 0.6); // ^^^ HARDCODED: 20 lookback, 0.6 confidence - NO Wave D config ``` **Issue**: `SharedMLStrategy::new()` does NOT accept `FeatureConfig` parameter. The constructor signature is: ```rust pub fn new(lookback_periods: usize, min_confidence_threshold: f64) -> Self ``` **Missing**: No way to pass `FeatureConfig::wave_d()` to enable 225-feature extraction. --- ### 2. Position Sizing Logic **File**: `services/trading_service/src/paper_trading_executor.rs` **Lines**: 567-575 ```rust fn calculate_position_size(&self, _prediction: &PendingPrediction) -> Result { // Simple fixed position size for paper trading // In production, this could use Kelly Criterion or volatility-adjusted sizing let position_size = 1.0; // 1 contract // ^^^ FIXED SIZE: No adaptive sizing based on regime or confidence if position_size > self.config.max_position_size { return Err(anyhow!("Position size {} exceeds maximum {}", position_size, self.config.max_position_size)); } Ok(position_size) } ``` **Missing Wave D Adaptive Logic**: - No regime state queries (`SELECT regime FROM regime_states`) - No adaptive multipliers (0.2x-1.5x based on regime) - No Kelly Criterion position sizing - No volatility-adjusted sizing **Expected Behavior** (from Wave D design): ```rust // Query regime state let regime = sqlx::query!("SELECT regime FROM get_latest_regime($1)", symbol) .fetch_one(&self.db_pool).await?; // Apply regime-adaptive multiplier let base_size = 1.0; let regime_multiplier = match regime.regime.as_str() { "Trending" => 1.5, // Increase size in trending markets "Ranging" => 0.8, // Reduce size in ranging markets "Volatile" => 0.5, // Minimize size in volatile markets "Transition" => 0.2, // Avoid trading during transitions _ => 1.0, // Normal sizing for unknown regimes }; let position_size = base_size * regime_multiplier * confidence_factor; ``` --- ### 3. Regime State Integration **Search Results**: ❌ NO regime queries found in `paper_trading_executor.rs` ```bash $ grep -rn "regime_states\|regime_transitions\|get_latest_regime" \ services/trading_service/src/paper_trading_executor.rs # Result: 0 matches ``` **Contrast with `trading.rs` (Trading Service)**: ```rust // services/trading_service/src/services/trading.rs:992-1023 async fn get_regime_state(&self, req: Request) -> Result, Status> { let regime_state = sqlx::query!( r#"SELECT regime, confidence, detected_at FROM get_latest_regime($1)"#, req.symbol ).fetch_one(&self.db_pool).await?; Ok(Response::new(GetRegimeStateResponse { current_regime: regime_state.regime.unwrap_or("Normal".to_string()), confidence: regime_state.confidence.unwrap_or(0.0), // ... })) } ``` **Paper Trading Executor**: No equivalent logic. --- ### 4. Feature Configuration Architecture **Analysis**: `SharedMLStrategy` uses `MLFeatureExtractor` which has a **legacy field** for feature count: **File**: `common/src/ml_strategy.rs` (lines 66-84) ```rust pub struct MLFeatureExtractor { pub lookback_periods: usize, /// Expected feature count (26=Wave A, 30=Wave A+4 extra, 36=Wave B, 65=Wave C) expected_feature_count: usize, // ❌ Outdated comment - no Wave D (225) price_history: Vec, volume_history: Vec, // ... } ``` **Problem**: `MLFeatureExtractor` does NOT use `FeatureConfig` from `ml/src/features/config.rs` which supports Wave D: **File**: `ml/src/features/config.rs` (lines 345-355) ```rust pub fn wave_d() -> Self { Self { enable_wave_a: true, enable_wave_b: true, enable_wave_c: true, enable_wave_d_regime: true, // ✅ Enables 24 regime features (201→225) // ... } } ``` **Root Cause**: Architecture mismatch between `common::ml_strategy` (legacy extractor) and `ml::features::config` (Wave D-aware). --- ## Integration Gaps ### Gap 1: No Wave D Feature Config **Current**: `SharedMLStrategy::new(20, 0.6)` - hardcoded defaults **Required**: Pass `FeatureConfig::wave_d()` to enable 225-feature extraction **Blocker**: `SharedMLStrategy` constructor does NOT accept `FeatureConfig` **Solution**: ```rust // Option A: Add new constructor impl SharedMLStrategy { pub fn new_with_feature_config( lookback: usize, confidence: f64, feature_config: FeatureConfig, ) -> Self { // ... } } // Option B: Modify existing constructor pub fn new( lookback: usize, confidence: f64, feature_config: Option, ) -> Self { let config = feature_config.unwrap_or(FeatureConfig::wave_a()); // ... } ``` --- ### Gap 2: No Regime State Queries **Current**: No database queries for `regime_states` or `regime_transitions` **Required**: Query latest regime before position sizing decisions **Blocker**: Database access exists (`self.db_pool`) but not used **Solution**: ```rust async fn get_regime_for_symbol(&self, symbol: &str) -> Result { let regime = sqlx::query!( r#" SELECT regime, confidence, detected_at FROM get_latest_regime($1) "#, symbol ) .fetch_one(&self.db_pool) .await .context("Failed to fetch regime state")?; Ok(RegimeState { regime: regime.regime.unwrap_or("Normal".to_string()), confidence: regime.confidence.unwrap_or(0.0), detected_at: regime.detected_at, }) } ``` --- ### Gap 3: No Adaptive Position Sizing **Current**: Fixed 1.0 contract size (line 570) **Required**: Regime-adaptive sizing (0.2x-1.5x) + confidence-based Kelly multiplier **Blocker**: Regime state not queried, Kelly logic not implemented **Solution**: ```rust async fn calculate_adaptive_position_size( &self, prediction: &PendingPrediction, ) -> Result { // Step 1: Get regime state let regime = self.get_regime_for_symbol(&prediction.symbol).await?; // Step 2: Apply regime-adaptive multiplier (Wave D design) let regime_multiplier = match regime.regime.as_str() { "Trending" => 1.5, "Ranging" => 0.8, "Volatile" => 0.5, "Transition" => 0.2, _ => 1.0, }; // Step 3: Apply confidence-based Kelly multiplier // Kelly formula: f* = (p*b - q) / b // For trading: simplified to linear confidence scaling let confidence_factor = prediction.ensemble_confidence.clamp(0.6, 1.0); let kelly_multiplier = (confidence_factor - 0.6) / 0.4; // 0.6→0.0, 1.0→1.0 // Step 4: Calculate final position size let base_size = 1.0; // Base contract size let adaptive_size = base_size * regime_multiplier * (1.0 + kelly_multiplier); // Step 5: Apply safety limits Ok(adaptive_size.clamp(0.2, 5.0)) } ``` --- ## Testing Implications ### Current Paper Trading Behavior 1. **Feature Set**: Uses Wave C baseline (201 features) - NO regime detection 2. **Position Sizing**: Fixed 1.0 contracts - NO adaptive sizing 3. **Regime Awareness**: None - trades blindly across all market conditions ### Expected Wave D Behavior 1. **Feature Set**: 225 features (201 + 24 regime detection) 2. **Position Sizing**: 0.2x-1.5x adaptive multipliers based on regime 3. **Regime Awareness**: Queries `regime_states`, avoids transitions ### Risk Assessment ⚠️ **HIGH RISK**: Paper trading will NOT validate Wave D features before production deployment. **Scenario**: If paper trading passes with Wave C config, we have NO evidence that: - 225-feature extraction works in production - Regime detection improves performance - Adaptive sizing reduces drawdowns **Recommendation**: Block production deployment until paper trading uses Wave D config. --- ## Action Items ### Priority 1: Enable Wave D Features (2 hours) - [ ] Modify `SharedMLStrategy::new()` to accept `FeatureConfig` parameter - [ ] Update `paper_trading_executor.rs` to use `FeatureConfig::wave_d()` - [ ] Verify 225-feature extraction in paper trading logs ### Priority 2: Implement Regime Queries (1 hour) - [ ] Add `get_regime_for_symbol()` method to `PaperTradingExecutor` - [ ] Query `regime_states` table before each trade - [ ] Log regime transitions for debugging ### Priority 3: Adaptive Position Sizing (2 hours) - [ ] Replace `calculate_position_size()` with `calculate_adaptive_position_size()` - [ ] Implement regime multipliers (0.2x-1.5x) - [ ] Add confidence-based Kelly multiplier - [ ] Validate position size range (0.2-5.0 contracts) ### Priority 4: Testing & Validation (1 hour) - [ ] Run paper trading with ES.FUT, NQ.FUT for 24 hours - [ ] Monitor regime transitions vs. position sizing - [ ] Compare performance: Wave C baseline vs. Wave D adaptive - [ ] Document results in `PAPER_TRADING_WAVE_D_VALIDATION.md` **Total Effort**: 6 hours --- ## Technical Debt ### Issue 1: Architecture Mismatch **Problem**: `common::ml_strategy::MLFeatureExtractor` does NOT use `ml::features::config::FeatureConfig`. **Current State**: - `MLFeatureExtractor` has hardcoded feature count expectations (comment: "26=Wave A, 36=Wave B, 65=Wave C") - No mention of Wave D (225 features) - No integration with `FeatureConfig::wave_d()` **Solution**: ```rust // common/src/ml_strategy.rs pub struct MLFeatureExtractor { pub lookback_periods: usize, feature_config: ml::features::config::FeatureConfig, // ✅ Use canonical config price_history: Vec, // ... } impl MLFeatureExtractor { pub fn new(lookback: usize, feature_config: FeatureConfig) -> Self { Self { lookback_periods: lookback, feature_config, // ... } } } ``` **Blocker**: Cross-crate dependency (`common` depends on `ml`). --- ### Issue 2: Kelly Criterion Stub **Problem**: Line 569 comment says "could use Kelly Criterion" but NOT implemented. **Current Code**: ```rust // In production, this could use Kelly Criterion or volatility-adjusted sizing let position_size = 1.0; // 1 contract ``` **Required Implementation**: ```rust use risk::kelly_sizing::{KellyResult, KellySizer}; async fn calculate_kelly_position(&self, prediction: &PendingPrediction) -> Result { // Query historical performance for win rate let win_rate = self.get_strategy_win_rate(&prediction.symbol).await?; // Use ensemble confidence as win probability let win_prob = prediction.ensemble_confidence; let loss_prob = 1.0 - win_prob; // Expected profit/loss ratio (from historical data) let profit_loss_ratio = 1.5; // 1.5:1 risk/reward // Kelly formula: f* = (p*b - q) / b let kelly_fraction = (win_prob * profit_loss_ratio - loss_prob) / profit_loss_ratio; // Use fractional Kelly (25%) for safety let fractional_kelly = kelly_fraction * 0.25; Ok(fractional_kelly.clamp(0.0, 1.0)) } ``` **Existing Code**: `services/trading_service/src/core/risk_manager.rs` has `KellySizer` but NOT used in paper trading. --- ## References ### Codebase Files - `services/trading_service/src/paper_trading_executor.rs` (897 lines) - `common/src/ml_strategy.rs` (MLFeatureExtractor definition) - `ml/src/features/config.rs` (FeatureConfig::wave_d() implementation) - `services/trading_service/src/services/trading.rs` (GetRegimeState gRPC method) - `services/trading_service/src/core/risk_manager.rs` (KellySizer implementation) ### Database Schema - `migrations/045_regime_detection.sql` (regime_states, regime_transitions tables) - Stored function: `get_latest_regime(symbol TEXT)` ### Wave D Documentation - `CLAUDE.md` (Wave D Phase 6 status, production targets) - `WAVE_D_DEPLOYMENT_GUIDE.md` (regime detection integration guide) - `WAVE_D_QUICK_REFERENCE.md` (adaptive sizing formulas) --- ## Conclusion **Status**: ⚠️ **PARTIAL INTEGRATION - CRITICAL GAPS** The paper trading executor is architecturally sound (uses `SharedMLStrategy`, ONE SINGLE SYSTEM) but **NOT configured for Wave D testing**: 1. ❌ No 225-feature extraction (stuck on Wave C baseline) 2. ❌ No regime state queries (blind to market conditions) 3. ❌ No adaptive position sizing (fixed 1.0 contracts) **Recommendation**: **BLOCK production deployment** until paper trading validates Wave D features. Implement action items (6 hours) and run 24-hour validation before proceeding. **Next Agent**: WIRE-15 should implement `calculate_adaptive_position_size()` with regime multipliers and Kelly logic. --- **Agent WIRE-14 signing off.** **Mission**: PARTIAL - Integration gaps identified, action plan provided. **Handoff**: WIRE-15 (Adaptive Position Sizing Implementation)