## Review findings ### Critical 1. **ADX is not ADX; it is a single 14-period DX value** - `src/market/trend_retest_features.py:137-154` - `_adx()` sums DM/TR across one window and directly returns `abs(+DI--DI)/(+DI+-DI)`. Real ADX requires a time series of DX values plus Wilder smoothing, normally needing roughly `2*length` observations. - This materially invalidates the core range gates at: - `src/strategies/confirmed_range_reversion_v78_2.py:55-60` - `src/strategies/confirmed_range_reversion_v78_2.py:95-96` - A volatile directional sequence can therefore be mislabeled as low-ADX range activity. No test compares the implementation against a known ADX reference series. 2. **The candle that fills a pending maker order is never evaluated for stop or target** - Runtime processes existing positions before pending fills: - `src/tools/v78_2_range_runtime.py:162-163` - `_manage_pending()` fills solely from that completed candle’s high/low and creates the position afterward: - `src/tools/v78_2_range_runtime.py:108-126` - Consequences: - A long order may fill at 99.3, trade through its 98.5 stop in the same candle, and still emerge as a healthy open position. - A fill-and-target candle does not realize the target. - MFE/MAE for the fill candle is discarded. - This is a strong positive bias because adverse same-bar paths disappear completely. `tests/test_confirmed_range_reversion_v78_2.py:89-106` only tests fill in one candle and exit in a later candle, so it codifies no protection against this error. ### Important 3. **Maker fills are materially too optimistic and do not establish ALO/passive eligibility** - Intent price is derived from the last closed 15m candle close: - `src/strategies/confirmed_range_reversion_v78_2.py:115-116` - But the spread comes from the current L2 snapshot. If price has moved after the candle close, this limit may already cross the current ask/bid and would be rejected by `Alo`. - Fill simulation then treats any candle touch—including equality at the candle extreme—as a full maker fill: - `src/tools/v78_2_range_runtime.py:118-125` - There is no current best-bid/ask check, tick-size rounding, minimum-size validation, queue position, traded-volume requirement, partial fill, or ALO rejection model. Promotion results would therefore overstate maker participation. 4. **Stop-gap exits are filled near the stop instead of at the adverse gap price** - `src/tools/v78_2_range_runtime.py:87-94` - When a candle opens through the stop, `raw_exit` remains the stop and only generic spread/impact slippage is applied. A stop-market exit cannot fill at a price better than an already adverse open. - The older runtime explicitly handles this case using the candle open (`src/tools/v77_trend_retest_runtime.py:274-277`), but v78.2 regresses it. - This understates tail losses and drawdown, directly weakening promotion evidence. 5. **Holding time and funding accounting stop advancing during missing-data periods** - Positions and orders advance only when a fresh feature is available: - `src/tools/v78_2_range_runtime.py:77-81` - `src/tools/v78_2_range_runtime.py:113-117` - Funding is then estimated from a single entry-time funding rate multiplied by `bars_held`: - `src/tools/v78_2_range_runtime.py:98-101` - Missing candles/data outages therefore pause both the time exit and funding accrual, even though the real position remains exposed. The fill candle’s holding interval is also omitted because of the lifecycle ordering above. - Funding-rate changes are never sampled over the lifecycle. There are no positive-funding long, positive-funding short, outage, or skipped-bar tests. 6. **“Confirmed range” can pass while one primary market is trending** - BTC and ETH ADX, SMA spread, and Bollinger width are averaged: - `src/strategies/confirmed_range_reversion_v78_2.py:55-66` - One strongly trending primary can be diluted by the other quiet primary. This does not establish that both BTC and ETH confirm a range and is particularly risky after v78.1’s range-classification failure. - Tests only create one combined bad example (`tests/test_confirmed_range_reversion_v78_2.py:30-39`); they do not test opposing/extreme BTC/ETH values whose average passes. 7. **Run identity persists across process restarts** - `src/tools/v78_2_range_runtime.py:145-147` - `run_id` falls back to the value in persisted state. Unlike the older runtime, v78.2 `main()` does not create a new `CTB_RUN_ID`: - `src/tools/v78_2_range_runtime.py:195-209` - External launcher also does not set one: `/home/agent/.hermes/scripts/ctb_v78_research_supervisor.py:62-68` - Thus `run_id` identifies an indefinitely reused state lineage, not an actual process/research run. Restart boundaries and before/after implementation evidence cannot be reliably separated. 8. **Data-window identity fingerprints timestamps, not the actual data** - `src/market/trend_retest_features.py:213-214` - The hash includes the oldest 15m timestamp and only the latest close timestamps for each timeframe. It does not hash candle values or verify continuity/uniqueness. - Revised OHLCV under the same timestamps is treated as identical, while a shifted fetch boundary can create a new ID without a new signal candle. Duplicate timestamps and gaps are also accepted by `normalize_closed_candles()` (`:78-81`). - This weakens both duplicate-entry prevention and the promotion report’s `unique_data_windows` claim. 9. **Promotion state checks fail open for malformed position/order containers** - `src/tools/v77_promotion_report.py:189-194` - A valid JSON state with `open_positions` or `pending_orders` of the wrong type is coerced to `{}`. The state can still pass `state_valid`, version matching, and: - `no_open_positions_for_promotion` at `:229` - Malformed non-empty lifecycle state should be an integrity blocker, not interpreted as flat. 10. **HYPE’s external-data exception is unauditable in promotion evidence** - HYPE intentionally removes `crowding_context_missing`: - `src/strategies/confirmed_range_reversion_v78_2.py:72-76` - The CoinGecko addition is present in `src/research/external_market_context.py:17-19`, but HYPE has no Binance crowding source. - Entry/trade journals do not record the external snapshot ID, source attribution, freshness, or which HYPE exception was applied: - `src/tools/v78_2_range_runtime.py:180-188` - Nevertheless trade rows are stamped `proxy_inputs_used=False`, and promotion only checks that field. This allows HYPE evidence to pass without reproducible provenance for a decision input and with a weaker gate than other assets. 11. **Supervisor can start duplicate runtimes if the PID file is missing or corrupt** - `src/tools/paper_runtime_supervisor.py:97-130` determines process existence only through `bot.pid`; missing PID means restart is automatically allowed. - External supervisor immediately starts the candidate: - `/home/agent/.hermes/scripts/ctb_v78_research_supervisor.py:103-107` - There is no per-runtime lock in `v78_2_range_runtime.py`. A still-running process with a lost/corrupt PID file can therefore be duplicated, racing on `state.json` and journals and invalidating single-position and promotion assumptions. 12. **Unreviewed v78.2 is already wired for automatic start** - Candidate defaults to `enabled=True`: - `src/tools/paper_runtime_supervisor.py:14-20,28` - It is the active external-supervisor strategy: - `/home/agent/.hermes/scripts/ctb_v78_research_supervisor.py:20-23` - The launcher is correctly fail-closed for live flags (`:65-68`), but the next scheduled supervisor invocation can start the uncommitted implementation despite “no smoke/start yet.” A separate reviewed/enabled activation boundary is missing. 13. **Promotion language conflicts with the explicit paper-only designation** - Candidate metadata declares `paper_only=True`, but the generic report emits: - `"eligible_for_manual_tiny_live_proposal"` at `src/tools/v77_promotion_report.py:279` - The report does not consume or enforce `CandidateSpec.paper_only`. It does retain `execution_allowed=False`, so this does not directly enable orders, but it creates an unsafe semantic promotion path for a version requested as explicitly paper-only. ## Most important missing tests - Reference-validated Wilder ADX, including trend, flat, and transition fixtures. - Pending fill where the same candle also crosses stop, target, or both. - Gap-through-stop behavior for long and short positions. - ALO rejection when the stale candle-derived limit crosses the current book. - Exact-touch versus traded-through maker fill, partial fill, queue/volume assumptions. - Positive/negative funding for both sides; missing-candle and restart intervals. - New run identity on restart and state/journal run consistency. - Candle gaps, duplicates, revised OHLCV, and rolling-window identity changes. - HYPE with fresh CoinGecko but absent Binance context, including source attribution. - Malformed/non-dict `open_positions` and `pending_orders` blocking promotion. - Duplicate-process/PID-file-loss supervisor behavior. - Explicit activation and “paper-only cannot produce live-proposal recommendation” boundaries. ## Review scope - Read-only review completed; no files created or modified. - I did not start/smoke the runtime or execute tests, consistent with the no-side-effects instruction.