HomeTrading Engine
Trading
Engine
A market scanner with a Claude veto on Bybit futures, built up over four levels from a grid proof-of-concept to a reflection loop. The scanner and its risk rules are judged by a 90-day backtest.
Closed the loop — the bot learns from its own trades.
Illustrative candles and one sample trade, not market data.
Backtest statement, run on 10 April 2026
The backtest, kept like a statement.
The scanner and its risk rules, simulated on 4h candles of 20 Bybit futures, with fees and slippage on every trade. The Claude gate is not part of the simulation.
Return, 90-day backtest
+20.5%$150.00 to $180.78 on a simulated balance: $30.78 net of fees and slippage.
Win rate
60%15 of 25 trades closed with a profit.
Profit factor
1.83Gross profit divided by gross loss, wins vs. losses.
Max drawdown
8.5%$12.70 from peak to trough, against the $150.00 start.
| Entry | Trades | Win rate | Net result | Running result |
|---|---|---|---|---|
| Opening balance | 150.00 | |||
| Momentum | 17 | 65% | +34.96 | |
| Mean reversion | 3 | 67% | +9.36 | |
| Breakout | 5 | 40% | −13.54 | |
| Closing balance | 25 | 60.0% | 180.78 |
How the 25 trades closed
- 13 · 52% Timeout: closed after 6 candles without reaching stop or target.
- 6 · 24% Stop-loss: price reached the initial stop.
- 4 · 16% Take-profit: price reached the target at twice the risk.
- 2 · 8% End of data: still open when the candles ended.
Direction and hold
- Long, 5 trades, 60% won
- +12.03
- Short, 20 trades, 60% won
- +18.75
- Average hold
- 4.2 candles (17h)
- Best trade, ETH short
- +9.44
- Worst trade, BTC short
- −6.50
How it was measured
- Window
--days 90, 4h candles- Market
- The 20 Bybit USDT perpetuals with the highest 24h volume on the day of the run
- Entry
- A scanner setup with score 70 or higher, entered at the open of the next candle
- Risk
- 3.0% of the balance per trade, leverage up to 5x, at most 2 open positions
- Costs
- Taker fee 0.055% and slippage 0.05%, each charged on entry and on exit
- Not simulated
- The Claude gate and the reflection loop
- Source
docs/backtest-results-2026-04-10.txt, the output ofbacktest/runner.py
These are the results of a simulation on historical candles. They are not a live trading result, not a forecast and not investment advice.
Evolution
Four levels, each shipped end-to-end.
Grid PoC, Scanner, LLM Gate, Reflection Loop.
v1
Grid
Proof of concept. Validated Bybit API + execution pipeline. Zero intelligence.
v2
Scanner
Added quantitative edge — 3 setup detectors on 4h candles. Momentum, breakout, mean-reversion.
Every 4 hours the scanner walks the Top-20 futures market. Setups crossing score 70 get handed to the LLM gate.
| Symbol | RSI | Price vs EMA | Volume | Score | Handed to the gate |
|---|---|---|---|---|---|
| TAO | 76.4 | below | 3.2× | 81 | |
| ETH | 71.2 | below | 2.8× | 74 | |
| 1000PEPE | 69.8 | above | 2.1× | 72 | |
| BTC | 62.1 | above | 1.4× | 68 | |
| SOL | 58.9 | above | 1.1× | 55 | |
| DOGE | 44.2 | above | 0.9× | 38 |
candles = self.exchange.get_ohlcv(coin, "4h", limit=168)
if len(candles) < 50:
continue
indicators = calculate_indicators(candles)
for detector in [detect_momentum, detect_breakout, detect_mean_reversion]:
setup = detector(indicators, coin)
if setup and setup.is_actionable(self.config.setup_score_threshold):
setups.append(setup)
breakif close > ema50 and ema20 > ema50 and 55 <= rsi <= 75:
direction = "long"
elif close < ema50 and ema20 < ema50 and 25 <= rsi <= 45:
direction = "short"v3
LLM Gate
Claude as veto layer. Sees funding rates, sentiment, recent trade patterns.
Claude sees the setup, the market context, and recent trade history — and returns a go / no_go with reasoning. It approves or rejects with specific reasons.
Setup from the scanner
ETH/USDT
mean_reversion · short
- RSI
- 71.2 (overbought)
- MACD
- bearish cross
- Volume
- +180% avg
- Funding
- +0.008%
Claude · reasoning
The decision layer
The gate never looked for trades — it approved or rejected setups the scanner had already qualified. The system prompt set the default to go rather than caution, and said so plainly.
You are an aggressive crypto trading analyst. Your job: find reasons to TAKE trades, not avoid them.
A technical setup with score 60+ has already passed quantitative filters. Your role is to check if market context supports or contradicts it. The default answer should be "go" unless you find a SPECIFIC, CONCRETE reason to reject.
…
Decision framework:
- Score 80+: Say "go" unless news directly contradicts (e.g. hack, regulatory ban)
- Score 60-79: Say "go" if sentiment is neutral or supportive
- Only say "no_go" for SPECIFIC reasons: breaking news against the trade, extreme crowding (funding rate > 0.05%), or a clear catalyst against the directionv4
Reflection
Closed the loop — the bot learns from its own trades.
Every closed trade gets a one-line reflection, and the gate re-reads the latest five on its next call. Once a week Claude turns the week's closed trades into two or three learnings, and the gate reads those as well.
A trade closes · sample fill
ETH/USDT
short · closed via take_profit
+$9.44
Entry 3412.4 → Exit 3384.1
Hold 19h · Leverage 3x
The reflection, one line
ETH short (low vol) → WIN: Setup traf TP, volle Reward realisiert
The detail text is German in the code: setup hit the take-profit, full reward realised. It is stored with the trade in SQLite.
The next gate prompt
RECENT TRADE REFLECTIONS (learn from these outcomes): - ETH short (low vol) → WIN: Setup traf TP, volle Reward realisiert
↻ feeds next decision
outcome = "WIN" if pnl > 0 else "LOSS"
reflection_map = {
("WIN", "trailing_stop"): "Trailing Stop hat Profit gesichert, Trend lief wie erwartet",
("WIN", "take_profit"): "Setup traf TP, volle Reward realisiert",
# …
("LOSS", "stop_loss"): "SL getriggert, Setup-Direction war falsch",
# …
}
detail = reflection_map.get((outcome, exit_reason), f"{exit_reason}")
return f"{coin} {direction} ({vol_class} vol) → {outcome}: {detail}"if reflections:
parts.append("")
parts.append("RECENT TRADE REFLECTIONS (learn from these outcomes):")
for r in reflections:
parts.append(f"- {r['reflection']}")Closing notes
What I settled on
What the backtest covers
The 90-day backtest simulates the scanner and the risk rules, with fees and slippage on every trade. The Claude gate is not part of the simulation, so the figures describe the scanner on its own.
Claude judges, it doesn't hunt
It only judged setups the scanner already qualified, never hunted for its own. If the model call fails, the gate answers no_go, so a failure costs a missed trade.
Bybit perpetuals
A futures-first API kept the execution layer simple. The taker fee of 0.055% is charged on entry and on exit of every simulated trade.
Reflections as context
Each closed trade produces one reflection line, stored with the trade. The LLM re-reads the latest five on the next decision, so they work as context, not logs.
Built with
Python · Bybit API via ccxt · Claude · SQLite · Telegram