La langue que parlent courtiers et bourses
Quand un système de trading envoie un ordre à un courtier ou à une bourse, il le fait très souvent en FIX, le protocole Financial Information eXchange, utilisé depuis le début des années 1990. Un message FIX est du texte brut : une liste de champs tag=valeur, chacun terminé par un caractère séparateur invisible, chr(1) (SOH). Chaque tag est un nombre au sens convenu : 35 est le type de message, 55 le symbole, 54 le sens, 38 la quantité, 44 le prix. Le format est ancien et peu glamour, et toute l'industrie le comprend.
# 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 forme d'un message
Un message a trois parties. L'en-tête commence par 8=FIX.4.2 (la version), puis 9= la longueur du corps, puis le type de message 35=. Le corps porte le contenu : pour un New Order Single (35=D), l'identifiant d'ordre client, le symbole, le sens (1 achat, 2 vente), la quantité, le type d'ordre (40=2 limite) et le prix. Le pied est 10=, une somme de contrôle : additionnez les codes des caractères de tout ce qui précède et gardez le reste de la division par 256, écrit sur trois chiffres. Le destinataire la recalcule pour détecter la corruption, et vérifie aussi la longueur du corps. Les sessions ajoutent le reste : connexion (35=A), heartbeats (35=0) et numéros de séquence comme ceux suivis dans le dernier sujet.
# 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 message FIX est un ensemble de champs
tag=valeurchacun terminé par un séparateur (chr(1)sur le fil) ; coupez chaque champ au premier=. - En-tête
8(version) et9(longueur du corps), corps à partir de35(type), pied10(somme de contrôle). - La somme de contrôle est la somme des codes des caractères modulo 256, sur trois chiffres ; utilisez un moteur éprouvé en production et journalisez chaque message.
À suivre : le code propre, les noms, les petites fonctions et les habitudes qui gardent lisible une base de code de trading.
Lab : ce qui est noté
- Lire un message avec
parse_fix25 pts - Sceller un message avec
checksum25 pts - Écrire des champs avec
build_fix25 pts - Envoyer un ordre avec
new_order_message25 pts