Skip to main content

Phase 2.1 交易系统(Trading System)

目标:理解一笔交易从下单到成交的完整生命周期,以及支撑这个流程的各个系统角色。


一、订单生命周期​

一笔交易不是"点击买入就完成了",背后是一条由多个系统协作完成的流水线。

用户下单
│
▼
[前端/APP] ──► [风控预检] ──► [OMS 订单管理] ──► [路由引擎]
│
┌──────────────┼──────────────┐
▼ ▼ ▼
[交易所A] [交易所B] [暗池/内部撮合]
│
▼
[成交回报]
│
▼
[OMS 更新订单状态]
│
▼
[持仓系统 / 清算系统]

订单状态机​

每笔订单在系统中的状态流转:

NEW(新建)
│
▼
PENDING_NEW(已发往交易所,等待确认)
│
├──► REJECTED(被拒绝,风控/交易所均可拒绝)
│
▼
OPEN(已在交易所挂单,等待撮合)
│
├──► PARTIALLY_FILLED(部分成交,仍在等待)
│ │
│ ▼
├──► FILLED(全部成交)
│
├──► PENDING_CANCEL(撤单请求已发出)
│ │
│ ▼
└──► CANCELLED(已撤单)

后端工程师需要理解:

  • 订单状态是有限状态机,状态转换必须严格控制
  • 同一笔订单可能收到多条成交回报(部分成交),需要累加处理
  • 网络超时时,订单状态可能是"未知",需要主动查询对账

二、订单类型详解​

市价单(Market Order)​

  • 含义:以当前市场最优价格立即成交
  • 特点:保证成交,不保证价格
  • 风险:在流动性差的市场可能"滑点"严重
  • 后端处理:不需要存储限价字段,优先级最高,直接撮合队列头部

限价单(Limit Order)​

  • 含义:只在指定价格或更优价格成交
  • 特点:保证价格,不保证成交
  • 后端处理:需要进入订单簿等待,存储 price 字段,按价格+时间排序

止损单(Stop Order)​

  • 含义:价格触达触发价后,自动变为市价单执行
  • 用途:限制亏损,如"跌到 90 元就卖出"
  • 后端处理:需要监听行情,条件触发前不进入订单簿

止损限价单(Stop-Limit Order)​

  • 含义:触发后变为限价单而非市价单
  • 后端处理:两阶段,先监听触发价,触发后挂限价单

冰山单(Iceberg Order)​

  • 含义:只展示部分数量,成交后自动补充剩余量
  • 用途:大单隐藏真实意图,减少市场冲击
  • 后端处理:需要维护"展示量"和"总量"两个字段,每次成交后刷新展示部分
// 冰山单的核心字段示意
public class IcebergOrder {
private BigDecimal totalQty; // 总数量(不对外暴露)
private BigDecimal displayQty; // 每次展示的数量
private BigDecimal executedQty; // 已成交数量
private BigDecimal visibleQty; // 当前订单簿可见数量

// 每次 visibleQty 被成交后,从剩余量中补充
public void replenish() {
BigDecimal remaining = totalQty.subtract(executedQty);
this.visibleQty = remaining.min(displayQty);
}
}

三、OMS vs EMS​

这两个系统经常被混淆,但职责不同:

维度OMS(Order Management System)EMS(Execution Management System)
核心职责订单全生命周期管理、合规检查、持仓记录智能路由、执行优化、延迟敏感
使用者基金经理、中后台交易员、算法交易
延迟要求毫秒级即可微秒级
主要功能订单状态追踪、持仓计算、合规审查最佳执行、VWAP/TWAP 算法、Smart Order Routing
数据存储需要持久化全量历史更关注实时内存状态

在实际系统中:

  • 小型券商可能 OMS+EMS 合一
  • 大型机构分离部署,OMS 侧重中后台,EMS 侧重前台执行

四、FIX 协议​

FIX(Financial Information eXchange)是金融行业的标准消息协议,1992 年由 Fidelity 和 Salomon Brothers 联合制定,如今几乎所有券商、交易所之间的通信都用它。

为什么需要 FIX​

没有 FIX 之前,每家公司用自己的消息格式,系统对接要开发大量适配器。FIX 统一了字段定义和消息结构。

消息格式​

FIX 消息是一串 Tag=Value 的键值对,用 \x01(SOH字符)分隔:

8=FIX.4.2\x019=148\x0135=D\x0149=CLIENT\x0156=BROKER\x01
34=100\x0152=20240101-09:30:00\x0111=ORDER001\x0121=1\x01
55=AAPL\x0154=1\x0138=100\x0140=2\x0144=150.50\x0160=20240101-09:30:00\x0110=123\x01

关键 Tag 含义​

Tag字段名含义常见值
8BeginStringFIX 版本FIX.4.2, FIX.4.4, FIXT.1.1
35MsgType消息类型D=新订单, 8=成交回报, F=撤单请求
49SenderCompID发送方标识你的公司代码
56TargetCompID接收方标识对方公司代码
11ClOrdID客户订单ID你系统内唯一订单号
55Symbol证券代码AAPL, 600000.SS
54Side买卖方向1=买, 2=卖
38OrderQty委托数量100
40OrdType订单类型1=市价, 2=限价
44Price限价价格150.50
39OrdStatus订单状态0=新建, 1=部分成交, 2=全部成交, 4=已撤
14CumQty累计成交量50
6AvgPx平均成交价150.30

核心消息类型​

D - New Order Single(新建单笔订单)

35=D        # 消息类型:新订单
11=ORD001 # 客户订单ID
55=AAPL # 证券代码
54=1 # 买入
38=100 # 数量100股
40=2 # 限价单
44=150.50 # 限价150.50

8 - Execution Report(成交/状态回报)

35=8        # 消息类型:成交回报
11=ORD001 # 对应的客户订单ID
17=EXEC001 # 成交编号
39=2 # 订单状态:全部成交
14=100 # 累计成交量
6=150.48 # 平均成交价

F - Order Cancel Request(撤单请求)

35=F        # 消息类型:撤单
11=CANCEL001 # 新的撤单请求ID
41=ORD001 # 要撤销的原始订单ID

用 QuickFIX/J 接收成交回报(示例)​

public class MyApplication implements Application {

@Override
public void fromApp(Message message, SessionID sessionID)
throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue, UnsupportedMessageType {

String msgType = message.getHeader().getString(MsgType.FIELD);

if (MsgType.EXECUTION_REPORT.equals(msgType)) {
handleExecutionReport(message);
}
}

private void handleExecutionReport(Message message) throws FieldNotFound {
String clOrdId = message.getString(ClOrdID.FIELD); // Tag 11
char ordStatus = message.getChar(OrdStatus.FIELD); // Tag 39
double cumQty = message.getDouble(CumQty.FIELD); // Tag 14
double avgPx = message.getDouble(AvgPx.FIELD); // Tag 6

switch (ordStatus) {
case OrdStatus.PARTIALLY_FILLED:
// 部分成交,更新持仓,订单继续等待
updatePosition(clOrdId, cumQty, avgPx);
break;
case OrdStatus.FILLED:
// 全部成交,更新持仓,订单关闭
updatePosition(clOrdId, cumQty, avgPx);
closeOrder(clOrdId);
break;
case OrdStatus.CANCELED:
closeOrder(clOrdId);
break;
case OrdStatus.REJECTED:
handleRejection(clOrdId, message);
break;
}
}
}

五、市场微结构与订单簿​

Level 1 vs Level 2 行情​

级别内容用途
Level 1最优买价(Bid)、最优卖价(Ask)、最新成交价普通用户看盘
Level 2多档买卖价格和数量(通常5档或10档)交易员判断市场深度
Level 3全部挂单明细(通常只有做市商能看)做市商

订单簿(Order Book)数据结构​

卖方(Ask/Offer)        买方(Bid)
价格 数量 价格 数量
151.00 500 150.50 1000
150.80 300 150.30 800
150.70 200 150.10 600
← Spread →
  • Bid-Ask Spread(买卖价差):卖价 - 最优买价,反映流动性成本
  • 流动性越好(大盘股),价差越小;流动性越差(小盘股),价差越大

订单簿的后端实现​

public class OrderBook {
// 买方:价格降序(最高价优先)
private final TreeMap<BigDecimal, Deque<Order>> bids =
new TreeMap<>(Comparator.reverseOrder());

// 卖方:价格升序(最低价优先)
private final TreeMap<BigDecimal, Deque<Order>> asks =
new TreeMap<>();

public void addOrder(Order order) {
if (order.getSide() == Side.BUY) {
bids.computeIfAbsent(order.getPrice(), k -> new ArrayDeque<>())
.addLast(order); // 同价格按时间排队(FIFO)
} else {
asks.computeIfAbsent(order.getPrice(), k -> new ArrayDeque<>())
.addLast(order);
}
}

// 获取最优买价
public BigDecimal getBestBid() {
return bids.isEmpty() ? null : bids.firstKey();
}

// 获取最优卖价
public BigDecimal getBestAsk() {
return asks.isEmpty() ? null : asks.firstKey();
}
}

撮合逻辑​

public List<Trade> match(Order incoming) {
List<Trade> trades = new ArrayList<>();

if (incoming.getSide() == Side.BUY) {
// 买单:找最低卖价,且卖价 <= 买单限价
while (!asks.isEmpty() && incoming.getRemainingQty().compareTo(ZERO) > 0) {
Map.Entry<BigDecimal, Deque<Order>> bestAsk = asks.firstEntry();

// 限价单:只有卖价 <= 买价才能成交
if (incoming.getType() == LIMIT &&
bestAsk.getKey().compareTo(incoming.getPrice()) > 0) {
break; // 无法成交,挂入订单簿等待
}

Order resting = bestAsk.getValue().peekFirst();
BigDecimal tradeQty = incoming.getRemainingQty().min(resting.getRemainingQty());
BigDecimal tradePrice = resting.getPrice(); // 以挂单价成交(价格优先)

trades.add(new Trade(incoming.getId(), resting.getId(), tradeQty, tradePrice));
incoming.fill(tradeQty);
resting.fill(tradeQty);

if (resting.isFullyFilled()) {
bestAsk.getValue().pollFirst();
if (bestAsk.getValue().isEmpty()) asks.remove(bestAsk.getKey());
}
}

if (!incoming.isFullyFilled()) {
addOrder(incoming); // 未成交部分挂入订单簿
}
}
// 卖单对称处理...

return trades;
}

六、高频交易(HFT)基本概念​

HFT 不是本阶段的重点,但需要理解基本概念,因为会影响系统设计决策。

核心特征​

  • 持仓时间极短(毫秒到秒级),通常日内平仓
  • 每日交易量极大,单笔利润极小(靠量取胜)
  • 极度依赖延迟优势(比竞争者快 1 微秒就是优势)

Co-location(共置/托管)​

HFT 公司将服务器直接放在交易所机房,物理距离缩短带来延迟优势。

  • 光速在光纤中传播 1ms ≈ 200km
  • 上海到深圳约 1500km,单程光传输约 7.5ms
  • HFT 公司为节省这几毫秒,愿意付高额托管费

对后端工程师的意义​

如果你不是做 HFT,不需要深入研究纳秒级优化。但要知道:

  • 为什么交易系统要避免 GC 停顿
  • 为什么用 Disruptor 而不是 java.util.concurrent.ArrayBlockingQueue
  • 为什么数据库写入不能在交易热路径上

本节小结​

概念核心要点
订单生命周期NEW → OPEN → FILLED/CANCELLED,状态机严格控制
订单类型市价/限价/止损/冰山,后端处理逻辑各不同
OMS vs EMSOMS 管全生命周期和合规,EMS 管执行优化
FIX 协议Tag=Value 格式,35=消息类型,成交回报(8)是核心
订单簿TreeMap 实现,价格优先+时间优先撮合
HFT理解概念即可,核心是延迟敏感,影响技术选型

下一节:Phase 2.2 风险管理