---
title: "7.6.6 · Capstone C5: market making o arbitraje en cripto"
description: "Guía del proyecto C5: carry de funding entre exchanges con costes, margen y límites por contraparte, ejecución con ccxt, validación y paper trading."
url: "https://pythonparatrading.com/curso-python-trading/nivel-7/capstone-c5-market-making-y-arbitraje-cripto/"
lang: "es"
image: "https://pythonparatrading.com/og/curso-python-trading/nivel-7/capstone-c5-market-making-y-arbitraje-cripto.png"
---

# 7.6.6 · 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.

- Curso: [Nivel 7: Producción, MLOps, regulación, carrera y capstone](https://pythonparatrading.com/curso-python-trading/nivel-7/)
- 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](https://pythonparatrading.com/curso-python-trading/nivel-7/capstone-requisitos-rubrica-y-defensa/), [2.3.6 · Introducción al market making: inventario, spread y selección adversa (Avellaneda-Stoikov)](https://pythonparatrading.com/curso-python-trading/nivel-2/market-making-avellaneda-stoikov/), [2.5.4 · Deflated Sharpe Ratio y Probability of Backtest Overfitting (PBO)](https://pythonparatrading.com/curso-python-trading/nivel-2/deflated-sharpe-ratio-y-pbo/), [3.7.1 · Perpetuos: mecánica del funding, mark price y liquidaciones](https://pythonparatrading.com/curso-python-trading/nivel-3/perpetuos-funding-mark-price-y-liquidaciones/), [3.7.2 · Arbitraje de funding, basis con futuros y arbitraje entre exchanges](https://pythonparatrading.com/curso-python-trading/nivel-3/arbitraje-de-funding-basis-y-entre-exchanges/), [3.7.4 · ccxt para operar en varios exchanges](https://pythonparatrading.com/curso-python-trading/nivel-3/ccxt-multi-exchange/), [4.2.4 · Stress testing y riesgos de correlación, liquidez, gap y contraparte](https://pythonparatrading.com/curso-python-trading/nivel-4/stress-testing-y-riesgos-de-correlacion-liquidez-y-contraparte/), [4.4.4 · APIs de exchanges cripto: ccxt, Binance, Bybit, Kraken y Coinbase](https://pythonparatrading.com/curso-python-trading/nivel-4/apis-cripto-ccxt-y-nativas/), [4.5.5 · Tolerancia a fallos: idempotencia, estado persistente y reconciliación al arrancar](https://pythonparatrading.com/curso-python-trading/nivel-4/tolerancia-a-fallos-idempotencia-y-reconciliacion/), [7.4.4 · MiCA después del transitorio: comprobar un proveedor en los registros](https://pythonparatrading.com/curso-python-trading/nivel-7/mica-casp-autorizados-y-registros/)

## Al terminar esta lección

- Delimitar el encargo del proyecto C5, en su variante de arbitraje de funding y basis o de market making, y sus entregables.
- Montar el esqueleto ejecutable: carry de 7 días por exchange, cartera con costes y margen, límites por contraparte y una alarma que retira el capital.
- Ejecutar las dos patas de un cash-and-carry contra exchanges simulados con la API unificada de ccxt, deshacer una pata huérfana y conciliar.
- Aplicar walk-forward y Deflated Sharpe, y explicar por qué en un carry no bastan: el riesgo que importa no está en la muestra.
- Planificar las cuatro semanas de paper trading en cuentas de demostración, la monitorización de contrapartes y la evidencia de la rúbrica.

## 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 |

<aside class="callout" data-kind="riesgo">

Los datos del esqueleto son **sintéticos** (nivel 3): XBQ y XEQ imitan a BTC y ETH, y los exchanges `alfa`, `boreal`,
`cenit` y `dedalo` imitan a Binance, Deribit, BitMEX y FTX. La prima del perpetuo, su estrechamiento y la quiebra de
`dedalo` están plantados con parámetros conocidos. Ningún resultado de esta guía es evidencia de que una estrategia
funcione en mercados reales. No es asesoramiento: la guía no recomienda ningún exchange ni ningún producto.

</aside>

## Arquitectura

```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

```python
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))
```
```output
(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               quiebra
```

6.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
$1 + 1/L$ de capital: el contado y el margen del corto a $L = 3$. 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.

```python
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()})
```
```output
{'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:

```python
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)
```
```output
                         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.00
```

La 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.

```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:

```python
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)
```
```output
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.0
```

La 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:

```python
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))
```
```output
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).

```python skip title="ejecutor_demo.py"
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.

```python
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")
```
```output
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ños
```

```python
informe = {
    "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))
```
```output
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 ($\gamma$, $k$, 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:

1. **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.
2. **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).
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).
4. **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

1. La alarma de contraparte salta por un rumor falso sobre `alfa` el 4 de marzo de 2024 y el capital se retira una
   semana. ¿Cuánto cuesta esa falsa alarma frente al esqueleto?

**Solución.**

```python
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")
```
```output
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 2024
```

La 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.

2. 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.**

```python
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)
```
```output
                           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.0
```

Con 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.

3. 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`.

## Autoevaluación

1. La mejor configuración del esqueleto tiene un Sharpe de 14,3 y un Deflated Sharpe de 1,000 con 8 pruebas. ¿Qué concluyes?

   - A. Que la estrategia está validada y se puede operar con apalancamiento alto
   - 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
   - C. Que hay anticipación en el código
   - D. Que hay que probar más configuraciones para que el DSR baje

   *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.

2. En cenit, la pata corta del perpetuo devuelve un error 503 después de haber comprado el contado. ¿Qué hace el ejecutor del esqueleto?

   - A. Reintenta la venta del perpetuo hasta que entre
   - B. Deja el contado comprado y espera al siguiente periodo
   - C. Abre el corto en otro exchange
   - D. Deshace la pata del contado, anota la incidencia y concilia: posición neta cero con el coste de la ida y la vuelta

   *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.

3. Con el capital repartido y un límite del 30 % por exchange, pero sin alarma, ¿qué pasa en noviembre de 2022?

   - A. Se pierde la parte que estaba en dedalo cuando se suspendieron las retiradas: un 30 % del capital, el límite entero
   - B. Nada: el límite evita cualquier pérdida
   - C. Se pierde todo el capital
   - D. Se pierde solo el funding de una semana

   *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.

4. Con reposición diaria del margen, ¿cuántos días del periodo habrían liquidado la pata corta a 3x y a 10x?

   - A. Ninguno en los dos casos
   - B. Muchos a 3x y ninguno a 10x
   - 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
   - D. Uno a 3x y uno a 10x

   *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.

5. ¿Cuál de estos hechos suspende el proyecto por sí solo, según la rúbrica común de 7.6.1?

   - A. Que el carry neto de 2023 a 2025 sea solo del 6,9 % anual
   - B. Una clave de API con permiso de retirada guardada en el historial de git del repositorio
   - C. Elegir market making en lugar de arbitraje
   - D. Que el paper trading muestre un deslizamiento mayor que el supuesto

   *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.

## Recursos

### Research

- [Liquidez Order Book - Trading Liquidez Agregada](https://pythonparatrading.com/limit-order-book-y-liquidez-de-mercado/) · Research-0008 · 11 min

### Documentación

- [ccxt: repositorio (licencia MIT)](https://github.com/ccxt/ccxt) · github.com
- [ccxt: documentación](https://docs.ccxt.com/) · docs.ccxt.com
- [Bybit API v5: Get Funding Rate History](https://bybit-exchange.github.io/docs/v5/market/history-fund-rate) · bybit-exchange.github.io
- [ESMA: Markets in Crypto-Assets Regulation (MiCA)](https://www.esma.europa.eu/esmas-activities/digital-finance-and-innovation/markets-crypto-assets-regulation-mica) · esma.europa.eu

### Libros

- [He, S., Manela, A., Ross, O. y von Wachter, V. (2022): Fundamentals of Perpetual Futures. arXiv:2212.06888](https://arxiv.org/abs/2212.06888) · arxiv.org
- [Avellaneda, M. y Stoikov, S. (2008): High-frequency trading in a limit order book](https://www.math.nyu.edu/~avellane/HighFrequencyTrading.pdf) · math.nyu.edu

Lección anterior: [7.6.5 · Capstone C4: una plataforma de investigación con LLMs y datos alternativos](https://pythonparatrading.com/curso-python-trading/nivel-7/capstone-c4-investigacion-con-llms/)
