DEV Community

Cover image for Stop Trading Like It's 1999 — I Built an Autonomous Vision-Capable Crypto Bot
qrak
qrak

Posted on Edited on

Stop Trading Like It's 1999 — I Built an Autonomous Vision-Capable Crypto Bot

Let me tell you a story about failure.

December 2025. I'm sitting in my Wrocław apartment after an 8-hour warehouse shift. My back hurts. I've been trying to make a trading bot work for two weeks. Every night, same result: the LLM generates beautiful-sounding analysis. Confident. Articulate. Completely wrong.

"RSI is 28, oversold. BUY."

Next morning: -4.2%.

The problem wasn't the LLM being stupid. It was me being lazy. I was dumping numbers into a prompt and hoping for magic — the exact thing every "LLM trading bot" tutorial on YouTube tells you to do.

I had three options: give up, buy a course, or do what nobody else was doing — read the actual research papers. I chose option three. 77 papers later, I understood the real problem. And over the last 7 months, I built a system that fixes it.


1. Chart Vision — The LLM Reads Candlestick Images

Every 4-hour cycle, the bot generates a Plotly candlestick chart with 5 overlay indicators (SMA, RSI, Volume, CMF, OBV). This image goes directly to Google Gemini 3.6 Flash. Not as raw bytes in text. As a visual inference.

I spent two weeks coding pattern detection algorithms — head and shoulders, wedges, trendlines. Then I compared the AI's visual reads against my code. The AI was better. By a lot. I deleted 900 lines of pattern detection code and never looked back.

Visual Token Optimization: Images are token-heavy. Google bills visual processing on a tile basis (258 tokens per 75×75 tile). I wrote a custom PNG/JPEG header parser in Python that reads image dimensions directly from raw bytes without decoding the full image. By dynamically scaling the chart from 4K to 1080p, I cut Google token costs by 75% while keeping pattern geometry sharp.

2. 50+ Indicators, JIT-Compiled with Numba

I refused to use slow pandas-ta wrappers that introduce huge latency overhead. I wrote the entire indicator engine (50+ indicators) in NumPy and Numba. Every calculation compiles to machine code on first call and caches the result:

from numba import njit
import numpy as np

@njit(cache=True)
def _ema_numba(prices: np.ndarray, period: int) -> np.ndarray:
    alpha = 2.0 / (period + 1)
    result = np.empty_like(prices)
    result[0] = prices[0]
    for i in range(1, len(prices)):
        result[i] = alpha * prices[i] + (1 - alpha) * result[i - 1]
    return result
Enter fullscreen mode Exit fullscreen mode

50+ indicators (MACD, ADX, Bollinger Bands, Ichimoku, Stochastic, DSP filters) compile and execute in microseconds on a standard CPU without requiring CUDA or GPUs.

3. Bull vs Bear Debate — One Prompt, Two Sides

TradingAgents spawns separate agents for bull and bear analysis. Smart, but each one costs an extra API call. My bot gives the LLM a single instruction: argue the bullish case first (with evidence), then argue the bearish case (with the same rigor). Then decide.

One prompt. Both perspectives. Zero extra tokens. The model holds both arguments in context simultaneously — it can't "forget" the counter-argument halfway through.

4. EV Framework — What Optiver Said LLMs Can't Do

Optiver found LLMs can explain expected value (EV) but can't execute it. They default to overly conservative heuristics. My EV Framework computes:

  • Historical win rate from matching vector memory setups
  • Expected P&L: $$\text{EV} = (\text{Win Rate} \times \text{Average Win}) - ((1 - \text{Win Rate}) \times \text{Average Loss}) - \text{Fees}$$
  • Kelly Criterion position sizing based on account equity ($10,000 simulated, max 10% hard cap)
  • Risk/reward validation before signal acceptance (minimum 1.5 R:R enforced)

The LLM provides the reasoning. The EV Framework provides the math. If the LLM says "strong buy" but EV comes back negative — HOLD.

5. Falsification Check — Name the Price That Proves You Wrong

Before any signal is accepted, the LLM must write an explicit invalidation sentence:

"This signal would be proven wrong if [specific price level or indicator condition] occurs."

If it can't name one — rejected. If the named condition triggers during the trade — position closed early by the background monitor. If the condition is vague — rejected, try again. This single check improved signal quality more than any prompt tweak.

6. Stateful Vector Memory & The Surprise Ratio

Every closed trade gets embedded into ChromaDB on the upgraded BAAI/bge-base-en-v1.5 (768D) embedding model with 15+ metadata fields: ADX, RSI, volume trend, news sentiment score, surprise ratio, and closed timestamp.

On every new analysis cycle, the bot queries ChromaDB: "Find me the 5 most similar trades to this current technical setup." These 5 trades are injected into the LLM prompt with their outcomes.

The Surprise Ratio Formula: To stop the bot from memorizing lucky wins (market noise), I introduced the Surprise Ratio:
$$\text{Surprise Ratio} = \frac{|\text{Realized P&L} - \text{Expected P&L}|}{|\text{Expected P&L}|}$$

If a trade won due to random news spikes rather than the entry thesis (surprise ratio > 1.5), memory tags it as ⚠️ high surprise so the LLM discounts it in future setup queries.

7. Decoupling Reasoning from Live CCXT Execution

In July 2026, I split the engine. Running API calls, news crawling, and live order placement in a single async thread is an operational hazard. If the Gemini API times out during a critical exit, it threatens funds.

  • Semantic Signal (Reasoning Engine): Runs the vision engine, news RAG, vector memory queries, and writes an atomic JSON decision file.
  • llm_trader_executor: A hardened sibling service that reads the JSON payload, queries live exchange order books via CCXT, sets leverage, and places protected entry and OCO (One-Cancels-the-Other) stop-loss orders. If transmission fails, a local dead-letter queue (failed_forwards.jsonl) stores the payload for replay.

8. Eight AI Agents Maintaining the Codebase

In the .ai/ directory, eight specialized agent prompts — one Supervisor plus seven workers. Each has a dedicated scope, journal, and personality. They've produced real commits:

  • 🧠 Supervisor — Routes work, scans codebase via vector search
  • Bolt — Performance: Numba JIT, async I/O optimization
  • 🎨 Palette — UX: ARIA labels, WebSocket dashboard styling, vis-network graphs
  • 🛡️ Sentinel — Security: rate limiting, CSP headers, API key isolation
  • Refactor — Clean code: killed 27 isinstance chains, enforced DI pattern
  • ✂️ Concise — Code reduction: cut 380 lines across prompt builder
  • 🐛 Bugfixer — Regressions: caught 3 bugs before they hit main
  • 🔥 Smoke Tests — Pre-flight: syntax, import scan, ruff lint before every commit

130+ commits since the agent system was introduced. Every one tested. Every one documented.

9. Validation Pipeline — Never Trust Raw LLM Output

Every AI response passes through:

  • TrendValidator — cross-checks LLM-reported trend against actual ADX calculations
  • PatternQualityScorer — deterministic score from real detection, not self-reporting
  • Falsification Check — reject signals that can't name their failure condition
  • 6 Risk Guards — symbol whitelist, max position, cooldown, min R:R, SL/TP validity

10. Zero-Cost Sentiment RAG — No API Keys

Reddit JSON (free, no auth) and RSS from CoinDesk, CoinTelegraph, Decrypt, CryptoSlate — enriched via Crawl4AI. (Note: We recently purged Twitter/Nitter sentiment processing because unauthenticated scraping became too unreliable). All news is processed through a RAG engine built on ChromaDB. No monthly bills. No API rate limits.


The Test Suite

1,300+ tests. Fully mocked — no network, no ChromaDB, no LLM API. They run in about 60 seconds on any machine. Codacy CLI v2 + ruff for automated quality gates. AST-based codebase vector search (query_codebase.py) for semantic code navigation.

Coverage includes: LLM output corruption, async race conditions, rate limiting with exponential backoff, vector DB boundaries, friction reporting for all 6 guard types, closed-loop feedback injection, ticker retry with network timeout handling.

When Optiver says LLMs "fail to incorporate new information after acting" — my tests verify the bot doesn't do that. When the arXiv survey says reproducibility is the bottleneck — my tests are deterministic.


This project is 100% self-funded. No company. No investors. Just a warehouse worker paying for servers and API keys. If this gave you an idea or saved you time — Buy Me a Coffee helps keep it running. qrak.org has more free content.


What It Costs

  • Google Gemini 3.6 Flash: free tier (1,500 requests/day)
  • Technical indicators: CPU only (Numba JIT, microseconds)
  • News + Sentiment: free (Reddit JSON, RSS + Crawl4AI enrichment)
  • Vector database: local ChromaDB, BAAI/bge-base-en-v1.5 768D embeddings
  • Hosting: home server (Ryzen 5700G, 32 GB RAM)
  • Dashboard: Cloudflare Tunnel (free tier)

Month-to-month cost: 0 zł. Not "almost free." Free.


Quick Start

git clone https://github.com/qrak/LLM_trader.git
cd LLM_trader
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp keys.env.example keys.env
python start.py
Enter fullscreen mode Exit fullscreen mode

Dashboard at localhost:8000.


Honest Limitations

What this bot IS:

  • A research-grade, open-source LLM trading engine
  • Paper trading only (real execution via separate CCXT service)
  • Fully transparent — every prompt, response, and trade log is inspectable
  • Self-improving through reflection engine

What it's NOT:

  • A get-rich-quick scheme
  • A production trading system (yet)
  • Something you should trust with real money without extensive testing

Links


Built in Wrocław, Poland. No degree. No bootcamp. Just Python, asyncio, and thousands of hours after warehouse shifts.


☕ Support: buymeacoffee.com/qrak — every coffee = real API calls

📊 Live: semanticsignal.qrak.org — watch it trade in real time

📚 More: qrak.org — research, articles, free resources

Top comments (2)

Collapse
 
algorhymer profile image
sassenheimer

Hmh... strange.
Current You builds moneybot.
Old You did something related to Ultima Online from the looks of it.
Hmh... I think Old You was fun from this cursory look.
Wonder what happened to Old You...

Well, I have to jump away, because I'm on a quest:
After 2 years of searching, I really need to find one person, who can solve a programming puzzle of mine.

Collapse
 
qrak profile image
qrak

What happened to the old me? Nothing. In my opinion, Ultima Online is the best MMO ever, especially on private shards. Younger people don't appreciate it—they play WoW or other boring Steam MMOs with 'leveling up'. UO was unique. There was no leveling, just skill grinding and stats. The old me didn't change; people changed. Nobody wants to play UO in Poland anymore, which is a shame. It's the best MMO ever. I'm thinking about building a similar game in the future with 3D graphics. POL and RunUO have their limitations. Greetins to you.