# Plan: Improve Emmanuel MTF Strategy Results

## Context

The emmanuel-mtf/v1 strategy ran a 360-day BTC backtest (15m primary, 4h context) producing only **1 trade** with **-0.3318 PnL**. Two distinct problems were identified from the condition audit:

1. **Indicator warmup dead zone (engine issue)**: SMA(200) on 4h needs 200 four-hour candles (~33 days) before it can produce a value. The backtest runner loads candles starting from `starts_at` — no pre-loading — so the first 33 days of ANY backtest using SMA(200) on 4h show "insufficient data" (3,199 of 34,560 bars). Not the cause of 1 trade, but wastes 9% of the window.

2. **Over-filtering (strategy issue)**: The entry rule has 11 conditions in `all_of`. Individual pass rates from the audit:

   | Condition | What it checks | Pass rate | Verdict |
   |-----------|---------------|-----------|---------|
   | 11 (not-first-touch) | `highest(high,20) - SMA(20) > SMA(20) * 0.015` | **7.0%** | **Fatal bottleneck** |
   | 2 (4h MA stack) | `SMA(20,4h) > SMA(200,4h)` | 41.2% | OK |
   | 1 (4h regime) | `close(4h) > SMA(200,4h)` | 42.8% | OK |
   | 10 (pullback evidence) | `abs(lowest(low,8) - SMA(20)) < SMA(20)*0.003` | 46.9% | Tight |
   | 4 (4h price > 20 SMA) | `close(4h) > SMA(20,4h)` | 49.2% | OK |
   | 9 (15m rising) | `close > close(offset=3)` | 50.4% | OK |
   | 6 (15m SMA rising) | `SMA(20) > SMA(20,offset=4)` | 51.0% | OK |
   | 7 (15m above SMA) | `close >= SMA(20)` | 51.3% | OK |
   | 3 (4h SMA rising) | `SMA(20,4h) > SMA(20,4h,offset=5)` | 51.4% | OK |
   | 8 (15m proximity) | `close - SMA(20) < SMA(20)*0.005` | 88.1% | Fine |
   | 5 (4h not extended) | `close(4h) - SMA(20,4h) < SMA(20,4h)*0.05` | 98.0% | Fine |
   | 0 (flat position) | position flat | 100% | Gate |

   Condition 11 requires the highest high in the last 20 fifteen-minute bars to be >1.5% above the 15m 20 SMA. At BTC ~$83K, that's ~$1,250 above the MA in a 5-hour window. On 15m bars, this is extremely rare — it filters 73 candidate bars down to 2.

### Transcript analysis vs v1 implementation

Emmanuel's actual method from the transcript:
- **200 SMA**: Used as flat support/resistance, NOT as a trend direction indicator. He says he wants it flat. v1 uses it correctly as a regime gate (`close > 200 SMA`), which is fine.
- **Entry**: Retrace to the rising 20 SMA and enter near it. v1's proximity approach is faithful.
- **"First touch" rule**: "Always pass on the first touch of the 20 MA after a huge parabolic run." This is about avoiding exhaustion, not requiring a prior impulse. v1 inverts the logic — it requires proof of a prior impulse, which is too strict on 15m.
- **Extension filter**: "Don't buy when super far from the 20 MA." v1's 5% on 4h and 0.5% on 15m are reasonable.

**The not-first-touch filter (condition 11) misinterprets the transcript.** Emmanuel says to SKIP the first touch after a parabolic move. The correct filter would be: "don't enter if this is the first pullback after an extended move." v1 instead requires evidence of a prior impulse, which is the opposite — it demands the very setup Emmanuel says to avoid.

## Recommendation

**Both changes are needed**, but the strategy fix has the larger impact:

### Change 1: Create `assets/strategy/emmanuel-mtf/v2.json`

Based on audit data and transcript re-analysis:

- **Remove condition 11** (not-first-touch). It misinterprets the transcript and is the fatal bottleneck at 7% pass rate. Replace with an extension guard: if `close - SMA(20) > SMA(20) * 0.02` on 15m, skip — this filters parabolic first-touches without requiring a prior impulse.
- **Widen condition 10** (pullback evidence) from 0.3% to 0.5%. At 46.9% pass rate it's the next tightest 15m filter. Widening gives more pullback candidates while still requiring the low to approach the SMA.
- **Add ADX(14) > 20 on 4h** as a trend-strength confirmation (borrowed from emmanuel-ma v8, which had the best single-TF results). This replaces some of the lost selectivity from removing condition 11.
- **Keep all other conditions** — the 4h trend stack (conditions 1-5) and 15m entry timing (conditions 6-9) are well-calibrated per the audit.

Expected effect: removing the 7% filter and widening the 47% filter should produce ~20-50 trades in a 360-day window instead of 1, while the new extension guard and ADX filter maintain quality.

**Files:**
- Create `assets/strategy/emmanuel-mtf/v2.json`
- Add `emmanuel_mtf_v2()` to `crates/swym-dal/src/seeds/common.rs`
- Update `assets/strategy/emmanuel-mtf/readme.md` lineage section

### Change 2: Add indicator warmup to backtest runner

Load candle data from before `starts_at` so indicators are pre-warmed when the backtest window begins. This eliminates the 33-day dead zone for SMA(200) on 4h and (critically) would fix the much worse case of SMA(200) on 1d (~200 days of dead zone).

**Approach**: Calculate the maximum warmup period from the strategy's indicator requirements. Load additional candles starting from `starts_at - warmup_duration`. Process them through `SwymInstrumentData` to build candle histories and EMA states, but don't feed them to the barter engine (no order generation). Then start the engine at `starts_at` with pre-populated instrument data.

**Implementation details:**

1. Add `compute_warmup_secs(config: &StrategyConfig) -> u64` to `crates/swym-dal/src/models/strategy_config.rs`:
   - Walk the rule tree to find the maximum `period` for each timeframe
   - Return `max(period * interval_secs)` across all timeframes
   - Example: SMA(200) on 4h = 200 * 14400 = 2,880,000s (~33.3 days)

2. In `services/paper-executor/src/runner.rs` `execute_backtest()`:
   - Compute warmup duration from the strategy
   - Set `data_starts_at = starts_at - warmup_duration`
   - Load primary + additional candles from `data_starts_at`
   - Process warmup candles (those before `starts_at`) through instrument data only — push candles into histories, update EMAs, but skip engine processing
   - Begin normal engine processing at `starts_at`

3. Update the paper run result to distinguish `data_start` (with warmup) from `engine_start` (user's requested start). The UI already shows both fields.

**Files:**
- `crates/swym-dal/src/models/strategy_config.rs` — add `compute_warmup_secs()`
- `services/paper-executor/src/runner.rs` — warmup candle loading + pre-processing
- `crates/swym-dal/src/models/run_config.rs` — no changes needed (data_start/engine_start already separate in DB schema)

## Verification

1. `cargo check` — compiles
2. `cargo clippy` — no warnings
3. Deploy to dev, run emmanuel-mtf v2 backtest (360 days, BTCUSDC, 15m candles):
   - Expect 20-50 trades (vs 1 for v1)
   - Expect 0 "insufficient data" bars on 4h SMA(200) conditions (warmup fix)
   - Verify condition audit shows reasonable pass rates across all conditions
4. Run emmanuel-mtf v1 with warmup fix — should still produce ~1 trade but with 0 insufficient data bars on SMA(200) conditions
5. `pnpm tsc --noEmit` — dashboard type-checks (no UI changes needed)
