El idioma que hablan brókers y bolsas
Cuando un sistema de trading envía una orden a un bróker o a una bolsa, muy a menudo lo hace en FIX, el protocolo Financial Information eXchange, en uso desde principios de los años 90. Un mensaje FIX es texto plano: una lista de campos tag=valor, cada uno terminado por un carácter separador invisible, chr(1) (SOH). Cada tag es un número con significado acordado: 35 es el tipo de mensaje, 55 el símbolo, 54 el lado, 38 la cantidad, 44 el precio. El formato es viejo y poco glamuroso, y toda la industria lo entiende.
# 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]La forma de un mensaje
Un mensaje tiene tres partes. La cabecera empieza con 8=FIX.4.2 (la versión), luego 9= la longitud del cuerpo, luego el tipo de mensaje 35=. El cuerpo lleva el contenido: para un New Order Single (35=D), el id de orden del cliente, el símbolo, el lado (1 compra, 2 venta), la cantidad, el tipo de orden (40=2 límite) y el precio. El final es 10=, un checksum: suma los códigos de los caracteres de todo lo anterior y conserva el resto de dividir entre 256, escrito con tres dígitos. El receptor lo recalcula para detectar corrupción, y también comprueba la longitud del cuerpo. Las sesiones añaden más: logon (35=A), heartbeats (35=0) y números de secuencia como los del tema anterior.
# 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- Un mensaje FIX son campos
tag=valorcada uno terminado por un separador (chr(1)en el cable); divide cada campo en el primer=. - Cabecera
8(versión) y9(longitud del cuerpo), cuerpo desde35(tipo), final10(checksum). - El checksum es la suma de los códigos de los caracteres módulo 256, en tres dígitos; usa un motor probado en producción y registra cada mensaje.
A continuación: código limpio, los nombres, las funciones pequeñas y los hábitos que mantienen legible una base de código de trading.
Lab: qué se califica
- Leer un mensaje con
parse_fix25 pts - Sellar un mensaje con
checksum25 pts - Escribir campos con
build_fix25 pts - Enviar una orden con
new_order_message25 pts