VWAP entry gate re-verified against the current archive (2026-09-29)
The live rule (4H ST Buy + clean 15m BO + session VWAP gap ≥1% at that candle, Daily EMA20>50/SMA200 filter — VWAP replaced 15m RSI<45 as the gate on 2026-09-27) re-run against today's archive: 388 candidate setups → 156 trades (53 symbols), 72.4% win rate, 50.6% hit +5%, avg return +3.81%, median +5.10%, profit factor 2.44, expectancy $381/trade, max drawdown −$8,866 (sequential $10,000/trade sizing). Still solid on fresh data — no change made at this point. The retired RSI-wait variant's exact risk-adjusted comparison against this VWAP version remains an open, deferred question (not re-run this session). Superseded the same day — see below.
VWAP gate reversed on a deeper split test — retired from the live strategy (2026-09-29)
| VWAP gap bucket | n | Win rate | Avg return | Profit factor | Expectancy |
| < 1% (below the live gate's threshold) | 35 | 82.9% | +4.76% | 10.40 | $476 |
| ≥ 1% (the gate that had been live since 2026-09-27) | 142 | 69.7% | +3.48% | 2.20 | $348 |
Same clean-BO-in-4H-uptrend population as the re-verification above, but split by the VWAP gap actually measured at each flip candle instead of pre-filtered to ≥1%. Splitting at exactly 0% wasn't usable (only 1 of 177 candidates ever had a negative gap — a clean breakout almost always already sits above the day's VWAP by construction), so the informative cut is at the gate's own 1% threshold. Result is the opposite of the finding that justified adding the gate on 2026-09-27: the below-threshold group now wins on every metric. Two caveats noted at the time — n=35 is a thin sample, and a profit factor of 10.40 is partly a function of few losers rather than necessarily a durable edge; this is also the same VWAP condition being re-tested for a third time this session (2026-09-27 original, today's re-verification, today's split), which is itself the kind of repeated-testing-against-the-same-data risk flagged for the portfolio-sim work. Decision: the VWAP gap ≥1% requirement was dropped from the live strategy — src/recent_events.py's _strategy_match() no longer checks it. Live entry is back to the original 2026-09-23 rule: Daily EMA20>50 + Price>Daily SMA200 + 4H Supertrend Buy + clean 15m BO, no RSI, no VWAP gate. VWAP Gap is still shown on Trade Now and AutoTrade for reference, just no longer required.
Dropping Supertrend entirely — 1H RSI<45 entry, no ST anywhere (2026-09-29)
| Variant | Trades | Win rate | Avg return | Profit factor | Max DD |
| Live strategy (ST Buy + clean 15m BO + VWAP, Daily filter) | 156 | 72.4% | +3.81% | 2.44 | −$8,866 |
| 15m BO (close > prior candle's high) + 4H EMA20>50 + Daily filter, gated on 1H RSI<45 — no Supertrend at all | 169 | 74.6% | +4.31% | 3.19 | −$7,938 |
| Same, but Weekly EMA20>50 instead of the Daily filter (no Daily check at all) | 256 | 66.0% | +2.19% | 1.73 | −$18,809 |
Entry: a symbol in a Daily uptrend (EMA20>50, price>SMA200) breaks out on the 15m chart (close above the prior 15m candle's high — no Supertrend flip required anywhere), gated on 1H RSI(14) < 45 at that same moment and 4H EMA20>50 for trend context. 5,339 raw breakout candidates narrow to 169 trades (3.2%) once the RSI gate is applied — strict, but the survivors beat the live strategy on every metric: higher win rate, higher avg return, better profit factor (3.19 vs 2.44), better expectancy ($431 vs $381/trade), smaller drawdown. Swapping the Daily filter for Weekly (dropping the Daily check) roughly doubles the universe (113 vs 53 symbols) and trade count but makes every quality metric worse — the Daily filter is doing real work here, not redundant confirmation. Not yet adopted — promising but tested on the same archive window other variants have already been checked against; needs a genuine forward-test period before being treated as a candidate.
Stop-loss at "breakdown of the 1H RSI candle" — backfires badly (2026-09-29)
| Variant (on the 1H RSI<45 entry above, Daily filter) | Trades | Stopped out | Win rate | Avg return | Profit factor | Max DD |
| No stop (baseline) | 169 | — | 74.6% | +4.31% | 3.19 | −$7,938 |
| Stop = low of the 1H candle whose RSI gated entry | 869 | 85.3% | 22.3% | +0.65% | 1.65 | −$11,536 |
85.3% of trades get stopped out before ever reaching the RSI≥80 target exit — the stop sits too close to entry relative to normal 15m noise, especially since this entry specifically requires 1H RSI<45 (weak momentum at entry), where a pullback/retest toward that 1H candle's low before the real move develops is common, not a failure signal. Trade count also jumps 169→869 since faster stop-outs free the symbol up for new entries sooner — same underlying archive, more turnover. Same lesson as the 2026-09-24 candle-low stop tests above: a naive stop on this style of pullback-buying strategy destroys more upside than it protects. Not adopted.
Buying the raw dip signal instead of waiting for BO confirmation (2026-09-29)
| Entry style | n | Win rate | Avg return | Median return | Hit +5% |
| EARLY — buy at the raw dip/support signal, no confirmation | 190 | 53.7% | +2.20% | +1.31% | 37.9% |
| BO15 — wait for the confirmed 15m Supertrend breakout (same 190 signals) | 190 | 51.1% | +1.74% | +0.46% | 35.8% |
Using the platform's own live signal-tracking data (data/archive/triggers.json, ~4-5 weeks, Aug 28–Sep 25) rather than a fresh archive re-scan: on the SAME 190 signals, buying immediately at the raw dip/support detection edges out waiting for a confirmed 15m BO on every metric, despite having zero filtering of its own. BO15 confirms, on average, +0.47 to +0.59% higher than the original signal price — quantifying the "entries feel late" observation that prompted this test.
Filtering EARLY entries further (fixed 5-trading-day forward return, unbiased by signal recency): Daily EMA20>50 and 4H EMA20>50 both made EARLY entries worse (49.5% / 52.7% win rate vs the 55.4% unfiltered baseline) — consistent with the 2026-09-22 finding above that Daily trend didn't correlate with outcome. Weekly EMA20>50 is the filter that actually helps: 99 filtered signals reached 69.7% win rate / +4.86% avg 5-day return, vs 154 unfiltered at 66.9% / +3.72% — a real, if modest, edge that replicates the original 2026-09-22 Weekly-context finding on an independent, more recent sample. Best candidate from this line of testing: buy at the raw signal, gated only by Weekly EMA20>50 — no Daily filter, no 4H filter, no breakout wait, no Supertrend. Small sample (~100-190 signals, one regime) — needs forward-testing, not yet adopted.
Full Weekly+Daily+4H+1H uptrend stack, pullback entry (2026-09-30)
| Universe layer (of 181 archived symbols) | Pass count |
| Weekly EMA20>EMA50>SMA200 alone | 96 |
| Daily EMA20>EMA50>SMA200 alone | 61 |
| 4H EMA20>EMA50>SMA200 alone | 30 |
| 1H EMA20>EMA50>SMA200 alone | 37 |
| Weekly + Daily both | 41 |
| Weekly + Daily + 4H | 18 |
| Full stack — all four timeframes at once | 11 — ABCL, AEHR, BE, COHU, ENTG, ERO, HPE, LITE, PBF, SMTC, TGB |
Universe filter: EMA20 > EMA50 > SMA200 simultaneously on Weekly, Daily, 4H, and 1H (a full bullish "staircase" on every timeframe at once) — a much stricter conjunction than anything else tested this session, deliberately requiring the whole multi-timeframe trend to agree. Entry on the 11 qualifying symbols: any 15m bar where price is below the 4H EMA20, OR below the 1H EMA50, OR 1H RSI(14) < 45 (any one pullback signal, while the broader stack holds). Result: 78 trades, 65.4% win rate, 34.6% hit +5%, avg return +2.46%, median +1.62%, profit factor 1.99, expectancy $246/trade, max drawdown −$5,024 — the smallest drawdown of any variant tested this session, but the sample is thin and concentrated (LITE, BE, SMTC and TGB alone account for 39 of the 78 trades).
Ceiling check — the same 78 trades re-scored as if each had sold at its own peak high during the hold, instead of waiting for the RSI≥80 exit: win rate 100% (by construction), avg return +6.41%, expectancy $641/trade — roughly 2.6× the actual result, meaning the current exit rule is capturing only ~38% of the move that was actually available on these trades (≈$30,800 left on the table across the 78 trades). Not a tradeable rule on its own (it assumes perfect foresight of the peak) — flagged as the strongest case this session for testing a trailing-stop exit instead of a flat RSI target, since it has the largest actual-vs-ceiling gap of anything backtested.
Everything tested this session, side by side
| Variant | Trades | Win rate | Avg return | Profit factor | Expectancy | Max DD |
| "Live strategy" as tested here (ST + clean 15m BO + VWAP, Daily filter) — VWAP gate retired the same day, see above; live rule is now ST + clean 15m BO + Daily filter only, no VWAP | 156 | 72.4% | +3.81% | 2.44 | $381 | −$8,866 |
| 1H RSI<45, no ST (4H EMA + Daily filter) | 169 | 74.6% | +4.31% | 3.19 | $431 | −$7,938 |
| 1H RSI<45, no ST (4H EMA + Weekly filter) | 256 | 66.0% | +2.19% | 1.73 | $219 | −$18,809 |
| ↳ + stop at 1H-BO-candle low (Daily filter) | 869 | 22.3% | +0.65% | 1.65 | $65 | −$11,536 |
| Full W/D/4H/1H stack + pullback entry (RSI≥80 exit) | 78 | 65.4% | +2.46% | 1.99 | $246 | −$5,024 |
| ↳ same 78 trades, sold at the peak (theoretical ceiling, not tradeable) | 78 | 100% | +6.41% | ∞ | $641 | n/a |
By profit factor: 1H RSI<45/no-ST/Daily-filter (3.19) > live strategy (2.44) > full W/D/4H/1H stack (1.99, but smallest drawdown of the real variants) > 1H RSI<45/Weekly-filter (1.73) > 1H-BO-candle stop (1.65, and actively destructive relative to its own no-stop baseline). None of these have replaced the live strategy yet — each carries the same overfitting caveat already established for the portfolio-simulation work (see the project_dipswing_portfolio_sim memory note): candidates need to be frozen and forward-tested on new data, not iterated against the same archive window that produced them.
Live strategy replaced — Daily/4H EMA + RSI<45 + plain 15m BO + near-support, no Supertrend on 15m (2026-09-30)
Adopted directly (not a backtest first) at the user's explicit specification: Daily EMA20>50, 4H EMA20>50 AND 4H Supertrend Buy, 15m RSI<45, a plain 15m breakout (close above the ENTIRE previous 15m candle — no Supertrend condition on 15m at all), and price within 3% of any EMA20/EMA50/SMA200 level on the Weekly, Daily, 4H, 1H or 15m chart (Daily SMA200 is no longer part of the rule — not requested). This is a real architecture change, not just a threshold tweak: every earlier rule (see above and 2026-09-23/24/27/29) required a fresh 15m Supertrend Sell→Buy flip to even generate a candidate — recent_events.py's event stream (and the Trade Now page's own rows) is built entirely around that flip. Since this rule has no 15m Supertrend condition, candidates are now evaluated from each signal's CURRENT state every 15-minute cycle (a new pass over every tracked signal, independent of the flip-event stream) rather than from flip events — see _strategy_match() and the "Autotrade candidates" block in build(). At this point the Trade Now page's rows were still unchanged (still only a fresh 15m Supertrend Buy creates/updates a row) — a symbol could trigger a real autotrade buy without ever appearing there, since the two mechanisms were fully decoupled. Superseded later the same day — the page was coupled to this same match, see below.
Near-support uses the same ±3% tolerance as the original dip scanner. 4H/1H/15m support only checks EMA20/EMA50 (not SMA200) — the local candle archive (~90 days) doesn't cover enough history for a real 200-bar SMA on those timeframes (200 4H bars needs ~100 trading days, 200 1H bars ~31; both computed for Weekly/Daily instead, from the existing 5y daily pull). Verified before deploy: the RSI-appending/near-support math checked against real archive data (VICR matched live — Daily EMA20>50 true, 4H EMA20>50 and ST Buy both true, 15m RSI 43.4, clean 15m BO true, price 0.08%/0.51%/0.51% from 1H EMA20/15m EMA20/15m EMA50 — correctly whipsaw-suppressed as already within 3% of its existing position). No backtest run yet on this exact combination — it replaces the live rule directly rather than being tested first, so treat the coming weeks as the first real read on it. Backtested a few hours later (same day) and found to be the worst-performing entry rule of anything tried this session — see the v2 replacement immediately below.
Live strategy replaced again, same day — v2: Daily EMA20>50/SMA200 + 4H EMA20>50 + 1H RSI<45 + plain 15m BO, no Supertrend anywhere (2026-09-30)
The v1 rule above was backtested after the fact (not before deploy) and came out worst of every variant tried all session: 320 trades, 62.5% win rate, +1.54% avg return, profit factor 1.38, expectancy $154/trade, max drawdown −$37,158 — worse profit factor and roughly 8x the drawdown of the next-worst real variant. A same-session test of the same entry with a +5% profit target added (exit at +5% OR 15m RSI≥80, whichever first) made win rate look better (78.1%) but expectancy WORSE ($117/trade) — capping winners without reducing the loser tail is a net negative in dollar terms, confirmed directly: one real trade (VICR, entered 7/1) hit the RSI≥80 exit while still down −21.84% from entry, since RSI reflects recent relative momentum, not recovery to entry price — the profit cap doesn't touch that risk, it just removes the big wins that used to offset it.
Replaced immediately with the variant that backtested BEST of everything tried this session (see "Dropping Supertrend entirely" above, first row of its comparison table): Daily EMA20>50 AND Price>Daily SMA200, 4H EMA20>50 (no 4H Supertrend condition), 1H RSI<45 (not 15m), and a plain 15m breakout — no Supertrend condition anywhere in the rule. Backtest: 169 trades, 74.6% win rate, +4.31% avg return, median +4.29%, profit factor 3.19, expectancy $431/trade, max drawdown −$7,938 — best profit factor and expectancy of any entry rule tested this session, live strategy included. Typical hold: median 7.9 calendar days (avg 11.9d, range same-day to 50.9d). Trade frequency: ~2.6/trading day averaged over the whole 66-day backtest window, concentrated on 50 of those 66 days (~3.4/active day, median 2.5) — one cold-start-burst day (6/26, 17 entries) skews the max. Every one of the 169 trades was positive at some point during its hold (none stayed underwater the whole way) — peak return averaged +7.73% (median +5.57%) against the +4.31% actually captured, suggesting a trailing-stop exit has more genuine room to help THIS variant than it did the abandoned v1/near-support rule, though a flat +5% target specifically was already shown (above) to hurt a very similar setup rather than help.
Implemented in _strategy_match(sig) using fields already tracked by Series — no new computation needed (unlike v1, which added EMA-value tracking and the near-support check specifically for itself; that code is left in place, unused by the live rule, since it's cheap and still exposed in the signal snapshot for reference). Same candidate-detection architecture as v1: since there's no Supertrend condition, matches are evaluated from each signal's current state every cycle (at this point still independent of the Trade Now page's then-separate 15m-ST-flip-driven rows — see below for when that changed too). Verified against real archive data before deploy (ALAB matched live — Daily EMA20>50 true, Price>SMA200 true, 4H EMA20>50 true, 1H RSI 39.6, clean 15m BO true).
Two more fixes the same day: stale alert copy, repeat alerts for already-held positions, then Trade Now coupled to the v2 rule (2026-09-30)
Two bugs surfaced from real alert emails within hours of the v2 deploy. First: the alert email's description text was still the v1 wording (4H Supertrend Buy required, 15m RSI, near-support) even though _strategy_match() was already v2 — a copy-only bug, confirmed by independently re-checking the actual matched symbols (ALAB/CLS/SANM) against the real v2 conditions directly, all genuinely true. Fixed by updating the hardcoded string in _send_bo15_alert().
Second, more substantive: SANM, CLS and BLSH (all open positions from 9/24-9/25, under earlier rules) kept appearing as "new" signals in consecutive 15-minute emails, with a visibly drifting entry price each time. Root cause: without a Supertrend-flip gate, the v2 rule's conditions can mechanically stay true across many consecutive 15m candles for a symbol whose price has drifted past is_near_last_trade()'s 3% whipsaw window — confirmed directly against the server's real trade log (all three genuinely open, 6-9% away from the prices that kept re-matching). autotrade_entries() already skips buying a symbol it holds, but the alert list was built before that check ran, so it had no way to know. Fixed by adding bo15_autotrade.currently_held_symbols() (reads the local trade log's open positions) and checking it in the candidate loop alongside the whipsaw check — suppresses the alert, not just the trade.
Then, at the user's explicit request ("no more supertrend alert"): the Trade Now page's own row-creation mechanism was replaced too — it previously ran on a completely separate trigger (a fresh 15m Supertrend Sell→Buy flip, via Series.buy_hits()), independent of whatever rule actually drove autotrade. Now the exact same candidates_by_sym match that feeds autotrade also drives the page's rows — one mechanism, not two. Since a state-based rule (no event to anchor to) needs its own notion of "first seen," a small persistent tracker (data/archive/bo15_v2_matches.json) records each symbol's first/last match time and a count, incrementing on each new candle it still matches and starting a fresh episode after a 24h gap since its last match — unit-tested against four scenarios (first match, consecutive match, idempotent re-run of the same candle, fresh episode after a gap) before deploy. The page's rows are intentionally NOT filtered by the whipsaw/already-held suppressions above (same as the old ST-flip version never was) — it's meant to show every genuine rule match, while autotrade/email apply those suppressions separately on top. Series.buy_hits() and the 15m-ST-flip event stream still exist in the code (unused) in case they're useful again later. Superseded hours later the same day — see below.
Trade Now page broadened to EVERY signal, match becomes a badge + opt-in filter, not a row gate (2026-09-30)
The match-gated row design above lasted only a few hours before being reversed, per explicit request: the page should show every signal from Live/Past Signals, like those pages do — not just symbols currently matching the autotrade rule. rows_by_sym now builds one row per symbol from all_sigs unconditionally (same population Live/Past Signals already use), with each row flagged sig["autotrade_match"] = whether it's in candidates_by_sym right now. A match gets a green "New AutoTrade Signal" badge next to its detection time; nothing about which symbols actually get bought changed — candidates_by_sym still drives autotrade_entries() exactly as before.
The persistent bo15_v2_matches.json tracker (first/last/count across runs, added hours earlier) was removed along with it — with every signal now getting a row regardless of matching, "count" simplified to how many distinct days a symbol has appeared as a signal in the lookback window, and "Entry Detected" reverted to meaning when the underlying dip signal was captured (via _parse_captured(), re-imported), not when it last matched the autotrade rule.
Added a new standalone "Show only AutoTrade signals" checkbox below the filter grid — unlike the grid's own EMA/RSI/ST/SMA200/BO/VWAP chips (which filter on raw individual conditions), this one filters on the composite autotrade_match flag directly, i.e. exactly the rows tagged "New AutoTrade Signal." Caught in testing and fixed before deploy: the filter grid's own chip defaults were still pre-checked to replicate the old match-gated rule (Daily+4H EMA20>50, Price>SMA200, 1H RSI<45, 15m BO) from the superseded design above — left in place, a "fresh" page load would have still shown 0 rows outside trading hours despite the backend now returning the full ~178-signal set. Reset every chip row to empty/unchecked by default (localStorage keys bumped so existing browser state doesn't override) so a fresh load genuinely shows everything, with the new toggle as the dedicated way to see just the autotrade matches. Verified twice with Playwright: first pass caught the stale-grid-defaults bug directly (0 rows instead of ~178 on a cleared-localStorage load), second pass confirmed the fix (178 rows, every chip row unchecked, new toggle present and working with no console/page errors in either state).
Full Daily+4H+1H uptrend stack, 15m RSI<40+BO entry — weaker than other variants, funnel diagnosed (2026-10-05)
| Funnel stage (of 78,858 total 15m clean-breakout candles) | Candles | % of all BO | % of prior stage |
| + Daily EMA20>50>200 also stacked | 32,972 | 41.8% | — |
| + 4H stack also true | 12,555 | 15.9% | 38.1% |
| + 1H stack also true | 9,882 | 12.5% | 78.7% |
| + 15m RSI<40 also true (final entries) | 1,137 | 1.4% | 11.5% |
| Variant (entry: 15m clean BO + RSI gate; exit: 15m RSI≥80) | Trades | Win rate | Avg return | Profit factor |
| Daily+4H+1H stack, RSI<40 (as specified) | 120 | 56.7% | +3.20% | 1.78 |
| Daily+4H+1H stack, RSI<45 | 135 | 58.5% | +3.55% | 1.88 |
| Daily+4H+1H stack, RSI<50 | 141 | 58.9% | +3.06% | 1.73 |
| Daily+1H only (drop 4H), RSI<40 | 144 | 50.7% | +0.84% | 1.15 |
| 4H+1H only (drop Daily), RSI<40 | 180 | 65.6% | +4.69% | 2.50 |
| 1H only, RSI<40 | 263 | 60.1% | +2.53% | 1.65 |
| Daily only, RSI<40 | 324 | 56.5% | +0.98% | 1.20 |
Requested test: Daily, 4H and 1H all EMA20>EMA50>EMA200 at once, entry at 15m RSI(14)<40 with a same-candle clean breakout confirmation, exit at 15m RSI≥80, no stop/time limit. Fresh ~2y Daily/1H pulled via yfinance (fixed dates, not a rolling window — the same lesson already learned from the crypto backtest's rolling-window bug) so the 4H/EMA200 math has real history behind it, rather than relying on the ~90-day local 15m archive alone. Result: 136 entries, 120 resolved, 56.7% win rate, +3.20% avg return, profit factor 1.78 — weaker than every BO15/crypto variant validated this session (BO15 live: 74.6%/3.19; crypto: PF 3.7-4.6). Only 35 of 197 archived symbols ever produced a single trade. Excluding the +127.52% MRNA outlier (verified via the raw archive candles — Aug 18 close $62.95 → Aug 19 open $116.25, held $130-175 the following week — a real, persistent gap, not a reverting single-candle glitch, so kept in rather than excluded): 117 resolved, 57.3% win, +2.33% avg, PF 1.58.
Funnel diagnosis (table above) found the 4H leg is the real bottleneck, not the RSI entry — once Daily is stacked, 4H alone kills 62% of what's left (38.1% pass rate) vs 1H's 21%. Testing variants of the filter combination directly: dropping the Daily leg entirely (keeping only 4H+1H) wins on every metric — 180 trades (vs 120), 65.6% win (vs 56.7%), +4.69% avg (vs +3.20%), PF 2.50 (vs 1.78). Daily alone is weak (PF 1.20) and actively hurts when stacked on top of 4H+1H: by the time Daily has been fully stacked for a while the move is already mature, so a 15m RSI<40 dip inside that context is a later-stage pullback rather than a fresh one. Not adopted — a real, free improvement over the originally-specified full-stack rule, but still well short of the live BO15 rule or the crypto variants; documented as a finding, not pushed further.
Single-timeframe entry: Supertrend Buy flip + EMA20>50, RSI≥80 exit — 15m vs 1H, and a censoring-bias trap caught mid-test (2026-10-08/09)
| Variant | Trades (resolved) | Open/unresolved | Win rate | Avg return | Profit factor | Worst / Best |
| 15m (233 symbols, ~105-day local archive) | 692 | 164 (19%) | 70.8% | +2.99% | 2.16 | -39.0% / +103.8% |
| 1H, same ~105-day archive window (first pass — discarded, see below) | 115 | 148 (56%) | 81.7% | +10.97% | 7.80 | — |
| 1H, re-tested with 2 years of fresh yfinance 1H history | 1,329 | 161 (11%) | 74.1% | +9.64% | 3.20 | -66.9% / +210.7% |
Entry: Supertrend (ATR 10, ×3) flips to Buy on a candle AND EMA20>EMA50 at that same candle, single timeframe, no multi-timeframe stack. Exit: RSI(14)≥80, no stop/time limit. Tested against the full 233-symbol tracked universe.
The first 1H pass (PF 7.80) was a trap, caught before reporting it as real: the local 1H archive only spans ~105 days, so a 1H entry needs far fewer calendar days than a 15m one to run out of data before resolving — 56% of 1H entries never got the chance to hit RSI≥80 within the window, and the ones that resolved were disproportionately the fast movers (slow grinds toward 80 just ran out of data and got silently excluded from the "resolved" sample). Re-pulled 2 years of 1H history via yfinance (fixed dates) to remove the censoring: unresolved dropped to 11%, matching 15m's own rate, and the real number is PF 3.20, not 7.80 — still comfortably ahead of 15m's 2.16 on every metric, just not the outlier number the short window implied.
Worst trade verified, not excluded: BULL (Webull Corp), entered 2025-07-22 at $17.00 right at what turned out to be a local top (hit $17.74 the next day), then a real, sustained 8-straight-month monthly decline on normal volume throughout (8-55M shares/day, no single-day crash) to a $4.63 low on 2026-03-30 — down 74%. The RSI≥80 exit didn't fire until 2026-04-14 at $5.82, on the first real bounce off that bottom, locking in -66.85% after a 9-month hold. Confirmed real via the raw daily OHLC, not a data glitch — and the same failure mode as CIFR/LIF in the real manual book: an exit rule that only watches for RSI≥80 has no way to tell a structural breakdown from a healthy pullback.
Trade frequency / holding period (2y window, ~497 trading days, 1H variant): 1,490 total entries → ~3.0/trading day across the universe (3.8/day on the 393 days with ≥1 entry, median 3, busiest day 23). Holding period (resolved only): mean 62.1 days, median 41 days, 25th/75th/90th pct = 15d / 87d / 155d, longest 537.9 days. A long-hold strategy, not a quick-turnaround one.
Train/test split (midpoint 2025-10-09, same 2y window): TRAIN (2024-10-10 to 2025-10-09) — 748 trades, 73.3% win, +8.65% avg, PF 2.83. TEST (2025-10-09 to 2026-10-08) — 676 trades, 74.6% win, +10.51% avg, PF 3.46. The edge replicated and actually strengthened out-of-sample — same pattern as the portfolio-sim's -8% stop validating, unlike its time_15d variant that didn't.
Risk-control attempts on the 1H ST+EMA20>50 variant — a 30-day cleanup and every price stop tested both backfire (2026-10-09)
| Variant | Trades | Stopped/cut | Win rate | Avg return | Profit factor |
| No stop, no cleanup (baseline) | 1,329 | — | 74.1% | +9.64% | 3.20 |
| + 30-day stale-cleanup layered on top (the same rule validated for the real manual trade book) | 2,226 | 1,372 (61.6%) | 58.8% | +2.82% | 1.57 |
| -5% price stop | 2,567 | 1,842 (71.7%) | 27.8% | +1.70% | 1.47 |
| Stop width | Trades | Win rate | Avg return | Profit factor |
| -8% | 1,470 | 36.8% | +2.00% | 1.40 |
| -10% | 1,467 | 42.1% | +2.29% | 1.40 |
| -15% | 1,444 | 52.6% | +2.86% | 1.41 |
| -20% | 1,419 | 60.7% | +3.98% | 1.55 |
| -30% | 1,383 | 69.3% | +6.20% | 1.90 |
| -40% | 1,354 | 72.5% | +7.59% | 2.24 |
| -50% | 1,340 | 73.4% | +8.34% | 2.50 |
| -60% | 1,333 | 73.9% | +9.01% | 2.82 |
| -70% | 1,331 | 74.0% | +9.47% | 3.09 |
| -80% (closest to baseline of any width tested) | 1,330 | 74.1% | +9.56% | 3.15 |
Two different risk-control ideas tested against the same 1H ST+EMA20>50/RSI≥80 baseline, both net-negative: the 30-day stale-cleanup that helped the real manual trade book (see the bo15_autotrade.py/positions.html change below) actively hurts this strategy — median hold here is 41 days, so a 30-day cutoff force-exits 61.6% of all trades before they can naturally reach RSI≥80, collapsing PF from 3.20 to 1.57. Different strategy shape, opposite answer: the manual book's trades are meant to be fast dip-bounces (median 2 days), so 30 days genuinely meant "this isn't working"; here it means "this hasn't finished yet."
A full stop-loss sweep from -5% to -80% found no width that beats having no stop at all — PF climbs monotonically as the stop widens but never crosses the 3.20 baseline, even at -80% (which almost never fires, since the single worst trade found was -78.7%). The floor is reached almost immediately (~PF 1.40, around -8% to -10%) and doesn't get meaningfully worse from there — the damage from clipping winners mid-drawdown happens fast and doesn't compound further as the stop tightens. The strategy's profit specifically comes from trades that survive a deep drawdown before recovering (BULL above is the extreme case: -74% before the exit finally fired) — any stop, at any width, removes exactly the trades that make it work. Neither idea adopted; risk here would need to come from position sizing or a cap on concurrent positions, not a per-trade exit rule.
Stricter pullback entry (full EMA20>50>200 stack + pullback to EMA20 + RSI~55) — same edge, worse tail risk (2026-10-09)
| Variant | Trades | Win rate | Avg return | Profit factor | Worst | Median hold |
| ST flip + EMA20>50 only (simple) | 1,329 | 74.1% | +9.64% | 3.20 | -66.9% | 41.0d |
| + full EMA20>50>200 stack, pullback within 1% of EMA20, RSI within ±2 of 55 | 1,324 | 72.9% | +10.19% | 3.20 (identical) | -78.7% | 43.8d |
Requiring the Supertrend-Buy regime to stay active while EMA20>50>200 fully stacks AND waiting for price to pull back within 1% of EMA20 with RSI landing within ±2 of 55 (abandoning the setup if Supertrend flips back to Sell before that happens) produces a profit factor identical to two decimal places against the simple flip+EMA20>50 entry, on almost the same trade count. The extra conditions enter later into the same moves rather than finding better ones — and entering later into an already-established uptrend is why the worst trade got measurably worse (-78.7% vs -66.9%): closer to the top when a BULL-style breakdown happens. Evidence against over-specifying this entry — more confirmation conditions added complexity and worse tail risk without adding edge.
Adding a MACD filter — the intuitive version hurts, the contrarian version is the best result this round but unvalidated (2026-10-09)
| MACD filter (on ST flip + EMA20>50, RSI≥80 exit) | Trades | Win rate | Avg return | Profit factor |
| No MACD filter (baseline) | 1,329 | 74.1% | +9.64% | 3.20 |
| MACD above signal (bullish confirmation — the intuitive filter) | 1,227 | 73.6% | +9.15% | 3.06 |
| MACD above signal AND rising | 1,224 | 73.5% | +9.14% | 3.05 |
| MACD rising, any sign | 1,326 | 74.1% | +9.63% | 3.20 (redundant — filters almost nothing) |
| MACD still BELOW signal — not yet confirmed (contrarian) | 421 | 76.0% | +12.46% | 4.37 |
MACD histogram = (EMA12−EMA26) − EMA9-of-that, normalized by close — same convention entry_features.py already uses elsewhere (*_macd_above). Requiring MACD to already confirm bullish at entry (histogram > 0) slightly hurts rather than helps (PF 3.06 vs 3.20) — a lagging indicator, so waiting for it to agree mostly just removes trades without improving the keepers. "MACD rising" alone changes almost nothing (near-total overlap with the baseline condition).
Inverting it — only entering while MACD hasn't confirmed yet (histogram still negative) — jumps profit factor to 4.37, the best number found this round, ahead of BO15's live 3.19 and in the range of the strongest crypto variants. Reading: catching the Supertrend+EMA flip while MACD is still lagging behind means entering earlier, closer to the real inflection, rather than waiting for every indicator to agree (by which point the easy part of the move is often already gone).
Flagged, not adopted — this cuts the sample to a third of baseline (421 trades) and "the contrarian version worked" is exactly the shape of result that needs a train/test check before being trusted, the same discipline already applied to the plain ST+EMA variant above. Not yet re-validated at the time of writing.
Overlap with the live BO15 rule — same stock pool, different entry timing, not a clean new edge (2026-10-09)
Checked whether the 1H ST+EMA20>50 strategy is just rediscovering BO15's own signals under a different name. Restricted to the ~105-day window where local 15m archive data exists for both (2026-06-25 to 2026-10-08), computing BO15's exact live _strategy_match() conditions directly (Daily EMA20>50 + Price>SMA200, 4H EMA20>50, 1H RSI<45, 15m BO — no whipsaw suppression applied, so this counts raw condition-true candles, not real independent trade signals) against the 1H strategy's entries in the same window.
86% symbol overlap (168 of 196 symbols the 1H strategy traded also matched BO15's conditions at some point in the window) — it's largely fishing in the same pond of trending names, not a disjoint universe; only 28 symbols were unique to the 1H strategy. But exact entry timing rarely coincides: only 13 of 493 1H entries (2.6%) landed on the same symbol+day as a BO15 match, and only about half the symbols (103/196) had an entry within ±3 days of one. Read: a different trigger (trend-following breakout vs. BO15's RSI-dip entry) layered on mostly the same underlying stocks — running both live would mean real correlated exposure to the same names, not diversification into new ones.
This round's net read: interesting, not yet implementable
The 1H ST+EMA20>50/RSI≥80 entry (PF 3.20, replicated out-of-sample at 2.83/3.46 train/test) is a genuinely strong result for a single condition on a single timeframe — comparable to BO15's four-condition, three-timeframe rule despite far less machinery, which is itself a point in its favor (less room to have overfit). But three real obstacles surfaced before it could be considered for implementation: (1) no risk-control fix exists yet — every stop width and the already-validated 30-day cleanup both actively hurt it, because its edge specifically depends on surviving deep drawdowns (BULL: -74% before the exit that finally worked); (2) implied capital load is large — ~3 entries/day with a 41-day median hold works out to roughly 120 simultaneous open positions at steady state, with no sizing/concurrency plan; (3) 86% symbol overlap with BO15 means it's largely correlated exposure to names already being traded, not a new, independent edge. None of this has been implemented — documented here as exploration, same status as every other unreplaced variant on this page.