# Consistency Auditor Memory ## Last Audit: 2026-02-13 (Post-Config Update v2 - NEW FINDINGS) ### AUDIT RESULT: ⚠️ 1 CRITICAL DIVERGENCE + 1 MEDIUM BUG **1. CRITICAL DIVERGENCE: Equity Curve SMA Stop Removed from Backtester Only** - **backtester.rs** lines 258-261: Explicitly REMOVED equity SMA stop with comment "creates pathological feedback loop" - **bot.rs** lines 809-825, 904-907, 991-992: STILL ENFORCES equity curve SMA stop (blocks buys when equity < 50-period SMA) - **Impact**: Bot will skip FAR MORE entries than backtest suggests. When holding losing positions, equity drops below SMA, blocking ALL new buys, preventing recovery. Backtest does NOT reflect this behavior. Live performance will be WORSE. - **Fix**: Remove `equity_below_sma()` function from bot.rs (lines 809-826, 904-907, 991-992) to match backtester - **Rationale**: Backtester comment is correct — this creates a self-reinforcing trap where losers prevent entry into winners - **Code location**: `/home/work/Documents/rust/invest-bot/src/bot.rs:809-826, 904-907, 991-992` **2. MEDIUM BUG: Confidence Scaling Math Error (Both Files)** - **indicators.rs:655** changed to `confidence = score / 10.0` - **bot.rs:1025** and **backtester.rs:828** reverse-engineer with `score = confidence * 12.0` - **Impact**: Regime threshold bumps use wrong score approximation (12x instead of 10x). Overestimates scores by 20%, making thresholds slightly easier to pass. Both files have the same bug, so they're consistent with each other, but both are mathematically wrong. - **Fix**: Change `* 12.0` to `* 10.0` in both files - **Severity**: Medium (not critical since both paths have the same error) - **Code location**: `/home/work/Documents/rust/invest-bot/src/bot.rs:1025` and `/home/work/Documents/rust/invest-bot/src/backtester.rs:828` --- ## Config Changes Since Last Audit (2026-02-13 v2) User reported these config changes: - Hourly indicator periods scaled 7x: - MACD: 84/182/63 (was 12/26/9 daily baseline) - Momentum: 441 (was 63 daily baseline) - EMA: 63/147/350 (was 9/21/50 daily baseline) - BB: 140 (was 20 daily baseline) - Volume MA: 140 (was 20 daily baseline) - Caution regime: sizing 0.25 (was 0.5), threshold bump +3.0 (was +2.0) - Risk: 1.5%/trade (was 1.2%), ATR stop 3.5x (was 3.0x), trail 3.0x (was 2.0x), activation 2.0x (unchanged) - Max 8 positions (was 7), top 15 momentum (was 20), time exit 80 bars (was 40), cooldown 10 bars (was 5) - Drawdown tiers: 12%/18%/25% → 15/40/60 bars (was 15%/20%/25% → 10/30/50) - RSI pullback window: 25-55 (was 30-50) in indicators.rs **Verified**: All config constants consistent between bot.rs and backtester.rs ✅ --- ## VERIFIED CONSISTENT (2026-02-13 Audit v2) ✅ ### Core Trading Logic ✅ - **Signal generation**: Both use shared `indicators::generate_signal()` (bot:876; bt:758,818) - **Position sizing**: Both use shared `Strategy::calculate_position_size()` (bot:537-542; bt:282-284) - Volatility-adjusted via ATR - Confidence scaling: 0.4 + 0.6 * confidence (changed from 0.7 + 0.3) - Max position size cap: 20% (was 25%) - Cash reserve: 5% - **Stop-loss/trailing/time exit**: Both use shared `Strategy::check_stop_loss_take_profit()` (bot:547-559; bt:462-468) - Hard max loss cap: 8% (was 5%) - ATR-based stop: 3.5x ATR below entry (was 3.0x) - Fixed fallback stop: 2.5% - Trailing stop: 3.0x ATR after 2.0x ATR gain (was 2.0x trail, 2.0x activation) - Time exit: 80 bars if below trailing activation (was 40) ### Portfolio Controls ✅ - **Cooldown timers**: Both implement 10-bar cooldown after stop-loss (bot:736-746; bt:383-388) [was 5] - **Ramp-up period**: Both limit to 1 new position per cycle/bar for first 15 bars (bot:618-626; bt:277-279) - **Drawdown circuit breaker**: Both trigger at 12%/18%/25% with 15/40/60-bar cooldowns (bot:368-408; bt:106-163) - **Peak reset on expiry**: Both reset peak to current value (bot:442; bt:197) ✅ (FIXED since last audit) - Tier 3 (25%+) requires bull regime to resume (was 15%/20%/25% → 10/30/50) - **Sector limits**: Both enforce max 2 per sector (bot:608-614; bt:268-274) - **Max concurrent positions**: Both enforce max 8 (bot:599-606; bt:263-265) [was 7] - **Momentum ranking**: Both filter to top 15 momentum stocks (bot:965-985; bt:718-729) [was 20] - **bars_held increment**: Both increment at START of trading cycle/bar (bot:909-912; bt:713-716) ### Config Constants — ALL CONSISTENT ✅ Both files import and use identical values from config.rs: - `ATR_STOP_MULTIPLIER`: 3.5x (was 3.0x) - `ATR_TRAIL_MULTIPLIER`: 3.0x (was 2.0x) - `ATR_TRAIL_ACTIVATION_MULTIPLIER`: 2.0x (unchanged) - `MAX_POSITION_SIZE`: 20% (was 25%) - `MAX_CONCURRENT_POSITIONS`: 8 (was 7) - `MAX_SECTOR_POSITIONS`: 2 - `DRAWDOWN_TIER1_PCT`: 12% (was 15%) - `DRAWDOWN_TIER1_BARS`: 15 (was 10) - `DRAWDOWN_TIER2_PCT`: 18% (was 20%) - `DRAWDOWN_TIER2_BARS`: 40 (was 30) - `DRAWDOWN_TIER3_PCT`: 25% (unchanged) - `DRAWDOWN_TIER3_BARS`: 60 (was 50) - `REENTRY_COOLDOWN_BARS`: 10 (was 5) - `RAMPUP_PERIOD_BARS`: 15 - `TOP_MOMENTUM_COUNT`: 15 (was 20) - `TIME_EXIT_BARS`: 80 (was 40) - `MIN_CASH_RESERVE`: 5% - `MAX_LOSS_PCT`: 8% (was 5%) - `REGIME_CAUTION_SIZE_FACTOR`: 0.25 (was 0.5) - `REGIME_CAUTION_THRESHOLD_BUMP`: 3.0 (was 2.0) ### Warmup Requirements ✅ **Hourly mode**: `max(245 MACD, 15 RSI, 350 EMA, 28 ADX, 140 BB, 441 momentum) + 5 = 446 bars` Calculation in `config.rs:239-252` (`IndicatorParams::min_bars()`) - Momentum period dominates warmup (441 bars = 63 * 7) - MACD needs slow + signal (182 + 63 = 245) - EMA trend: 350 (50 * 7) - ADX needs 2x period for smoothing (14 * 2 = 28) - BB period: 140 (20 * 7) - RSI-2/3 warmup covered by RSI-14 requirement (15 > 3) Both bot.rs and backtester.rs fetch sufficient historical data and validate bar count before trading: - bot.rs: lines 830, 853-860 - backtester.rs: lines 476, 523-530 ### Entry/Exit Flow ✅ **Both follow identical two-phase execution**: - Phase 1: Process all sells (stop-loss, trailing, time exit, signals) - Phase 2: Process buys for top momentum stocks only - bot.rs lines 947-1035 - backtester.rs lines 731-844 --- ## INTENTIONAL DIFFERENCES (Not Bugs) ✅ ### 1. Slippage Modeling - **Backtester**: Applies 10 bps on both entry and exit (backtester.rs:86-93) - **Live bot**: Uses actual fill prices from Alpaca API (bot.rs:644-648) - **Verdict**: Expected difference. Backtester simulates realistic costs; live bot gets market fills. ### 2. PDT Protection Strategy - **bot.rs**: Blocks non-stop-loss sells if would trigger PDT (lines 693-705) - **backtester.rs**: Blocks entries in last 2 hours of hourly day (lines 238-244) - **Verdict**: Two different approaches to PDT prevention. Daily mode prevents day trades by construction (phase separation makes same-bar buy+sell impossible). Hourly mode uses different strategies but both achieve PDT compliance. --- ## STRATEGY ARCHITECTURE (2026-02-13) ### Regime-Adaptive with Bear Market Protection The strategy uses **ADX for regime detection** and **SPY for market filter**: #### SPY MARKET REGIME FILTER (Primary Risk Gate) - **Bull** (SPY > EMA-200, EMA-50 > EMA-200): full size, normal thresholds - **Caution** (SPY < EMA-50, SPY > EMA-200): 25% size, +3.0 threshold bump - **Bear** (SPY < EMA-200, EMA-50 < EMA-200): NO new longs at all #### ADX REGIME (Signal Type Selection) - **ADX < 20**: Range-bound → mean reversion signals preferred - **ADX > 25**: Trending → momentum pullback signals preferred ### Hierarchical Signal Generation (indicators.rs) **NOT additive "indicator soup"** — uses gated filters: **LAYER 1 (GATE)**: Trend confirmation required - Price > EMA-trend - EMA-short > EMA-long Without both, no buy signal generated. **LAYER 2 (ENTRY)**: Momentum + pullback timing - Positive momentum (ROC > 0) - RSI-14 pullback (25-55 range, widened from 30-50) **LAYER 3 (CONVICTION)**: Supplementary confirmation - MACD histogram positive - ADX > 25 with DI+ > DI- - Volume above average ### Signal Thresholds - **StrongBuy**: total_score >= 7.0 - **Buy**: total_score >= 4.0 - **StrongSell**: total_score <= -7.0 - **Sell**: total_score <= -4.0 - **Hold**: everything else Confidence: `(total_score.abs() / 10.0).min(1.0)` (changed from /12.0) --- ## KEY LESSONS ### 1. Equity Curve SMA Creates Pathological Feedback Loop Without removing it from the bot, losing positions drag equity below SMA, blocking ALL new entries, which prevents recovery. This is a self-reinforcing trap. The backtester correctly removed it; the bot must follow. ### 2. Shared Logic Eliminates Most Drift Extracting common logic into `strategy.rs` and `indicators.rs` ensures bot and backtester CANNOT diverge on core trading decisions. Almost all consistency issues now are portfolio-level controls, not signal logic. ### 3. Config Constants Propagation Works Well Using `config.rs` constants throughout prevents hardcoded values. All recent parameter changes (7x hourly scaling, 0.25 Caution sizing, tiered drawdowns) were automatically consistent because both files import the same constants. ### 4. Warmup Must Account for Longest Indicator Chain For hourly mode with 7x scaling, momentum_period=441 dominates warmup. The `+ 5` safety margin in `min_bars()` is critical for EMA initialization edge cases. ### 5. Math Errors Can Be Consistently Wrong The confidence scaling bug (using 12.0 instead of 10.0) is in both files, so they produce the same wrong behavior. This makes it non-critical for consistency but still a bug to fix. ### 6. Drawdown Peak Reset Now Fixed Previous audit found bot.rs was missing peak reset on halt expiry. This has been FIXED (bot.rs:442 now resets peak). --- ## AUDIT CHECKLIST (For Future Audits) When new changes are made, verify: 1. **Signal generation**: Still using shared `indicators::generate_signal()`? 2. **Position sizing**: Still using shared `Strategy::calculate_position_size()`? 3. **Risk management**: Still using shared `Strategy::check_stop_loss_take_profit()`? 4. **Equity curve stop**: Check if REMOVED in both files (don't re-add to backtester!) 5. **Cooldown timers**: Identical logic in both files? 6. **Ramp-up period**: Identical logic in both files? 7. **Drawdown halt**: Identical trigger logic? Peak reset on expiry? 8. **Sector limits**: Same `MAX_SECTOR_POSITIONS` constant? 9. **Max positions**: Same `MAX_CONCURRENT_POSITIONS` constant? 10. **Momentum ranking**: Same `TOP_MOMENTUM_COUNT` constant? 11. **bars_held increment**: Both increment at START of cycle/bar? 12. **Warmup calculation**: Does `min_bars()` cover all indicators? 13. **NaN handling**: Safe defaults for all indicator checks? 14. **ATR guards**: Checks for `> 0.0` before division? 15. **Config propagation**: Are new constants used consistently? 16. **Math in regime logic**: Confidence scaling uses correct multiplier (10.0 not 12.0)? --- ## FILES LOCATIONS - `/home/work/Documents/rust/invest-bot/src/bot.rs` — Live trading loop (1119 lines) - `/home/work/Documents/rust/invest-bot/src/backtester.rs` — Historical simulation (1217 lines) - `/home/work/Documents/rust/invest-bot/src/strategy.rs` — Shared position sizing and risk management (163 lines) - `/home/work/Documents/rust/invest-bot/src/indicators.rs` — Shared signal generation (673 lines) - `/home/work/Documents/rust/invest-bot/src/config.rs` — All strategy parameters (268 lines) - `/home/work/Documents/rust/invest-bot/src/types.rs` — Data structures (262 lines) **Total**: 3,702 lines audited (2026-02-13 v2) --- ## CRITICAL REMINDERS - **NEVER re-add equity curve SMA stop to backtester** — it was removed for good reason - **Remove equity curve SMA stop from bot.rs** — lines 809-826, 904-907, 991-992 - **Warmup for hourly mode is 446 bars** — momentum_period=441 dominates - **Confidence → score conversion is `score = confidence * 10.0`** not 12.0 - **Both files correctly reset peak on drawdown halt expiry** (verified in this audit) - **PDT protection differs by timeframe** — daily uses phase separation, hourly uses entry/exit blocking