12.3 FIX协议¶
FIX协议的基本构建块是 标签-值对。每个数据字段都由三部分组成:
- Tag(标签):一个唯一的整数,代表字段的含义(例如,
35代表MsgType,44代表Price)。 - Value(值):字段的实际数据(例如,
D代表NewOrderSingle,150.25代表价格)。 - 分隔符:一个ASCII字符,通常是 SOH(Start of Header,ASCII值 0x01),在日志和示例中常显示为
|或^。
格式: Tag=Value<SOH>
FIX消息的结构
完整的FIX消息由三部分组成,按顺序排列:
消息头 | 消息体 | 消息尾
8=FIX.4.4|9=122|35=D|34=2|49=BUYSIDE|52=20231027-10:30:00.000|56=SELLSIDE|11=ORD10001|55=AAPL|54=1|38=100|40=2|44=150.25|10=052|
1. 消息头
每条消息都必须以消息头开始。它包含用于消息路由和处理的基础信息。
- 必填字段:
8=BEGINSTRING: FIX协议的版本(例如,FIX.4.2,FIX.4.4,FIXT.1.1)。9=BODYLENGTH: 消息体的长度(从Tag 35之后开始,到Tag 10之前的SOH结束)。这个字段对于消息的分帧和验证至关重要。35=MSGTYPE: 消息类型,决定了消息体的结构和含义。- 常见例子:
D = NewOrderSingle,8 = ExecutionReport,5 = Logout,A = Logon。 - 其他常用头字段:
34=MSGSEQNUM: 消息序列号,用于检测丢包和保持通信同步。49=SENDERCOMPID: 发送方标识。56=TARGETCOMPID: 接收方标识。52=SENDINGTIME: 消息发送时间戳。
2. 消息体
消息体包含了该特定消息类型的具体业务数据。其内容完全由消息头中的 MsgType (35) 决定。
- 示例:
NewOrderSingle (35=D)的消息体可能包含: 11=CLORDID: 客户端订单ID。55=SYMBOL: 交易标的(如股票代码)。54=SIDE: 买卖方向(1 = Buy,2 = Sell)。38=ORDERQTY: 订单数量。44=PRICE: 价格。40=ORDTYPE: 订单类型(1 = Market,2 = Limit)。
3. 消息尾
消息尾主要用于消息的完整性校验。
- 必填字段:
10=CHECKSUM: 校验和。这是消息中最后一个字段,是所有字节的和对256取模后的值(包括字段之间的SOH分隔符)。- 计算方式:将消息中
8=开始到SOH之前的所有字符的ASCII值相加,然后对256取模。 - 格式:校验和一定是三位数,例如
052,123。
| 部分 | 标签-值对 | 解释 |
|---|---|---|
| 消息头 | 8=FIX.4.4 |
使用FIX 4.4版本 |
9=122 |
消息体长度为122字节 | |
35=D |
这是一个 NewOrderSingle 消息 |
|
34=2 |
消息序列号为2 | |
49=BUYSIDE |
发送方是"BUYSIDE" | |
52=20231027-10:30:00.000 |
发送时间 | |
56=SELLSIDE |
接收方是"SELLSIDE" | |
| 消息体 | 11=ORD10001 |
唯一的客户端订单ID |
55=AAPL |
交易苹果公司股票 | |
54=1 |
买卖方向:买入 (1) |
|
38=100 |
订单数量:100股 | |
40=2 |
订单类型:限价单 (2) |
|
44=150.25 |
限价价格:150.25美元 | |
| 消息尾 | 10=052 |
校验和(用于验证消息在传输过程中没有损坏) |
重复组
FIX协议中重要的概念是重复组。它用于表示一个字段的列表或数组。
-
结构:
-
一个“领头”字段,指明组内有多少个条目。
-
每个条目包含一组相同的字段序列。
-
示例:一条执行报告可能包含多个成交记录。
数据类型的编码
FIX协议中的值遵循特定的编码规则:
- 字符串:
55=AAPL - 整型:
38=100 - 浮点数:
44=150.25 - 布尔值:
347=N(N=false, Y=true) - 日期/时间:
52=20231027-10:30:00.000(格式为YYYYMMDD-HH:MM:SS.sss) - 字符:
54=1
#include "circular_array_orderbook.hpp"
#include "quickfix/Application.h"
#include "quickfix/FileLog.h"
#include "quickfix/FileStore.h"
#include "quickfix/MessageCracker.h"
#include "quickfix/SessionSettings.h"
#include "quickfix/SocketInitiator.h"
#include "quickfix/fix44/MarketDataIncrementalRefresh.h"
#include "quickfix/fix44/Message.h"
#include "quickfix/fix44/MessageCracker.h"
using namespace circular_array;
using namespace FIX;
// FIX应用
template <typename OrderBookType>
class MyFIXApplication : public Application, public MessageCracker {
public:
explicit MyFIXApplication(OrderBookType& order_book)
: order_book_(order_book) {}
void onCreate(const FIX::SessionID&) override {}
void onLogon(const FIX::SessionID& sessionID) override {}
void onLogout(const FIX::SessionID& sessionID) override {}
void toAdmin(FIX::Message&, const FIX::SessionID&) override {}
void toApp(FIX::Message&, const FIX::SessionID&) override {}
void fromAdmin(const FIX::Message&, const FIX::SessionID&) override {}
void fromApp(const FIX::Message& message,
const FIX::SessionID& session) override {
crack(message, session);
}
// FIX 4.4 行情增量更新消息
void onMessage(const FIX44::MarketDataIncrementalRefresh& message,
const SessionID&) override {
// 1. 获取行情更新条目数量(NoMDEntries)
int update_count = message.groupCount(FIX::FIELD::NoMDEntries);
if (update_count <= 0) {
std::cerr << "FIX MarketDataIncrementalRefresh: No entries found"
<< std::endl;
return;
}
// 2. 遍历每个行情更新条目
for (int i = 1; i <= update_count; ++i) {
FIX44::MarketDataIncrementalRefresh::NoMDEntries md_entry_group;
message.getGroup(i, md_entry_group); // 提取单条行情更新
// 3. 提取核心字段(必须校验存在性,避免崩溃)
Order new_order;
bool is_valid = extract_order_fields(md_entry_group, new_order);
if (!is_valid) {
std::cerr << "Invalid MD Entry (index: " << i << "), skip" << std::endl;
continue;
}
// 4. 确定订单类型(买/卖):基于MDEntryType
FIX::MDEntryType md_entry_type;
md_entry_group.get(md_entry_type);
bool is_bid = (md_entry_type.getValue() == FIX::MDEntryType_BID);
// 5. 确定更新动作(新增/修改/删除):基于MDUpdateAction
FIX::MDUpdateAction md_update_action;
md_entry_group.get(md_update_action);
handle_order_update(md_update_action, new_order, is_bid);
}
}
private:
OrderBookType& order_book_;
// 提取FIX行情字段到自定义Order对象
bool extract_order_fields(
const FIX44::MarketDataIncrementalRefresh::NoMDEntries& md_group,
Order& out_order) {
// 3.1 提取订单ID(MDEntryID)
if (!md_group.isSet(FIX::FIELD::MDEntryID)) {
std::cerr << "Missing required field: MDEntryID" << std::endl;
return false;
}
FIX::MDEntryID md_entry_id;
md_group.get(md_entry_id);
out_order.id = std::stoi(md_entry_id.getValue());
// 3.2 提取订单价格(MDEntryPx)
if (!md_group.isSet(FIX::FIELD::MDEntryPx)) {
std::cerr << "Missing required field: MDEntryPx (OrderID: "
<< out_order.id << ")" << std::endl;
return false;
}
FIX::MDEntryPx md_entry_px;
md_group.get(md_entry_px);
out_order.price = md_entry_px.getValue();
// 3.3 提取订单数量(MDEntrySize)
if (!md_group.isSet(FIX::FIELD::MDEntrySize)) {
std::cerr << "Missing required field: MDEntrySize (OrderID: "
<< out_order.id << ")" << std::endl;
return false;
}
FIX::MDEntrySize md_entry_size;
md_group.get(md_entry_size);
out_order.quantity = static_cast<int>(md_entry_size.getValue());
return true;
}
// 根据FIX更新动作,调用订单簿对应接口
void handle_order_update(const FIX::MDUpdateAction& update_action,
const Order& order, bool is_bid) {
switch (update_action.getValue()) {
case FIX::MDUpdateAction_NEW:
order_book_.add_order(order, is_bid);
std::cout << "Add Order: ID=" << order.id << ", Price=" << order.price
<< ", Qty=" << order.quantity
<< ", Type=" << (is_bid ? "Bid" : "Offer") << std::endl;
break;
case FIX::MDUpdateAction_CHANGE:
order_book_.update_order(order, is_bid);
std::cout << "Update Order: ID=" << order.id
<< ", New Qty=" << order.quantity << std::endl;
break;
case FIX::MDUpdateAction_DELETE:
order_book_.delete_order(order.id, order.price, is_bid);
std::cout << "Delete Order: ID=" << order.id
<< ", Price=" << order.price << std::endl;
break;
default:
std::cerr << "Unsupported MDUpdateAction: " << update_action.getValue()
<< std::endl;
break;
}
}
};
// ------------------------------
// 测试:初始化FIX应用与订单簿
// ------------------------------
int main(int argc, char** argv) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " [FIX配置文件路径]" << std::endl;
return 1;
}
try {
// 1. 初始化FIX会话配置
SessionSettings settings(argv[1]);
// 2. 初始化订单簿
// 基础版订单簿(精度2位小数,深度100个档位)
BasicLimitOrderBook basic_order_book(2, 100);
// 3. 初始化FIX应用(绑定订单簿)
MyFIXApplication<BasicLimitOrderBook> fix_app(basic_order_book);
// 4. 初始化FIX组件(存储、日志、发起器)
FileStoreFactory store_factory(settings);
FileLogFactory log_factory(settings);
SocketInitiator initiator(fix_app, store_factory, settings, log_factory);
// 5. 启动FIX连接,开始接收行情
initiator.start();
std::cout << "FIX Initiator Started. Press 'q' to quit..." << std::endl;
// 6. 等待退出信号
while (std::cin.get() != 'q');
// 7. 停止FIX连接
initiator.stop();
return 0;
} catch (const std::exception& e) {
std::cerr << "Main Error: " << e.what() << std::endl;
return 1;
}
}
纳斯达克ITCH/OUCH协议(二进制):代码需按固定字节长度解析(如ITCH消息固定长度,无需文本转义),其中ITCH用于接收报价/成交数据(需解析“订单新增-成交-撤销”标识),OUCH用于发送订单(需封装“订单类型-数量-价格”二进制包);