券商和交易所使用的语言
交易系统向券商或交易所发送订单时,很多时候使用 FIX(Financial Information eXchange 协议),它自 20 世纪 90 年代初就在使用。FIX 消息是纯文本:一串 tag=value 字段,每个字段以不可见的分隔字符 chr(1)(SOH)结尾。每个 tag 是一个含义约定好的数字:35 是消息类型,55 是代码,54 是买卖方向,38 是数量,44 是价格。这种格式老旧而不起眼,但整个行业都认得它。
# A FIX message is tag=value fields each ended by a separator (shown here as |)
message = "8=FIX.4.2|9=60|35=D|11=ord-1|55=AAPL|54=1|38=100|40=2|44=184.50|10=123|"
fields = {}
for part in message.split("|"):
if part: # the last separator leaves an empty piece
tag, value = part.split("=", 1) # split at the FIRST = only
fields[int(tag)] = value
print(fields[35], fields[55], fields[38], fields[44]) # D AAPL 100 184.50
print(sorted(fields)) # [8, 9, 10, 11, 35, 38, 40, 44, 54, 55]消息的结构
消息分三部分。头部以 8=FIX.4.2(版本)开头,接着是 9= 的正文长度,然后是消息类型 35=。正文承载内容:对 New Order Single(35=D)来说,就是客户订单号、代码、方向(1 买、2 卖)、数量、订单类型(40=2 限价)和价格。尾部是 10=,即校验和:把它之前所有字符的编码相加,再对 256 取余,写成三位数。接收方重新计算以发现损坏,同时检查正文长度。会话层还有更多内容:登录(35=A)、心跳(35=0)以及上一节追踪过的那种序列号。
# The checksum: add up the character codes, keep the remainder modulo 256, three digits
def checksum(text):
return f"{sum(ord(c) for c in text) % 256:03d}"
print(checksum("A")) # 065 the code of "A" is 65
print(checksum("aaa")) # 035 3 * 97 = 291 and 291 - 256 = 35
print(checksum("8=FIX.4.2|35=0|")) # a heartbeat header# Writing fields: tag=value and a separator after each one
def build_fix(fields, sep="|"):
return "".join(f"{tag}={value}{sep}" for tag, value in fields)
body = build_fix([(35, "D"), (11, "ord-1"), (55, "AAPL"), (54, 1), (38, 100)])
print(body) # 35=D|11=ord-1|55=AAPL|54=1|38=100|
print(len(body)) # 34 tag 9 holds this length# A complete order: header with the body length, the body, then the checksum trailer
def checksum(text):
return f"{sum(ord(c) for c in text) % 256:03d}"
def build_fix(fields, sep="|"):
return "".join(f"{tag}={value}{sep}" for tag, value in fields)
def new_order_message(cl_ord_id, symbol, side, quantity, price, sep="|"):
code = "1" if side == "buy" else "2"
body = build_fix([(35, "D"), (11, cl_ord_id), (55, symbol), (54, code), (38, quantity), (40, 2), (44, f"{price:.2f}")], sep)
head = build_fix([(8, "FIX.4.2"), (9, len(body))], sep) + body
return head + build_fix([(10, checksum(head))], sep)
message = new_order_message("ord-1", "AAPL", "buy", 100, 184.5)
print(message) # 8=FIX.4.2|9=...|35=D|11=ord-1|...|44=184.50|10=...|
print(message.split("|")[-2]) # the checksum field不要手写生产环境的引擎解析和构造消息是理解 FIX 的好办法,但真实连接需要会话逻辑:登录、心跳、序列号重置、重发请求以及各交易场所特有的方言。生产环境请使用经过验证的引擎(如 QuickFIX),自己的代码只保留订单逻辑。格式错误或重复的订单可能造成真金白银的损失,所以要记录收发的每一条消息。
- FIX 消息是以分隔符(线路上为
chr(1))结尾的tag=value字段;在第一个=处拆分每个字段。 - 头部
8(版本)和9(正文长度),正文从35(类型)开始,尾部10(校验和)。 - 校验和是字符编码之和对 256 取余,写成三位数;生产环境用经过验证的引擎,并记录每条消息。
下一节:整洁代码,命名、小函数,以及让交易代码库保持可读的习惯。
实验:评分内容
- 用
parse_fix读取消息25 分 - 用
checksum给消息加封25 分 - 用
build_fix写出字段25 分 - 用
new_order_message发送订单25 分