La pression avant que le prix bouge
Un prix bouge rarement tout seul ; il bouge parce que des ordres arrivent et retirent de la liquidité. Ne regarder que les transactions fait manquer l'essentiel, car l'essentiel de l'action est constitué d'ordres placés et annulés en haut du carnet. Le déséquilibre du flux d'ordres (OFI) résume chaque mise à jour du meilleur bid et du meilleur ask en un seul nombre signé : positif quand les acheteurs ajoutent ou que les vendeurs se retirent, négatif dans le cas inverse. Sur de courts horizons, il prédit remarquablement bien les prochains changements de prix.
def book_imbalance(bid_size, ask_size):
total = bid_size + ask_size
if total == 0:
return 0.0
return (bid_size - ask_size) / total
print(book_imbalance(450, 200)) # 0.3846 more size on the bid: upward pressure
print(book_imbalance(100, 400)) # -0.6 sellers dominate
print(book_imbalance(0, 0)) # 0.0 an empty top does not divide by zeroUne mise à jour, un nombre
Comparez deux cotations successives. Côté bid : si le prix du bid monte ou reste, sa nouvelle taille s'ajoute ; s'il baisse ou reste, son ancienne taille est retirée (à prix inchangé, les deux s'appliquent, donc c'est la variation de taille qui compte). Côté ask, les signes sont inversés : un ask plus bas ou égal ajoute de la pression vendeuse (- nouvelle taille ask), un ask plus haut ou égal retire l'ancienne (+ ancienne taille ask). Les quatre termes sont (b >= pb) * bs - (b <= pb) * pbs - (a <= pa) * as + (a >= pa) * pas, chaque comparaison valant 1 ou 0.
# The order-flow imbalance of one quote update, tried on four changes of the top of the book
def ofi_event(prev, cur):
pb, pbs, pa, pas_ = prev
b, bs, a, as_ = cur
return (b >= pb) * bs - (b <= pb) * pbs - (a <= pa) * as_ + (a >= pa) * pas_
q0 = (99.5, 300, 100.0, 200)
print(ofi_event(q0, (99.5, 450, 100.0, 200))) # 150 more size on the same bid
print(ofi_event(q0, (99.6, 100, 100.0, 200))) # 100 a higher bid: the whole new size counts
print(ofi_event(q0, (99.4, 300, 100.0, 200))) # -300 the bid price fell: the old size is gone
print(ofi_event(q0, (99.5, 300, 100.1, 200))) # 200 the ask retreated: less selling pressureUne série, et ce qu'elle explique
Appliquer la règle à chaque paire de cotations successives donne une série d'OFI. Sur une courte fenêtre, par exemple une seconde, additionnez les événements et comparez à la variation du prix milieu : la relation est presque linéaire, d_mid = slope * OFI. La pente dit de combien chaque unité de flux déplace le prix, et elle diminue quand le carnet est profond. On l'ajuste comme une droite des moindres carrés passant par l'origine : sum(ofi * dmid) / sum(ofi ** 2).
# A series of events from consecutive quotes, set beside the change of the mid price
import numpy as np
def ofi_event(prev, cur):
pb, pbs, pa, pas_ = prev
b, bs, a, as_ = cur
return (b >= pb) * bs - (b <= pb) * pbs - (a <= pa) * as_ + (a >= pa) * pas_
quotes = [(99.5, 300, 100.0, 200), (99.5, 450, 100.0, 200), (99.6, 100, 100.0, 150), (99.6, 100, 100.1, 400)]
events = np.array([ofi_event(prev, cur) for prev, cur in zip(quotes, quotes[1:])], dtype=float)
print(events) # [150. 150. 150.]
mid = np.array([(q[0] + q[2]) / 2 for q in quotes])
dmid = np.diff(mid)
print(np.round(dmid, 3)) # the mid moved in the direction of the flow# Recover the price impact of the flow with a slope through the origin
import numpy as np
def impact_slope(ofi, dmid):
return float(np.sum(ofi * dmid) / np.sum(ofi ** 2))
rng = np.random.default_rng(1)
flow = rng.normal(0, 200, size=500) # order-flow imbalance per second
price_change = 0.00005 * flow + rng.normal(0, 0.004, size=500) # the mid moves with the flow, plus noise
print(round(impact_slope(flow, price_change), 6)) # 5e-05, the true impact per unit of flow- Le déséquilibre du carnet
(bid - ask) / (bid + ask)est entre -1 et +1 ; protégez le cas vide. - OFI d'une mise à jour :
(b >= pb) * bs - (b <= pb) * pbs - (a <= pa) * as + (a >= pa) * pas; une série l'applique à chaque paire consécutive. - Les variations du prix milieu sont presque linéaires en l'OFI :
slope = sum(ofi * dmid) / sum(ofi**2); c'est un signal de court horizon, surtout utile pour l'exécution.
À suivre : le code asynchrone avec asyncio, gérer de nombreux flux de données sans attendre chacun.
Lab : ce qui est noté
- Comparer les deux côtés avec
book_imbalance25 pts - Noter une mise à jour avec
ofi_event25 pts - Suivre la pression en continu avec
ofi_series25 pts - Chiffrer le flux avec
impact_slope25 pts