TRAD3: turning a pretty reversal chart into code that doesn't lie in hindsight
Every trader loves drawing the same pattern on a historical chart: a strong impulse, a climax, a break of structure, a retest, a new trend. On the screenshot it looks obvious. The problem is it's all drawn after the reversal already happened — in real time the same pattern looks like noise from ten similar false breakouts, and only one of them is real.

Runs 24/7 against Binance's top crypto perpetuals plus gold and oil, and pushes every alert straight to Telegram. Panel access is Telegram Login-gated to the owner for now.
Not phase 1 → phase 5, but a score
The first naive version was a rigid chain of states: impulse fades → climax → break of level → retest → confirmation. Pretty on paper; in practice half of real reversals never complete that chain — sometimes there's no clear climax, sometimes there's no retest because the trend already reversed without one (a classic V-shaped reversal).
So TRAD3 is built on a mandatory core plus weighted scoring, not a rigid sequence:
- ◆Mandatory: a confirmed break of structure (BOS) and confirmation after it — either a retest with a reversal candle, or an impulsive continuation without a retest.
- ◆Optional, by points: impulse fading before the break (+2), climax on volume/volatility (+2), a break with expanding volume (+1), RSI/OBV divergence (+1), a clear rejection on the retest (+2).
A signal only flies to Telegram once the score clears a threshold — and the message itself shows what that score is made of.
Not “the system said enter,” but “here are five components, three fired — judge for yourself.”
A pure function instead of a stateful machine
The key requirement across the whole system: swing points and levels must never move retroactively. A swing high only counts as confirmed once price has pulled back one and a half to two ATR from it — the system deliberately sees structure with a delay of a few bars. That's the price for a backtest that can't lie: what the system showed yesterday won't change today.
Technically this simplified the architecture more than expected: instead of a murky stateful automaton with persistent state, I ended up with a pure function that recomputes the entire phase picture from scratch on every tick, over a rolling window of ~400 closed bars. Since past swings are never repainted by definition, recomputing from zero gives the same result as an incremental update — without a whole class of bugs like “forgot to reset a counter after a restart.” The only thing that needs to persist between ticks is the fact that a given signal was already sent — a plain UNIQUE constraint in SQLite keyed on symbol, tf, ts_of_break, entry_type.
Two containers, one shared volume
Binance (ccxt, REST) ──► detection.py (свинги, BOS, ретест, скоринг)
│
├─► SQLite (WAL): состояния машин, лог сигналов, MAE/MFE
├─► mplfinance: PNG-график с разметкой
├─► Telegram-бот (aiogram): алерт с кнопками 👍/👎/🗑
└─► веб-панель (FastAPI): та же история, с графикамиTwo Docker containers — the engine and the web panel — share one SQLite volume. The panel deliberately isn't on Next.js: it's a read-only showcase over the same database, it doesn't need a second Node process, FastAPI + Jinja2 is enough. Sign-in is via the Telegram Login Widget, access is currently limited to my own account.
I also factored out a source-agnostic Candle(symbol, tf, ts, o, h, l, c, volume, source) abstraction. Right now the only source is Binance, but once forex through MT5 happens (that physically needs a Windows terminal), the detection core won't care where a bar came from. The difference between “crypto now” and “forex later” is just the implementation of one Feed class, not a rewrite of the engine.



Binance doesn't only trade crypto
On the first run, tickers like XAU/USDT, CL/USDT, and SKHYNIX/USDT snuck into the top-25 by volume — gold, oil, a South Korean stock. Turns out Binance USDM Futures has long sold not just crypto perpetuals but “TRADIFI” perpetuals on commodities and equities, and by volume they comfortably compete with altcoins. Had to filter by the underlyingType == "COIN" field in market metadata.
A fun side effect: since gold and oil are there as genuine exchange-traded crypto perpetuals, I simply added XAU/USDT and CL/USDT straight to the watchlist — no Windows VPS or MT5 needed, those are only required for real forex pairs like EURUSD.
Telegram alerts


What's actually proven, and what isn't
Battle-tested
- ✓Backfill across 27 instruments (25 crypto + gold + oil) runs on real history without a single error.
- ✓The full path from signal to alert — entry/stop/target calculation, chart rendering, Telegram delivery with buttons, saving to the panel — was run end to end by hand.
Not proven yet
- ○Thresholds (breakout buffer in ATR, retest zone width, timeouts, scoring weights) are defaults from the initial architecture discussion, not the result of a backtest.
- ○The system currently runs in pure paper mode: not a single real order, only alerts and my own thumbs-up/down labeling.
The honest next step is to run this same detection offline over a year or two of history and see what percentage of BOS turns out false, instead of trusting numbers picked “by feel” during a brainstorm. I deliberately chose to see the live system in action first and calibrate later, based on accumulated statistics — that's a conscious trade-off, not an oversight.
Three directions
- ◆Offline calibration of thresholds against accumulated signal history.
- ◆Forex via MT5 — EURUSD/GBPUSD/USDJPY (gold and oil are already covered through Binance).
- ◆Maybe access for a couple more people — Telegram Login already verifies identity, it just needs a wider allow-list.
For now — watching the alert feed and learning whether to trust my own score.
Need a similar alerting or trading system?
From market data to a Telegram alert with a chart — I'll design the pipeline for your instruments.