Lección 2.3.6 · 70 min
Introducción al market making: inventario, spread y selección adversa (Avellaneda-Stoikov)
Un creador de mercado cotiza compra y venta a la vez y gana la horquilla si las dos se ejecutan. Pierde por dos vías: el inventario que acumula cuando solo se ejecuta un lado y las órdenes de quien sabe más que él. Esta lección estima la intensidad de las órdenes de una sesión sintética, simula un creador con cotizaciones simétricas y otro con el modelo de Avellaneda y Stoikov, y descompone el resultado en horquilla, inventario y selección adversa.
- Módulo
- 2.3 · Familias de estrategias
- Duración
- 70 min
Lecciones del nivel 2
2.1 · Estadística y econometría para trading
- 2.1.1 Contrastes de hipótesis, p-valores, bootstrap y regresión con errores robustos
- 2.1.2 Estacionariedad: ADF, KPSS, diferenciación y diferenciación fraccional
- 2.1.3 Modelos ARMA y ARIMA: autocorrelación simple (ACF) y parcial (PACF)
- 2.1.4 Modelos de volatilidad: ARCH, GARCH, EGARCH y GJR con la librería arch
- 2.1.5 Cointegración: Engle-Granger, Johansen y vida media de la reversión
- 2.1.6 Filtro de Kalman para hedge ratios dinámicos
- 2.1.7 Detección de regímenes: modelos de Markov ocultos y cambios estructurales
- 2.1.8 El problema de las pruebas múltiples: Bonferroni, FDR y el «factor zoo»
2.2 · Anatomía de un sistema de trading
- 2.2.1 Hipótesis de ventaja: riesgo, comportamiento, estructura e información
- 2.2.2 Universo, señal, filtro, entrada, salida, sizing y gestión del riesgo
- 2.2.3 Frecuencias: intradía, swing y posicional, y la capacidad de una estrategia
- 2.2.4 Diario de investigación y registro de experimentos contra el data snooping
2.3 · Familias de estrategias
- 2.3.1 Seguimiento de tendencia y momentum
- 2.3.2 Reversión a la media: RSI y Bollinger, reversión intradía y huecos
- 2.3.3 Pairs trading y arbitraje estadístico: distancia, cointegración y Kalman
- 2.3.4 Carry en divisas, futuros y bonos; estacionalidad y efectos calendario
- 2.3.5 Factores (value, calidad, baja volatilidad, tamaño) y estrategias de eventos
- 2.3.6 Introducción al market making: inventario, spread y selección adversa (Avellaneda-Stoikov)
- 2.3.7 Estrategias específicas de cripto: momentum, arbitraje de funding y basis
2.4 · Backtesting: motores y paradigmas
- 2.4.1 Backtest vectorizado desde cero: matrices, desfase, costes y límites
- 2.4.2 Backtest por eventos desde cero: bucle, cartera, órdenes y fills
- 2.4.3 vectorbt: barridos masivos de parámetros y simulación de carteras
- 2.4.4 backtesting.py y backtrader: dos motores didácticos y su estado
- 2.4.5 NautilusTrader: arquitectura, datos, estrategias y el mismo código en backtest y en real
- 2.4.6 LEAN/QuantConnect y Zipline Reloaded; bt para asignación de activos
- 2.4.7 Modelado de costes: comisiones, horquilla, impacto, financiación y préstamo de títulos
- 2.4.8 Modelado de fills: órdenes limitadas, cola, fills parciales, huecos y stops
2.5 · Sesgos y validación
- 2.5.1 Sesgos de backtest: anticipación, supervivencia, selección y narrativa
- 2.5.2 In-sample y out-of-sample: walk-forward anclado y móvil
- 2.5.3 Validación cruzada combinatoria purgada para estrategias
- 2.5.4 Deflated Sharpe Ratio y Probability of Backtest Overfitting (PBO)
- 2.5.5 Monte Carlo: remuestreo de trades, bootstrap de bloques y permutaciones de señales
- 2.5.6 Sensibilidad de parámetros: mesetas frente a picos, robustez entre activos y periodos
- 2.5.7 Datos sintéticos para pruebas de estrés: GBM, GARCH y bootstrapping
- 2.5.8 Checklist de «backtest honesto» antes de pasar a paper trading
2.6 · Métricas de rendimiento
El negocio de cotizar a los dos lados
Un creador de mercado (market maker) mantiene a la vez una orden limitada de compra por debajo del precio medio y otra de venta por encima. Si llega una orden a mercado de cada lado, compra a su bid, vende a su ask y se queda la diferencia: la horquilla. No apuesta por la dirección del precio; vende inmediatez a quien la necesita (lección 1.1.1).
Fuente Mermaid
flowchart TB
M[Precio medio] --> C[Cotizar bid y ask]
C --> F{¿Ejecución?}
F -- un lado --> I[Inventario]
F -- los dos --> H[Horquilla cobrada]
I --> R[Riesgo de precio]
R --> C
H --> CEl problema está en la rama de la izquierda. Las órdenes no llegan emparejadas: si llegan cinco ventas seguidas, el creador compra cinco veces y se queda largo, expuesto a que el precio baje. Y no todas las órdenes son iguales: algunas vienen de quien sabe que el precio va a moverse, y esas ejecutan al creador justo antes de que el movimiento le haga perder. Son las dos fuentes de pérdida que esta lección mide:
| Componente | Signo | De qué depende |
|---|---|---|
| Horquilla capturada | + | Distancia de las cotizaciones al medio y número de ejecuciones |
| Inventario | ± | Tamaño de la posición y volatilidad del precio mientras se mantiene |
| Selección adversa | − | Proporción de órdenes informadas y cuánto mueven el precio después |
El flujo de órdenes de una sesión
mm2_flujo.parquet tiene cada orden a mercado de la sesión: cuándo llega, de qué lado (compra toma la oferta,
venta el bid), su tamaño y su alcance, la distancia al medio hasta la que barre el libro. Una cotización de venta
a δ del medio se ejecuta con una compra si el alcance de esa compra es al menos δ. mm2_medio.parquet es el precio
medio al final de cada segundo.
import numpy as np
import pandas as pd
flujo = pd.read_parquet("mm2_flujo.parquet")
medio = pd.read_parquet("mm2_medio.parquet")
inicio = pd.Timestamp("2025-03-12 13:30:00", tz="UTC")
T = len(medio) # segundos de la sesión
print(len(flujo), "órdenes,", T, "segundos")
print(flujo.groupby("lado")["tamano"].agg(["size", "mean"]).round(0))
print(f"informadas: {flujo['informada'].mean():.1%}")
flujo.groupby("informada")["alcance"].describe()[
["count", "mean", "50%", "max"]].round(4)42092 órdenes, 23400 segundos
size mean
lado
compra 20889 300.0
venta 21203 302.0
informadas: 10.0%
count mean 50% max
informada
False 37903.0 0.0250 0.017 0.281
True 4189.0 0.0492 0.034 0.691En 6,5 horas llegan 42.092 órdenes a mercado, casi las mismas de cada lado (0,89 y 0,91 por segundo), de unas 300 acciones de media. El 10,0 % son informadas, y su alcance es el doble: una media de 4,9 céntimos frente a 2,5 de las no informadas. Quien sabe que el precio va a moverse está dispuesto a barrer más niveles del libro para ejecutar ya, y eso tiene una consecuencia directa para el creador: cuanto más lejos del medio cotiza, mayor es la proporción de informados entre las órdenes que le llegan.
Intensidad de ejecución según la distancia al medio
El modelo de Avellaneda y Stoikov (2008) supone que una cotización a distancia δ del medio se ejecuta como un proceso de Poisson de intensidad
es la intensidad de una cotización en el propio medio y dice lo deprisa que cae al alejarse. Se estima contando, para cada δ, cuántas órdenes de un lado tienen un alcance de al menos δ y dividiendo por la duración de la sesión; en escala logarítmica la relación es una recta de pendiente .
deltas = np.arange(0, 0.1001, 0.005)
def intensidad(ordenes):
lam = np.array([(ordenes["alcance"] >= d).sum() / T
for d in deltas])
pend, ordenada = np.polyfit(deltas, np.log(lam), 1)
return np.exp(ordenada), -pend, lam
filas = {}
for lado in ["compra", "venta"]:
g = flujo[flujo["lado"] == lado]
for nombre, sub in [("todas", g), ("no informadas",
g[~g["informada"]])]:
A, k, _ = intensidad(sub)
filas[(lado, nombre)] = {"A_por_s": A, "k_por_usd": k}
est = pd.DataFrame(filas).T
print(est.round(3))
A = est.xs("todas", level=1)["A_por_s"].mean()
k = est.xs("todas", level=1)["k_por_usd"].mean()
print(f"\nparámetros del creador: A = {A:.2f} por s, "
f"k = {k:.1f} por USD")
print(f"semihorquilla que maximiza δ·λ(δ): "
f"1/k = {100 / k:.2f} céntimos") A_por_s k_por_usd
compra todas 0.866 35.327
no informadas 0.824 39.969
venta todas 0.883 35.433
no informadas 0.836 40.230
parámetros del creador: A = 0.87 por s, k = 35.4 por USD
semihorquilla que maximiza δ·λ(δ): 1/k = 2.83 céntimosCon las no informadas, la estimación recupera casi exactamente los parámetros plantados: entre 0,82 y 0,84 por segundo (verdad 0,81) y entre 40,0 y 40,2 por USD (verdad 40). Con todo el flujo, que es lo único que ve un creador real, sube a 0,87 y baja a 35,4: las informadas, con más alcance, hacen la cola más gruesa. El creador usará estos parámetros, los observables. La semihorquilla que maximiza el ingreso de un lado sin riesgo, , es de 2,83 céntimos.
La figura enseña la intensidad medida para cada distancia, en escala logarítmica, con la recta ajustada: los puntos caen casi sobre ella. A un céntimo del medio, una cotización de venta se ejecuta unas 0,6 veces por segundo; a 5 céntimos, 0,15; a 10, menos de 0,03. Cada céntimo más de horquilla cuesta un 30 % de las ejecuciones.
Selección adversa: lo que pasa después de cada orden
El marcado (markout) de una orden es cuánto se mueve el medio en su dirección en los segundos siguientes: . Para el creador que la ejecuta, es lo que pierde por la información que traía la orden. Glosten y Milgrom (1985) explican la horquilla con este mecanismo: el creador no distingue a los informados y cobra a todos lo suficiente para compensar lo que pierde con ellos.
m = np.r_[medio["medio"].iloc[0], medio["medio"].to_numpy()]
# m[s] = medio al empezar el segundo s (el último conocido)
seg = np.floor((flujo["ts"] - inicio).dt.total_seconds()).astype(int)
seg = seg.clip(0, T - 1).to_numpy()
signo = np.where(flujo["lado"] == "compra", 1, -1)
informada = flujo["informada"].to_numpy()
markout = {}
for h in [1, 5, 10, 20, 30, 45, 60]:
mo = 100 * signo * (m[np.minimum(seg + h, T)] - m[seg])
markout[h] = {"informadas_cent": mo[informada].mean(),
"no_informadas_cent": mo[~informada].mean()}
pd.DataFrame(markout).T.round(3) informadas_cent no_informadas_cent
1 0.066 0.002
5 0.311 0.004
10 0.616 0.007
20 1.323 -0.010
30 1.998 -0.008
45 1.959 -0.001
60 1.945 0.009Tras una orden informada, el medio se mueve en su dirección de forma casi lineal durante 30 segundos, hasta 2,0 céntimos, y después se queda ahí: es el impacto plantado. Tras una no informada no se mueve nada (de −0,010 a +0,009 céntimos). Una ejecución contra un informado cuesta al creador, de media, 2 céntimos por acción de movimiento en contra, casi lo mismo que cobra de semihorquilla. La figura dibuja las dos curvas.
Un simulador de creador de mercado
El simulador recorre la sesión segundo a segundo. Al empezar cada segundo, el creador fija su bid y su ask a partir del último medio conocido y de su inventario, redondeados al tick (el bid hacia abajo y el ask hacia arriba). Cada orden que llega en ese segundo le ejecuta 100 acciones si su alcance llega a su cotización. Las simplificaciones son importantes y conviene decirlas: su cotización es la única del libro a ese precio, se ejecuta siempre entera, no paga comisiones y la cambia sin latencia.
La contabilidad separa las tres partes del resultado. La horquilla es lo que cobra en cada ejecución frente al medio de ese momento; el inventario, lo que gana o pierde la posición con los cambios del medio, ; la suma de las dos es exactamente el resultado final valorado al medio. La selección adversa se mide aparte, con el marcado a 30 segundos de las ejecuciones contra órdenes informadas: es la parte del inventario que se pierde por ellas.
TICK, LOTE = 0.01, 100
por_segundo = [[] for _ in range(T)]
for i, s in enumerate(seg):
por_segundo[s].append(i)
alcance = flujo["alcance"].to_numpy()
def simular(cotizar):
q, caja, horquilla, inventario = 0, 0.0, 0.0, 0.0
ejec, q_seg = [], np.zeros(T)
for s in range(T):
bid, ask = cotizar(m[s], q, s)
bid = np.floor(bid / TICK + 1e-9) * TICK
ask = np.ceil(ask / TICK - 1e-9) * TICK
for i in por_segundo[s]:
if signo[i] == 1 and alcance[i] >= ask - m[s] - 1e-9:
q -= LOTE; caja += ask * LOTE
horquilla += (ask - m[s]) * LOTE
ejec.append((s, -1, informada[i]))
elif signo[i] == -1 and alcance[i] >= m[s] - bid - 1e-9:
q += LOTE; caja -= bid * LOTE
horquilla += (m[s] - bid) * LOTE
ejec.append((s, 1, informada[i]))
inventario += q * (m[s + 1] - m[s])
q_seg[s] = q
e = np.array(ejec)
s_e, lado_e, inf_e = e[:, 0].astype(int), e[:, 1], e[:, 2] == 1
mo = lado_e * (m[np.minimum(s_e + 30, T)] - m[s_e]) * LOTE
res = {"resultado": caja + q * m[T], "horquilla": horquilla,
"inventario": inventario,
"sel_adversa_30s": mo[inf_e].sum(),
"ejecuciones": len(e), "pct_informadas": 100 * inf_e.mean(),
"q_desv": q_seg.std(), "q_max_abs": np.abs(q_seg).max()}
return res, q_segLa primera estrategia cotiza simétrica alrededor del medio, a la semihorquilla que maximiza el ingreso de cada lado, sin mirar el inventario:
def simetrica(medio_s, q, s):
return medio_s - 1 / k, medio_s + 1 / k
res_sim, q_sim = simular(simetrica)
pd.Series(res_sim).round(1)resultado 26169.0
horquilla 41382.5
inventario -15213.5
sel_adversa_30s -4709.5
ejecuciones 12820.0
pct_informadas 17.1
q_desv 3553.9
q_max_abs 15500.0
dtype: float64La horquilla capturada es de 41.383 USD en 12.820 ejecuciones de 100 acciones: unos 3,2 céntimos por acción, porque la cotización a 2,83 céntimos se redondea hacia fuera al tick y el medio es múltiplo de medio céntimo. El inventario se lleva 15.214 USD: sin control, la posición deriva como un paseo aleatorio y llega a 15.500 acciones en valor absoluto, con una desviación típica de 3.554. El resultado final, 26.169 USD, depende sobre todo de en qué dirección se movió el precio mientras el inventario era grande. La selección adversa, medida aparte, es de −4.710 USD: un 17,1 % de las ejecuciones son contra informados, más que su 10 % del flujo, porque llegan más lejos.
Avellaneda-Stoikov: precio de reserva y horquilla óptima
Avellaneda y Stoikov resuelven el problema de un creador con aversión al riesgo , inventario y un horizonte en el que cierra la posición. Su solución aproximada tiene dos piezas. La primera es el precio de reserva, el precio al que al creador le daría igual comprar o vender una unidad más:
Con inventario positivo, queda por debajo del medio ; con inventario negativo, por encima. La segunda es la horquilla óptima alrededor de :
El primer término es una prima por el riesgo de inventario, mayor cuanto más queda de sesión; el segundo depende de la forma de la demanda () y, con pequeño, tiende a , la horquilla simétrica de antes. La diferencia entre las dos estrategias está, sobre todo, en el desplazamiento por inventario.
Fuente Mermaid
flowchart TB Q[Inventario q] --> R[Precio de reserva] S[Medio s] --> R V[σ, γ, T - t] --> R R --> B[Bid = r - δ] R --> A[Ask = r + δ] K[k, γ] --> D[Semihorquilla δ] D --> B D --> A
La volatilidad del medio es de 0,0065 USD por √s (la del generador). Con en acciones, tiene unidades de 1/USD y valores pequeños: con y 100 acciones, el desplazamiento al abrir la sesión es de un céntimo.
SIGMA = 0.0065
def avellaneda_stoikov(gamma):
def cotizar(medio_s, q, s):
resto = T - s
r = medio_s - q * gamma * SIGMA**2 * resto
semi = 0.5 * (gamma * SIGMA**2 * resto
+ 2 / gamma * np.log(1 + gamma / k))
return r - semi, r + semi
return cotizar
res_as, q_as = simular(avellaneda_stoikov(1e-4))
pd.DataFrame({"simétrica": res_sim,
"AS γ=1e-4": res_as}).round(1) simétrica AS γ=1e-4
resultado 26169.0 38055.0
horquilla 41382.5 39647.5
inventario -15213.5 -1592.5
sel_adversa_30s -4709.5 -4453.0
ejecuciones 12820.0 13165.0
pct_informadas 17.1 16.6
q_desv 3553.9 225.7
q_max_abs 15500.0 1700.0Con la misma sesión y casi la misma horquilla capturada (39.648 frente a 41.383 USD), el creador de Avellaneda-Stoikov reduce la pérdida por inventario de 15.214 a 1.593 USD y su resultado sube a 38.055. La desviación típica del inventario baja de 3.554 a 226 acciones y el máximo, de 15.500 a 1.700. La selección adversa casi no cambia (−4.453 frente a −4.710): el control de inventario no protege de los informados, que siguen llegando y siguen moviendo el precio. Es una pérdida que solo se reduce cobrando más horquilla o detectando el flujo tóxico.
La figura de la cabecera enseña la diferencia: el inventario de la estrategia simétrica vaga durante toda la sesión por miles de acciones; el de Avellaneda-Stoikov, destacado, oscila cerca de cero, porque cada compra baja el precio de reserva y hace más probable la venta siguiente. La figura de abajo descompone el resultado de las dos.
barrido = {g: simular(avellaneda_stoikov(g))[0]
for g in [2e-5, 1e-4, 5e-4, 2e-3]}
pd.DataFrame(barrido).T[["resultado", "horquilla", "inventario",
"sel_adversa_30s", "q_desv"]].round(0) resultado horquilla inventario sel_adversa_30s q_desv
0.00002 37652.0 40854.0 -3202.0 -4615.0 563.0
0.00010 38055.0 39647.0 -1592.0 -4453.0 226.0
0.00050 22447.0 22923.0 -475.0 -4457.0 136.0
0.00200 -76819.0 -76500.0 -319.0 -4224.0 105.0Con el control es más flojo (desviación de 563 acciones) y el resultado parecido; con , el inventario casi no se mueve (136), pero la horquilla capturada baja a la mitad: el precio de reserva se aleja tanto del medio, con poco inventario, que el creador regala parte de la horquilla para deshacerse de él. Con la horquilla es negativa (−76.500 USD): con 100 acciones de inventario, el desplazamiento supera la semihorquilla y una de las cotizaciones cruza el medio. El creador compra por encima del medio o vende por debajo en cada ejecución. no es un parámetro que se pueda elegir mirando solo el riesgo.
El riesgo de inventario en muchas sesiones
Una sola sesión no dice cuánto varía el resultado. Con los parámetros estimados se pueden simular muchas: el medio como un paseo aleatorio de volatilidad σ por segundo y, en cada segundo, la ejecución de cada lado con probabilidad (al menos una orden que llegue a la cotización). Estas sesiones no tienen órdenes informadas, así que aíslan el riesgo de inventario. Se simulan 200 sesiones a la vez, vectorizadas con NumPy, con semilla fija.
def sesiones(gamma, n=200, semilla=7):
rng = np.random.default_rng(semilla)
s_m = np.full(n, 48.0)
q, caja = np.zeros(n), np.zeros(n)
for s in range(T):
resto = T - s
if gamma == 0:
r, semi = s_m, 1 / k
else:
r = s_m - q * gamma * SIGMA**2 * resto
semi = 0.5 * (gamma * SIGMA**2 * resto
+ 2 / gamma * np.log(1 + gamma / k))
bid = np.floor((r - semi) / TICK + 1e-9) * TICK
ask = np.ceil((r + semi) / TICK - 1e-9) * TICK
p_ask = 1 - np.exp(-A * np.exp(-k * np.maximum(ask - s_m, 0)))
p_bid = 1 - np.exp(-A * np.exp(-k * np.maximum(s_m - bid, 0)))
vende = rng.random(n) < p_ask
compra = rng.random(n) < p_bid
q += LOTE * (compra.astype(float) - vende)
caja += LOTE * (ask * vende - bid * compra)
s_m = s_m + SIGMA * rng.standard_normal(n)
return caja + q * s_m, q
mc = {}
for nombre, g in [("simétrica", 0), ("AS γ=2e-5", 2e-5),
("AS γ=1e-4", 1e-4), ("AS γ=5e-4", 5e-4)]:
pyg, q_fin = sesiones(g)
mc[nombre] = {"media": pyg.mean(), "desv": pyg.std(),
"peor": pyg.min(),
"q_final_abs": np.abs(q_fin).mean()}
pd.DataFrame(mc).T.round(0) media desv peor q_final_abs
simétrica 36890.0 6385.0 6774.0 6732.0
AS γ=2e-5 36362.0 564.0 35028.0 1248.0
AS γ=1e-4 35344.0 398.0 34230.0 777.0
AS γ=5e-4 27902.0 309.0 27095.0 538.0En 200 sesiones sin informados, la estrategia simétrica gana de media 36.890 USD con una desviación típica de 6.385, y su peor sesión deja 6.774; el inventario medio al cierre es de 6.732 acciones. Avellaneda-Stoikov con gana algo menos de media (35.344), pero con una desviación típica 16 veces menor (398) y una peor sesión de 34.230. Con la dispersión sigue bajando y la media cae a 27.902. Es el intercambio que el modelo formaliza: menos riesgo de inventario a cambio de menos horquilla.
Llama la atención que el inventario al cierre no sea cero con Avellaneda-Stoikov (777 acciones de media con ). La fórmula multiplica el inventario por , que tiende a cero al final de la sesión: en los últimos minutos el creador deja de preocuparse por la posición, justo cuando tiene que cerrarla. Es un defecto conocido de la versión con horizonte finito.
Del modelo al mercado
El modelo de Avellaneda y Stoikov es el punto de partida de la literatura de market making, no una estrategia lista. Guéant, Lehalle y Fernandez-Tapia (2013) resuelven el mismo problema con un límite de inventario y obtienen aproximaciones de las cotizaciones óptimas que, lejos del final del horizonte, no dependen del tiempo, lo que evita el defecto de la fórmula cerca del cierre. En un mercado real faltan, además, piezas que la simulación ignora:
- Competencia y cola. Otros creadores cotizan en el mismo nivel y la prioridad de precio-tiempo decide quién se ejecuta (lecciones 1.1.3 y 2.4.8). La competencia estrecha la horquilla hasta que el beneficio esperado es pequeño.
- Latencia. Una cotización que no se actualiza a tiempo es la que eligen los informados: la selección adversa real depende mucho de la velocidad.
- Costes y rebajas. Las bolsas cobran por tomar liquidez y a menudo pagan una rebaja por ponerla; para un creador, esa rebaja puede ser una parte grande del margen.
- Inventario entre días y saltos. La posición que queda al cierre está expuesta al hueco de apertura (lección 2.3.2), y una noticia puede mover el precio muchas horquillas de golpe.
Ejercicios
- Repite la estrategia simétrica con semihorquillas de 1, 2, 3 y 5 céntimos en la sesión de MMQ. ¿Qué semihorquilla da más horquilla capturada y cuál el mejor resultado?
Solución
def fija(d):
return lambda medio_s, q, s: (medio_s - d, medio_s + d)
filas = {f"{100 * d:.0f} cent": simular(fija(d))[0]
for d in [0.01, 0.02, 0.03, 0.05]}
pd.DataFrame(filas).T[["resultado", "horquilla", "inventario",
"ejecuciones", "pct_informadas"]].round(0) resultado horquilla inventario ejecuciones pct_informadas
1 cent 14146.0 32910.0 -18764.0 26842.0 12.0
2 cent 20662.0 40980.0 -20317.0 18403.0 14.0
3 cent 26169.0 41382.0 -15213.0 12820.0 17.0
5 cent 22838.0 32584.0 -9745.0 6227.0 23.0La horquilla capturada es máxima entre 2 y 3 céntimos (40.980 y 41.382 USD), cerca de : con un céntimo hay el doble de ejecuciones pero se cobra muy poco por cada una; con 5, se cobra mucho pero se ejecuta la mitad que con 3. El mejor resultado está en 3 céntimos, aunque en una sola sesión el término de inventario (de −9.745 a −20.317 USD) lo mueve tanto que el orden podría cambiar con otro día. La proporción de informadas sube del 12 % al 23 % al alejar la cotización: cuanto más ancha la horquilla, más tóxico el flujo que la alcanza.
- Comprueba la descomposición: para el creador de Avellaneda-Stoikov, la suma de horquilla e inventario tiene que dar el resultado final. ¿Qué parte del inventario explica la selección adversa?
Solución
print(f"horquilla + inventario = "
f"{res_as['horquilla'] + res_as['inventario']:.1f}")
print(f"resultado = {res_as['resultado']:.1f}")
print(f"selección adversa (30 s, informadas) = "
f"{res_as['sel_adversa_30s']:.1f}")horquilla + inventario = 38055.0
resultado = 38055.0
selección adversa (30 s, informadas) = -4453.0La suma da exactamente el resultado, 38.055 USD: horquilla e inventario son una descomposición contable completa. La
selección adversa, −4.453 USD, es una medida de cuánto pierden en 30 segundos las posiciones abiertas contra
informados. El inventario total de Avellaneda-Stoikov es −1.593 USD, menos negativo que esa cifra: el resto de
movimientos del inventario ganó algo. Con los datos reales no hay columna informada, así que la selección adversa
se estima con el marcado de todas las ejecuciones, que mezcla lo informado con el ruido.
- Un compañero propone «quitar» la selección adversa del simulador no cotizando nunca en los 30 segundos siguientes a una orden informada. ¿Qué error tiene la propuesta?
Solución
Usa información que el creador no tiene: la columna informada existe porque los datos son sintéticos, pero en un
mercado real nadie sabe qué orden es informada cuando llega. Es anticipación en estado puro. Lo que sí se puede hacer
es estimar la toxicidad con lo observable (tamaño de la orden, alcance, desequilibrio reciente del flujo, movimiento
del medio tras las ejecuciones anteriores) y ensanchar la horquilla o retirarse cuando sube; y esa estimación hay que
contrastarla sin mirar la columna informada, que solo sirve para comprobar después si el método acierta.
Respuesta plegada en cada pregunta
Autoevaluación
Con λ(δ) = 0,87·exp(−35,4·δ), ¿qué semihorquilla maximiza el ingreso esperado por segundo de un lado, δ·λ(δ), sin riesgo de inventario?
- AUn tick (1 céntimo)
- Bδ = 1/k ≈ 2,8 céntimos
- CCuanto más ancha, mejor
- DLa mitad de la horquilla del mercado
Ver respuesta
Respuesta B. δ = 1/k ≈ 2,8 céntimos
La derivada de δ·A·exp(−kδ) se anula en δ = 1/k. Más cerca del medio se ejecuta más a menudo pero se gana menos por ejecución; más lejos, al revés. Es también el límite de la horquilla de Avellaneda-Stoikov cuando la aversión al riesgo tiende a cero.
¿Qué hace el precio de reserva de Avellaneda-Stoikov cuando el creador está largo de 500 acciones?
- ASube, para comprar más
- BNo cambia: solo cambia la horquilla
- CBaja por debajo del medio: el bid y el ask bajan, así que es más fácil vender y más difícil comprar, y el inventario tiende a volver a cero
- DSe iguala al último precio negociado
Ver respuesta
Respuesta C. Baja por debajo del medio: el bid y el ask bajan, así que es más fácil vender y más difícil comprar, y el inventario tiende a volver a cero
r = s − q·γ·σ²·(T − t). Con q > 0, r < s. Las dos cotizaciones se desplazan hacia abajo con r: el ask queda más cerca del medio (se ejecuta más) y el bid más lejos (se ejecuta menos).
En la sesión simulada, el marcado a 30 segundos de las órdenes informadas es de +2,0 céntimos y el de las no informadas, de 0,0. ¿Qué significa para el creador?
- AQue cada vez que una orden informada le ejecuta, el medio se mueve 2 céntimos en su contra en los 30 segundos siguientes: pierde lo que ganó de horquilla y algo más
- BNada: es ruido
- CQue debe operar solo con las informadas
- DQue el medio tiene tendencia
Ver respuesta
Respuesta A. Que cada vez que una orden informada le ejecuta, el medio se mueve 2 céntimos en su contra en los 30 segundos siguientes: pierde lo que ganó de horquilla y algo más
Es la selección adversa de Glosten y Milgrom (1985): quien sabe más opera contra el creador justo cuando el precio va a moverse. Como no sabe quién es quién, el creador tiene que cobrar a todos una horquilla que compense lo que pierde con los informados.
Con un γ demasiado alto, el creador de Avellaneda-Stoikov de la lección pierde dinero en la horquilla. ¿Por qué?
- APorque la fórmula está mal
- BPorque se ejecuta menos
- CPorque los costes son mayores
- DPorque con inventario el precio de reserva se aleja tanto del medio que una de las cotizaciones queda al otro lado: compra por encima del medio o vende por debajo
Ver respuesta
Respuesta D. Porque con inventario el precio de reserva se aleja tanto del medio que una de las cotizaciones queda al otro lado: compra por encima del medio o vende por debajo
El desplazamiento q·γ·σ²·(T − t) crece con γ y con el inventario. Cuando supera la semihorquilla, el creador paga por deshacerse del inventario en lugar de cobrar. Las implementaciones reales limitan ese desplazamiento y el inventario máximo.
¿Por qué el resultado de la simulación de la lección es mucho mejor de lo que obtendría un creador real?
- APorque los datos son de un solo día
- BPorque supone que su cotización es la única del libro y se ejecuta entera siempre que la orden llega a su precio: sin cola, sin competencia por la prioridad, sin comisiones ni latencia
- CPorque usa el precio medio
- DPorque no hay órdenes informadas
Ver respuesta
Respuesta B. Porque supone que su cotización es la única del libro y se ejecuta entera siempre que la orden llega a su precio: sin cola, sin competencia por la prioridad, sin comisiones ni latencia
En un libro real hay otros creadores en el mismo nivel y la prioridad de precio-tiempo decide quién se ejecuta (lección 2.4.8). La competencia estrecha la horquilla hasta que el beneficio esperado se acerca a cero, y la latencia hace que las cotizaciones viejas sean las que eligen los informados.
Ideas clave
- La intensidad de ejecución cae de forma exponencial con la distancia al medio: en la sesión, λ(δ) = 0,87·exp(−35,4·δ) con todo el flujo, frente a 0,81·exp(−40·δ) de las no informadas.
- Un creador gana la horquilla y pierde por inventario y por selección adversa; el resultado se descompone en esas tres partes.
- Las órdenes informadas mueven el medio 2 céntimos en 30 segundos; las no informadas, nada: el marcado (markout) mide la selección adversa.
- Avellaneda-Stoikov desplaza las dos cotizaciones con el inventario (precio de reserva) y fija una horquilla que depende de γ, σ y k; reduce la desviación típica del inventario de miles de acciones a cientos.
- Con γ demasiado alto el precio de reserva cruza el medio y el creador paga por deshacerse del inventario.
- La simulación supone una cotización única, sin cola, comisiones ni latencia: es una herramienta para entender, no una estimación de beneficio.
Recursos
Research
Documentación
- Avellaneda y Stoikov (2008): High-frequency trading in a limit order book, Quantitative Finance 8(3)
- Glosten y Milgrom (1985): Bid, ask and transaction prices in a specialist market with heterogeneously informed traders, Journal of Financial Economics 14(1)
- Guéant, Lehalle y Fernandez-Tapia (2013): Dealing with the inventory risk, Mathematics and Financial Economics 7(4)