A self-evolving BTCUSDT paper-trading research platform. Twenty-five isolated wallets compete every week. Losers are eliminated and permanently banned. A local LLM writes their replacements. The platform live-trades every closed 5-minute candle, refreshes its market awareness hourly from real cited sources, and survives restarts via atomic snapshots. Nothing is trusted — not the model, not the market data, not the generated code.
Paper trading only. No real exchange orders are ever placed and no funds are held. Nothing here is a profitability claim or investment advice.
| 12 active wallets | Twelve structurally distinct strategies, 10,000 USDT each |
| 12 shadow wallets | Virtual evaluation capital, never mixed into active totals |
| 1 Dark Horse | Permanent wallet, never reset, exempt from elimination |
| 140,000 USDT | Active baseline (12 × 10k + Dark Horse + Darkhorse - Daily) |
| Local Qwen | Writes and mutates strategies autonomously via llama.cpp |
| Continuous live loop | Polls Binance every 15s; every newly closed 5m candle is traded by all wallets |
| Hourly awareness | CoinGecko + mempool.space + news RSS, synthesized into a cited brief by the local LLM |
| Restart persistence | Versioned atomic snapshots; startup restores and gap-replays instead of resetting |
Every week: rank by profit → eliminate every loser and every zero-trade strategy →
permanently ban their code and structure → generate ceil(n/2) novel +
floor(n/2) mutation replacements → promote atomically.
| Active portfolio | Shadow (virtual capital) |
|---|---|
![]() |
![]() |
| Dark Horse | All 25 wallets |
|---|---|
![]() |
![]() |
Active, shadow and Dark Horse capital are rendered in visually distinct panels so virtual money can never be misread as real equity.
Not twelve presets of one grid — twelve materially different signal engines, each
with its own signal() and conceptual family. This is proven by test: all
twelve score pairwise below the 0.65 structural-similarity threshold under the
same novelty policy that governs new candidates.
| # | Strategy | Distinctive characteristic |
|---|---|---|
| 1 | Volatility-Adaptive Inventory Grid | Multi-level inventory-managed range |
| 2 | Bollinger Z-Score Reversion | Statistical deviation, with falling-knife veto |
| 3 | Rolling VWAP Deviation | Volume-weighted fair-value reversion |
| 4 | RSI/Stochastic Exhaustion | Oscillator exhaustion + recovery trigger |
| 5 | Donchian Breakout | Price-channel break, volume-confirmed |
| 6 | EMA Trend Pullback | Trend continuation after a controlled dip |
| 7 | MACD Histogram Momentum | Momentum acceleration/deceleration |
| 8 | Bollinger–Keltner Squeeze | Volatility compression → expansion |
| 9 | Chandelier Trend Follower | Long-horizon ATR trailing |
| 10 | Multi-Timeframe Momentum | Return momentum across 4 horizons, inverse-vol sized |
| 11 | OBV / Relative-Volume Breakout | Volume-flow-confirmed accumulation |
| 12 | Regime-Switching Ensemble | Deterministic regime → independent subpolicies |
Enforced in code and locked by tests — not aspirations.
- Fixed-point money everywhere.
floatis rejected at the boundary in both the domain (money.py) and the database (money is stored as exact decimal text, because SQLite'sNumericsilently round-trips through binary float). - Wallet isolation. Cross-wallet postings are structurally impossible; each wallet only mutates itself.
- Fees counted exactly once — acquisition into cost basis, disposal from
proceeds. A flat round trip yields exactly
-(fees). - No same-candle churn. A per-wallet candle watermark makes repeated fills against one open candle impossible.
- Deterministic + bit-reproducible. The same seed replays to identical ledgers, and active/shadow wallets running the same strategy evolve identically.
- Profit is the only ranking value. No Sharpe, drawdown, or committee vote can alter rank — enforced by schema validators, not convention.
Generated strategy code is treated as hostile.
- Never imported into any core process. Each tick runs in a
python -Isubprocess: sanitized environment, temp cwd, hard timeout, POSIX rlimits. - AST deny-by-default, hardened after an independent verifier proved the
original was escapable:
getattr+ string dunders reachedobject.__subclasses__()(299 classes, incl.osgadgets). Now all dunder access and every reflection builtin are rejected. - SSRF-resistant DataBroker. Deny-by-default allowlist; DNS-resolved private/ link-local/metadata IPs blocked; every redirect revalidated; the model cannot add hosts.
- Fail-closed API. Mutations require a token (401/403/400/422); errors are redacted to a correlation ID; zero unsafe DOM sinks in the frontend.
- Identity-verified process control. A recycled PID receives no signal at all.
See docs/threat-model.md and
docs/audits/phase13-verification.md — the
latter records every defect independent verifiers found, including two severe ones.
python -m venv .venv
.venv/Scripts/python -m pip install -r requirements-dev.txt
# LIVE: real BTCUSDT candles from Binance public. No API key required.
.venv/Scripts/python -m tradebot.api.devserver --port 5555 --live
# Offline: seeded synthetic market, touches no network.
.venv/Scripts/python -m tradebot.api.devserver --port 5555
# -> http://127.0.0.1:5555/--live is wired and working. Public BTCUSDT data requires no credentials —
deliberately: requiring exchange keys for paper trading was audit finding
A10. Every request goes through the DataBroker allowlist
(data-api.binance.vision, GET only).
What live mode actually does:
- backfills 1000 real 5m candles and replays them through the real execution engine;
- then keeps trading: a
LiveLooppolls Binance every 15s and runs every wallet plus the permanent committee on each newly closed 5m candle — replay, live trading, and outage catch-up all share one idempotentTickEnginecode path; - replays any gap (downtime, network outage) in order on reconnect — parity-tested against an uninterrupted run;
- fetches the real exchange filters — Binance's actual
LOT_SIZEstep is0.00001, not the 1-satoshi default, so fills obey true venue rules; - excludes the in-progress candle — only completed bars ever drive a decision or a mark;
- re-marks equity every 15s from the newest closed candle;
- fails loudly if live data can't be fetched, rather than silently serving
synthetic prices; a refresh failure keeps the last good mark and flags the
source
degraded.
Prices are never parsed through a binary float — decimal strings go straight to
Decimal.
If you later add private endpoints (not required, and not recommended for a
paper platform), credentials go in .env — never in the dashboard, never in git:
# .env (gitignored; CI Gate 1 fails the build if it is ever tracked)
BINANCE_API_KEY=...
BINANCE_API_SECRET=...TRADEBOT_LLM_PROVIDER=llama_cpp
TRADEBOT_LLM_BASE_URL=http://172.29.72.68:18081/v1
TRADEBOT_LLM_HEALTH_URL=http://172.29.72.68:18081/health
TRADEBOT_LLM_EXPECTED_MODEL_ARTIFACT=Qwen3VL-30B-A3B-Instruct-Q4_K_M.ggufThe served model ID is discovered via /v1/models — never assumed to equal
the GGUF filename. (It currently resolves to Qwen3-VL-30B-A3B-Instruct.) If the
model is down the platform reports degraded and keeps trading; it never
fabricates analysis.
Every hour an AwarenessService pulls CoinGecko market stats, mempool.space
on-chain data, and news RSS — all through the same deny-by-default DataBroker —
and has the local model synthesize a structured, cited brief. It replaces the
old synthetic macro/fundamental/on-chain placeholders and degrades honestly: a
stale brief is capped and falls back to the placeholder rather than pretending
to be fresh. Exposed at /system/awareness and on the dashboard's
market-awareness panel; /system/live reports the trading heartbeat.
| Gate | Result |
|---|---|
| Tests | 947 passing (full suite) |
Coverage (tradebot/*) |
97% (ratchet; see docs/testing.md) |
| Ruff / Mypy | clean (63 files) |
| Bandit / pip-audit | 0 issues / no known vulnerabilities |
| Tick performance | 0.62 ms for 24 wallets (10 ms budget) |
CI runs 8 gates: hygiene, correctness, security, database, frontend, deterministic replay, performance, release candidate.
Architecture · Accounting · Execution · Plugin SDK · Evolution policy · Dark Horse · DataBroker · Threat model · Testing · Release checklist
The new tradebot package is a release candidate, not a finished replacement:
- Legacy event import is not implemented — the platform starts fresh.
- Coverage is 97%, not 100%.
- Frontend has static safety analysis; no jsdom/Playwright suite yet.
- The legacy flat modules still exist and still carry their original findings.
Full detail in docs/release-checklist.md.



