现货撮合面试手册 | 油条TV - Web3 与 AI 开发社区
← 返回
现货撮合面试手册
Web3 / AI 笔记 · 20 小时前
写一篇
这份手册面向 Java 后端、交易系统和数字资产交易所岗位,重点覆盖现货撮合、限价单、市价单、资金风险控制、可靠性设计以及业务场景追问。
内容标记:
[必会]:必须能独立讲清楚,最好能画流程或写伪代码。
[高频]:面试中很容易被追问,至少准备一个完整业务例子。
[加分]:适合系统设计题或高级岗位,用来体现生产经验。
项目代码参考:
src/main/java/com/zood/cex/matching/domain/OrderBook.java
src/main/java/com/zood/cex/matching/domain/SkipListPriceLevelIndex.java
src/main/java/com/zood/cex/matching/engine/SymbolEngine.java
src/main/java/com/zood/cex/matching/service/TradingPairMatchingService.java
src/main/java/com/zood/cex/matching/service/SpotMatchingService.java
优先级 主题 至少要能回答 [必会] 1价格优先、时间优先 为什么同价订单必须 FIFO,成交价取谁的价格 [必会] 2限价单与市价单 能否立即成交、剩余数量是否入簿、如何控制滑点 [必会] 3GTC、IOC、FOK、POST_ONLY 四者的成交与剩余处理有什么区别 [必会] 4余额冻结与结算 下单冻结什么、部分成交扣什么、撤单解冻什么 [必会] 5单交易对单线程 为什么不对订单簿做多线程并发写,如何横向扩展 [高频] 6幂等与超时 API 超时后为什么不能直接生成新订单号重试 [高频] 7撮合失败处理 WAL、订单状态、冻结资产、成交事件如何保证一致 [高频] 8市价买单语义 按基础币数量买,还是按计价币金额买 [高频] 9风险控制 价格笼子、精度、限额、频控、自成交保护 [加分] 10恢复与消息可靠性 WAL、快照、Outbox、下游幂等如何配合
现货撮合的核心是维护买卖双边订单簿,按价格优先、同价时间优先执行。每个交易对由单线程撮合器顺序消费命令,避免并发写导致结果不确定。下单前资产服务先做校验和冻结,撮合器产生不可变的成交结果,结算服务再按成交明细转移冻结资产。限价 GTC 的剩余数量可以挂单,IOC 和市价单的剩余取消,FOK 要么全成要么拒绝,POST_ONLY 保证只做 maker。生产系统还要用 clientOrderId 幂等、WAL 恢复、Outbox 投递和价格保护处理超时与故障。
ETH 是基础币 base asset,订单数量通常用 ETH 表示。
USDT 是计价币 quote asset,价格表示一枚 ETH 值多少 USDT。
买入 ETH 时消耗 USDT,卖出 ETH 时消耗 ETH。
quoteAmount = price * baseQuantity
示例:以 3,000 USDT 的价格成交 2 ETH,成交额为 6,000 USDT。
Maker:订单先进入订单簿,为市场提供流动性。
Taker:新订单立即与已有挂单成交,拿走流动性。
一笔订单可以先作为 taker 部分成交,剩余部分再入簿成为 maker。
POST_ONLY 的业务意义是确保订单不会立即吃单,常用于做市商控制手续费和策略身份。
bids 买盘:价格从高到低,最高买价是买一。
asks 卖盘:价格从低到高,最低卖价是卖一。
价格索引:price -> PriceLevel
价格档内:FIFO queue<Order>
价格索引用跳表,平均查找、插入、删除复杂度为 O(log n);同一价格档内部用 FIFO 队列保证时间优先。
先成交价格最低的卖单,因为它对买方最有利。
同一卖价有多笔订单时,先成交更早进入订单簿的订单。
这样可以保证公平性、确定性和可回放性。只要输入命令顺序一致,撮合输出就应该一致。
通常使用 maker,也就是订单簿中已有挂单的价格。
例如卖盘中已有 100 USDT 的卖单,新买单限价 105 USDT,成交价是 100 USDT,买方获得了价格改善。买方多冻结的部分应在结算后释放。
面试中不要只回答“取买卖均价”。撮合规则必须明确、稳定且可回放,主流连续竞价通常按被动方价格成交。
客户端下单
-> 网关鉴权、频控、生成请求上下文
-> 交易规则校验
-> 风控与资产冻结
-> 按 symbol 路由到撮合器
-> 写 WAL
-> 订单簿撮合
-> 产生订单事件和成交事件
-> 结算 available / frozen
-> Outbox 投递 RabbitMQ
-> 行情、订单、资产推送
撮合决定“谁与谁以什么价格成交多少”。
结算决定“双方账户余额如何变化、手续费如何扣除”。
NEW
-> OPEN
-> PARTIALLY_FILLED
-> FILLED
-> CANCELED
-> REJECTED
-> FAILED
REJECTED:业务条件不满足,例如余额不足、FOK 无法全成。
FAILED:系统执行异常,例如内部依赖不可用且无法完成命令。
CANCELED:订单曾经有效,但未成交剩余量被用户或系统取消。
成交流水应该不可变;订单状态可以流转,但不能覆盖或删除历史事实。
修改价格或增加数量会改变订单的竞争条件,通常应失去原来的时间优先级。因此常见实现是:
撤销原挂单。
对剩余资产重新做风控和冻结调整。
用新的 sequence 创建替代订单。
部分交易所允许减少数量时保留优先级,但这必须是明确的交易规则,不能由代码偶然决定。
限价买单表示成交价不能高于指定价格;限价卖单表示成交价不能低于指定价格。
限价买单:buyPrice >= bestAsk
限价卖单:sellPrice <= bestBid
能成交的部分立即成交。
未成交剩余量进入订单簿。
一直有效,直到全部成交、用户撤单或系统按规则撤单。
例子:卖盘有 100 x 1 ETH,用户提交 105 x 3 ETH 的限价买单:
1 ETH 以 maker 价 100 成交。
剩余 2 ETH 以 105 进入买盘。
订单状态为部分成交并挂单。
立即成交能成交的部分。
剩余数量取消,不进入订单簿。
下单时先检查指定价格范围内的可成交深度。
能全量成交才执行。
不能全量成交则整笔拒绝,不能产生部分成交。
实现 FOK 时,预检查和真正撮合必须在同一个交易对串行执行上下文中完成,否则预检查后盘口可能被其他线程修改。
如果订单会立即与对手盘成交,整笔拒绝或按交易规则调整价格。
如果不会立即成交,则进入订单簿成为 maker。
面试追问:为什么不能先成交一部分再挂剩余?因为这样订单已经成为 taker,违反了只做 maker 的承诺。
frozenQuote = limitPrice * quantity + estimatedFee
如果买单以更低的 maker 价成交,应按实际成交额扣减,多冻结的计价币释放回 available。部分成交后只继续冻结剩余挂单所需的金额和费用缓冲。
市价单不指定确定成交价格,而是从当前对手盘最优价开始逐档吃单,直到数量或金额满足、盘口耗尽,或者触发保护条件。
市价单不能进入订单簿。盘口不足时,剩余部分应取消或拒绝,不能变成“没有价格的挂单”。
市价卖通常容易定义:卖出指定的基础币数量,例如卖出 2 ETH。
模式 用户输入 结束条件 示例 按基础币数量 要买多少 ETH 累计成交数量达到目标 买入 2 ETH 按计价币金额 最多花多少 USDT 累计成交额达到预算 花 5,000 USDT 买 ETH
[高频] 面试时要先确认接口语义,再讨论冻结逻辑。本项目当前 marketBuy 使用基础币数量;真实 CEX 前端也经常提供按 USDT 金额买入的模式。
按金额市价买:冻结用户输入的计价币预算和手续费缓冲。
按数量市价买:没有固定价格,不能简单使用最新成交价冻结。常见方案有:
根据盘口深度计算预计最坏成交额。
使用标记价或指数价加最大滑点生成保护价。
冻结 保护价 * 数量 + 手续费缓冲。
如果盘口不足或超出保护价,拒绝或取消剩余量。
100 x 1 ETH
101 x 2 ETH
105 x 5 ETH
1 ETH 成交于 100。
2 ETH 成交于 101。
1 ETH 成交于 105。
总成交额为 407 USDT。
成交均价为 101.75 USDT。
最新成交价或卖一价不等于最终平均成交价。大额市价单会消耗多档流动性,因此必须考虑滑点。
最大滑点比例,例如不得偏离参考价超过 3%。
价格笼子,只允许在指数价或标记价的一定范围内成交。
最大成交金额和最大订单数量。
盘口深度不足时拒绝或部分成交后取消。
行情过期、指数价异常或交易对暂停时禁止市价单。
不要回答“市价单可以不计价格一直成交”。生产系统必须给市价单设置边界,否则薄盘口或异常挂单可能造成极端成交。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
submit(taker):
validate(taker)
handle POST_ONLY and FOK
oppositeBook = taker is BUY ? asks : bids
while taker has remaining quantity and oppositeBook is not empty:
bestLevel = oppositeBook.firstEntry()
if limit order cannot cross bestLevel.price:
break
while taker has remaining quantity and bestLevel is not empty:
maker = bestLevel.peek()
qty = min(taker.remaining, maker.remaining)
create trade at maker.price
decrease both orders
remove fully-filled maker
remove empty price level
handle taker remaining quantity by timeInForce
设订单簿有 n 个价格档,本次成交击穿 k 笔 maker 订单:
插入或删除价格档平均 O(log n)。
获取最优价 O(1)。
撮合遍历约为 O(k),再加价格档维护成本。
生成深度快照前 d 档约为 O(d)。
不要只说“撮合是 O(log n)”。真正成交必须逐笔生成 trade,至少要访问被成交的 k 笔订单。
有序结构,容易拿最优价和顺序遍历深度。
平均插入、查找、删除为 O(log n)。
实现比复杂平衡树直观,适合教学和定制。
同一份结构通过 comparator 支持买盘倒序和卖盘正序。
生产中也可以使用红黑树、基数结构、数组化价格网格或专门设计的内存结构。选型要结合价格精度、价格范围、订单规模和延迟目标。
同一个订单簿的修改具有严格顺序。单线程 actor 的优势:
不需要在撮合热路径使用复杂锁。
命令顺序明确,价格时间优先容易保证。
输入相同,输出确定,便于 WAL 回放。
避免锁竞争、死锁和并发状态撕裂。
横向扩展方式不是给同一本订单簿增加写线程,而是按交易对分片:
BTC-USDT -> engine A
ETH-USDT -> engine B
SOL-USDT -> engine C
热门交易对可独占进程或 CPU 核;冷门交易对可以共享分片。单个超热门交易对通常通过更高效的数据结构、批处理、绑核和减少分配来纵向优化,而不是随意让多个线程同时改簿。
“时间优先”依赖一个权威顺序,不能直接相信客户端时间。常见做法是由网关、日志系统或撮合入口分配单调递增 sequence,再由交易对撮合器按 sequence 消费。
多实例部署时还要保证同一 symbol 只有一个有效 leader,避免双写订单簿。可以通过一致性路由、分区日志和租约或 fencing token 防止脑裂。
用户状态:是否登录、是否冻结、是否允许交易。
交易对状态:是否开放交易、是否只允许撤单。
订单规则:数量、金额、价格必须为正。
精度校验:价格 tick size、数量 step size。
限额校验:最小和最大数量、最小和最大名义金额。
资产校验:available 是否足够,冻结操作是否成功。
价格保护:是否偏离参考价过大。
频控:用户、IP、API key、交易对维度的速率限制。
风控通过不代表一定成交,只代表订单有资格进入撮合系统。
available:可用于新下单或提现的余额。
frozen:已被挂单占用,不能重复消费的余额。
下单:available USDT -= 1,000
frozen USDT += 1,000
成交:frozen USDT -= actualTradeAmount + fee
available ETH += filledQuantity
撤单:frozen USDT -= unusedAmount
available USDT += unusedAmount
所有余额变化都应写不可变账本流水,并带业务幂等键。缓存中的余额不是最终审计凭证。
订单每产生一笔成交,结算服务按 trade 处理:
买方:从冻结 USDT 扣实际成交额和费用,增加可用 ETH。
卖方:从冻结 ETH 扣成交数量,增加可用 USDT,扣除费用。
剩余挂单继续占用对应 frozen。
订单完成或取消后,释放多冻结的余额。
结算接口必须以 tradeId 幂等。RabbitMQ 重投同一成交事件时,不能重复加减余额。
同一用户或同一实际控制账户的买卖单相互成交可能制造虚假成交量。常见策略:
CANCEL_TAKER:取消新进入的 taker。
CANCEL_MAKER:取消订单簿中的 maker。
CANCEL_BOTH:双方都取消。
DECREMENT_AND_CANCEL:抵消可自成交数量但不产生真实 trade。
策略应在撮合器内部执行,因为只有撮合器知道当前 maker 和 taker 的归属。
价格笼子:限制订单价格相对指数价或标记价的偏离。
动态熔断:短时间波动超过阈值时暂停撮合或只允许撤单。
异常行情保护:参考价过期时禁用市价单。
胖手指保护:大额、大数量或小数位异常订单二次确认或拒绝。
单用户敞口:限制未成交挂单总额和单方向累计数量。
法币入金存在拒付、盗刷和撤销风险。
新设备、新账户或异常 IP 触发风控观察期。
场外或第三方支付渠道需要等待最终结算。
反洗钱、制裁筛查或人工审核尚未完成。
链上充值可能还未达到足够确认数。
交易所可能允许站内交易但禁止提现,也可能直接冻结可用余额。面试时要区分 交易冻结、提现冻结 和 链上确认中,不能笼统地说“区块链到账慢”。
尚未入队。
已入队但尚未处理。
已撮合成功,但响应丢失。
已落库,但消息或网络暂时失败。
因此客户端不能换一个订单号盲目重试。应使用相同 clientOrderId 查询或重试,服务端以:
symbol + userId + clientOrderId
WAL 先记录撮合命令,再修改内存订单簿。进程重启后按顺序回放命令,恢复相同状态。
WAL 成功前不能对外承诺订单已接受。
WAL 记录必须有校验、顺序号和持久化边界。
回放只重建状态,不能无条件重新发布历史成交事件。
定期快照可以缩短恢复时间,恢复时先加载快照再回放后续 WAL。
撮合或结算落库与消息发送不能依赖“先写数据库,再直接发 MQ”的双写侥幸。常见模式:
业务数据和 outbox event 在同一数据库事务提交。
Outbox Worker 扫描未投递事件并发送 RabbitMQ。
发送成功后记录投递状态。
消费者按 eventId 或 tradeId 幂等消费。
Outbox 通常保证至少一次投递,不保证天然只有一次。所谓 exactly-once 业务效果来自幂等、唯一约束和状态机,而不是假设 MQ 永不重复。
不能回滚已经向市场公开的成交,也不能删除 trade。正确方向是:
成交事实持久化并带唯一 tradeId。
结算任务重试,重复处理由唯一约束拦截。
多次失败进入死信队列并告警。
对相关账户或交易对进行保护性限制,防止风险扩大。
通过账本对账任务发现并修复不平。
人工补偿也必须追加流水,不能直接篡改历史流水。
确保旧 leader 已失效,避免双主。
加载最近一次订单簿快照。
从快照 sequence 之后回放 WAL。
校验订单簿摘要、序列号和余额占用关系。
恢复命令消费和行情发布。
严格系统会先进入只撤单或停止交易状态,确认恢复完成后再开放下单。
先观察队列水位、p95/p99 延迟、处理速率、拒绝数和最近处理时间,再采取:
网关限流,对普通用户和做市商设置不同配额。
队列达到高水位时快速拒绝,避免无限等待和内存耗尽。
撤单命令是否优先要谨慎设计,不能破坏已分配的全局顺序。
热门 symbol 独占实例或 CPU 核。
减少日志、对象分配、同步 IO 和热路径外部调用。
市场异常时进入只撤单、暂停交易或熔断状态。
扩容不能简单启动第二个实例同时处理同一订单簿,必须先解决 symbol 所有权和顺序问题。
已成交部分生成不可变 trade,并按实际 maker 价格结算。
如果是 GTC,剩余数量按原限价进入买盘,保留对应冻结 USDT。
如果是 IOC,剩余数量取消并解冻未使用 USDT。
如果是 FOK,预检查不足时整笔拒绝,不应出现部分成交。
价格改善产生的多冻结金额和最终未使用手续费缓冲要释放。
常见 IOC 语义:成交 2 ETH,剩余 3 ETH 取消。
要求全成的市价 FOK:整笔拒绝。
如果支持外部流动性,可将剩余部分路由给外部市商,但要明确超时、价格保护和对账规则。
绝不能把剩余 3 ETH 作为无价格挂单放进订单簿。
客户端为一次业务意图生成稳定的 clientOrderId。服务端对 symbol + userId + clientOrderId 建幂等约束:
相同请求返回原订单。
同一个 clientOrderId 携带不同参数时应拒绝并告警,而不是静默返回。
资产冻结也使用同一业务幂等键,避免重复冻结。
用户应携带相同 clientOrderId 重试或查询订单。服务端返回第一次执行的订单,不能再撮合一次。504 只代表调用方没有及时拿到结果,不代表撮合命令一定失败。
如果命令尚未写入 WAL,系统不能承诺成功,冻结资产需要按订单接收结果回滚或补偿。
如果 WAL 已持久化但尚未响应,恢复后会回放同一命令。
如果已产生 trade,恢复必须得到相同结果,并靠 tradeId 保证结算幂等。
恢复期间暂停该 symbol 下单,避免双主和顺序错乱。
这说明边界设计有问题。撮合热路径通常以有序日志或 WAL 作为权威输入,数据库订单表是事件投影,而不是让内存状态与普通数据库事务互相回滚。短期可以停止交易对并恢复重放,长期应通过日志、Outbox 和幂等投影消除这类不一致窗口。
成交命令和撤单命令进入同一 symbol 队列,由 sequence 决定先后:
成交先执行:撤单只能取消成交后的剩余量。
撤单先执行:订单先从订单簿移除,后续不能再作为 maker 成交。
不能让 HTTP 线程直接从订单簿删除订单,否则会和撮合线程竞态。
在正确的单线程撮合上下文中,预检查和执行之间没有其他订单簿写命令插入,所以应当全成。如果把预检查放到撮合器外部,盘口可能变化,就会破坏 FOK 语义。
按本项目规则整笔拒绝。另一种产品规则是自动调整到不会成交的价格,但必须在接口文档中明确。无论哪种方案,都不能让订单先吃单再声称它是 maker。
撮合时比较 maker 与 taker 的用户、账户组或受益所有人标识,根据 STP 策略取消 taker、maker 或双方。只比较 userId 可能不足,因为子账户和关联账户也可能属于同一控制人。
可能正常,因为订单、成交、深度属于不同事件投影,网络和消费者延迟不同。解决思路:
所有行情增量带单调 sequence。
客户端先取快照,再按 sequence 应用增量。
发现 sequence 缺口时丢弃本地状态并重新拉快照。
用户订单和资产推送也带版本号,前端按版本去重和覆盖。
API 明确市价单语义和最大可花金额。
风控计算保护价和最大滑点。
撮合器严格执行价格边界。
行情服务保证参考价及时有效。
监控检测薄盘口和异常成交并触发熔断。
消费者以 tradeId 或 eventId 做唯一约束,在同一数据库事务中检查并写入账本。重复事件返回已处理结果,不再次变更余额。ack 必须发生在业务事务成功之后。
如果使用事务 Outbox,短时 MQ 故障不必立即停止撮合:事件先可靠落库,MQ 恢复后补发。但要监控 outbox 积压;如果积压导致订单、结算或行情无法在风险容忍时间内完成,就应降级或暂停交易。
不能草率处理。超时不代表外部订单一定未成交,直接挂回可能造成双重成交。应查询外部订单状态,使用外部 clientOrderId 幂等,并在状态不确定时冻结剩余量、进入人工或异步对账,直到确认最终状态。
类型 可立即成交 可部分成交 剩余入簿 无法满足时 GTC 是 是 是 保留剩余量 IOC 是 是 否 取消剩余量 FOK 是 否 否 整笔拒绝或取消 POST_ONLY 否 否 是 会吃单则拒绝
阶段 核心问题 主要产物 撮合 谁和谁以什么价格成交多少 trade 清算 双方应收、应付、手续费是多少 settlement instruction 结算 账户余额最终如何记账 ledger entries、balance
状态 含义 例子 REJECTED 订单不满足业务规则,未成为有效订单 余额不足、FOK 深度不足 CANCELED 有效订单的未成交部分被终止 用户撤单、IOC 剩余取消 FAILED 系统执行异常,结果需查询或补偿 内部服务异常、持久化失败
建议按下面顺序回答,避免一上来只讲 Redis 或 MQ:
支持哪些订单类型和 TIF。
市价买是按数量还是金额。
成交优先级和成交价规则。
是否允许部分成交、自成交和外部流动性。
一致性、吞吐和延迟目标。
API Gateway / Auth
-> Order Service / Risk
-> Asset Freeze
-> Symbol Router
-> Matching Engine
-> Trade Log
-> Settlement / Ledger
-> Market Data / User Push
同一 symbol 内严格有序。
每个 symbol 只有一个写入 leader。
不同 symbol 并行。
热门交易对独立部署,冷门交易对按 shard 合并。
下单前原子冻结。
tradeId 幂等结算。
双向账本流水与余额更新在同一事务。
撤单只释放未成交剩余。
定时对账验证总账、分户账和链上资产。
clientOrderId 防重复下单。
WAL 加快照恢复订单簿。
Outbox 解决数据库与消息双写。
消费者幂等抵抗重复投递。
leader fencing 防止双主。
限流、熔断、只撤单模式控制故障影响。
每个 symbol 的队列水位和处理速率。
撮合延迟 p50、p95、p99。
下单拒绝率、失败率、超时率。
成交吞吐和订单簿深度。
WAL 落盘延迟和恢复点差距。
Outbox、RabbitMQ、结算任务积压。
账本不平、负余额和重复 trade 告警。
我把订单簿拆成价格索引和同价 FIFO 两层。买盘使用倒序 comparator,卖盘使用正序 comparator,所以双方都能用 firstEntry 取得最优价。价格档默认用跳表,撮合过程逐笔生成 trade,并在订单完全成交后删除队列节点和空价格档。
对应代码:OrderBook、SkipListPriceLevelIndex、PriceLevel。
HTTP 请求可以并发到达,但 SpotMatchingService 会按 symbol 路由到 SymbolEngine。每个 SymbolEngine 使用单线程队列执行下单、撤单、改单和快照命令,因此 OrderBook 不需要方法级 synchronized。不同交易对可以并行,同一个交易对的结果保持确定。
对应代码:SpotMatchingService、SymbolEngine。
下单使用 symbol、userId、clientOrderId 组成业务幂等键。撮合命令先写 WAL 再修改内存订单簿,API 超时后客户端可以使用同一个 clientOrderId 重试。恢复时回放 WAL 重建订单簿,但不会重复发布历史成交事件。
对应代码:TradingPairMatchingService、FileMatchingWriteAheadLog。
教学项目已经体现核心设计,但面试中应主动说明生产化还需要:
WAL 的批量刷盘、校验、分段、复制和高可用。
单 symbol leader 选举、租约和 fencing。
风控冻结与撮合接收之间更严格的状态编排。
成交与账本的幂等结算、对账和补偿闭环。
STP、价格笼子、熔断、交易状态机。
快照加增量行情的 sequence 缺口恢复。
压测、故障注入、容量模型和灾备演练。
能指出边界比把教学实现包装成“生产可用”更有说服力。
[必会] 市价单不是“按最新价成交”,而是按盘口逐档成交。
[必会] API 超时不等于订单失败,必须依靠幂等查询或重试。
[必会] FOK 不能先部分成交再回滚,因为已经产生的成交可能已被市场观察。
[必会] 市价单剩余量不能进入订单簿。
[必会] 撮合成功不代表资金结算已经完成。
[高频] 同一交易对单线程不等于整个交易所只有一个线程。
[高频] MQ 的至少一次投递要求消费者幂等,不要只说“设置不重复消费”。
[高频] Redis 锁不能天然解决订单簿顺序、一致性和回放问题。
[高频] 余额表不是账本,审计和补偿必须依赖不可变流水。
[加分] 外部流动性超时是未知状态,不能直接当成失败重新下单。
价格优先、时间优先。
maker 价成交。
限价单与市价单。
GTC、IOC、FOK、POST_ONLY。
available、frozen 和部分成交结算。
下单 -> 冻结 -> 路由 -> WAL -> 撮合 -> trade -> 结算 -> Outbox -> 推送
至少口述第 9 节的 Q1、Q2、Q4、Q5、Q7、Q12、Q13。每个答案都按以下结构组织:
先给结论
-> 再讲状态如何变化
-> 再讲资金如何变化
-> 再讲幂等与异常处理
-> 最后补监控和生产边界
能不看答案连续讲清下面问题,现货撮合主线就比较稳了:
为什么订单簿要按价格档和同价 FIFO 拆成两层?
限价买单价格高于卖一时,成交价为什么不是买方限价?
GTC、IOC、FOK、POST_ONLY 的剩余数量分别如何处理?
市价买单按币数量和按金额买有什么不同?
市价买单不知道最终价格,资产应该怎么冻结?
部分成交后 available 和 frozen 如何变化?
为什么单交易对更适合单线程撮合?
同一交易对如何扩容,又如何防止双主?
HTTP 504 后客户端应该如何重试?
WAL、快照、Outbox 分别解决什么问题?
撮合成功但结算失败,为什么不能撤销成交?
撤单与成交并发时,最终结果由什么决定?
RabbitMQ 重复投递 trade 时如何避免重复入账?
如何防止自成交和异常价格成交?
你的教学项目离真实交易所还差哪些关键能力?