第一章:单机交易系统的诞生——从手工记账到电子化交易

大家好,我是你们的老朋友。

今天咱们聊聊交易系统的起点。说实话,每次我带新人入行,都会先问一个问题:“你觉得最早的交易系统长什么样?”

很多人会想到满屏跳动的K线、高速运转的服务器。其实不是。最早的交易系统,就是一张纸、一支笔,外加一个算盘。嗯,你没听错。

1.1 手工记账时代:交易员的“肌肉记忆”

我入行时,带我的老交易员给我看过他当年的“交易本”。那是一个硬皮笔记本,每一页都画着格子,左边是买单,右边是卖单。价格、数量、时间,全靠手写。

你想想看,那时候的撮合是怎么做的?

  • 客户打电话进来:“老王,帮我买1000股工商银行,价格5.20元。”
  • 交易员翻开本子,找到工商银行那一页,看看有没有人卖5.20元。
  • 有?成交!没有?记下来,等着。

说白了,这就是最原始的订单簿。只不过它是纸质的,而且完全依赖人的记忆和反应速度。

核心概念:订单簿(Order Book)本质上就是一个“价格-数量”的映射表。买单按价格从高到低排列,卖单按价格从低到高排列。这个逻辑,从手工时代到现在的分布式系统,从来没变过。

我曾在一次内部培训中做过实验:让两个实习生用手工方式模拟撮合10笔交易,平均耗时47秒。而同样的交易量,电子系统只需要0.01秒。差距有多大?你自己品。

3.2 电子化的第一步:把纸上的东西搬进内存

到了80年代末90年代初,PC开始普及。有人就想:“为什么不用电脑来记账呢?”

于是,第一代电子化交易系统诞生了。它的核心逻辑极其简单:

  1. 把订单簿从笔记本搬到内存里——用一个数组或者链表来存。
  2. 撮合逻辑从“人脑判断”变成“代码循环”——遍历买单和卖单,找到匹配的。
  3. 成交记录从“手写”变成“打印”或者“存文件”。

我见过一个90年代的老系统,代码是用C语言写的,整个撮合引擎就一个函数,不到200行。但就是这200行代码,撑起了一个营业部每天几千笔的交易量。

一个小技巧:如果你现在去研究那些老系统的源码,会发现它们的核心数据结构就是两个有序链表。一个存买单(降序),一个存卖单(升序)。每次新订单进来,就从链表头开始比较。这个设计到今天依然是所有撮合引擎的基石。

1.3 最原始的撮合引擎:价格优先、时间优先

咱们来拆解一下这个“200行代码”到底干了什么。

第一步:订单进来,先判断是买还是卖。

第二步:如果是买单,就去卖单链表里找。从表头开始,只要卖单价格 ≤ 买单价格,就成交。成交价取卖单价(这叫“被动单优先”)。

第三步:如果卖单数量不够,剩下的买单继续挂在链表里。如果卖单数量多了,剩下的卖单继续等。

第四步:如果是卖单,反过来操作。

你看,就这么简单。但这里面有个关键点:时间优先。什么意思?

假设有两个买单,价格都是5.20元。谁先来,谁先成交。在链表里,就是谁排在前面谁先被遍历到。

注意:我曾经在维护一个老系统时发现过一个bug——新订单插入链表时,如果价格相同,它被插到了尾部。结果导致后到的订单反而先成交。这就是典型的时间优先实现错误。所以,插入位置一定要保证先来后到

下面我画了一张图,帮你理解这个流程:

单机交易系统核心流程:订单簿与撮合引擎 新订单到达 判断买卖方向 遍历卖单链表 遍历买单链表 卖单价 ≤ 买单价? 买单价 ≥ 卖单价? 成交,更新订单簿 成交,更新订单簿 内存中的订单簿 买单(降序): 5.20元 1000股 5.19元 500股 5.18元 2000股 卖单(升序): 5.21元 800股 5.22元 1200股 5.23元 600股

1.4 代码实现:一个极简的撮合引擎

光说不练假把式。我写了一个最简版本的撮合引擎,用Python实现。你一看就懂:

class Order:
    def __init__(self, order_id, side, price, quantity):
        self.order_id = order_id
        self.side = side  # 'buy' or 'sell'
        self.price = price
        self.quantity = quantity
        self.next = None  # 链表指针

class OrderBook:
    def __init__(self):
        self.buy_head = None   # 买单链表头(降序)
        self.sell_head = None  # 卖单链表头(升序)
    
    def add_order(self, order):
        if order.side == 'buy':
            self._insert_buy(order)
            self._match()
        else:
            self._insert_sell(order)
            self._match()
    
    def _insert_buy(self, order):
        # 按价格降序插入,同价按时间顺序
        if not self.buy_head or order.price > self.buy_head.price:
            order.next = self.buy_head
            self.buy_head = order
        else:
            curr = self.buy_head
            while curr.next and curr.next.price >= order.price:
                curr = curr.next
            order.next = curr.next
            curr.next = order
    
    def _match(self):
        # 撮合逻辑:只要买单头 >= 卖单头,就成交
        while self.buy_head and self.sell_head and \
              self.buy_head.price >= self.sell_head.price:
            buy = self.buy_head
            sell = self.sell_head
            trade_qty = min(buy.quantity, sell.quantity)
            trade_price = sell.price  # 被动单价格
            
            print(f"成交:{trade_qty}股 @ {trade_price}元")
            
            buy.quantity -= trade_qty
            sell.quantity -= trade_qty
            
            if buy.quantity == 0:
                self.buy_head = buy.next
            if sell.quantity == 0:
                self.sell_head = sell.next

个人经验:这个代码虽然简单,但我在实际项目中见过它的“升级版”——把链表换成了跳表(Skip List),把O(n)的插入变成了O(log n)。但核心逻辑一模一样。所以,别小看这几十行代码,它是所有交易系统的灵魂

1.5 单机系统的天花板

单机系统能撑多久?我告诉你一个真实数据。

指标 手工记账 单机电子化
每秒处理订单数 0.1笔 100-500笔
单笔撮合延迟 5-30秒 1-10毫秒
支持交易品种 ≤10个 ≤100个
故障恢复时间 立即(人脑记忆) 分钟级(重启)
最大用户数 ≤10人 ≤100人

看到没?单机系统虽然比手工强了上千倍,但它有硬伤:

  • 单点故障——机器一挂,全完蛋。我经历过一次硬盘损坏,整个营业部停了半天,那叫一个惨。
  • 性能瓶颈——CPU、内存就那么多,订单量一上来,CPU直接100%,撮合延迟从毫秒变成秒级。
  • 扩展困难——想加一个交易品种?得改代码、重新编译、停机部署。每次升级都像在拆炸弹。

避坑指南:我曾经接手过一个单机系统,它的订单簿是存在文件里的。每次撮合前先读文件,撮合完再写回去。你想想看,这得有多慢?所以,千万别把订单簿持久化到磁盘,内存才是它的家。持久化可以用日志,但订单簿本身必须常驻内存。

1.6 小结:这一章你学到了什么?

咱们回顾一下:

  • 最早的交易系统是手工记账,订单簿就是一本笔记本。
  • 电子化之后,订单簿搬到了内存里,用链表或者数组实现。
  • 撮合引擎的核心逻辑是“价格优先、时间优先”,代码不到200行。
  • 单机系统有天花板:性能、可靠性、扩展性都不够。

嗯,这一章就到这里。下一章咱们聊聊:当单机撑不住的时候,人们是怎么想到用“主从复制”来解决问题的。到时候我会分享一个我亲身经历的“宕机两小时”事故,保证让你印象深刻。


专注资料整理