Là où les prix se forment
Un prix à l'écran est le résultat d'une file d'attente. En bourse, acheteurs et vendeurs placent des ordres à cours limité : « acheter 300 à 99,50 », « vendre 200 à 100,00 ». Ceux qui ne sont pas exécutés restent dans le carnet d'ordres : les offres d'achat (bids), la meilleure au prix le plus élevé, et les offres de vente (asks), la meilleure au prix le plus bas. Une transaction a lieu quand un ordre entrant rencontre un ordre en attente. Tout ce que les sujets précédents appellent prix, écart, glissement et impact vient de cette structure.
# A side of the book as a dict {price: total size}
bids = {99.5: 300, 99.4: 500, 99.0: 1200}
asks = {100.0: 200, 100.1: 400, 100.5: 800}
best_bid, best_ask = max(bids), min(asks)
print(best_bid, best_ask) # 99.5 100.0
print(round(best_ask - best_bid, 2), (best_bid + best_ask) / 2) # the spread 0.5 and the mid 99.75Écart, prix milieu et profondeur
Le meilleur bid et le meilleur ask forment le haut du carnet. Leur différence est l'écart (spread), prix de la négociation immédiate : un acheteur qui prend l'ask et revend au bid perd l'écart. À mi-chemin se trouve le prix milieu, la référence usuelle d'un prix juste. La profondeur est la taille en attente à chaque niveau et au-delà, et dit combien peut s'échanger avant que le prix bouge. Un côté vide signifie qu'il n'y a aucun prix de ce côté, donc écart et milieu sont indéfinis : None, pas zéro.
def best_bid_ask(bids, asks):
return (max(bids) if bids else None, min(asks) if asks else None)
def spread_and_mid(bids, asks):
bid, ask = best_bid_ask(bids, asks)
if bid is None or ask is None:
return None, None
return ask - bid, (bid + ask) / 2
print(spread_and_mid({99.5: 300, 99.4: 500}, {100.0: 200, 100.1: 400})) # (0.5, 99.75)
print(spread_and_mid({}, {100.0: 200})) # (None, None): one side is emptyAjouter des ordres sans modifier le passé
Un nouvel ordre à cours limité rejoint un niveau de prix existant, ajoutant à sa taille totale, ou ouvre un nouveau niveau. L'écrire comme une fonction qui renvoie un nouveau dict plutôt que de modifier l'ancien présente un avantage pratique : on garde un instantané du carnet avant l'ordre, utile pour tester et rejouer. Copiez avec dict(side), modifiez la copie, renvoyez-la. Une fonction qui modifie en silence son argument est une source classique de bugs quand le même carnet sert à deux endroits.
def add_limit_order(side, price, size):
new = dict(side) # a copy: the original book is not touched
new[price] = new.get(price, 0) + size
return new
bids = {99.5: 300, 99.4: 500}
after = add_limit_order(bids, 99.5, 100) # joins the existing level
after = add_limit_order(after, 99.3, 50) # opens a new level
print(after) # {99.5: 400, 99.4: 500, 99.3: 50}
print(bids) # {99.5: 300, 99.4: 500} unchangedApparier un ordre au marché
Un achat au marché prend les asks du prix le plus bas vers le haut : tout le premier niveau, puis le deuxième, jusqu'à ce que l'ordre soit exécuté ou le carnet vide. Parcourez les prix triés, prenez min(left, size_at_price) à chacun, enregistrez l'exécution, puis supprimez le niveau (pris entièrement) ou réduisez-le (pris en partie). La liste des exécutions donne le prix moyen vu dans le sujet sur les exécutions, et le carnet restant est le marché après votre transaction : plus mince en haut, avec un moins bon meilleur ask.
def match_market_buy(asks, size):
remaining = dict(asks)
fills = []
left = size
for price in sorted(asks): # cheapest first
if left <= 0:
break
take = min(left, asks[price])
fills.append((price, take))
left -= take
if take == asks[price]:
del remaining[price] # the level is used up
else:
remaining[price] = asks[price] - take
return fills, remaining
asks = {100.0: 200, 100.1: 400, 100.5: 800}
fills, remaining = match_market_buy(asks, 500)
print(fills) # [(100.0, 200), (100.1, 300)]
print(remaining) # {100.1: 100, 100.5: 800}- Chaque côté est un dict
{price: size}: le meilleur bid estmax(bids), le meilleur askmin(asks); l'écart estask - bid, le milieu leur moyenne, et les deux valentNonesi un côté est vide. - Ajoutez un ordre en copiant le dict, en ajoutant au prix, puis en renvoyant la copie ; le carnet passé reste inchangé.
- Un achat au marché parcourt les asks triés, prenant
min(left, size)par niveau, supprimant les niveaux épuisés, réduisant ceux pris en partie ; les vrais carnets ont aussi une file à chaque niveau.
À suivre : le déséquilibre du flux d'ordres, lire la pression dans le carnet pour anticiper le prochain mouvement de prix.
Lab : ce qui est noté
- Trouver le haut du carnet avec
best_bid_ask25 pts - Mesurer l'écart avec
spread_and_mid25 pts - Poser un ordre avec
add_limit_order25 pts - Consommer les ventes avec
match_market_buy25 pts