Lección 7.6.6 · 65 min
Capstone C5: market making o arbitraje en cripto con control de contraparte
El encargo es una mesa pequeña de arbitraje cripto que cobra el funding de los perpetuos sin apostar por el precio, o que cotiza a los dos lados, y que sobrevive a la quiebra de un exchange. Esta guía fija el alcance, ejecuta el esqueleto con los datos del nivel 3, incluida la quiebra plantada de dedalo, y aplica la rúbrica común a lo que se defiende ante el tribunal.
- Módulo
- 7.6 · Capstone final
- Duración
- 65 min
- Requisitos
- 7.6.1 · Capstone: requisitos comunes, rúbrica del tribunal y preparación de la defensa, 2.3.6 · Introducción al market making: inventario, spread y selección adversa (Avellaneda-Stoikov), 2.5.4 · Deflated Sharpe Ratio y Probability of Backtest Overfitting (PBO), 3.7.1 · Perpetuos: mecánica del funding, mark price y liquidaciones, 3.7.2 · Arbitraje de funding, basis con futuros y arbitraje entre exchanges, 3.7.4 · ccxt para operar en varios exchanges, 4.2.4 · Stress testing y riesgos de correlación, liquidez, gap y contraparte, 4.4.4 · APIs de exchanges cripto: ccxt, Binance, Bybit, Kraken y Coinbase, 4.5.5 · Tolerancia a fallos: idempotencia, estado persistente y reconciliación al arrancar, 7.4.4 · MiCA después del transitorio: comprobar un proveedor en los registros
Lecciones del nivel 7
7.1 · Ingeniería de software para trading
7.2 · Infraestructura y despliegue
7.3 · MLOps para modelos de trading
- 7.3.1 Registro de experimentos, modelos y datos: MLflow, DVC y una ejecución reproducida de principio a fin
- 7.3.2 Reentrenamiento programado, validación automática y champion/challenger
- 7.3.3 Monitorización del modelo en vivo y desactivación automática
- 7.3.4 Servir modelos: ONNX, inferencia en CPU o GPU y el coste de los LLM en producción
7.4 · Regulación, ética y fiscalidad
- 7.4.1 MiFID II: clientes, idoneidad y trading algorítmico
- 7.4.2 ESMA y CFD: apalancamiento, cierre y saldo negativo
- 7.4.3 PRIIPs, KID y ETFs estadounidenses
- 7.4.4 MiCA después del transitorio: comprobar un proveedor en los registros
- 7.4.5 Abuso de mercado: spoofing, layering y el bot que los imita sin querer
- 7.4.6 Fiscalidad del trading en España
- 7.4.7 Ética: datos personales, inteligencia artificial y la venta de señales
7.5 · Carrera profesional quant
7.6 · Capstone final
- 7.6.1 Capstone: requisitos comunes, rúbrica del tribunal y preparación de la defensa
- 7.6.2 Capstone C1: un sistema de futuros multi-activo, de la previsión a la cuenta de papel
- 7.6.3 Capstone C2: libro de opciones sistemático, superficie de volatilidad y griegas en tiempo real
- 7.6.4 Capstone C3: cartera de acciones y ETFs con ML, HRP y fiscalidad española
- 7.6.5 Capstone C4: una plataforma de investigación con LLMs y datos alternativos
- 7.6.6 Capstone C5: market making o arbitraje en cripto con control de contraparte
El encargo
Una mesa de tesorería con capital propio quiere un rendimiento que no dependa de la dirección del precio de las criptomonedas. Encarga un sistema de una de estas dos familias, a elegir por el alumno:
- Arbitraje de funding y basis. Comprar contado y vender perpetuo (o futuro trimestral) en la misma cantidad, cobrar el funding o fijar el basis, y repartir el capital entre varios exchanges (lecciones 2.3.7, 3.7.1 y 3.7.2).
- Market making. Cotizar a los dos lados de un libro, cobrar la horquilla y controlar el inventario y la selección adversa, con un modelo tipo Avellaneda-Stoikov (lección 2.3.6).
En las dos, el encargo pone por delante lo mismo: el capital está depositado en exchanges que pueden caer. El sistema tiene que decir cuánto hay en cada uno, por qué, qué lo saca de allí y cuánto se pierde si uno quiebra. Una estrategia que rinde mucho con todo el capital en un solo exchange no cumple el encargo, aunque el backtest sea bueno.
Queda fuera del encargo: DeFi y MEV (lección 3.7.3), opciones, estrategias direccionales, apalancamiento por encima de lo que el esqueleto justifica, operar con dinero real y cualquier exchange que no pase la comprobación de la lección 7.4.4 (autorización como proveedor de servicios de criptoactivos con MiCA, o registro equivalente fuera de la UE).
| Entregable | Qué contiene | Cómo se comprueba |
|---|---|---|
| Repositorio | Plantilla de 7.6.1, uv y Docker, un comando por etapa | make todo reproduce el informe en una máquina limpia |
| Registro de contrapartes | Exchanges, autorización, límites, segregación, pruebas de retirada | Cada exchange con capital tiene su ficha y su límite |
| Registro de pruebas | Cada configuración probada, con su serie | El número de pruebas del DSR sale de este fichero |
| Informe de validación | Walk-forward, DSR, escenarios de quiebra y de liquidación | Las reglas de parada están fechadas antes de los resultados |
| Ejecutor | Dos patas con identificadores propios, deshacer, conciliar | Pruebas con fallos inyectados en el simulador |
| Diario de paper trading | Cuatro semanas en cuentas de demostración | Cada periodo de funding tiene su fila y su conciliación |
| Memoria y defensa | Plantilla de 7.6.1, 20 minutos de exposición | Ante el tribunal |
Arquitectura
Fuente Mermaid
flowchart TB D[Datos de exchanges] --> S[Señal de carry] S --> A[Asignación] C[Contrapartes] --> A A --> R[Riesgo y margen] R --> E[Ejecutor ccxt] E --> X1[Exchange 1] E --> X2[Exchange 2] X1 --> K[Conciliación] X2 --> K K --> M[Monitor]
El flujo es el de cualquier sistema en vivo (lección 4.5.1), con dos piezas propias del proyecto. El monitor de contrapartes es una entrada de la asignación al mismo nivel que la señal: puede sacar a un exchange del reparto aunque sea el que más paga. Y la asignación no reparte por rendimiento esperado sin más: reparte con límites por exchange que se fijan con el escenario de quiebra, no con el backtest.
| Pieza | Librería | Dónde se aprendió |
|---|---|---|
| Funding, mark price y liquidación | pandas | 1.2.6, 3.7.1 |
| Cash-and-carry, basis, diferenciales y contraparte | pandas | 2.3.7, 3.7.2 |
| Market making e inventario | numpy | 2.3.6 |
| API unificada, errores, límites e idempotencia | ccxt, ccxt.pro | 3.7.4 |
| Firmas, nonces, relojes y streams de usuario | ccxt, APIs nativas | 4.4.4 |
| Credenciales y permisos mínimos | variables de entorno, gestor de secretos | 4.4.5 |
| Riesgo de contraparte y estrés | pandas | 4.2.4 |
| Margen y liquidaciones en derivados | pandas | 4.2.6 |
| Órdenes, reconexiones y conciliación | máquina de estados, ccxt | 4.5.2, 4.5.5 |
| Pruebas múltiples y DSR | registro de pruebas | 2.2.4, 2.5.4 |
| Monitorización y desactivación | métricas, permiso con caducidad | 7.2.3, 7.3.3 |
| Proveedores autorizados con MiCA | registro de ESMA y de la CNMV | 3.7.5, 7.4.4 |
Datos
El esqueleto usa los datos del nivel 3 (public/curso/datos/nivel-3/); la variante de market making, los del nivel 2:
| Fichero | Qué es | Para qué sirve aquí |
|---|---|---|
cripto3_funding.parquet | Funding cada 8 h, índice, mark y último precio del perpetuo; 4 exchanges, 2 activos, 2020-2025 | Señal, cobro y cobertura |
cripto3_exchanges.csv | Fichas y cronología de la quiebra de dedalo | Escenario de contraparte y alarma |
cripto3_1h.parquet | Barras de 1 h del perpetuo de alfa con el mark | Margen y liquidaciones |
cripto3_futuros.parquet, cripto3_diario.parquet | Trimestrales y cierres diarios por exchange | Basis y dislocaciones (3.7.2) |
xsn_funding_8h.csv, xsn_ccxt_ohlcv.json | Funding de otro token y el formato de ccxt | Pruebas del cargador de datos |
mm2_medio.parquet, mm2_flujo.parquet | Una sesión segundo a segundo con flujo informado | Variante de market making |
La respuesta conocida, según el README del nivel 3: la fórmula del funding es la de Binance (prima más un término de
interés recortado), con un suelo de 0,01 % por periodo, el 10,95 % anual; la prima alcista se estrecha con una vida
media de 2,5 años, y dedalo suspende retiradas el 8 de noviembre de 2022 a las 12:00 y deja los saldos a cero el 11.
La lección 3.7.2 midió el carry por exchange y año y la prima de dedalo frente a alfa.
Con datos reales, el historial de funding se descarga de cada exchange con ccxt (fetch_funding_rate_history) o con su
API nativa; por ejemplo, la documentación de la API v5 de Bybit describe la ruta del historial de funding. Las
comisiones por nivel, los topes de funding, el intervalo de cada contrato y las reglas de margen se leen en la
documentación de cada exchange el día que se escribe la ficha, con fecha: cambian, y no se copian de un tutorial. El
intervalo de funding no se supone de 8 horas: se calcula con las marcas de tiempo del historial. Las comprobaciones de
autorización con MiCA (registro provisional de ESMA, registro de la CNMV) son las de la lección 7.4.4.
Esqueleto ejecutable
El esqueleto es la columna vertebral del arbitraje de funding, de punta a punta, en el entorno principal del curso y sin red: datos, señal, asignación con límites y alarma, margen, ejecución de dos patas con ccxt, validación e informe. La variante de market making se monta sobre el simulador de 2.3.6 (ejercicio 2).
Datos con huella y la cronología de la quiebra
import hashlib
import json
import numpy as np
import pandas as pd
from scipy.stats import norm
def huella(ruta):
"""SHA-256 abreviado de un fichero de datos (va al informe)."""
with open(ruta, "rb") as f:
return hashlib.sha256(f.read()).hexdigest()[:12]
FICHEROS = ["cripto3_funding.parquet", "cripto3_exchanges.csv",
"cripto3_1h.parquet"]
HUELLAS = {f: huella(f) for f in FICHEROS}
fu = pd.read_parquet("cripto3_funding.parquet")
def tabla(col):
return fu.pivot(index="ts", columns=["exchange", "activo"],
values=col)
F, I, P = (tabla("tasa_funding"), tabla("precio_indice"),
tabla("ultimo"))
ev = pd.read_csv("cripto3_exchanges.csv").dropna(subset=["ts_utc"])
EV = dict(zip(ev["evento"], pd.to_datetime(ev["ts_utc"], utc=True)))
print(F.shape, "periodos de 8 h x pares;", F.index[0].date(), "a",
F.index[-1].date())
print(ev[["exchange", "ts_utc", "evento"]].to_string(index=False))(6575, 8) periodos de 8 h x pares; 2020-01-01 a 2025-12-31
exchange ts_utc evento
dedalo 2022-11-06 00:00 rumores
dedalo 2022-11-08 12:00 retiradas_suspendidas
dedalo 2022-11-11 14:00 cese_negociacion
dedalo 2022-11-11 23:00 quiebra6.575 periodos de 8 horas por par, de 2020 a 2025, y la cronología de dedalo: rumores el 6 de noviembre de 2022,
retiradas suspendidas el 8 a las 12:00, cese de la negociación el 11 a las 14:00 y quiebra esa noche. En los datos de
funding, dedalo deja de tener filas tras el último periodo antes del cese.
Señal, asignación y simulador
La señal es el carry de 7 días: la media de los últimos 21 funding publicados, anualizada. Un par entra cuando su carry supera un umbral y sale cuando baja de otro (histéresis, para no pagar comisiones en cada oscilación). El capital se reparte por igual entre los pares activos, con un límite por exchange, y cada unidad de nominal pide de capital: el contado y el margen del corto a . El simulador decide en cada periodo con lo publicado hasta ese momento y cobra en el siguiente el funding y la cobertura: la diferencia entre lo que hace el contado (el índice) y lo que hace el perpetuo, que no es cero aunque la posición sea neutral. Cada cambio de nominal paga dos lados de 5 pb.
PER = 3 * 365 # periodos de funding por año
carry7 = F.rolling(21).mean() * PER # carry anual de 7 días, en t
cobertura = (I.pct_change() - P.pct_change()).to_numpy()
pares = list(F.columns)
exch = np.array([e for e, _ in pares])
ts, Fv, Cv = F.index, F.to_numpy(), carry7.to_numpy()
def simular(entrada=0.11, salida=0.0, limite=0.30, alarma=True,
modo="igual", coste=5e-4, L=3, vetos=()):
"""Largo contado + corto perpetuo en cada par activo. Decide en t
con lo publicado hasta t; cobra funding y cobertura en t + 1.
`vetos`: (exchange, desde, hasta) en que se retira el capital."""
vetos = list(vetos) + ([("dedalo", EV["rumores"], None)]
if alarma else [])
activo, nom = np.zeros(len(pares), bool), np.zeros(len(pares))
cap_u = 1 + 1 / L # capital por unidad de nominal
capital, congelado, perdida, giro = 1.0, 0.0, 0.0, 0.0
atrapado, serie = False, []
for t in range(len(ts) - 1):
fuera = {e for e, d, h in vetos
if ts[t] >= d and (h is None or ts[t] < h)}
if ts[t] >= EV["retiradas_suspendidas"] and not atrapado:
atrapado = True # lo que queda, atrapado
if "dedalo" not in fuera:
congelado = nom[exch == "dedalo"].sum() * cap_u
if atrapado:
fuera.add("dedalo")
if ts[t] >= EV["cese_negociacion"] and congelado:
perdida = congelado / capital
capital, congelado = capital - congelado, 0.0
c = Cv[t]
ok = ~np.isnan(c) & ~np.isin(exch, list(fuera))
activo = np.where(activo, c > salida, c > entrada) & ok
w = np.zeros(len(pares))
if activo.any() and modo == "mejor":
w[np.where(activo, c, -np.inf).argmax()] = 1.0
elif activo.any():
w[activo] = 1 / activo.sum()
for e in set(exch[w > 0]): # límite por contraparte
m = exch == e
w[m] *= min(1.0, limite / w[m].sum())
nuevo = (capital - congelado) * w / cap_u
cambio = np.abs(nuevo - nom).sum()
giro += cambio
nom = nuevo
cobro = (np.nan_to_num(Fv[t + 1])
+ np.nan_to_num(cobertura[t + 1]))
capital += (nom * cobro).sum() - 2 * coste * cambio
serie.append(capital)
return pd.Series(serie, index=ts[1:]), perdida, giro
def metricas(s):
r = s.pct_change().dropna()
anos = (s.index[-1] - s.index[0]).days / 365.25
return {"final": s.iloc[-1],
"anual %": 100 * (max(s.iloc[-1], 1e-9) ** (1 / anos) - 1),
"Sharpe": r.mean() / r.std() * np.sqrt(PER),
"caída máx. %": 100 * (s / s.cummax() - 1).min()}
base, perdida, giro = simular()
print({k: round(float(v), 2) for k, v in metricas(base).items()})
anos = 100 * base.resample("YE").last().pct_change().dropna()
print("por año %:", {d.year: round(v, 1) for d, v in anos.items()}){'final': 2.29, 'anual %': 14.84, 'Sharpe': 14.22, 'caída máx. %': -0.75}
por año %: {2021: 27.2, 2022: 7.6, 2023: 7.9, 2024: 7.4, 2025: 6.2}Con entrada en el 11 % (justo por encima del suelo de la fórmula), salida cuando el carry de la semana se vuelve negativo, límite del 30 % por exchange y alarma de contraparte, el capital pasa de 1 a 2,29 en seis años: un 14,8 % anual, con una caída máxima del 0,75 %. Por año, un 27,2 % en 2021 y entre el 6,2 % y el 7,9 % desde 2022: la prima se estrecha y el carry se acerca al suelo dividido por el capital que pide cada unidad de nominal (10,95 % / 1,33 ≈ 8,2 %), menos los costes. El Sharpe de 14,2 no es una errata: es lo que da un carry sin la cola dentro de la muestra.
Contraparte: concentración, límites y alarma
La alarma de la celda anterior saca a dedalo del reparto en cuanto aparece el evento de rumores de insolvencia en el
registro de incidencias (en real, el monitor de contrapartes: noticias, retrasos en las retiradas, descuento del
contado frente al índice, salidas de fondos). Sin ella, y con otras formas de repartir:
VARIANTES = {
"todo en el que más paga": dict(limite=1.0, alarma=False,
modo="mejor"),
"repartido, sin límite": dict(limite=1.0, alarma=False),
"repartido, límite 30 %": dict(limite=0.30, alarma=False),
"repartido, límite 15 %": dict(limite=0.15, alarma=False),
"límite 30 % y alarma": dict(limite=0.30, alarma=True),
}
filas = {}
for nombre, kw in VARIANTES.items():
s, perd, _ = simular(**kw)
filas[nombre] = metricas(s) | {"pérdida en dedalo %": 100 * perd}
pd.DataFrame(filas).T.round(2) final anual % Sharpe caída máx. % pérdida en dedalo %
todo en el que más paga 0.01 -50.59 -0.20 -99.18 99.12
repartido, sin límite 1.42 5.98 0.47 -40.15 39.91
repartido, límite 30 % 1.61 8.25 0.72 -30.04 29.95
repartido, límite 15 % 1.38 5.50 0.90 -15.04 14.99
límite 30 % y alarma 2.29 14.84 14.22 -0.75 0.00La figura de la cabecera dibuja la primera columna. Con todo el capital en el exchange que más paga, que en noviembre
de 2022 era dedalo, se pierde el 99 % del capital de ese momento. Repartido por igual sin límite, el 39,9 %; con
un límite del 30 %, el 30 %; con el 15 %, el 15 %. El límite convierte una pérdida total en una pérdida acotada, pero
no la evita: lo que estaba en dedalo cuando se suspendieron las retiradas se queda allí. Solo la alarma, que actúa
60 horas antes, deja la pérdida en cero.
Dos advertencias para la memoria. La alarma está escrita después de conocer el caso FTX: en el tribunal hay que decirlo y medir también lo que cuestan las falsas alarmas (ejercicio 1). Y un límite del 15 % reduce la pérdida a la mitad que el del 30 %, pero también el rendimiento, porque con pocos exchanges activos parte del capital se queda sin usar.
Fuente Mermaid
stateDiagram-v2 [*] --> NORMAL NORMAL --> VIGILANCIA: señal débil VIGILANCIA --> NORMAL: se aclara VIGILANCIA --> RETIRADA: señal fuerte NORMAL --> RETIRADA: rumor o retraso RETIRADA --> VETADO: capital fuera VETADO --> NORMAL: revisión manual
Margen de la pata corta
El corto del perpetuo se liquida si el mark sube hasta su precio de liquidación, aunque el contado haya ganado lo mismo (lecciones 3.7.1 y 4.2.6). Con el margen repuesto cada día, la pregunta es cuántos días la subida del mark desde el inicio del día habría llegado a ese precio:
h1 = pd.read_parquet("cripto3_1h.parquet")
h1["dia"] = h1["ts"].dt.floor("D")
ref = h1.groupby(["activo", "dia"])["mark_cierre"].first()
techo = h1.groupby(["activo", "dia"])["mark_maximo"].max()
subida = techo / ref - 1 # peor subida del mark en el día
MMR = 0.005 # margen de mantenimiento
margen = {}
for L in (2, 3, 5, 10):
umbral = (1 + 1 / L) / (1 + MMR) - 1
margen[f"{L}x"] = {"subida que liquida %": 100 * umbral,
"días XBQ": int((subida["XBQ"] >= umbral).sum()),
"días XEQ": int((subida["XEQ"] >= umbral).sum())}
peor = 100 * subida.groupby(level=0).max()
print(f"peor subida del mark en un día: XBQ {peor['XBQ']:.1f} %, "
f"XEQ {peor['XEQ']:.1f} %")
pd.DataFrame(margen).T.round(1)peor subida del mark en un día: XBQ 15.9 %, XEQ 18.1 %
subida que liquida % días XBQ días XEQ
2x 49.3 0.0 0.0
3x 32.7 0.0 0.0
5x 19.4 0.0 0.0
10x 9.5 16.0 37.0La peor subida del mark en un día fue del 15,9 % en XBQ y del 18,1 % en XEQ. A 3x hace falta un 32,7 %: ningún día. A 5x, un 19,4 %: ninguno, por poco en XEQ. A 10x basta un 9,5 %, y eso pasó 16 días en XBQ y 37 en XEQ. El esqueleto usa 3x con reposición diaria; la memoria tiene que justificar la frecuencia real de reposición, que depende de cuánto se tarda en mover fondos entre cuentas.
Ejecución de dos patas con ccxt
El ejecutor habla con los exchanges a través de la API unificada de ccxt. En el esqueleto, cada exchange es una clase
en memoria que hereda de ccxt.Exchange: mercados unificados (XBQ/USDQ y el perpetuo lineal XBQ/USDQ:USDQ),
precisión, órdenes, saldo y posiciones con las estructuras de ccxt. En cenit, el perpetuo está caído:
import ccxt
from ccxt.base.errors import ExchangeNotAvailable
class Simulado(ccxt.Exchange):
"""Exchange en memoria con la API unificada de ccxt: mercados,
órdenes, saldo y posiciones. Sin red (la versión HTTP con firma
y límites de peticiones está en la lección 3.7.4)."""
def describe(self):
return self.deep_extend(super().describe(), {
"id": "simulado", "has": {"createOrder": True,
"fetchPositions": True}})
def __init__(self, nombre, precio, caidos=()):
super().__init__({})
self.id, self.px, self.caidos = nombre, precio, set(caidos)
self.reloj, self.n = 1_704_067_200_000, 0 # 2024-01-01 UTC
self.saldo, self.pos = {"USDQ": 100_000.0, "XBQ": 0.0}, 0.0
comun = {"base": "XBQ", "quote": "USDQ", "baseId": "XBQ",
"quoteId": "USDQ", "active": True, "taker": 5e-4,
"maker": 2e-4, "limits": {"cost": {"min": 10}},
"precision": {"amount": 0.001, "price": 0.1}}
mercados = (
{"id": "XBQUSDQ", "symbol": "XBQ/USDQ", "type": "spot",
"spot": True},
{"id": "XBQ-PERP", "symbol": "XBQ/USDQ:USDQ",
"type": "swap", "swap": True, "linear": True,
"contract": True, "settle": "USDQ", "contractSize": 1})
self.set_markets([self.safe_market_structure(comun | m)
for m in mercados])
def create_order(self, symbol, type, side, amount, price=None,
params={}):
if symbol in self.caidos:
raise ExchangeNotAvailable(f"{self.id} 503 {symbol}")
m, s = self.market(symbol), 1 if side == "buy" else -1
q = float(self.amount_to_precision(symbol, amount))
px = round(self.px * (1 + s * 2e-4), 1) # media horquilla
fee = round(q * px * m["taker"], 4)
if m["spot"]:
self.saldo["XBQ"] += s * q
self.saldo["USDQ"] -= s * q * px + fee
else:
self.pos += s * q
self.saldo["USDQ"] -= fee
self.reloj, self.n = self.reloj + 250, self.n + 1
return self.safe_order({
"id": f"{self.id}-{self.n}", "timestamp": self.reloj,
"clientOrderId": params.get("clientOrderId"),
"symbol": symbol, "type": type, "side": side, "amount": q,
"filled": q, "average": px, "status": "closed",
"fee": {"cost": fee, "currency": "USDQ"}}, m)
def fetch_positions(self, symbols=None, params={}):
return [self.safe_position({
"symbol": "XBQ/USDQ:USDQ", "contracts": abs(self.pos),
"side": "short" if self.pos < 0 else "long", "info": {}})]
def abrir_par(ex, q, cid):
"""Compra contado y vende perpetuo; si la segunda pata falla,
deshace la primera. Devuelve las órdenes ejecutadas."""
hechas = [ex.create_order("XBQ/USDQ", "market", "buy", q,
params={"clientOrderId": f"{cid}-c"})]
try:
hechas.append(ex.create_order(
"XBQ/USDQ:USDQ", "market", "sell", q,
params={"clientOrderId": f"{cid}-p"}))
except ExchangeNotAvailable as e:
print(f"{ex.id}: {e}; se deshace el contado")
hechas.append(ex.create_order(
"XBQ/USDQ", "market", "sell", q,
params={"clientOrderId": f"{cid}-x"}))
return hechas
def conciliar(ex, ordenes):
"""Posiciones según las órdenes propias frente al exchange."""
libro = {"XBQ/USDQ": 0.0, "XBQ/USDQ:USDQ": 0.0}
for o in ordenes:
libro[o["symbol"]] += o["filled"] * (1 if o["side"] == "buy"
else -1)
p = ex.fetch_positions()[0]
real = {"XBQ/USDQ": ex.saldo["XBQ"], "XBQ/USDQ:USDQ":
p["contracts"] * (-1 if p["side"] == "short" else 1)}
coste = 100_000 - ex.saldo["USDQ"] - ex.saldo["XBQ"] * ex.px
return libro == real, real, round(coste, 2)
for ex in (Simulado("alfa", 42_000.0),
Simulado("cenit", 42_010.0, caidos=["XBQ/USDQ:USDQ"])):
ordenes = abrir_par(ex, 0.5, f"c5-{ex.id}-0001")
print(ex.id, [(o["clientOrderId"], o["side"], o["average"])
for o in ordenes])
print(" cuadra, posiciones, coste:", conciliar(ex, ordenes))alfa [('c5-alfa-0001-c', 'buy', 42008.4), ('c5-alfa-0001-p', 'sell', 41991.6)]
cuadra, posiciones, coste: (True, {'XBQ/USDQ': 0.5, 'XBQ/USDQ:USDQ': -0.5}, 25.2)
cenit: cenit 503 XBQ/USDQ:USDQ; se deshace el contado
cenit [('c5-cenit-0001-c', 'buy', 42018.4), ('c5-cenit-0001-x', 'sell', 42001.6)]
cuadra, posiciones, coste: (True, {'XBQ/USDQ': 0.0, 'XBQ/USDQ:USDQ': 0.0}, 29.4)En alfa entran las dos patas y la conciliación cuadra: 0,5 XBQ de contado y −0,5 de perpetuo, con un coste de 25,2
USDQ entre comisiones y horquilla. En cenit, el contado entra, el perpetuo devuelve un 503 y el ejecutor deshace el
contado con su propio identificador (-x): la posición queda a cero y el intento cuesta 29,4 USDQ. Los
identificadores propios (clientOrderId) son los que permiten saber, tras un timeout, si una orden existe sin
duplicarla (lección 3.7.4).
Contra un exchange real, el mismo código usa la clase de ccxt del exchange y su cuenta de demostración. Va con skip:
necesita red y claves, que se leen del entorno y nunca tienen permiso de retirada (lección 4.4.5).
import os
import ccxt
ex = ccxt.bybit({"apiKey": os.environ["BYBIT_KEY"],
"secret": os.environ["BYBIT_SECRET"]})
ex.enable_demo_trading(True) # cuenta de demostración
ex.load_markets()
hist = ex.fetch_funding_rate_history("BTC/USDT:USDT", limit=21)
horas = (hist[-1]["timestamp"] - hist[0]["timestamp"]) / 3.6e6 / 20
carry7 = (sum(h["fundingRate"] for h in hist) / len(hist)
* 24 / horas * 365)
if carry7 > 0.11:
q = float(ex.amount_to_precision("BTC/USDT:USDT", 0.01))
ex.create_order("BTC/USDT", "market", "buy", q,
params={"clientOrderId": "c5-0001-c"})
ex.create_order("BTC/USDT:USDT", "market", "sell", q,
params={"clientOrderId": "c5-0001-p"})
print(ex.fetch_positions(["BTC/USDT:USDT"]))enable_demo_trading existe en la versión 4.5.89 de ccxt para Bybit y para Binance. Antes de usarlo hay que leer en
la documentación de cada exchange qué cubre su entorno de demostración (mercados, saldos ficticios, límites): no es
idéntico al real.
Registro de pruebas, validación e informe
Las ocho combinaciones de umbrales (entrada en el 8, 11, 15 y 20 %; salida en el 0 y el 5 %) quedan en
experimentos/registro_pruebas.jsonl, solo de añadir y con los campos de la guía 7.6.1, y el número de pruebas del
Deflated Sharpe se cuenta en ese fichero. El walk-forward elige cada año con los anteriores y no opera si la elegida no
gana en entrenamiento.
REJILLA = [(e, s) for e in (0.08, 0.11, 0.15, 0.20)
for s in (0.0, 0.05)]
import os
REG = "experimentos/registro_pruebas.jsonl" # solo de añadir (7.6.1)
os.makedirs("experimentos", exist_ok=True)
def sharpe(x):
return float(x.mean() / x.std() * np.sqrt(PER))
series = {}
for e, s in REJILLA:
x = simular(e, s)[0].pct_change().dropna()
series[(e, s)] = x
cfg = {"señal": "carry7", "entrada": e, "salida": s,
"limite": 0.30, "apalancamiento": 3, "coste_pb": 5}
with open(REG, "a", encoding="utf-8") as f:
f.write(json.dumps({
"id": hashlib.sha256(json.dumps(cfg, sort_keys=True)
.encode()).hexdigest()[:10],
"hipotesis": "el carry de 7 días persiste",
"config": cfg, "datos": HUELLAS["cripto3_funding.parquet"],
"resultado": {"sharpe": round(sharpe(x), 4), "n": len(x)}},
ensure_ascii=False) + "\n")
with open(REG, encoding="utf-8") as f:
N = sum(1 for _ in f)
oos = []
for ano in range(2021, 2026):
ent = {k: sharpe(x[x.index.year < ano]) for k, x in series.items()}
elegida = max(ent, key=ent.get)
x = series[elegida][series[elegida].index.year == ano]
oos.append(x if ent[elegida] > 0 else 0 * x)
print(ano, "elegida", elegida, f"| fuera: {100 * x.sum():.1f} %"
f" (Sharpe {sharpe(x):.1f})")
oos = pd.concat(oos)
def max_esperado(n):
g = np.euler_gamma
return ((1 - g) * norm.ppf(1 - 1 / n)
+ g * norm.ppf(1 - 1 / (n * np.e)))
def psr(x, umbral):
sr = x.mean() / x.std()
a, k = x.skew(), x.kurt() + 3
return float(norm.cdf((sr - umbral) * np.sqrt(len(x) - 1) / np.sqrt(
1 - a * sr + (k - 1) / 4 * sr ** 2)))
sr = pd.Series({k: x.mean() / x.std() for k, x in series.items()})
mejor = max(series, key=lambda k: sharpe(series[k]))
sr0 = sr.std() * max_esperado(N)
print(f"N = {N}; mejor {mejor}: Sharpe {sharpe(series[mejor]):.1f},"
f" DSR {psr(series[mejor], sr0):.3f}; walk-forward: Sharpe "
f"{sharpe(oos):.1f}")
anual = 100 * oos[oos.index.year >= 2023].sum() / 3
print(f"carry neto 2023-2025: {anual:.1f} % al año; una quiebra con "
f"el 30 % del capital dentro se come {30 / anual:.1f} años")2021 elegida (0.08, 0.0) | fuera: 23.9 % (Sharpe 21.2)
2022 elegida (0.08, 0.0) | fuera: 7.4 % (Sharpe 11.2)
2023 elegida (0.08, 0.0) | fuera: 7.6 % (Sharpe 13.7)
2024 elegida (0.08, 0.0) | fuera: 7.2 % (Sharpe 14.9)
2025 elegida (0.08, 0.0) | fuera: 6.0 % (Sharpe 12.8)
N = 8; mejor (0.08, 0.0): Sharpe 14.3, DSR 1.000; walk-forward: Sharpe 14.3
carry neto 2023-2025: 6.9 % al año; una quiebra con el 30 % del capital dentro se come 4.3 añosinforme = {
"proyecto": "C5", "datos": HUELLAS,
"estrategia": {"entrada": mejor[0], "salida": mejor[1],
"limite_por_exchange": 0.30, "apalancamiento": 3,
"coste_pb": 5, "n_pruebas": N},
"validacion": {"sharpe_walk_forward": round(sharpe(oos), 1),
"dsr": round(psr(series[mejor], sr0), 3),
"carry_neto_2023_2025_pct": round(anual, 1)},
"riesgo": {"perdida_dedalo_sin_alarma_pct": round(
100 * simular(alarma=False)[1], 1),
"anos_de_carry_por_quiebra_al_limite": round(
30 / anual, 1),
"dias_liquidacion_3x": margen["3x"]["días XBQ"]
+ margen["3x"]["días XEQ"]},
}
informe["decision"] = (
"paper trading" if informe["validacion"]["dsr"] >= 0.95
and informe["validacion"]["sharpe_walk_forward"] > 0
else "parar o rediseñar")
with open("informe_c5.json", "w", encoding="utf-8") as f:
json.dump(informe, f, ensure_ascii=False, indent=1)
for parte in ("estrategia", "validacion", "riesgo", "decision"):
print(parte, json.dumps(informe[parte], ensure_ascii=False))estrategia {"entrada": 0.08, "salida": 0.0, "limite_por_exchange": 0.3, "apalancamiento": 3, "coste_pb": 5, "n_pruebas": 8}
validacion {"sharpe_walk_forward": 14.3, "dsr": 1.0, "carry_neto_2023_2025_pct": 6.9}
riesgo {"perdida_dedalo_sin_alarma_pct": 30.0, "anos_de_carry_por_quiebra_al_limite": 4.3, "dias_liquidacion_3x": 0}
decision "paper trading"El walk-forward elige cada año la entrada del 8 % sin umbral de salida, y el Sharpe fuera de muestra es de 14,3; el Deflated Sharpe con 8 pruebas es 1,000. La regla automática dice «paper trading». La memoria tiene que decir también la otra línea del informe: con el 30 % del capital en un exchange, una quiebra se come 4,3 años de carry neto al ritmo de 2023-2025 (6,9 % anual). Ese es el número que decide el tamaño de los límites, y no sale de ningún contraste estadístico.
Validación
La validación del carry tiene dos partes que no se pueden sustituir la una por la otra.
La parte estadística, como en cualquier proyecto del curso: walk-forward anual con ventana creciente (lección 2.5.2), registro de pruebas y Deflated Sharpe (2.5.4), o CPCV (2.5.3) si hay más parámetros. Aquí sirve para lo que sirve: comprobar que los umbrales no están ajustados a la muestra y que el carry sobrevive a los costes en cada año. En la variante de market making, los parámetros (, , límite de inventario) se eligen en unas sesiones y se miden en otras, y la métrica incluye la selección adversa medida aparte (2.3.6).
La parte de escenarios, que es la que decide. Un carry tiene una distribución de rendimientos con muchos días pequeños y positivos y un salto raro: su Sharpe muestral es enorme mientras el salto no está en la muestra. Lo que se valida es la pérdida en cada escenario y su relación con el carry:
| Escenario | Cómo se mide | Qué se registra |
|---|---|---|
| Quiebra de un exchange | Capital en él cuando se cierran las retiradas | Pérdida y años de carry que se come |
| Liquidación de la pata corta | Subida del mark entre reposiciones de margen | Días que liquidarían a cada apalancamiento |
| Funding negativo prolongado | Semanas seguidas pagando (2022 en los datos) | Pérdida y giro de la histéresis |
| Pata huérfana | Fallo de la segunda orden | Coste de deshacer y frecuencia en paper trading |
| Dislocación del contado | Descuento de un exchange frente al índice (3.7.2) | Pérdida de la cobertura si se usa su contado |
Las reglas de parada, fechadas en la memoria antes de la semana 4: carry neto fuera de muestra no positivo en el último año; DSR por debajo de 0,95 con el número de pruebas registrado; una quiebra en el límite que se come más de cinco años de carry neto, salvo que se baje el límite; cualquier día de liquidación al apalancamiento elegido en la historia disponible. Los umbrales son criterio del curso, no una norma.
Riesgos y paper trading
Lo que de verdad tumba una mesa de arbitraje cripto:
- Contraparte. Quiebra, congelación de retiradas, mezcla de fondos de clientes. El caso FTX, según las demandas de la CFTC y la SEC, lo resume la lección 3.7.2. Defensas: límites, retirada de beneficios, exchanges autorizados con MiCA (7.4.4), pruebas de retirada periódicas y la alarma.
- Liquidación de la pata corta por una subida rápida con el margen sin reponer, o por una mecha del último precio en un exchange que liquida con él en lugar de con el mark (3.7.1).
- Patas huérfanas y duplicadas. Una orden que entra en un exchange y no en el otro, o un reintento que duplica la orden tras un timeout (3.7.4).
- Funding que cambia de signo y deja a la mesa pagando mientras la histéresis decide.
- Claves. Una clave con permiso de retirada filtrada vacía la cuenta; las claves del ejecutor solo operan, y las direcciones de retirada están en una lista blanca configurada a mano (4.4.5).
- Market making: selección adversa, inventario que se acumula en una caída y cotizaciones que siguen vivas cuando el proceso se cae (cancel-on-disconnect, si el exchange lo ofrece; 4.5.2).
Los límites y el kill switch: capital máximo por exchange, apalancamiento máximo, distancia mínima al precio de
liquidación, número máximo de órdenes por minuto, pérdida diaria máxima (4.2.5), y el estado de cada contraparte del
diagrama de arriba. Un exchange en RETIRADA no recibe órdenes que aumenten la exposición.
El plan de las cuatro semanas de paper trading, en las cuentas de demostración de al menos dos exchanges:
- Cada periodo de funding: descarga del funding publicado, señal, asignación, órdenes de las dos patas y conciliación de órdenes, saldos y posiciones (4.5.5). Una diferencia sin explicar bloquea el periodo siguiente.
- Cada día: margen y distancia a la liquidación por posición, estado de cada contraparte, cobertura (contado frente a perpetuo) y deslizamiento frente al supuesto de 5 pb (4.5.3).
- Cada semana: carry cobrado frente al simulado para las mismas fechas, incidencias del ejecutor (patas huérfanas, rechazos, límites de peticiones) y una prueba de fallo provocada (cortar la conexión a mitad de una apertura).
- Al final: informe con las diferencias entre el simulador y lo ejecutado. Cuatro semanas no dicen nada de la cola; dicen si el ejecutor, la conciliación y el monitor funcionan.
Hitos y rúbrica
El calendario es el de la guía 7.6.1: de 40 a 80 horas en unas 10 semanas, con las cuatro de paper trading en paralelo a la memoria.
| Semana | Entregable verificable |
|---|---|
| 1 | Repositorio de la plantilla; cargador de funding con huellas; registro de contrapartes con la comprobación de 7.4.4 |
| 2 | Señal, simulador con costes, margen y cobertura; respuesta conocida del nivel 3 reproducida |
| 3 | Asignación con límites y alarma; escenario de quiebra; reglas de parada escritas y fechadas |
| 4 | Walk-forward, DSR y escenarios; decisión de paso a paper trading |
| 5 | Ejecutor con ccxt contra el simulador: dos patas, deshacer, idempotencia, conciliación, con pruebas |
| 6 | Cuentas de demostración, claves sin retirada, monitor de contrapartes y alertas |
| 7-10 | Paper trading, conciliación por periodo y monitor; memoria técnica; ensayo de la defensa |
La rúbrica es la común del curso (criterio propio, no una norma externa), con la evidencia que pide en C5:
| Criterio | Peso | Evidencia en C5 |
|---|---|---|
| Reproducibilidad | 15 % | uv.lock, Docker, un comando; huellas de datos; simulador determinista con reloj propio |
| Datos | 15 % | Funding con su hora de publicación, exchanges desaparecidos incluidos (sin sesgo de supervivencia), fichas fechadas |
| Validación anti-sobreajuste | 20 % | Walk-forward, DSR con el registro, escenarios de quiebra y de liquidación con su pérdida |
| Riesgo y ejecución | 15 % | Costes, margen, límites por contraparte, alarma, ejecutor de dos patas con deshacer y conciliación |
| Paper trading y monitorización | 15 % | Cuatro semanas en demostración, conciliación por periodo, estado de contrapartes |
| Calidad del código | 10 % | Pruebas del ejecutor con fallos inyectados, del simulador y de la asignación; lint; configuración fuera del código |
| Memoria técnica y defensa | 10 % | Plantilla de 7.6.1; límites justificados con el escenario de quiebra |
Errores que suspenden por sí solos, con su forma en C5: anticipación demostrable (usar el funding de un periodo para
decidir la posición que lo cobra); resultados que no se reproducen con el comando documentado; backtest sin costes o
sin margen; claves de API en el repositorio o en su historial, más aún con permiso de retirada; ocultar el número de
pruebas o elegir el periodo a posteriori (por ejemplo, empezar en 2023 para no ver la quiebra, o quitar dedalo del
universo, que es sesgo de supervivencia); menos de cuatro semanas de paper trading.
Ejercicios
- La alarma de contraparte salta por un rumor falso sobre
alfael 4 de marzo de 2024 y el capital se retira una semana. ¿Cuánto cuesta esa falsa alarma frente al esqueleto?
Solución
falsa = [("alfa", pd.Timestamp("2024-03-04", tz="UTC"),
pd.Timestamp("2024-03-11", tz="UTC"))]
s_f, _, _ = simular(vetos=falsa)
coste = 1 - s_f.iloc[-1] / base.iloc[-1]
b24 = base[base.index.year == 2024]
diario = (b24.iloc[-1] / b24.iloc[0] - 1) / 365
print(f"capital final: {base.iloc[-1]:.4f} sin la falsa alarma, "
f"{s_f.iloc[-1]:.4f} con ella")
print(f"coste: {100 * coste:.3f} % del capital, unos "
f"{coste / diario:.1f} días de carry de 2024")capital final: 2.2940 sin la falsa alarma, 2.2920 con ella
coste: 0.087 % del capital, unos 4.3 días de carry de 2024La falsa alarma cuesta un 0,087 % del capital, unos 4,3 días de carry de 2024: se cierran y se vuelven a abrir las
patas de alfa (cuatro lados con costes) y durante una semana parte del capital se queda sin usar, porque el límite
del 30 % impide llevar a los otros exchanges lo que sale de alfa. Frente al 30 % que se pierde en una quiebra al
límite, una alarma que salta por error varias veces al año sigue siendo barata. Lo que hay que vigilar es que no salte
tan a menudo que el sistema pase más tiempo fuera que dentro, y registrar cada disparo con su motivo para calibrarla.
- Si eliges market making: al simulador de Avellaneda-Stoikov de la lección 2.3.6 añádele un límite duro de inventario (no cotizar el lado que lo supera) y una comisión de maker. ¿Cuánto queda del resultado?
Solución
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)
m = np.r_[medio["medio"].iloc[0], medio["medio"].to_numpy()]
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)
alcance = flujo["alcance"].to_numpy()
por_seg = [[] for _ in range(T)]
for i, s in enumerate(seg):
por_seg[s].append(i)
def creador(q_max, comision, gamma=1e-4, k=35.4, sigma=0.0065):
"""Avellaneda-Stoikov (2.3.6) con límite duro de inventario y
comisión de maker sobre el efectivo de cada ejecución."""
q, caja, n = 0, 0.0, 0
for s in range(T):
resto = T - s
r = m[s] - q * gamma * sigma ** 2 * resto
semi = 0.5 * (gamma * sigma ** 2 * resto
+ 2 / gamma * np.log(1 + gamma / k))
bid = np.floor((r - semi) / 0.01 + 1e-9) * 0.01
ask = np.ceil((r + semi) / 0.01 - 1e-9) * 0.01
for i in por_seg[s]:
if (signo[i] == 1 and q - 100 >= -q_max
and alcance[i] >= ask - m[s] - 1e-9):
q, caja = q - 100, caja + 100 * ask * (1 - comision)
n += 1
elif (signo[i] == -1 and q + 100 <= q_max
and alcance[i] >= m[s] - bid - 1e-9):
q, caja = q + 100, caja - 100 * bid * (1 + comision)
n += 1
return {"resultado": caja + q * m[T], "ejecuciones": n}
pd.DataFrame({
"sin límite, sin comisión": creador(10 ** 9, 0.0),
"límite 1000, sin comisión": creador(1000, 0.0),
"límite 1000, maker 1 pb": creador(1000, 1e-4),
"límite 1000, maker 2 pb": creador(1000, 2e-4),
}).T.round(1) resultado ejecuciones
sin límite, sin comisión 38061.0 13169.0
límite 1000, sin comisión 38135.0 13163.0
límite 1000, maker 1 pb 31748.4 13163.0
límite 1000, maker 2 pb 25361.7 13163.0Con Avellaneda-Stoikov el inventario ya se mantiene pequeño (en 2.3.6 no pasó de 1.700 acciones), así que el límite de 1.000 casi no cambia el resultado: 38.135 USD frente a 38.061. La comisión sí: cada punto básico de maker sobre el efectivo negociado (unos 64 millones de USD en 13.163 ejecuciones de 100 acciones) se lleva unos 6.400 USD, un 17 % del resultado. La estructura de comisiones de cada exchange (maker, taker, descuentos por volumen) decide si el market making tiene sentido, y se lee en su página oficial con fecha. El límite duro sigue siendo obligatorio: protege de lo que el modelo no prevé, como un salto del precio o un flujo informado que no se detiene.
- Antes de depositar capital en un exchange nuevo, ¿qué va en su ficha del registro de contrapartes y qué señales alimentan su estado en el monitor?
Solución
En la ficha: entidad legal y LEI, país, autorización como proveedor de servicios de criptoactivos con MiCA y su
comprobación en el registro de ESMA y de la CNMV con fecha (lecciones 3.7.5 y 7.4.4), segregación de los activos de
clientes según los términos del contrato, servicios que se usarán, límite de capital y su justificación con el
escenario de quiebra, permisos de cada clave (sin retirada), lista blanca de direcciones, resultado de la última prueba
de retirada y fecha de la próxima revisión. En el monitor: retrasos o fallos en las retiradas de prueba, descuento del
contado frente al índice de los demás exchanges (3.7.2), funding muy distinto del resto sin causa de mercado, caídas de
la API fuera de lo normal, noticias de solvencia o actuaciones de supervisores y cambios en el registro de ESMA o en
la lista de entidades no autorizadas (7.4.4). Las señales débiles pasan a VIGILANCIA; una fuerte, o varias débiles a
la vez, a RETIRADA.
Respuesta plegada en cada pregunta
Autoevaluación
La mejor configuración del esqueleto tiene un Sharpe de 14,3 y un Deflated Sharpe de 1,000 con 8 pruebas. ¿Qué concluyes?
- AQue la estrategia está validada y se puede operar con apalancamiento alto
- BQue la validación estadística no ve el riesgo que importa: la muestra sin la quiebra no tiene saltos, y el riesgo de contraparte se valida con escenarios y límites
- CQue hay anticipación en el código
- DQue hay que probar más configuraciones para que el DSR baje
Ver respuesta
Respuesta B. Que la validación estadística no ve el riesgo que importa: la muestra sin la quiebra no tiene saltos, y el riesgo de contraparte se valida con escenarios y límites
Un carry cobra poco y casi siempre; su pérdida llega de golpe y rara vez (una quiebra, una liquidación). El Sharpe de la muestra mide la parte tranquila. Con la quiebra de dedalo dentro y sin alarma, el mismo sistema pierde el 30 % del capital en un día: eso es lo que hay que defender ante el tribunal.
En cenit, la pata corta del perpetuo devuelve un error 503 después de haber comprado el contado. ¿Qué hace el ejecutor del esqueleto?
- AReintenta la venta del perpetuo hasta que entre
- BDeja el contado comprado y espera al siguiente periodo
- CAbre el corto en otro exchange
- DDeshace la pata del contado, anota la incidencia y concilia: posición neta cero con el coste de la ida y la vuelta
Ver respuesta
Respuesta D. Deshace la pata del contado, anota la incidencia y concilia: posición neta cero con el coste de la ida y la vuelta
Una pata sola es una apuesta direccional que la estrategia no quiere tener. Reintentar a ciegas tras un error puede duplicar órdenes (lección 3.7.4); cubrirse en otro exchange cambia la contraparte sin decisión previa. Deshacer cuesta 29,4 USDQ en el ejemplo y deja el libro cuadrado.
Con el capital repartido y un límite del 30 % por exchange, pero sin alarma, ¿qué pasa en noviembre de 2022?
- ASe pierde la parte que estaba en dedalo cuando se suspendieron las retiradas: un 30 % del capital, el límite entero
- BNada: el límite evita cualquier pérdida
- CSe pierde todo el capital
- DSe pierde solo el funding de una semana
Ver respuesta
Respuesta A. Se pierde la parte que estaba en dedalo cuando se suspendieron las retiradas: un 30 % del capital, el límite entero
El límite acota la pérdida, no la evita: el capital que estaba en dedalo el 8 de noviembre a las 12:00 quedó atrapado. Lo que la evita en el esqueleto es la alarma de contraparte, que retira el capital con los rumores del día 6, antes de la suspensión.
Con reposición diaria del margen, ¿cuántos días del periodo habrían liquidado la pata corta a 3x y a 10x?
- ANinguno en los dos casos
- BMuchos a 3x y ninguno a 10x
- CNinguno a 3x (haría falta una subida del 32,7 % del mark en un día) y 53 entre XBQ y XEQ a 10x
- DUno a 3x y uno a 10x
Ver respuesta
Respuesta C. Ninguno a 3x (haría falta una subida del 32,7 % del mark en un día) y 53 entre XBQ y XEQ a 10x
La peor subida del mark en un día fue del 15,9 % en XBQ y del 18,1 % en XEQ, lejos del 32,7 % que liquida a 3x. A 10x basta un 9,5 %, y eso pasó 16 días en XBQ y 37 en XEQ. Los últimos puntos de rendimiento del apalancamiento se pagan con liquidaciones.
¿Cuál de estos hechos suspende el proyecto por sí solo, según la rúbrica común de 7.6.1?
- AQue el carry neto de 2023 a 2025 sea solo del 6,9 % anual
- BUna clave de API con permiso de retirada guardada en el historial de git del repositorio
- CElegir market making en lugar de arbitraje
- DQue el paper trading muestre un deslizamiento mayor que el supuesto
Ver respuesta
Respuesta B. Una clave de API con permiso de retirada guardada en el historial de git del repositorio
Credenciales en el repositorio o en su historial suspenden por sí solas, y una clave con permiso de retirada es la peor de todas (lección 4.4.5). Un carry modesto o un deslizamiento mayor del esperado son resultados que se documentan y se defienden.
Ideas clave
- C5 es una mesa de arbitraje o de market making en cripto cuyo riesgo principal no es el precio, sino las contrapartes: los exchanges guardan el capital.
- El esqueleto cobra el funding con contado largo y perpetuo corto a 3x en los exchanges que pagan más que el suelo de la fórmula, con 5 pb por lado y reposición diaria del margen: un 14,8 % anual, que baja al 6-8 % desde 2022.
- Ante la quiebra de dedalo, concentrar en el que más paga pierde todo; repartir con un límite del 30 % pierde el 30 %; solo la alarma que retira el capital con los rumores evita la pérdida.
- El ejecutor envía las dos patas con identificadores propios, deshace una pata huérfana y concilia contra lo que dice el exchange antes del siguiente periodo.
- Un Sharpe de 14 y un DSR de 1,000 no validan un carry: miden la parte tranquila. Con el 30 % del capital en un exchange, una quiebra se come 4,3 años de carry neto.
- El paper trading va en cuentas de demostración de los exchanges, con las claves sin permiso de retirada y la monitorización de cada contraparte.