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.

v1 Grid
v2 Scanner
v3 LLM Gate
v4 Reflection

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.83

Gross profit divided by gross loss, wins vs. losses.

Max drawdown

8.5%

$12.70 from peak to trough, against the $150.00 start.

Result by setup typeamounts in USDT, simulated
The bars show the running net result, from 0 at the opening balance to +30.78 at the close.
EntryTradesWin rateNet resultRunning result
Opening balance150.00
Momentum1765%+34.96
Mean reversion367%+9.36
Breakout540%−13.54
Closing balance2560.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 of backtest/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.

One scan, sample values
SymbolRSIPrice vs EMAVolumeScoreHanded to the gate
TAO76.4below3.2×81
ETH71.2below2.8×74
1000PEPE69.8above2.1×72
BTC62.1above1.4×68
SOL58.9above1.1×55
DOGE44.2above0.9×38
Detectors: momentum, breakout, mean-reversion3 setups found
scanner/market_scanner.pyexcerpt
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)
        break
scanner/setups.pyexcerpt
if 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.

Two sample setups at the gate

Setup from the scanner

ETH/USDT

mean_reversion · short

Score74/100
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.

sentiment/llm_gate.py · SYSTEM_PROMPTexcerpt
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 direction

v4

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

trading/monitor.pyexcerpt
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}"
sentiment/llm_gate.pyexcerpt
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