crypto-trading-arena — put four LLMs in front of the same candle and watch who buys what

Standing up Kafka by hand to sit four LLMs in front of the same candle, throwing the same mission at them three times to measure how much the verdict moves, and

Part 23 — Trading Arena: put four LLMs in front of the same candle and watch who buys what

Done when: you have a screen up showing several LLMs looking at the same candle and reaching different trading decisions.

What it does Puts several LLMs in front of the same live market data and the same candle, lets each trade from its own paper wallet, and compares them on one screen
Who it fits Anyone who wants to see with their own eyes, before picking a model, whether these things really behave differently on identical data
Who it doesn't fit Anyone who just wants one answer about what to buy right now. This is an arena, not an advisor
What you end up with One dashboard where 4 agents each hold their own balance, positions and fill history
What you need · cost Code free (Apache-2.0) / Model one paid API key — this whole test cost about $1.06 across four models / Market data free (Binance public API, no key) / weltiq only if you connect it, on the free plan
Time · difficulty 40 minutes, broker included. Difficulty medium-high — four terminal windows at once and hand-editing config.json
Relationship to weltiq An integration experiment. The decision carries over, but size and timing have to be rewritten by hand
Test environment · date 2026-09-22 · macOS 15 (Apple Silicon) · Python 3.13 · Binance spot
Disclosure I write this under a partnership with weltiq. Everything said about the tool comes from measurements I took myself

1. What this is

Market data arrives in one place and is pushed out to several agents identically; each agent then reasons with its own LLM and calls buy, sell, calculator and check balance tools. The wallet is held by a single tool-side process, so who bought what, when and at what price all lands in one table.

The heart of it is the fairness machinery. There is one place that builds the candles, and the sentence that place builds goes to every agent in the same characters. No agent fetches its own data. That is what lets you say "the conclusions differ because the models differ".

Repository ryan-yuuu/crypto-trading-arena (GitHub)
Stars · forks 128 · 40
License Apache-2.0
Last commit 2026-07-08 — quiet for two and a half months
Own tests 139 passing. The repository publishes 22 open bugs as documentation and has pinned 7 of them as deliberately failing tests
Market data Binance spot or Coinbase. No futures path

Repositories that post their open bugs as a list are rare. Nearly everything you have to watch out for in this article was already written down on that list.


2. Installing — the one path I verified

You need a message broker (Kafka), and there is no way around it. The README tells you to use Docker; on a machine without Docker you can stand Kafka up directly with Homebrew. That is the path I verified.

# 1) broker
brew install kafka                     # includes the JDK, 4 minutes
cat > server.properties <<'EOF'
process.roles=broker,controller
node.id=1
controller.quorum.bootstrap.servers=localhost:9243
listeners=PLAINTEXT://localhost:9242,CONTROLLER://localhost:9243
advertised.listeners=PLAINTEXT://localhost:9242
inter.broker.listener.name=PLAINTEXT
controller.listener.names=CONTROLLER
listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
offsets.topic.replication.factor=1
transaction.state.log.replication.factor=1
transaction.state.log.min.isr=1
log.dirs=/tmp/arena-kafka
EOF
kafka-storage format -t $(kafka-storage random-uuid) -c server.properties --standalone
kafka-server-start server.properties &

# 2) the tool
git clone https://github.com/ryan-yuuu/crypto-trading-arena && cd crypto-trading-arena
uv sync
cp config.example.json config.json     # models, keys, symbols and fees are edited here
export OPENROUTER_API_KEY=...          # same name that config.json references with ${...}

I changed three things in config.json.

"trading": {
  "exchange": "binance",
  "binance_symbols": ["BTCUSDT", "ETHUSDT", "SOLUSDT"],
  "fees": { "taker_bps": 60 }
}

Then bring it up in four windows (or four background processes).

uv run python -m exchanges.binance             --bootstrap-servers localhost:9242
uv run python -m deploy.tools_and_dashboard    --bootstrap-servers localhost:9242
uv run python -m deploy.response_viewer        --bootstrap-servers localhost:9242
uv run python -m deploy.agent --from-config <name> --strategy default \
                              --bootstrap-servers localhost:9242   # one per model

Where it gets stuck

Symptom Cause How to get past it
You cannot specify controller.quorum.voters and format the node with --standalone Kafka 4.x uses a dynamic quorum Use controller.quorum.bootstrap.servers instead of controller.quorum.voters
The market-data connector dies with Environment variable 'OPENROUTER_API_KEY' is not set The market connector needs no model key at all, yet it parses the whole config file. On top of that, this one process is the one that doesn't read .env export it into the environment before starting
Brought it up on Coinbase and the agents never see a candle An open bug the repository publishes (high priority) — the candle store is not wired into the Coinbase connector Run it on Binance. Every result in this article is Binance
The dashboard looks mangled Terminal width Widen it past 170 columns, or set COLUMNS=170

3. First result — this is what it looks like when it works

Bring up four agents, leave it about 10 minutes, and you get this screen. The four models received the same candle at the same moment and behaved differently.

Balances, positions and fill history for 4 agents. They received the same 11 market updates, yet gemini filled 10 times while haiku and gpt-5-nano never placed a single order.

When this screen comes up, it worked. Here is how to read it.

What each of them actually thought about that same candle is readable verbatim in a separate window.

deepseek, having analyzed all three symbols, writing that it has to be careful about new entries because of the 60bp fee


4. Getting the most out of it — change one fee line

What separates this from other multi-agent experiments is that it feeds the fee to the model as an input to the decision. Every tick, the sentence that goes to the agents opens with this.

A taker fee of 60 bps (0.60%) is charged on every fill (both buys and sells).
Factor this into your sizing and P&L targets — a round-trip costs 120 bps.

Set taker_bps to 0 in config.json and that same slot becomes Trading is fee-free in this deployment. So I held everything else fixed and changed only that line, then ran two rounds.

4-1. Two live rounds — the same four agents, 11 market updates each

60bp fee 0bp fee
Fills 12 43
Agents that ordered 2 / 4 4 / 4
Notional filled $169,332 $1,439,744 (8.5x)
Fees paid $1,016 $0

The two models that never placed an order in the 60bp round (haiku and gpt-5-nano) both ordered in the 0bp round.

The same screen after setting fees to zero. All four agents traded, and every "Fees paid" reads $0.00.

4-2. Again, on the same candle — 60 replays

The two rounds sit at different market times. So I took one prompt exactly as the connector actually emitted it, changed only the fee sentence, and replayed it 10 times each against the same tools and the same starting account. The market is now completely fixed.

Model Rounds that ordered at 60bp Rounds that ordered at 0bp
deepseek-v3.2 1 / 10 5 / 10
gemini-3.1-flash-lite 8 / 10 6 / 10
gpt-5-nano 0 / 10 2 / 10
Total notional filled $156,392 $462,480 (3.0x)

The verdict does change. But it changes differently per model. Two of them clearly open up when the fee is removed, and gemini-flash-lite moved the other way — it swings about the same whether the fee is there or not. And what happened to that model in the live 60bp round is right there on the first screen: it filled 10 times, paid $1,004 in fees alone, and ended with total equity of $99,065.

So what this tool tells you is not "does this strategy beat the fee" but "is this model even looking at the fee". For picking a model, the second question is the more useful one.

4-3. Two things to know before you start

The default rate does not match the exchange that supplied the data. The config default of 60bp is Coinbase's base tier, and Binance spot, which is where this article's candles came from, is 10bp. Run the default against Binance and you pay 6 times the real cost. If you are going to experiment with fees, fix this first.

Quantities are only accepted to one decimal place. Measured on BTC.

0.05 BTC  rejected   0.09 BTC  rejected   0.15 BTC  rejected
0.1  BTC  accepted   0.2  BTC  accepted

Orders are not blocked outright, but on a $100,000 account a BTC position can only be adjusted in 8.6% steps. "Risk 2% of capital" is simply not available on BTC. deepseek actually computed 100000 * 0.02, arrived at $2,000, then gave up on BTC and went to SOL.


5. Moving it into weltiq — an integration experiment

weltiq setup (same as every part): sign up ({{CTA_URL}}) → My → issue a key under Connections → claude mcp add weltiq https://weltiq.ai/mcp --header "Authorization: Bearer <key>"

Give this tool the mission "trade Bitcoin and make me a profit, and explain why you did what you did" — I typed it in Korean, 비트코인을 거래해서 수익을 내줘, 그리고 왜 그렇게 했는지 설명해줘 — and it returns direction, quantity, target price and invalidation price in one go. Here is what came back from throwing the same mission at it three times.

Run Decision Target Invalidation
1 Buy 0.5 BTC 85,845 85,400
2 Buy 1.1 BTC 87,290 −2%
3 Buy 1.1 BTC 86,800~87,000 —

The direction was the same all three times, and the quantity spread from 0.5 to 1.1 — more than double. The way run 2 arrived at its target is very much this tool's signature — "to clear the 120bp round-trip fee and still keep 1–2%, $85,532 × 1.021 = $87,290".

Carrying run 1's decision into a weltiq slot is five steps.

  1. Write it as a rule document. Entry close > 85400, limit exit 85845, protective stop 0.5%, quantity 0.1
  2. POST /v1/strategy-rules and the document reads back as a single line — entry: (close > 85400) → enter long E · True → price-condition exit (limit=85845) → exit: protective stop 0.5%
  3. POST /v1/ai/thesis to submit why you decided that, in plain language. Without a rationale, the slot is not created
  4. POST /v1/containers to create the slot. Stop conditions (max loss, time limit) are mandatory
  5. Check the fill, then stop and delete

Here is how that day turned out.

Buy 0.1 BTC @ 85,483.07 · fee $8.55
Sell (liquidated on stop) 0.1 BTC @ 85,471.36 · fee $8.55
Realized P&L −$1.17
Total fees $17.10
Net −$18.27 (−0.18%), 14 minutes

What does not carry over

Why you must not take the other side's returns at face value

Here is how the two books accounted for the same round trip.

Arena paper wallet weltiq slot
Rate 60bp (config default) 10bp (Binance spot, actual)
Round-trip fee $102.64 $17.10

Same decision, same market, and the cost comes out 6 times apart. On top of that, the repository itself lists three open accounting bugs — one of them being "realized P&L subtracts the exit fee but unrealized P&L does not". You cannot add those two numbers together. Use this tool to measure the decision, and measure the performance on the side you carried the decision to. That is the right split.


6. After using it

What was good

What fell short

Cost

Code · market data Free
Model About $1.06 for this entire test (four models, 88 live calls + 63 replays)
Per tick $0.001~0.01 per agent on the cheap models
weltiq Three slots on the free plan

7. What to do next

If you are in the middle of choosing a model, use this tool as a model-temperament meter. Forget the return ranking; look at "does it see the fee / can it wait / how much does it put on at once" and the value shows up. After that, one question is left — you have the decision, but who is watching it 24 hours a day.

The next part comes at the same question from the other side: instead of standing several up, a tool that hands one of them a long leash.

weltiq setup — 3 lines

  1. Sign up: {{CTA_URL}}
  2. My → Connections → issue a key
  3. claude mcp add weltiq https://weltiq.ai/mcp --header "Authorization: Bearer <key>"

Write a decision out as a rule and it runs in simulation on live candles, leaving fills, fees and slippage in a ledger. Simulation only.


The full test record (the install workarounds, the raw text of all 3 missions, the raw text of the 60-run fee A/B, and the defect list) is at reports/a/23-crypto-trading-arena/.