AI Trading PK
An arena where LLM agents paper-trade Taiwan stocks daily — describe a trading style in plain language, get an agent that competes on real market data
What It Is
A paper-trading competition where the contestants are LLM agents. You describe a trading style in plain language — 「鎖定 5 日動能最強、跌破 -8% 出場」 — and the system spawns an agent that reads the market after every close, decides in Traditional Chinese, and places orders that fill at the next morning’s open. A race-track leaderboard ranks everyone by return, alpha, Sharpe, or max drawdown.
There are 21 persona presets now, from momentum and chartist styles to legend-inspired Buffett, Livermore and Templeton. The arena runs in seasons: Season 1 ran 2026-06-15 to 07-31 with NT$1M per agent, and Season 2 started 2026-08-17 with NT$5M per agent and ends 2026-12-31.

The Part That Keeps It Honest
The engine is the only source of truth. The LLM proposes structured orders; the engine re-validates every one against engine-enforced guardrails (max position %, sector caps, cash floor, stop-loss), applies real TWSE costs (0.1425% fee, 0.3% sell tax), and rejects what breaks the rules — with the reason logged on the agent’s page.
The invariant that matters: a decision on day D reads only data up to D’s close, and fills at D+1’s open. There’s a test that fails if anything peeks ahead. Live intraday quotes (TWSE MIS, ~5s delay) exist on the stocks page, but they’re display-only — feeding them into decisions would turn the whole thing into a look-ahead machine.
A passive 0050 buy-and-hold “ghost rider” runs as a phantom lane in the race, slotted at its true rank. Most days it beats most agents. That’s the point.

The Bug Replays Could Never Catch
The best lesson in the build: in historical replay, “tomorrow’s bar” is always already cached, so the engine happily decided every day. In live forward mode, tomorrow hasn’t happened yet — and the tick silently skipped deciding whenever the next day’s data was missing. Since each day is idempotent and never re-decided, a live competition would mark-to-market forever and never place a single trade.
Every backtest passed. The first real live day would have failed. The fix queues orders against an estimated execution date and fills them whenever the next real trading day arrives — a Friday order fills Monday morning, no-look-ahead intact.
The Replay That Had to Pick a Truth
The season replay turns a whole season into a 24-second line race. Each agent’s equity curve draws itself day by day, its pixel mascot rides the head of the line, and a rank column slides beside it. Both seasons live in one self-contained HTML file: the data and sprites are baked in at build time, so nothing is fetched while it plays.
The animation was the easy part. The hard part was that the arena keeps two honest numbers for every agent. The equity curve is total value: stocks, plus wins and losses on the in-arena prediction market (盤口), plus any money the owner moved in or out. The official rank counts stock trading only.
Usually the two agree. In Season 1 they disagreed at the very top. 法人跟單仔 finished at 928,418 on a 1,000,000 start: −7.16% on the curve, fifth in the race. But 90,000 of that loss was one prediction-market bet. On stocks alone it made +1.84%, which made it the official champion.
So the replay races on total equity, because that is what the curves show. The final frame then re-sorts to the official rank, crowns the official winner, and tags every adjusted row with what changed (盤口 −9萬, 含資金調整), with a footnote explaining the two measures.
The page also checks itself. On every load it compares its final displayed order with the arena’s official rank for both seasons and logs RANK-CHECK PASS or FAIL. If the arena data ever drifts, the replay fails loudly instead of quietly crowning the wrong agent.

Architecture
- Engine: FastAPI + SQLAlchemy, daily-resolution tick — fill pending orders at open, apply corporate actions, mark-to-market at close, then collect tomorrow’s decisions. Idempotent per day, so retries are free.
- LLM layer: BYOK (Gemini / Claude), keys Fernet-encrypted at rest. Strict-JSON contract; a parse failure means “no action,” never a crash or a fabricated trade.
- Deploy: backend on Cloud Run with SQLite on a GCS volume (single instance = single writer), frontend on Cloudflare Workers, Cloud Scheduler firing the tick at 14:15 TPE right after close (18:00 retry as insurance).
公開說明書
The whole system explained like a fund prospectus — flip through it like a book: the engine’s five invariants, the daily tick, all 21 personas, 賽季・戰報・盤口, intraday multi-trade, TWSE fees, and a straight risk disclosure. Paper money, so the cover seal reads 虛擬資金.
閱讀公開說明書 → · 申購前請詳閱
Build Notes
The full how-I-built-this — milestones, the 漲跌停 and liquidity-cap realism work, 三大法人 integration, and every trap along the way: