697 lines
75 KiB
Plaintext
697 lines
75 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Matching Engine FIFO\n",
|
|
"\n",
|
|
"Motor de matching de órdenes con prioridad precio-tiempo (FIFO).\n",
|
|
"\n",
|
|
"**Objetivo:** Implementar un order book con matching FIFO que podamos usar después para simular mercados con datos reales de exchanges.\n",
|
|
"\n",
|
|
"**Estructura:**\n",
|
|
"1. Tipos de datos (Order, Trade, OrderBook)\n",
|
|
"2. Order Book con inserción y cancelación\n",
|
|
"3. Matching engine FIFO\n",
|
|
"4. Tests y visualización"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 1. Tipos de datos"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Tipos definidos: Side, OrderType, OrderStatus, Order, Trade\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"from __future__ import annotations\n",
|
|
"from dataclasses import dataclass, field\n",
|
|
"from enum import Enum\n",
|
|
"from typing import Optional\n",
|
|
"from collections import defaultdict\n",
|
|
"from sortedcontainers import SortedDict\n",
|
|
"import time\n",
|
|
"import uuid\n",
|
|
"\n",
|
|
"\n",
|
|
"class Side(Enum):\n",
|
|
" BUY = \"buy\"\n",
|
|
" SELL = \"sell\"\n",
|
|
"\n",
|
|
"\n",
|
|
"class OrderType(Enum):\n",
|
|
" LIMIT = \"limit\"\n",
|
|
" MARKET = \"market\"\n",
|
|
"\n",
|
|
"\n",
|
|
"class OrderStatus(Enum):\n",
|
|
" NEW = \"new\"\n",
|
|
" PARTIAL = \"partial\"\n",
|
|
" FILLED = \"filled\"\n",
|
|
" CANCELLED = \"cancelled\"\n",
|
|
"\n",
|
|
"\n",
|
|
"@dataclass\n",
|
|
"class Order:\n",
|
|
" \"\"\"Una orden en el libro.\"\"\"\n",
|
|
" side: Side\n",
|
|
" price: float # 0 para market orders\n",
|
|
" qty: float # cantidad original\n",
|
|
" remaining: float = 0 # cantidad pendiente\n",
|
|
" order_type: OrderType = OrderType.LIMIT\n",
|
|
" order_id: str = field(default_factory=lambda: str(uuid.uuid4()))\n",
|
|
" timestamp: float = field(default_factory=time.time)\n",
|
|
" status: OrderStatus = OrderStatus.NEW\n",
|
|
"\n",
|
|
" def __post_init__(self):\n",
|
|
" if self.remaining == 0:\n",
|
|
" self.remaining = self.qty\n",
|
|
"\n",
|
|
"\n",
|
|
"@dataclass\n",
|
|
"class Trade:\n",
|
|
" \"\"\"Un trade ejecutado por el matching engine.\"\"\"\n",
|
|
" price: float\n",
|
|
" qty: float\n",
|
|
" buyer_order_id: str\n",
|
|
" seller_order_id: str\n",
|
|
" timestamp: float = field(default_factory=time.time)\n",
|
|
" trade_id: str = field(default_factory=lambda: str(uuid.uuid4()))\n",
|
|
"\n",
|
|
"\n",
|
|
"print(\"Tipos definidos: Side, OrderType, OrderStatus, Order, Trade\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 2. Order Book\n",
|
|
"\n",
|
|
"Estructura del libro de órdenes:\n",
|
|
"- **Bids** (compras): ordenados por precio descendente, FIFO dentro del mismo precio\n",
|
|
"- **Asks** (ventas): ordenados por precio ascendente, FIFO dentro del mismo precio\n",
|
|
"\n",
|
|
"Usamos `SortedDict` para mantener los niveles de precio ordenados y `deque` para la cola FIFO en cada nivel."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"OrderBook definido\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"from collections import deque\n",
|
|
"\n",
|
|
"\n",
|
|
"class OrderBook:\n",
|
|
" \"\"\"Libro de órdenes con niveles de precio ordenados y colas FIFO por nivel.\"\"\"\n",
|
|
"\n",
|
|
" def __init__(self):\n",
|
|
" # SortedDict: price -> deque[Order]\n",
|
|
" # Bids: negamos el precio para que SortedDict ordene desc\n",
|
|
" self._bids: SortedDict = SortedDict() # key = -price\n",
|
|
" self._asks: SortedDict = SortedDict() # key = price\n",
|
|
" self._orders: dict[str, Order] = {} # order_id -> Order (lookup rápido)\n",
|
|
"\n",
|
|
" def add(self, order: Order) -> None:\n",
|
|
" \"\"\"Añade una orden al libro (sin matching, solo inserción).\"\"\"\n",
|
|
" book = self._bids if order.side == Side.BUY else self._asks\n",
|
|
" key = -order.price if order.side == Side.BUY else order.price\n",
|
|
"\n",
|
|
" if key not in book:\n",
|
|
" book[key] = deque()\n",
|
|
" book[key].append(order)\n",
|
|
" self._orders[order.order_id] = order\n",
|
|
"\n",
|
|
" def cancel(self, order_id: str) -> Optional[Order]:\n",
|
|
" \"\"\"Cancela una orden por ID. Retorna la orden cancelada o None.\"\"\"\n",
|
|
" order = self._orders.pop(order_id, None)\n",
|
|
" if order is None:\n",
|
|
" return None\n",
|
|
"\n",
|
|
" book = self._bids if order.side == Side.BUY else self._asks\n",
|
|
" key = -order.price if order.side == Side.BUY else order.price\n",
|
|
"\n",
|
|
" if key in book:\n",
|
|
" q = book[key]\n",
|
|
" try:\n",
|
|
" q.remove(order)\n",
|
|
" except ValueError:\n",
|
|
" pass\n",
|
|
" if not q:\n",
|
|
" del book[key]\n",
|
|
"\n",
|
|
" order.status = OrderStatus.CANCELLED\n",
|
|
" return order\n",
|
|
"\n",
|
|
" @property\n",
|
|
" def best_bid(self) -> Optional[float]:\n",
|
|
" \"\"\"Mejor precio de compra.\"\"\"\n",
|
|
" if not self._bids:\n",
|
|
" return None\n",
|
|
" return -self._bids.peekitem(0)[0]\n",
|
|
"\n",
|
|
" @property\n",
|
|
" def best_ask(self) -> Optional[float]:\n",
|
|
" \"\"\"Mejor precio de venta.\"\"\"\n",
|
|
" if not self._asks:\n",
|
|
" return None\n",
|
|
" return self._asks.peekitem(0)[0]\n",
|
|
"\n",
|
|
" @property\n",
|
|
" def spread(self) -> Optional[float]:\n",
|
|
" \"\"\"Spread bid-ask.\"\"\"\n",
|
|
" if self.best_bid is None or self.best_ask is None:\n",
|
|
" return None\n",
|
|
" return self.best_ask - self.best_bid\n",
|
|
"\n",
|
|
" @property\n",
|
|
" def midprice(self) -> Optional[float]:\n",
|
|
" \"\"\"Precio medio.\"\"\"\n",
|
|
" if self.best_bid is None or self.best_ask is None:\n",
|
|
" return None\n",
|
|
" return (self.best_bid + self.best_ask) / 2\n",
|
|
"\n",
|
|
" def depth(self, side: Side, levels: int = 5) -> list[tuple[float, float]]:\n",
|
|
" \"\"\"Profundidad del libro: [(price, total_qty), ...] para N niveles.\"\"\"\n",
|
|
" book = self._bids if side == Side.BUY else self._asks\n",
|
|
" result = []\n",
|
|
" for key in book.islice(0, levels):\n",
|
|
" price = -key if side == Side.BUY else key\n",
|
|
" total_qty = sum(o.remaining for o in book[key])\n",
|
|
" result.append((price, total_qty))\n",
|
|
" return result\n",
|
|
"\n",
|
|
" def __repr__(self):\n",
|
|
" bids = self.depth(Side.BUY, 3)\n",
|
|
" asks = self.depth(Side.SELL, 3)\n",
|
|
" return f\"OrderBook(best_bid={self.best_bid}, best_ask={self.best_ask}, spread={self.spread}, bids_top3={bids}, asks_top3={asks})\"\n",
|
|
"\n",
|
|
"\n",
|
|
"print(\"OrderBook definido\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 3. Matching Engine FIFO\n",
|
|
"\n",
|
|
"Lógica de matching:\n",
|
|
"1. Orden de **compra** se matchea contra asks (de menor a mayor precio)\n",
|
|
"2. Orden de **venta** se matchea contra bids (de mayor a menor precio)\n",
|
|
"3. Dentro de cada nivel de precio: **FIFO** (primera en llegar, primera en ejecutarse)\n",
|
|
"4. El precio del trade es siempre el de la orden **pasiva** (la que ya estaba en el libro)\n",
|
|
"5. Si la orden agresora no se llena completamente, se inserta en el libro como orden pasiva"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"MatchingEngineFIFO definido\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"class MatchingEngineFIFO:\n",
|
|
" \"\"\"Motor de matching con prioridad precio-tiempo (FIFO).\"\"\"\n",
|
|
"\n",
|
|
" def __init__(self):\n",
|
|
" self.book = OrderBook()\n",
|
|
" self.trades: list[Trade] = []\n",
|
|
"\n",
|
|
" def submit(self, order: Order) -> list[Trade]:\n",
|
|
" \"\"\"Procesa una orden: matchea lo posible y el resto va al libro.\"\"\"\n",
|
|
" new_trades = self._match(order)\n",
|
|
" self.trades.extend(new_trades)\n",
|
|
"\n",
|
|
" # Si queda cantidad y es limit, insertar en el libro\n",
|
|
" if order.remaining > 0 and order.order_type == OrderType.LIMIT:\n",
|
|
" order.status = OrderStatus.PARTIAL if order.remaining < order.qty else OrderStatus.NEW\n",
|
|
" self.book.add(order)\n",
|
|
"\n",
|
|
" return new_trades\n",
|
|
"\n",
|
|
" def _match(self, aggressor: Order) -> list[Trade]:\n",
|
|
" \"\"\"Matchea la orden agresora contra el lado opuesto del libro.\"\"\"\n",
|
|
" trades = []\n",
|
|
"\n",
|
|
" # Seleccionar el lado opuesto\n",
|
|
" if aggressor.side == Side.BUY:\n",
|
|
" passive_book = self.book._asks # asks ordenados asc\n",
|
|
" price_key_fn = lambda k: k # key es el precio directo\n",
|
|
" can_match = lambda passive_price: (\n",
|
|
" aggressor.order_type == OrderType.MARKET or\n",
|
|
" passive_price <= aggressor.price\n",
|
|
" )\n",
|
|
" else:\n",
|
|
" passive_book = self.book._bids # bids ordenados desc (key negado)\n",
|
|
" price_key_fn = lambda k: -k # desnegar para obtener precio real\n",
|
|
" can_match = lambda passive_price: (\n",
|
|
" aggressor.order_type == OrderType.MARKET or\n",
|
|
" passive_price >= aggressor.price\n",
|
|
" )\n",
|
|
"\n",
|
|
" keys_to_remove = []\n",
|
|
"\n",
|
|
" for key in list(passive_book.keys()):\n",
|
|
" if aggressor.remaining <= 0:\n",
|
|
" break\n",
|
|
"\n",
|
|
" passive_price = price_key_fn(key)\n",
|
|
" if not can_match(passive_price):\n",
|
|
" break # los siguientes niveles son peores\n",
|
|
"\n",
|
|
" queue = passive_book[key]\n",
|
|
"\n",
|
|
" while queue and aggressor.remaining > 0:\n",
|
|
" passive = queue[0] # FIFO: primera de la cola\n",
|
|
" fill_qty = min(aggressor.remaining, passive.remaining)\n",
|
|
"\n",
|
|
" # Ejecutar trade al precio pasivo\n",
|
|
" trade = Trade(\n",
|
|
" price=passive_price,\n",
|
|
" qty=fill_qty,\n",
|
|
" buyer_order_id=aggressor.order_id if aggressor.side == Side.BUY else passive.order_id,\n",
|
|
" seller_order_id=passive.order_id if aggressor.side == Side.BUY else aggressor.order_id,\n",
|
|
" )\n",
|
|
" trades.append(trade)\n",
|
|
"\n",
|
|
" # Actualizar cantidades\n",
|
|
" aggressor.remaining -= fill_qty\n",
|
|
" passive.remaining -= fill_qty\n",
|
|
"\n",
|
|
" if passive.remaining <= 0:\n",
|
|
" passive.status = OrderStatus.FILLED\n",
|
|
" queue.popleft()\n",
|
|
" self.book._orders.pop(passive.order_id, None)\n",
|
|
" else:\n",
|
|
" passive.status = OrderStatus.PARTIAL\n",
|
|
"\n",
|
|
" if not queue:\n",
|
|
" keys_to_remove.append(key)\n",
|
|
"\n",
|
|
" # Limpiar niveles vacíos\n",
|
|
" for key in keys_to_remove:\n",
|
|
" del passive_book[key]\n",
|
|
"\n",
|
|
" if aggressor.remaining <= 0:\n",
|
|
" aggressor.status = OrderStatus.FILLED\n",
|
|
"\n",
|
|
" return trades\n",
|
|
"\n",
|
|
" def cancel(self, order_id: str) -> Optional[Order]:\n",
|
|
" \"\"\"Cancela una orden del libro.\"\"\"\n",
|
|
" return self.book.cancel(order_id)\n",
|
|
"\n",
|
|
"\n",
|
|
"print(\"MatchingEngineFIFO definido\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 4. Tests básicos"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"✓ test_basic_match\n",
|
|
"✓ test_partial_fill\n",
|
|
"✓ test_fifo_priority\n",
|
|
"✓ test_price_priority\n",
|
|
"✓ test_no_match_spread\n",
|
|
"✓ test_market_order\n",
|
|
"✓ test_cancel\n",
|
|
"\n",
|
|
"=== Todos los tests pasaron ===\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"def test_basic_match():\n",
|
|
" \"\"\"Dos órdenes opuestas al mismo precio → 1 trade.\"\"\"\n",
|
|
" engine = MatchingEngineFIFO()\n",
|
|
"\n",
|
|
" # Sell limit a 100\n",
|
|
" sell = Order(side=Side.SELL, price=100.0, qty=10.0)\n",
|
|
" engine.submit(sell)\n",
|
|
"\n",
|
|
" # Buy limit a 100 → debe matchear\n",
|
|
" buy = Order(side=Side.BUY, price=100.0, qty=10.0)\n",
|
|
" trades = engine.submit(buy)\n",
|
|
"\n",
|
|
" assert len(trades) == 1, f\"Expected 1 trade, got {len(trades)}\"\n",
|
|
" assert trades[0].price == 100.0\n",
|
|
" assert trades[0].qty == 10.0\n",
|
|
" assert buy.status == OrderStatus.FILLED\n",
|
|
" assert sell.status == OrderStatus.FILLED\n",
|
|
" print(\"✓ test_basic_match\")\n",
|
|
"\n",
|
|
"\n",
|
|
"def test_partial_fill():\n",
|
|
" \"\"\"Buy de 15 contra sell de 10 → fill parcial, 5 queda en libro.\"\"\"\n",
|
|
" engine = MatchingEngineFIFO()\n",
|
|
"\n",
|
|
" sell = Order(side=Side.SELL, price=100.0, qty=10.0)\n",
|
|
" engine.submit(sell)\n",
|
|
"\n",
|
|
" buy = Order(side=Side.BUY, price=100.0, qty=15.0)\n",
|
|
" trades = engine.submit(buy)\n",
|
|
"\n",
|
|
" assert len(trades) == 1\n",
|
|
" assert trades[0].qty == 10.0\n",
|
|
" assert buy.remaining == 5.0\n",
|
|
" assert buy.status == OrderStatus.PARTIAL\n",
|
|
" assert engine.book.best_bid == 100.0\n",
|
|
" print(\"✓ test_partial_fill\")\n",
|
|
"\n",
|
|
"\n",
|
|
"def test_fifo_priority():\n",
|
|
" \"\"\"Dos sells al mismo precio → la primera se llena primero (FIFO).\"\"\"\n",
|
|
" engine = MatchingEngineFIFO()\n",
|
|
"\n",
|
|
" sell1 = Order(side=Side.SELL, price=100.0, qty=5.0)\n",
|
|
" sell2 = Order(side=Side.SELL, price=100.0, qty=5.0)\n",
|
|
" engine.submit(sell1)\n",
|
|
" engine.submit(sell2)\n",
|
|
"\n",
|
|
" buy = Order(side=Side.BUY, price=100.0, qty=7.0)\n",
|
|
" trades = engine.submit(buy)\n",
|
|
"\n",
|
|
" assert len(trades) == 2, f\"Expected 2 trades, got {len(trades)}\"\n",
|
|
" assert trades[0].qty == 5.0 # sell1 completamente llena\n",
|
|
" assert trades[1].qty == 2.0 # sell2 parcial\n",
|
|
" assert sell1.status == OrderStatus.FILLED\n",
|
|
" assert sell2.status == OrderStatus.PARTIAL\n",
|
|
" assert sell2.remaining == 3.0\n",
|
|
" print(\"✓ test_fifo_priority\")\n",
|
|
"\n",
|
|
"\n",
|
|
"def test_price_priority():\n",
|
|
" \"\"\"Sell a 99 antes que sell a 100 → buyer obtiene mejor precio.\"\"\"\n",
|
|
" engine = MatchingEngineFIFO()\n",
|
|
"\n",
|
|
" sell_expensive = Order(side=Side.SELL, price=100.0, qty=5.0)\n",
|
|
" sell_cheap = Order(side=Side.SELL, price=99.0, qty=5.0)\n",
|
|
" engine.submit(sell_expensive)\n",
|
|
" engine.submit(sell_cheap)\n",
|
|
"\n",
|
|
" buy = Order(side=Side.BUY, price=100.0, qty=8.0)\n",
|
|
" trades = engine.submit(buy)\n",
|
|
"\n",
|
|
" assert len(trades) == 2\n",
|
|
" assert trades[0].price == 99.0 # primero la más barata\n",
|
|
" assert trades[0].qty == 5.0\n",
|
|
" assert trades[1].price == 100.0 # luego la cara\n",
|
|
" assert trades[1].qty == 3.0\n",
|
|
" print(\"✓ test_price_priority\")\n",
|
|
"\n",
|
|
"\n",
|
|
"def test_no_match_spread():\n",
|
|
" \"\"\"Buy a 99, sell a 100 → no matchea, ambas en libro.\"\"\"\n",
|
|
" engine = MatchingEngineFIFO()\n",
|
|
"\n",
|
|
" sell = Order(side=Side.SELL, price=100.0, qty=10.0)\n",
|
|
" engine.submit(sell)\n",
|
|
"\n",
|
|
" buy = Order(side=Side.BUY, price=99.0, qty=10.0)\n",
|
|
" trades = engine.submit(buy)\n",
|
|
"\n",
|
|
" assert len(trades) == 0\n",
|
|
" assert engine.book.best_bid == 99.0\n",
|
|
" assert engine.book.best_ask == 100.0\n",
|
|
" assert engine.book.spread == 1.0\n",
|
|
" print(\"✓ test_no_match_spread\")\n",
|
|
"\n",
|
|
"\n",
|
|
"def test_market_order():\n",
|
|
" \"\"\"Market order matchea a cualquier precio disponible.\"\"\"\n",
|
|
" engine = MatchingEngineFIFO()\n",
|
|
"\n",
|
|
" sell = Order(side=Side.SELL, price=105.0, qty=10.0)\n",
|
|
" engine.submit(sell)\n",
|
|
"\n",
|
|
" buy = Order(side=Side.BUY, price=0, qty=5.0, order_type=OrderType.MARKET)\n",
|
|
" trades = engine.submit(buy)\n",
|
|
"\n",
|
|
" assert len(trades) == 1\n",
|
|
" assert trades[0].price == 105.0 # al precio de la pasiva\n",
|
|
" assert trades[0].qty == 5.0\n",
|
|
" print(\"✓ test_market_order\")\n",
|
|
"\n",
|
|
"\n",
|
|
"def test_cancel():\n",
|
|
" \"\"\"Cancelar una orden la remueve del libro.\"\"\"\n",
|
|
" engine = MatchingEngineFIFO()\n",
|
|
"\n",
|
|
" sell = Order(side=Side.SELL, price=100.0, qty=10.0)\n",
|
|
" engine.submit(sell)\n",
|
|
" assert engine.book.best_ask == 100.0\n",
|
|
"\n",
|
|
" cancelled = engine.cancel(sell.order_id)\n",
|
|
" assert cancelled is not None\n",
|
|
" assert cancelled.status == OrderStatus.CANCELLED\n",
|
|
" assert engine.book.best_ask is None\n",
|
|
" print(\"✓ test_cancel\")\n",
|
|
"\n",
|
|
"\n",
|
|
"# Ejecutar todos\n",
|
|
"test_basic_match()\n",
|
|
"test_partial_fill()\n",
|
|
"test_fifo_priority()\n",
|
|
"test_price_priority()\n",
|
|
"test_no_match_spread()\n",
|
|
"test_market_order()\n",
|
|
"test_cancel()\n",
|
|
"print(\"\\n=== Todos los tests pasaron ===\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 5. Visualización del Order Book"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import matplotlib.pyplot as plt\n",
|
|
"import numpy as np\n",
|
|
"\n",
|
|
"\n",
|
|
"def plot_orderbook(engine: MatchingEngineFIFO, levels: int = 10, title: str = \"Order Book\"):\n",
|
|
" \"\"\"Visualiza la profundidad del order book.\"\"\"\n",
|
|
" bids = engine.book.depth(Side.BUY, levels)\n",
|
|
" asks = engine.book.depth(Side.SELL, levels)\n",
|
|
"\n",
|
|
" fig, ax = plt.subplots(figsize=(10, 5))\n",
|
|
"\n",
|
|
" if bids:\n",
|
|
" bid_prices, bid_qtys = zip(*bids)\n",
|
|
" bid_cum = np.cumsum(bid_qtys)\n",
|
|
" ax.barh(range(len(bids)), bid_qtys, color='#2ecc71', alpha=0.7, label='Bids')\n",
|
|
" for i, (p, q) in enumerate(bids):\n",
|
|
" ax.text(q + 0.1, i, f\"{p:.2f} ({q:.1f})\", va='center', fontsize=9)\n",
|
|
"\n",
|
|
" if asks:\n",
|
|
" ask_prices, ask_qtys = zip(*asks)\n",
|
|
" y_offset = len(bids) + 1 # gap visual\n",
|
|
" ax.barh(range(y_offset, y_offset + len(asks)), ask_qtys, color='#e74c3c', alpha=0.7, label='Asks')\n",
|
|
" for i, (p, q) in enumerate(asks):\n",
|
|
" ax.text(q + 0.1, y_offset + i, f\"{p:.2f} ({q:.1f})\", va='center', fontsize=9)\n",
|
|
"\n",
|
|
" ax.set_yticks([])\n",
|
|
" ax.set_xlabel('Quantity')\n",
|
|
" ax.set_title(f\"{title}\\nSpread: {engine.book.spread:.2f} | Mid: {engine.book.midprice:.2f}\" if engine.book.spread else title)\n",
|
|
" ax.legend()\n",
|
|
" plt.tight_layout()\n",
|
|
" plt.show()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"OrderBook(best_bid=99.85, best_ask=100.25, spread=0.4000000000000057, bids_top3=[(99.85, 4.8), (99.72, 2.8), (99.48, 22.799999999999997)], asks_top3=[(100.25, 5.4), (100.29, 6.3), (100.41, 8.2)])\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"image/png": "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rVq1QqdOndC9e3cEBweXWlwXt1O3oMfbOAsKCtCqVSssX75cZ7xu3bqlT5yIiOgtwOKaiIiK6Nu3L9avX4+TJ0+KO34/LyIiAomJiRgzZkypuQqXPickJGgtn3748GGRO6eNGzdGRkZGqcViRcrLywMAZGRkAPi/+erawfvKlSuwsrKCubk5TE1NYWZmVmw/IyOjIoVnQEAAmjRpgrlz58LS0hIzZswo97wL35d97969cud4XnHvn27cuDHOnz8PNze3Ut9Rre87rAs3Ort48SKaNGmis8/zv8OLG7nFx8frtaSeiIjoZeIz10REVMTUqVMhk8kwZswYPH78WCuWkpKCsWPHwszMDFOnTi01V8+ePSGVSrF69WqtO6Uv7vwNAIMGDcJff/2Fo0ePFomlpaWJhXBFCg0NBQC0adMGwLNnpp2dnbFlyxakpaWJ/S5evIhjx47By8sLwLO7we7u7ggJCdF6xdSDBw+wbds2dOnSpchdfQCYM2cOpkyZgpkzZ+KHH34odX4RERHIzc0t0l747LeuZdPlYW5uDrVaXaR90KBB+Pfff3U+3/306VNkZmZq5Xj+mhXH3d0dFhYWWLx4MbKysrRihX9G2rVrh9q1a2Pt2rVar/w6fPgwLl++jD59+uh7akRERC8F71wTEVERDg4O2LJlC3x9fdGqVSuMHDkSDRs2RGJiIjZu3IhHjx5h+/btJb5qqVCtWrUwZcoULF68GH379oWXlxeio6Nx+PBhWFlZafWdOnUq9u/fj759+2LEiBF49913kZmZiQsXLuDXX39FYmJikWPK4t9//8XPP/8MAMjJycH58+fx448/wsrKSmtJ+LfffgtPT0906tQJI0eOFF/FZWlpia+++krsFxgYiN9++w1dunTBuHHjUK1aNfz444/Izs7Wegf4i7799luo1WqMHz8eFhYWGDp0aLF9lyxZgrNnz2LAgAFo3bo1AODcuXPYunUratSooXNjuPJ499138csvv+Dzzz9H+/btIZfL0a9fP3zyySfYuXMnxo4di/DwcLi4uCA/Px9XrlzBzp07cfToUfEu+rvvvovff/8dy5cvh52dHRo2bKjzdWQKhQIrVqyAv78/2rdvL74X/fz589BoNNiyZQukUimWLFkCPz8/uLq6YsiQIeKruBo0aIDJkydXyHkTERFVmFe8WzkREb3GYmNjhSFDhgi2traCVCoVbGxshCFDhmi9wqlQ4au4Hj58WCSWn58vzJ8/X7C1tRVkMpnQrVs34eLFi0L9+vWLvLrpyZMnwsyZM4UmTZoI1atXF6ysrITOnTsLy5YtE3JycgRB+L9XcX377bd6n8uLr+IyMjISateuLQwZMkS4du1akf6///674OLiIshkMkGhUAj9+vUT4uLiivQ7d+6c4OHhIcjlcsHMzEzo3r27EBUVpdXn+VdxPX9NhgwZIlSrVk3Yt29fsfOOjIwUxo8fL7Rs2VKwtLQUpFKpUK9ePWHEiBFarwAThOJfxbVr1y6tfoXX77///a/YlpGRIfj4+AhKpVIAoPVarpycHGHJkiWCk5OTYGJiIqhUKuHdd98V5s+fL6jVarHflStXhK5du4qvDiv8bV98FVeh/fv3C507dxavcYcOHYTt27dr9fnll1+Etm3bCiYmJkKNGjUEX19f4c6dO8VeLyIioldFIgh67GZCRERERERERMXiM9dEREREREREBmJxTURERERERGQgFtdEREREREREBmJxTURERERERGQgFtdEREREREREBmJxTURERERERGQgFtdERPRaGDFiBBo0aPCqp1Hpvvrqq0o/z27duqFbt26l9jt+/DgkEgmOHz9eqfMhIiJ6G7C4JiKqgi5cuICBAweifv36MDU1RZ06ddCrVy+sXr36VU/tldq/fz/eeecdmJqaol69epg3bx7y8vLKnCc4OBgSiQRyuVxn/PLly+jduzfkcjlq1KiBTz75BA8fPjR0+loSExMhkUggkUgQGBios4+vr2+J86xs8fHxmDx5Mjp37gxTU1NIJBIkJiYW21/f3yctLQ2jR49GrVq1YG5uju7du+PcuXN6z0uf3+f56/viZ8eOHXqPRUREVKjaq54AERGVTVRUFLp374569eph1KhRsLGxwe3bt/H333/ju+++Q0BAwKue4itx+PBh9O/fH926dcPq1atx4cIFBAYGIjk5GT/88IPeeTIyMjBt2jSYm5vrjN+5cwddu3aFpaUlFi1ahIyMDCxbtgwXLlzA6dOnUb169Yo6JQCAqakptm/fjtmzZ2u1Z2ZmIiQkBKampkWOOXbsWIXOoTh//fUXVq1ahRYtWqB58+aIiYkptq++v09BQQH69OmD8+fPY+rUqbCyssKaNWvQrVs3nD17Fg4ODiXOqay/z5Ah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XjmJiIiIiKhyVXvVEyAqKycnJxw7dkz8Pn36dLi6uorfXVxcEBUVJX4fPHiwVvxFhYW1k5MT1q5dC4lEUuL4RkbF/51U4Q7f8fHx4tLy33//Henp6eIO4Wq1Gv/88w8iIiKwe/fuIjlat26NmJgY8Xtubi7i4uK0NkKLi4uDtbU1bG1tS5wrERERERG9HCyuqcqJjY1F48aNIZVKceDAAWzatAl//PGHGI+OjkaLFi1QUFCAn3/+GcePHy/2tVXp6eno3bs3mjZtig0bNhQprC9evIjs7Gy0bt0a+fn5WLduHS5dugQPDw+d+aRSKTw8PBAeHi4W13///be40zcAfPTRR+jduzfGjx+vM8fQoUOxfPlyHDp0CG5ubli8eDGsrKzQtWtXsU9YWBj69Omj3wUjIiIiIqJKx2XhVOXs3LkT9erVg0qlwrJly7Bv3z60bt1ajK9atQrW1taoVasWdu3ahbCwMNjZ2YlxJycnBAcHAwD27t2Lv//+G7t374ZCoRDfZV0Yf/jwIYYOHQqlUok6depgz549OHLkCBo2bFjs/MaPH4/NmzeL321sbGBvby9+TExMYGlpCSsrKwBAREQE5HK52N/R0RE///wzJk6cCKVSid9++w379+9HtWrP/i6soKAAwcHBxRbnRERERET08kmEF7dW1kN6ejosLS0x+twqVLeQVca8iAAAq5v4v+oplIuHhwcmTZoET0/PCs+9bds2HDx4UPwLACIiIiKiN0VhralWq8VNfKsKLgsnqgRHjx6ttNw+Pj7w8fGptPxERERERFR2XBZOREREREREZCAW10REREREREQGMmhZ+LeNh1e5dfBEREREREREFY13romIiIiIiIgMxOKaiIiIiIiIyEAsromIiIiIiIgMZNAz11Ovb+F7romKUVXf0U1ERERERGXHO9dEREREREREBmJxTURERERERGQgFtdEpKV37944dOhQpeQODg6Gr69vpeQmIiIiInqVWFwTvUTXr1+Hp6cnVCoV6tSpg6VLl2rFz549iy5dukChUKBRo0bYunVrsbmys7PRrVs31K5dGwqFAs2aNcO6deu0+oSEhKB169ZQKBRo2LAhVqxYUeL8wsPD8fDhQ3h5eQEAEhMTIZFIIJfLxU+/fv2KPf7gwYPo2rUrVCoVateujYEDB+LOnTtifMiQITh9+jSio6NLnAcR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",
|
|
"text/plain": [
|
|
"<Figure size 1000x500 with 1 Axes>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data",
|
|
"transient": {}
|
|
}
|
|
],
|
|
"source": [
|
|
"# Crear un libro con varias órdenes para visualizar\n",
|
|
"import random\n",
|
|
"random.seed(42)\n",
|
|
"\n",
|
|
"engine = MatchingEngineFIFO()\n",
|
|
"\n",
|
|
"# Poblar bids alrededor de 100\n",
|
|
"for i in range(20):\n",
|
|
" price = round(100 - random.uniform(0.1, 2.0), 2)\n",
|
|
" qty = round(random.uniform(1, 20), 1)\n",
|
|
" engine.submit(Order(side=Side.BUY, price=price, qty=qty))\n",
|
|
"\n",
|
|
"# Poblar asks alrededor de 100\n",
|
|
"for i in range(20):\n",
|
|
" price = round(100 + random.uniform(0.1, 2.0), 2)\n",
|
|
" qty = round(random.uniform(1, 20), 1)\n",
|
|
" engine.submit(Order(side=Side.SELL, price=price, qty=qty))\n",
|
|
"\n",
|
|
"print(engine.book)\n",
|
|
"plot_orderbook(engine, levels=8, title=\"Order Book Sintético\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 6. Simulación: impacto de una market order\n",
|
|
"\n",
|
|
"Veamos cómo una market order grande barre niveles del libro y mueve el precio."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"=== ANTES ===\n",
|
|
"Best ask: 100.25\n",
|
|
"Best bid: 99.85\n",
|
|
"Spread: 0.4000\n",
|
|
"Midprice: 100.0500\n",
|
|
"\n",
|
|
"Ask depth (5 niveles): [(100.25, 5.4), (100.29, 6.3), (100.41, 8.2), (100.43, 14.9), (100.5, 18.9)]\n",
|
|
"\n",
|
|
"=== MARKET BUY 50 ===\n",
|
|
"Trades ejecutados: 5\n",
|
|
" 5.4 @ 100.25\n",
|
|
" 6.3 @ 100.29\n",
|
|
" 8.2 @ 100.41\n",
|
|
" 14.9 @ 100.43\n",
|
|
" 15.2 @ 100.50\n",
|
|
"\n",
|
|
"Precio promedio ponderado: 100.4109\n",
|
|
"Slippage vs best ask: 0.1609\n",
|
|
"\n",
|
|
"=== DESPUÉS ===\n",
|
|
"Best ask: 100.5\n",
|
|
"Best bid: 99.85\n",
|
|
"Spread: 0.6500000000000057\n",
|
|
"OrderBook(best_bid=99.85, best_ask=100.5, spread=0.6500000000000057, bids_top3=[(99.85, 4.8), (99.72, 2.8), (99.48, 22.799999999999997)], asks_top3=[(100.5, 3.699999999999994), (100.53, 6.5), (100.54, 1.6)])\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# Estado antes\n",
|
|
"print(\"=== ANTES ===\")\n",
|
|
"print(f\"Best ask: {engine.book.best_ask}\")\n",
|
|
"print(f\"Best bid: {engine.book.best_bid}\")\n",
|
|
"print(f\"Spread: {engine.book.spread:.4f}\")\n",
|
|
"print(f\"Midprice: {engine.book.midprice:.4f}\")\n",
|
|
"print(f\"\\nAsk depth (5 niveles): {engine.book.depth(Side.SELL, 5)}\")\n",
|
|
"\n",
|
|
"# Market buy grande: comprar 50 unidades\n",
|
|
"big_buy = Order(side=Side.BUY, price=0, qty=50.0, order_type=OrderType.MARKET)\n",
|
|
"trades = engine.submit(big_buy)\n",
|
|
"\n",
|
|
"print(f\"\\n=== MARKET BUY 50 ===\")\n",
|
|
"print(f\"Trades ejecutados: {len(trades)}\")\n",
|
|
"for t in trades:\n",
|
|
" print(f\" {t.qty:.1f} @ {t.price:.2f}\")\n",
|
|
"\n",
|
|
"avg_price = sum(t.price * t.qty for t in trades) / sum(t.qty for t in trades) if trades else 0\n",
|
|
"print(f\"\\nPrecio promedio ponderado: {avg_price:.4f}\")\n",
|
|
"print(f\"Slippage vs best ask: {avg_price - trades[0].price:.4f}\" if trades else \"\")\n",
|
|
"\n",
|
|
"print(f\"\\n=== DESPUÉS ===\")\n",
|
|
"print(f\"Best ask: {engine.book.best_ask}\")\n",
|
|
"print(f\"Best bid: {engine.book.best_bid}\")\n",
|
|
"print(f\"Spread: {engine.book.spread}\")\n",
|
|
"print(engine.book)"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3 (ipykernel)",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.13.7"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
}
|