// AGENT 19.1.2 - Bollinger Bands & ATR Addition // Insert this code RIGHT BEFORE the final normalization line (line ~452) // Current state: 18 features (7 base + 3 oscillators + 3 volume + 5 EMA) // After adding: 23 features (18 + 4 BB + 1 ATR) // Bollinger Bands (20-period SMA ± 2 standard deviations) if self.price_history.len() >= 20 { let recent_prices: Vec = self.price_history.iter().rev().take(20).copied().collect(); // Calculate 20-period SMA (middle band) let bb_middle = recent_prices.iter().sum::() / 20.0; // Calculate standard deviation let variance = recent_prices.iter() .map(|&p| (p - bb_middle).powi(2)) .sum::() / 20.0; let std_dev = variance.sqrt(); // Upper and lower bands (2 standard deviations) let bb_upper = bb_middle + (2.0 * std_dev); let bb_lower = bb_middle - (2.0 * std_dev); let current_price = self.price_history.last().copied().unwrap_or(0.0); // Normalize bands relative to current price let bb_upper_norm = if current_price != 0.0 { (bb_upper - current_price) / current_price } else { 0.0 }; let bb_middle_norm = if current_price != 0.0 { (bb_middle - current_price) / current_price } else { 0.0 }; let bb_lower_norm = if current_price != 0.0 { (bb_lower - current_price) / current_price } else { 0.0 }; // %B indicator: (price - lower_band) / (upper_band - lower_band) // This tells us where price is relative to the bands (0-1 scale) let bb_percent_b = if bb_upper != bb_lower { (current_price - bb_lower) / (bb_upper - bb_lower) } else { 0.5 // Default to middle if bands collapsed }; features.push(bb_upper_norm); features.push(bb_middle_norm); features.push(bb_lower_norm); features.push(bb_percent_b - 0.5); // Center around 0 } else { // Not enough data for Bollinger Bands features.extend_from_slice(&[0.0, 0.0, 0.0, 0.0]); } // ATR (14-period Average True Range) // Uses simulated high/low from high_low_history (price ± 0.1%) if self.price_history.len() >= 15 && self.high_low_history.len() >= 15 { let mut true_ranges = Vec::new(); for i in 1..15 { let idx = self.price_history.len() - 15 + i; let (high, low) = self.high_low_history[idx]; let prev_close = self.price_history[idx - 1]; // True Range is the greatest of: // 1. Current high - current low // 2. Abs(current high - previous close) // 3. Abs(current low - previous close) let tr = (high - low) .max((high - prev_close).abs()) .max((low - prev_close).abs()); true_ranges.push(tr); } // ATR is the average of true ranges let atr = true_ranges.iter().sum::() / 14.0; let current_price = self.price_history.last().copied().unwrap_or(1.0); let atr_normalized = if current_price != 0.0 { atr / current_price } else { 0.0 }; features.push(atr_normalized); } else { features.push(0.0); } // UPDATE SimpleDQNAdapter weights from 18 to 23 features (around line 47): // // OLD (18 features): // let weights = vec![ // 0.1, -0.05, 0.2, 0.15, -0.1, 0.08, 0.03, // Original 7 features // 0.12, 0.09, 0.11, // Williams %R, ROC, Ultimate Oscillator // 0.07, 0.06, 0.05, // OBV, MFI, VWAP // 0.13, 0.14, 0.10, // EMA norms // 0.18, -0.15 // EMA crosses // ]; // // NEW (23 features): // let weights = vec![ // 0.1, -0.05, 0.2, 0.15, -0.1, 0.08, 0.03, // Original 7 features // 0.12, 0.09, 0.11, // Williams %R, ROC, Ultimate Oscillator // 0.07, 0.06, 0.05, // OBV, MFI, VWAP // 0.13, 0.14, 0.10, // EMA norms // 0.18, -0.15, // EMA crosses // 0.08, -0.05, -0.08, 0.10, // Bollinger Bands (upper, middle, lower, %B) // 0.15 // ATR // ]; // // Update comment: // // Initialize with simulated weights for 23 features: // // price_return(1), short_ma(1), volatility(1), volume_ratio(1), volume_ma_ratio(1), // // hour(1), day_of_week(1), williams_r(1), roc(1), ultimate_oscillator(1), // // obv(1), mfi(1), vwap(1), ema_9_norm(1), ema_21_norm(1), ema_50_norm(1), // // ema_9_21_cross(1), ema_21_50_cross(1), // // bb_upper(1), bb_middle(1), bb_lower(1), bb_percent_b(1), atr(1) = 23 total // UPDATE test comment (around line 352): // OLD: Total: 18 features (7 original + 3 oscillators + 3 volume + 5 EMA) // NEW: Total: 23 features (7 original + 3 oscillators + 3 volume + 5 EMA + 4 BB + 1 ATR) // // assert_eq!(features.len(), 23, "Should have 23 features including BB and ATR at iteration {}", i);