I Built a Trading Bot That Doesn't Just Calculate — It Sees, Remembers, and Learns from Its Failures
The engineering history of an open-source, vision-capable crypto bot — first commit 21 December 2025.
1. The Wrocław warehouse spark
In December 2025 I was living in Wrocław, Poland, working 8-hour warehouse shifts. My back was sore, my energy depleted, and my mind was stuck on one question: why are all the "AI trading bots" out there so dumb?
I did what every YouTube tutorial recommends. Pull RSI, MACD and Bollinger Bands in Python, write a prompt template, dump the floats into an LLM API. The decisions came back confident, articulate — and wrong. "RSI is at 28, oversold. BUY." Then the market went down instead, and the paper portfolio went with it.
The model was not the problem. The architecture was. I had built a stateless calculator and called it a reasoning engine.
2. The timeline, taken from git log
Every date below is the date a file first appeared in the repository or a commit landed. You can check all of them with git log --date=short --diff-filter=A.
Phase 1 — December 2025: the stateless calculator
First commit 21 Dec 2025, multimodal support via OpenRouter on 28 Dec. The candle_limit = 999 value has been in config.ini since that very first commit, the order book is fetched at 50 levels, and the bot had no memory of anything it did.
Phase 2 — January 2026: the bot gets eyes
The dashboard server lands 6 Jan; the ChartGenerator (16 Jan) renders OHLCV and RSI into a PNG the model can actually look at, with the image-export retry logic that Plotly/Kaleido makes necessary. The bot stops reading floats and starts reading a chart.
Phase 3 — May 2026: the brain
16 May: the vector-memory era. brain_experience, brain_context and brain_reflection arrive — closed trades become 768D embeddings in ChromaDB, retrievable before the next decision, with a reflection pass that writes rules in plain language. A few days earlier (12 May) came the blunt commit message "make bot profitable with Gemini Flash 3 Preview" — that one aged badly, and I left it in the history on purpose.
Phase 4 — July 2026: the bot starts maintaining itself
26 Jul: the .ai/ directory appears — a supervisor plus seven specialist agents (Bolt, Palette, Sentinel, Refactor, Concise, Bugfixer, Smoke Tests) with their own journals. 30 Jul: Reddit/RSS sentiment lands, and with it the public landing page.
Phase 5 — August 2026: the executor and the verdict journal
14 Aug: executor integration. The engine stops assuming what happened to its orders and starts reading a verdict journal — one line per decision, written by the executor service, keyed by the bot's own order_id. Orders that cannot be confirmed go to a dead-letter queue instead of vanishing.
Phase 6 — September 2026: providers, tests, and a smaller ego
12 Sep: provider transport consolidation and the official DeepSeek API added next to Google AI. 19 Sep: the test suite is reorganised into domain modules and the entry gate is reduced to expected value alone — the fixed R/R floor is gone, and the brain earns its own floor from its own expectancy. 1,549 tests collected, 1,532 passing, 17 skipped.
3. The mathematics and the engineering
The Numba JIT indicator engine
Indicators are written in NumPy and decorated with Numba's @njit(cache=True), so they compile to machine code on first call and cache the result. src/indicators/ contains 97 Numba-compiled functions (134 across the whole src/ tree) with zero external technical-analysis dependencies. Real code, not a screenshot:
@njit(cache=True)
def supertrend_numba(high: np.ndarray, low: np.ndarray, close: np.ndarray,
length: int = 10, multiplier: float = 3.0) -> tuple[np.ndarray, np.ndarray]:
"""Calculate Supertrend indicator."""
n = len(close)
atr = atr_numba(high, low, close, length)
hl2 = (high + low) / 2
upperband = hl2 + multiplier * atr
lowerband = hl2 - multiplier * atr
# ... band adjustments, then the trend/direction loop
return trend, directionsrc/indicators/trend/trend_indicators.py — trimmed; the full function is in the repo.
Chart vision
Plotly renders a 1920×1080 candlestick chart (SMA, RSI, CMF, OBV, swing annotations) to PNG and it is sent to a multimodal model alongside the numeric stack. Images arrive from an export pipeline that hangs often enough that the code carries timeouts and exponential-backoff retries around it — src/analyzer/pattern_engine/chart_generator.py.
The Surprise Ratio
A support break that won because of a random news spike should not become a rule. Every closed trade gets a surprise score:
Anything above 1.5 is tagged ⚠️ high surprise in vector memory so the model discounts it later (src/trading/brain_reflection.py). The bot learns from what it understood, not from what it got lucky on.
What constrains an entry
The project prompts the model to discuss expected value, but the repository does not currently enforce a deterministic expected-value gate. Deterministic checks concern the symbol whitelist, position caps and validated stop/target values. The optional min_rr_entry setting is currently 0.0, so this page does not present it as an active entry gate.
What is not in here
- No Kelly criterion. An earlier version of this page claimed Kelly-sized positions. There is no Kelly code in the repository. Position size is the model's proposal, clamped to a 10% of capital cap, with 1% / 2% / 3% fallbacks by conviction level.
- No magic win rate. I have no sustained profitable period to show, so I do not quote one.
- No backtest-to-live claim. Simulated fills have no slippage, no partial fills and no liquidity limits. Paper numbers are not live numbers.
4. Honest status — September 2026
- Trading: simulated capital only (a 10,000 USDC paper book). No real funds are involved anywhere in this project.
- Executor: implemented as a separate service and wired to an exchange testnet (
ENABLE_TESTNET=true, sandbox endpoints). It has placed test orders and journals their verdicts; live trading with real money is not enabled. - Profitability: unproven. Development has had losing stretches and the code comments admit it. If that ever changes, this page changes with it.
- Source: the whole thing is at github.com/qrak/LLM_trader. Read the code before you believe a word of this page.
5. Quick start
git clone https://github.com/qrak/LLM_trader.git && cd LLM_trader
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp keys.env.example keys.env # add GOOGLE_STUDIO_API_KEY, DEEPSEEK_API_KEY or OPENROUTER_API_KEY
python start.py # dashboard at http://localhost:8000Paper trading is the default. The executor only sees decisions when you enable it — and it defaults to testnet.
6. Errata — what this page used to claim
Kept visible on purpose, because a page that silently edits its own history is worth less than one that shows its corrections:
- "Kelly Criterion position sizing" — removed. No Kelly code exists in the repo.
- "a hard expected-value or 1.5 R/R gate rejects the signal" — removed. The current code does not enforce an expected-value gate, and
min_rr_entry = 0.0. - "how the system earns real money" — removed. The executor runs against an exchange testnet, and no real capital is at stake.
- "1,270 automated tests" (August) → 1,549 today. The number moves with the code, so it is now generated from a test run, not typed from memory.
- Chart vision and vector memory were previously dated February–April 2026. The code says 22 Dec 2025 and 16 May 2026 — the timeline above is the one that matches
git log.