Building a Prediction Market From Scratch (2): The Concepts That Make the Rest Easy
There’s no code in this one. Before we open up the engine, I want to make sure a handful of words mean the same thing to you as they do to me — prediction market, YES and NO shares, the order book, price-time priority. Get these into your bones and the code in the next chapters stops looking like code and starts looking like common sense. Skip them and you’ll spend the whole series translating in your head.
(This is chapter two. The hands-on “boot it and place an order” walkthrough is chapter one, and it’ll land once the public repo is up. You can read this first either way — concepts before keyboard works fine here.)
这一章没有代码。在掀开引擎之前,我想先确认几个词在你脑子里和在我脑子里是同一个意思——预测市场、YES 和 NO 份额、订单簿、价格-时间优先。把这几个吃进骨头里,后面几章的代码就不再像代码,而像常识。跳过它们,你接下来一整个系列都得在脑子里做翻译。
(这是第二章。“把系统跑起来、下出第一单"那篇动手的是第一章,等公开仓库就绪后补上。先读哪篇都行——在这件事上,先概念后键盘是顺的。)
What a prediction market actually is
The cleanest way I know to explain it: a prediction market lets people bet on whether something in the future will happen, and the price of that bet is the crowd’s estimate of the odds.
Take a concrete question: “Will it rain tomorrow?” The market turns it into a share.
- A YES share pays out 100 cents if it does rain, and nothing if it doesn’t.
- A NO share pays out 100 cents if it doesn’t rain, and nothing if it does.
So at any moment, the YES price plus the NO price comes to about 100 cents. If YES is trading at 60, the crowd is effectively saying “about 60% chance.” The price is a live, money-backed probability — which is exactly why these markets tend to read reality better than a poll. People are voting with their wallets, not their mouths.
Say Alice buys one YES share at 60 cents. If it rains, she gets 100 back — up 40. If it doesn’t, she’s out the 60. That’s the whole game, and everything we build sits on top of this one idea.
预测市场到底是什么
我知道的最干净的讲法是这样:预测市场让人对未来会不会发生某件事下注,而这个注的价格,本身就是众人对概率的估计。
拿一个具体问题:“明天会下雨吗?“市场把它变成一份份额。
- 一份 YES 份额:真下雨,兑付 100 分;不下雨,归零。
- 一份 NO 份额:不下雨,兑付 100 分;下雨,归零。
所以任意时刻,YES 价加 NO 价大约等于 100 分。如果 YES 卖到 60,众人其实是在说"大概 60% 的概率”。这个价格,是一个实时的、有真金白银垫在底下的概率——这恰恰是预测市场往往比问卷更贴近现实的原因。人们在用钱包投票,不是用嘴。
假设 Alice 用 60 分买了一份 YES。下雨,她拿回 100,赚 40;不下雨,这 60 就没了。整个游戏就这么回事,而我们要搭的一切,都立在这一个想法上面。
YES and NO are two faces of one coin
Here’s the observation that quietly simplifies the entire engine. Since YES and NO always add up to 100, “buying NO at 40” and “selling YES at 60” are the same bet wearing different clothes. Both of them are wagering that it won’t rain.
That has a real engineering payoff: an event only needs one order book. The person who wants to buy YES and the person who wants to buy NO are natural counterparties — you can quietly translate “buy NO at 40” into “sell YES at 60” and let them trade on the same YES book. The engine in this project does exactly that: a small normalization step flips a NO order into a YES order and routes it to the canonical book. We’ll watch it happen in the matching chapter.
There’s a second way to get shares, and it’s worth knowing now because it’s how Polymarket works too. It’s called split, or minting: you put up 100 cents of collateral and get one YES and one NO share in return. Then you sell the half you don’t want. The reverse is merge — hold one YES and one NO, hand them back, get your 100 cents. This project implements both.
YES 和 NO 是一枚硬币的两面
下面这个观察,悄悄把整个引擎简化了。既然 YES 和 NO 永远加起来等于 100,那"用 40 买 NO"和"用 60 卖 YES”,就是同一个注换了身衣服。两者都在押"不会下雨”。
这在工程上有实打实的好处:一个事件只需要一个订单簿。想买 YES 的人和想买 NO 的人天然是对手盘——你可以悄悄把"用 40 买 NO"翻译成"用 60 卖 YES",让他们在同一个 YES 簿上成交。这个项目的引擎干的就是这事:一小步归一化,把 NO 单翻成 YES 单,送到那个规范化的簿上去。等讲撮合时我们会亲眼看它发生。
还有第二种拿到份额的方式,现在就值得知道,因为 Polymarket 也是这么干的。它叫 split,或者说铸造:你押上 100 分抵押,换回一份 YES 和 一份 NO。然后把不想要的那一半卖掉。反过来叫 merge——手里同时攥着一份 YES 和一份 NO,交回去,拿回你的 100 分。这两样,项目里都实现了。
The order book
The order book is the heart of any trading system. It’s just the ledger of everyone’s unfilled intentions — who wants to buy, who wants to sell, and at what price:
ASKS — people willing to sell (higher prices on top)
┌──────────────────────────┐
│ 65¢ ── 20 shares │
│ 62¢ ── 50 shares │ ← best ask = 62
├──────────────────────────┤ ← the gap between them is the "spread"
│ 60¢ ── 100 shares │ ← best bid = 60
│ 58¢ ── 30 shares │
└──────────────────────────┘
BIDS — people willing to buy (lower prices on bottom)A bid is “I’ll buy at this price” — say, 60¢ for 100 shares. An ask is “I’ll sell at this price.” A trade happens the moment some bid’s price meets or beats some ask’s price.
In this project the book is kept with a sorted map from price to quantity (so the levels stay in order automatically) and a queue of order IDs at each price (so we remember who showed up first). When we crack open the matching code, that’s all you’ll find under the hood — a bid walks down from the lowest ask, an ask walks up from the highest bid, and trades fall out where they cross.
订单簿
订单簿是任何交易系统的心脏。说白了,它就是所有人"还没成交的意愿"的账本——谁想买、谁想卖、什么价:
卖盘 ASKS —— 想卖的人(价高在上)
┌──────────────────────────┐
│ 65¢ ── 20 份 │
│ 62¢ ── 50 份 │ ← 最优卖价 = 62
├──────────────────────────┤ ← 中间这道缝叫"价差 spread"
│ 60¢ ── 100 份 │ ← 最优买价 = 60
│ 58¢ ── 30 份 │
└──────────────────────────┘
买盘 BIDS —— 想买的人(价低在下)买单(bid) 是"我愿意以这个价买"——比如 60¢ 买 100 份。卖单(ask) 是"我愿意以这个价卖"。当某个买单的价格碰上或压过某个卖单的价格,成交就在那一刻发生。
在这个项目里,簿是用一个"价格到数量"的有序映射来存的(价位自动排好序),外加每个价位上一条订单 ID 的队列(这样我们记得谁先来的)。等掀开撮合代码,底下就这么点东西——买单从最低卖价往上走,卖单从最高买价往下走,在交叉的地方掉出成交。
Price-time priority, and why it’s fair
When a crowd lines up on the same side, who gets filled first? The rule is price first, then time.
Better price goes ahead — a higher bid, a lower ask, jumps the queue. When two orders sit at the same price, the one that arrived first gets filled first. That’s why each price level is a queue: newcomers join the back, fills come off the front.
I like this rule because it’s the quiet thing that keeps a market honest. You can’t buy your way to the front by being clever about timing or connections — you get ahead by offering a better price, or by being early. Nothing else. The first time you implement it yourself and watch three orders at the same price fill in the exact order they arrived, it stops being a definition and starts being something you trust.
And two flavors of order, while we’re here. A limit order names a price — “no worse than this” — and whatever doesn’t fill right away rests on the book and waits. (That’s the order Alice places in the hands-on chapter.) A market order skips the price and just takes whatever’s best available right now; anything it can’t fill is cancelled rather than left sitting.
价格-时间优先,以及它为什么公平
当一群人挤在同一边,谁先成交?规则是价格优先,然后时间。
价格好的排前面——更高的买价、更低的卖价,插到队前。两个单价格一样时,先到的先成交。这就是为什么每个价位是一条队列:新来的排队尾,成交从队头取。
我喜欢这条规则,因为它是那个悄悄让市场保持诚实的东西。你没法靠在时机或关系上耍小聪明买到队首——你想往前,只能靠出一个更好的价,或者来得更早。没有别的路。等你第一次自己把它写出来,看着同一价位上的三个单,严丝合缝按到达顺序成交,它就不再是一句定义,而变成你信得过的东西。
顺带说两种单。限价单(limit) 指定一个价——“不能比这更差”——当下没吃完的部分会挂在簿上等着。(动手那章 Alice 下的就是这种。)市价单(market) 不管价格,直接吃掉此刻最优的量;吃不掉的部分,直接取消,不留在簿上。
The same shapes, on-chain: Polymarket
Everything you just read has a twin on Polymarket — the largest prediction market in the world — except theirs runs on a blockchain. I find the parallel genuinely useful, because it tells you what’s essential and what’s just an implementation choice.
Their matching is an off-chain order book, same idea as ours. Their YES/NO shares are on-chain tokens (the Conditional Token Framework — ERC-1155 tokens, each redeemable for 1 USDC if its side wins); ours are just numbers in a positions map. Their “split” and “merge” are contract calls that mint and burn those tokens against USDC collateral; ours are commands the engine handles in memory. When a trade settles, their CTF Exchange contract checks signatures and atomically swaps USDC for tokens; ours writes rows to a database. Their collateral is USDC; ours is an internal balance. And when the real-world event finally resolves, they lean on UMA’s optimistic oracle to decide the outcome; we let an admin call resolve.
Same skeleton — match off-chain, settle centrally — with the settlement layer swapped out. So none of this is a toy version of some “real” thing. It’s the real mechanism, with the database standing in for the chain. Each choice has its trade-offs, and I’ll put them side by side when we get to the money and to settlement.
同样的形状,搬到链上:Polymarket
你刚读的每一样东西,在 Polymarket——全世界最大的预测市场——上都有一个双胞胎,只不过它们跑在区块链上。我觉得这个对照真的有用,因为它告诉你:什么是本质,什么只是实现上的选择。
它们的撮合是一个链下订单簿,和我们一个思路。它们的 YES/NO 份额是链上代币(条件代币框架 CTF——ERC-1155 代币,赢的那一边每份可赎回 1 USDC);我们的就是持仓表里的几个数字。它们的"split"和"merge"是合约调用,拿 USDC 抵押去铸造、销毁这些代币;我们的是引擎在内存里处理的命令。一笔成交结算时,它们的 CTF Exchange 合约验签名、原子地把 USDC 和代币对换;我们的是往数据库写几行。它们的抵押物是 USDC;我们的是内部余额。等现实世界的事件最终揭晓,它们靠 UMA 的乐观预言机来定结果;我们让管理员调一下 resolve。
同一副骨架——链下撮合、集中结算——只是把结算层换了个零件。所以这一切都不是某个"真东西"的玩具版。它就是真机制,只是用数据库顶替了链。每种选择各有取舍,等讲到钱、讲到结算时,我会把它们摆在一块儿对比。
Where we go next
That’s the whole vocabulary. A YES share is a 100-cent bet on something happening; YES and NO add to 100, so one book serves both sides; the book matches by price then time; limit orders rest, market orders don’t. Hold those four and the engine ahead will feel less like a wall of Rust and more like a careful write-down of things you already believe.
Next chapter we step back and look at the architecture — three services, talking over Redis, and the journey a single order takes from an HTTP call to a matched trade. And yes, every line of it lands in the open repo, free, no strings.
接下来去哪
整套词汇就这些。一份 YES 是对某件事会发生的 100 分注;YES 加 NO 等于 100,所以一个簿就够两边用;簿按价格、再按时间撮合;限价单会挂着等,市价单不会。攥住这四样,前面的引擎就不再像一堵 Rust 的墙,而更像把你本来就信的东西,仔细写下来而已。
下一章我们退一步看架构——三个服务,通过 Redis 对话,以及一笔订单从一次 HTTP 调用走到一笔成交,中间经过的旅程。还有,每一行都会落进公开仓库,免费,没有附加条件。