---
title: "3.3.1 Microestructura FX: OTC, ECN y last look"
description: "Cómo se negocian las divisas: OTC, ECN y libro agregado de varios proveedores, sesiones y last look medido orden a orden con el FX Global Code."
url: "https://pythonparatrading.com/curso-python-trading/nivel-3/microestructura-fx-otc-ecn-y-last-look/"
lang: "es"
image: "https://pythonparatrading.com/og/curso-python-trading/nivel-3/microestructura-fx-otc-ecn-y-last-look.png"
---

# 3.3.1 · Microestructura FX: OTC, ECN, last look y sesiones

> En divisas no hay una bolsa ni un libro único: cada proveedor de liquidez cotiza por su cuenta y quien recibe los precios arma su propio libro. Esta lección reconstruye ese libro con cinco proveedores ficticios en tres sesiones, mide el last look de cada uno orden a orden y calcula lo que cuesta de verdad operar con ellos, más allá de la horquilla que enseñan.

- Curso: [Nivel 3: Derivados: futuros, spreads, forex y opciones](https://pythonparatrading.com/curso-python-trading/nivel-3/)
- Módulo: 3.3 · Forex avanzado
- Duración: 60 min
- Requisitos: [1.1.3 · Libro de órdenes: bid, ask, spread y profundidad](https://pythonparatrading.com/curso-python-trading/nivel-1/libro-de-ordenes-bid-ask-y-profundidad/), [1.2.4 · Forex: pares, pips, lotes, rollover y CFD](https://pythonparatrading.com/curso-python-trading/nivel-1/forex-pares-pips-lotes-y-rollover/), [2.4.7 · Modelado de costes: comisiones, horquilla, impacto, financiación y préstamo de títulos](https://pythonparatrading.com/curso-python-trading/nivel-2/modelado-de-costes/), [2.4.8 · Modelado de fills: órdenes limitadas, cola, fills parciales, huecos y stops](https://pythonparatrading.com/curso-python-trading/nivel-2/modelado-de-fills/)

## Al terminar esta lección

- Describir la estructura OTC del mercado de divisas: proveedores de liquidez bancarios y no bancarios, plataformas, agregadores y clientes.
- Construir el libro agregado de varios proveedores con pandas y medir su horquilla, quién pone el mejor precio y cuánto tiempo está cruzado.
- Explicar el last look según el Principio 17 del FX Global Code y distinguir la comprobación de precio simétrica de la asimétrica.
- Medir en las órdenes de un cliente la retención, el rechazo condicionado al movimiento del precio y el markout de las ejecuciones, y recuperar los parámetros plantados.
- Comparar proveedores por su coste efectivo (semi-spread más coste de los rechazos) y no solo por la horquilla cotizada.

## Un mercado sin libro central

La lección 1.2.4 presentó el mercado de divisas como un mercado OTC que sigue al sol de Sídney a Nueva York. Aquí
interesa su mecánica fina: quién pone los precios, cómo llegan al cliente y qué pasa entre que el cliente pulsa
«comprar» y la operación queda hecha. En una bolsa (lección 1.1.3) hay un libro de órdenes central con reglas
públicas: una orden que cruza el mejor precio se ejecuta, sin más. En divisas al contado no existe ese libro único.
Hay muchos proveedores de liquidez (*liquidity providers*, LP) que publican precios a sus clientes por canales
distintos, y una parte importante de esos precios no es firme: el proveedor se reserva una última mirada antes de
aceptar.

```mermaid
flowchart TB
  B[LP bancarios] --> ID[Interbancario]
  B --> MD[Multidealer y ECN]
  N[LP no bancarios] --> MD
  B --> SD[Plataforma propia]
  MD --> AG[Agregador]
  SD --> AG
  AG --> C[Fondo o CTA]
  AG --> R[Bróker minorista]
  R --> M[Minorista]
```

- **Proveedores de liquidez.** Grandes bancos y, cada vez más, empresas no bancarias de creación de mercado
  electrónica. Cotizan compra y venta de forma continua y ganan la horquilla; su riesgo es la selección adversa
  (lección 2.3.6): que les ejecuten justo antes de que el precio se mueva.
- **Mercado interbancario.** Los bancos operan entre sí, a menudo en plataformas con un libro central de órdenes
  firmes. Ahí sí hay algo parecido a una bolsa, pero solo para participantes con acuerdos de crédito entre ellos.
- **Plataformas multidealer y ECN.** Reciben precios de varios proveedores y los distribuyen a los clientes. En una
  ECN (*electronic communication network*) cada proveedor publica su flujo de precios; el cliente ve todos y elige.
- **Plataforma propia (*single-dealer*).** Cada gran banco tiene la suya, con sus precios para sus clientes.
- **Agregador.** El programa del cliente (o de su bróker) que junta los flujos de varios proveedores, construye un
  libro y decide a quién mandar cada orden. El libro que ve un fondo no lo ve nadie más: depende de con quién tiene
  acuerdos.
- **Brókeres minoristas.** Toman precios de uno o varios proveedores, añaden su margen y los ofrecen a sus clientes;
  algunos internalizan el flujo (son la contraparte) y otros lo pasan al mercado.

Que no haya libro central tiene una consecuencia práctica para quien hace backtests: **no hay un precio oficial**. El
EURUSD de un proveedor de datos es el de sus fuentes, y dos fuentes no coinciden a la décima de pip (lección 1.3.8).

### Cuánto y con quién

La referencia para medir el mercado es la encuesta trienal que los bancos centrales hacen cada tres años, coordinados
por el Banco de Pagos Internacionales (BPI). En abril de 2025 participaron 52 países. El Banco de España publica la
parte española[^1]:

| Mercado español de divisas, abril de 2025 | Volumen medio diario |
|---|---|
| Total | 67,9 miles de millones de USD (39,2 en abril de 2022) |
| FX swaps | 44,8 miles de millones (66 %) |
| Contado | 11,8 miles de millones (17 %) |
| Con otras entidades declarantes (bancos) | 53 % |
| Con otras instituciones financieras | 41 % |
| Con clientes no financieros | 6 % |
| Con contrapartidas extranjeras | 94 % |

Dos datos de la tabla sirven para entender el resto de la lección. El contado, lo que opera un sistema de trading en
divisas, es una parte minoritaria del volumen: la mayor parte son FX swaps, la herramienta de financiación y de
cobertura de la lección 3.3.3. Y casi la mitad del volumen ya no es entre bancos: lo negocian otras instituciones
financieras, la categoría en la que entran los fondos y los creadores de mercado no bancarios.

## Cinco proveedores, tres sesiones

Los datos de esta lección son de **QEVUSD**, el par de un evro ficticio que imita al EURUSD, el 12 de marzo de 2025:
una hora en cada apertura, Tokio (00:00–01:00 UTC, etiquetada `asia`), Londres (07:00–08:00) y Nueva York
(13:00–14:00). Hay un precio de referencia que nadie ve y cinco proveedores que lo cotizan, cada uno con su latencia,
su horquilla y su política de last look. Son datos sintéticos con los parámetros plantados en
`fx3_lp_verdad.csv`: la lección los mide como si no los conociera y al final los compara.

```python
import numpy as np
import pandas as pd

cot = pd.read_parquet("fx3_ecn_cotizaciones.parquet")
print(len(cot), "cotizaciones de", cot["lp"].nunique(), "proveedores")
cot.head(4)
```
```output
193244 cotizaciones de 5 proveedores
                                ts sesion        lp       bid       ask  tam_bid  tam_ask
0 2025-03-12 00:00:00.175000+00:00   asia  LP-Borea  1.473744  1.473916  1000000  1000000
1 2025-03-12 00:00:00.278000+00:00   asia  LP-Atlas  1.473716  1.473944  3000000  1000000
2 2025-03-12 00:00:00.311000+00:00   asia  LP-Delta  1.473798  1.473862  3000000  1000000
3 2025-03-12 00:00:00.360000+00:00   asia  LP-Delta  1.473798  1.473862  5000000  2000000
```

Cada fila es una actualización de precio de un proveedor: su compra (`bid`), su venta (`ask`) y la cantidad en firme
en cada lado, entre 1 y 5 millones de evros. Las marcas de tiempo tienen milisegundos, que en divisas es una
resolución gruesa: un proveedor rápido actualiza varias veces por segundo. Por proveedor y sesión:

```python
PIP = 1e-4
cot["spread"] = (cot["ask"] - cot["bid"]) / PIP
spread = cot.pivot_table(index="lp", columns="sesion",
                         values="spread", aggfunc="median")
ritmo = cot.groupby(["lp", "sesion"]).size().unstack() / 3600
pd.concat({"spread_pips": spread, "cotiz_por_s": ritmo},
          axis=1).round(2)
```
```output
         spread_pips                    cotiz_por_s
sesion          asia londres nueva_york        asia londres nueva_york
lp
LP-Atlas        2.02    1.26       1.26        1.09    2.94       3.29
LP-Borea        1.66    1.04       1.04        1.40    4.00       4.39
LP-Cirro        0.92    0.58       0.58        2.09    6.01       6.57
LP-Delta        0.74    0.46       0.46        1.73    4.99       5.43
LP-Eris         1.28    0.80       0.80        1.37    3.99       4.40
```

Los dos bancos más clásicos, `LP-Atlas` y `LP-Borea`, cotizan horquillas de 1,26 y 1,04 pips en Londres; los dos no
bancarios, `LP-Cirro` y `LP-Delta`, de 0,58 y 0,46. En la hora de Tokio todas las horquillas son un 60 % más anchas y
el ritmo de actualización cae a menos de la mitad: el evro se negocia, pero sus grandes centros están cerrados (la
lección 1.2.4 midió el mismo patrón hora a hora en el EURUSD del nivel 1). Londres y Nueva York se parecen entre sí.

*Fig. — Horquilla de QEVUSD por proveedor en la hora de Tokio y en la de Londres, y la del libro agregado de los cinco.* Gráfico de pesas: para cada proveedor, un punto con la horquilla mediana en la hora de Tokio y otro con la de Londres, unidos por una línea. LP-Atlas 2,02 y 1,26 pips; LP-Borea 1,66 y 1,04; LP-Eris 1,28 y 0,80; LP-Cirro 0,92 y 0,58; LP-Delta 0,74 y 0,46. El libro agregado, media ponderada por tiempo, 0,70 en Tokio y 0,19 en Londres (destacado): agregar estrecha la horquilla sobre todo cuando hay mucha actividad.

La figura pone las horquillas de Tokio y de Londres de cada proveedor junto a la del libro agregado, que se construye
en la sección siguiente. Ya se ve el punto de partida de toda la lección: el proveedor con la horquilla más estrecha
es `LP-Delta`. Si el precio que enseña fuera el precio al que se ejecuta, la decisión estaría tomada.

## El libro agregado: el mejor precio de todos

Un agregador hace lo que hace una bolsa, pero con precios ajenos: para cada instante guarda la última cotización de
cada proveedor y toma el bid más alto y el ask más bajo. El mejor bid y el mejor ask pueden ser de proveedores
distintos. La función de abajo lo hace por sesión con `pivot_table` y `ffill` (la última cotización vigente de cada
uno) y guarda cuánto dura cada estado del libro, porque una media por evento daría más peso a los momentos con más
actualizaciones.

```python
def libro_agregado(g):
    b = g.pivot_table(index="ts", columns="lp", values="bid",
                      aggfunc="last").ffill().dropna()
    a = g.pivot_table(index="ts", columns="lp", values="ask",
                      aggfunc="last").ffill().dropna()
    libro = pd.DataFrame({"mejor_bid": b.max(axis=1),
                          "mejor_ask": a.min(axis=1),
                          "lp_bid": b.idxmax(axis=1)})
    libro["spread"] = (libro["mejor_ask"]
                       - libro["mejor_bid"]) / PIP
    t = libro.index.to_series()
    libro["dur"] = t.diff().shift(-1).dt.total_seconds()
    return libro.dropna()

libros = {s: libro_agregado(g) for s, g in cot.groupby("sesion")}

def resumen(l):
    w = l["dur"] / l["dur"].sum()          # peso por duración
    return pd.Series({
        "spread_medio": (w * l["spread"]).sum(),
        "cruzado_pct": 100 * w[l["spread"] < 0].sum(),
    })

pd.DataFrame({s: resumen(l) for s, l in libros.items()}).T.round(2)
```
```output
            spread_medio  cruzado_pct
asia                0.70         0.00
londres             0.19        20.01
nueva_york          0.27        11.51
```

En Londres, la horquilla media del libro agregado es de 0,19 pips, menos de la mitad que la del mejor proveedor
solo (0,46). Es la ventaja de agregar: el mejor bid y el mejor ask rara vez son del mismo proveedor en el mismo
instante. Pero la segunda columna dice algo incómodo: durante el 20 % de la hora de Londres el libro está **cruzado**,
con el mejor bid por encima del mejor ask, y el 11,5 % de la de Nueva York. En Tokio, nunca.

Un libro cruzado parece dinero gratis: comprar al ask de uno y vender al bid de otro. No lo es. Cada proveedor publica
el precio de referencia con su propio retraso (5 ms el más rápido, 60 ms el más lento, según el archivo de verdad), y
cuando el precio se mueve rápido, como en la apertura de Londres, la cotización del lento se queda atrás y cruza la del
rápido. Quien intente aprovecharlo manda una orden contra el precio atrasado, que es justo la que el last look de ese
proveedor está diseñado para rechazar. En una bolsa, un libro cruzado no puede existir: las órdenes que se cruzan se
ejecutan. En un ECN con precios no firmes, sí.

```python
l = libros["londres"]
cuota = l.groupby("lp_bid")["dur"].sum() / l["dur"].sum()
(100 * cuota).round(1).sort_values(ascending=False)
```
```output
lp_bid
LP-Delta    54.5
LP-Cirro    24.7
LP-Eris     10.7
LP-Borea     5.7
LP-Atlas     4.4
Name: dur, dtype: float64
```

`LP-Delta` tiene el mejor bid el 54,5 % del tiempo y `LP-Cirro` el 24,7 %: entre los dos, casi el 80 %. Un agregador
que mande cada orden al mejor precio le dará a Delta más de la mitad del flujo. Por eso importa tanto lo que hace
Delta con las órdenes que recibe.

## Last look: la última mirada del proveedor

El FX Global Code, el código de buenas prácticas del mercado de divisas que mantiene el Comité Global de Divisas
(GFXC) de los bancos centrales, define el last look en su Principio 17 como una práctica de la negociación
electrónica en la que el participante que recibe una solicitud de operación tiene una **última oportunidad de
aceptarla o rechazarla** en función del precio cotizado[^2]. El cliente manda una orden contra un precio publicado; el
proveedor la retiene unos milisegundos, comprueba y decide.

```mermaid
stateDiagram-v2
  [*] --> Cotiza
  Cotiza --> Solicitud: orden del cliente
  Solicitud --> Retencion: ventana
  Retencion --> Ejecutada: pasa
  Retencion --> Rechazada: no pasa
  Rechazada --> Cotiza: pedir de nuevo
  Ejecutada --> [*]
```

El Código no prohíbe la práctica, pero la acota:

- Si se usa, debe ser un **mecanismo de control de riesgos** para verificar la validez (que los datos de la operación
  sean correctos y haya crédito suficiente) y el precio (que el precio solicitado siga siendo coherente con el que
  podría obtener el cliente en ese momento).
- El proveedor debe explicar, como mínimo, si las variaciones del precio en un sentido u otro influyen en su decisión,
  cuánto tarda en decidir y para qué usa el last look.
- No se puede usar para **obtener información** sin intención de aceptar, y el proveedor no puede negociar durante la
  ventana (ni ajustar precios ni cubrirse) con la información de la solicitud.

El informe del grupo de trabajo del GFXC de agosto de 2021 añade dos precisiones útiles para medir[^3]. La primera,
la distinción entre comprobación **simétrica** (la misma tolerancia alrededor del precio actual en los dos sentidos:
rechaza si el precio se ha movido más que la tolerancia, a favor o en contra del proveedor) y **asimétrica** (acepta
cuando el precio se ha movido a favor del proveedor y rechaza cuando se ha movido en su contra). La segunda:
incorporar cualquier retraso adicional al necesario para hacer las comprobaciones se considera contrario al Principio
17. El mismo informe señala como posibles indicios de mal uso las tasas de rechazo altas con tiempos de respuesta
largos y elementos asimétricos, y los tiempos de respuesta distintos entre aceptaciones y rechazos.

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

El last look existe por una razón económica. Un proveedor publica precios a cientos de clientes a la vez; si el precio
se mueve, todos pueden mandarle órdenes contra la cotización vieja en el mismo milisegundo. Sin una forma de
protegerse, cotizaría horquillas más anchas para todos. El last look es la protección; el precio de esa protección lo
paga el cliente en forma de rechazos. La pregunta no es si el last look es bueno o malo, sino cuánto cuesta con cada
proveedor.

</aside>

## Medir el last look orden a orden

`fx3_ecn_ordenes.parquet` son 2.266 órdenes IOC (ejecutar ya o cancelar) de un cliente a los cinco proveedores, de 1
a 5 millones de evros. Cada orden trae el precio solicitado, las horas de envío y de respuesta, el estado y el precio de
referencia al enviar y al responder (`mid_envio`, `mid_respuesta`), algo que un cliente real no tiene: tendría que
estimarlo con el medio del libro agregado. Lo primero es el signo: para una compra, que el precio suba durante la
retención es bueno para el cliente y malo para el proveedor; para una venta, al revés.

```python
ords = pd.read_parquet("fx3_ecn_ordenes.parquet")
signo = np.where(ords["lado"] == "compra", 1, -1)
ords["retencion_ms"] = (ords["ts_respuesta"] - ords["ts_envio"]
                        ).dt.total_seconds() * 1000
# movimiento durante la retención: + = a favor del cliente
ords["mov"] = (signo * (ords["mid_respuesta"] - ords["mid_envio"])
               / PIP).round(1)
ords["rechazada"] = ords["estado"] == "rechazada"
por_lp = ords.groupby("lp").agg(
    ordenes=("rechazada", "size"),
    rechazo_pct=("rechazada", "mean"),
    retencion_ms=("retencion_ms", "median"))
por_lp["rechazo_pct"] *= 100
por_lp.round(1)
```
```output
          ordenes  rechazo_pct  retencion_ms
lp
LP-Atlas      290          1.7           4.0
LP-Borea      328         14.6          43.0
LP-Cirro      496         12.9          59.0
LP-Delta      820         27.7         152.0
LP-Eris       332          9.9          33.0
```

`LP-Delta` rechaza el 27,7 % de las órdenes y tarda una mediana de 152 ms en responder; `LP-Atlas`, el 1,7 % en
4 ms. `LP-Delta` es además el que más órdenes recibe (820), porque es el que más veces tiene el mejor precio. La
retención medida es la plantada más la latencia de red, de 1 a 3 ms: 4, 43, 59, 152 y 33 ms frente a 2, 40, 60, 150 y
30 plantados.

La tasa de rechazo sola no distingue entre una comprobación justa y una que no lo es. Lo que la distingue es **cuándo**
rechaza cada uno: con el precio quieto, cuando se ha movido a favor del cliente o cuando se ha movido a favor del
proveedor.

```python
ords["contexto"] = np.select(
    [ords["mov"] > 0.05, ords["mov"] < -0.05],
    ["contra_lp", "a_favor_lp"], "quieto")
tasa = ords.pivot_table(index="lp", columns="contexto",
                        values="rechazada", aggfunc="mean")
(100 * tasa).round(1)
```
```output
contexto  a_favor_lp  contra_lp  quieto
lp
LP-Atlas         0.0        NaN     1.7
LP-Borea        59.6       44.2     0.4
LP-Cirro         1.1       74.1     0.9
LP-Delta         0.4       93.4     0.3
LP-Eris          0.0       73.8     0.8
```

La tabla separa las cinco políticas sin necesidad de conocerlas:

- **`LP-Atlas`**: sin last look. Rechaza el 1,7 % con el precio quieto, nunca por el movimiento (y en 4 ms el precio
  casi nunca se mueve: no hay órdenes suyas en la columna `contra_lp`).
- **`LP-Borea`**: simétrico. Rechaza el 44,2 % cuando el precio se mueve en su contra y el 59,6 % cuando se mueve a su
  favor. Al cliente le rechaza también operaciones que le habrían salido caras.
- **`LP-Cirro`, `LP-Delta` y `LP-Eris`**: asimétricos. Rechazan el 74 %, el 93 % y el 74 % cuando el precio va en su
  contra, y casi nada (1,1 %, 0,4 % y 0 %) cuando va a su favor.

Con el movimiento exacto se recuperan el umbral y la probabilidad de rechazo:

```python
ords["mov_tramo"] = ords["mov"].clip(-0.3, 0.3)
por_mov = ords.pivot_table(index="mov_tramo", columns="lp",
                           values="rechazada", aggfunc="mean")
(100 * por_mov.drop(columns="LP-Atlas")).round(0)
```
```output
lp         LP-Borea  LP-Cirro  LP-Delta  LP-Eris
mov_tramo
-0.3           69.0       0.0       1.0      0.0
-0.2           40.0       6.0       0.0      0.0
-0.1           62.0       0.0       0.0      0.0
 0.0            0.0       1.0       0.0      1.0
 0.1           44.0      50.0      94.0     71.0
 0.2           38.0      96.0      93.0     70.0
 0.3           55.0      82.0      93.0     80.0
```

El precio de referencia se mueve en saltos de 0,1 pips, así que el movimiento solo toma esos valores (los extremos
agrupan todo lo que pasa de ±0,3). `LP-Delta`, con umbral 0, rechaza el 93–94 % de cualquier movimiento en su contra,
cerca del 95 % plantado. `LP-Eris` (umbral 0,05) rechaza el 70–80 %, cerca del 70 %. `LP-Cirro` tiene el umbral en
0,10 pips: con un movimiento de exactamente 0,1 rechaza la mitad (está en la frontera, y la comparación de un
movimiento igual al umbral cae a uno u otro lado por redondeo), y a partir de 0,2 el 82–96 %, cerca del 90 % plantado.
`LP-Borea` rechaza entre el 38 % y el 69 % a los dos lados: el 60 % plantado, con el ruido de 10 a 24 órdenes por
casilla. La figura lo resume:

*Fig. — Tasa de rechazo de cada proveedor con last look según cuánto se movió el precio durante la retención.* Líneas con la tasa de rechazo (0 a 100 %) frente al movimiento del precio durante la retención, de −0,3 a +0,3 pips (positivo = en contra del proveedor). LP-Delta, destacada, está en 0–1 % con movimientos negativos o nulos y salta al 93–94 % con cualquier movimiento positivo. LP-Cirro y LP-Eris saltan igual, con LP-Cirro al 50 % en +0,1 (su umbral) y al 82–96 % después; LP-Eris al 70–80 %. LP-Borea, simétrico, rechaza entre el 38 % y el 69 % a los dos lados y nada con el precio quieto.

### Tiempos de respuesta: aceptadas frente a rechazadas

El informe del GFXC señala los tiempos de respuesta distintos entre aceptaciones y rechazos como un posible indicio de
mal uso. En los datos se pueden comparar:

```python
ords.pivot_table(index="lp", columns="estado",
                 values="retencion_ms", aggfunc="median")
```
```output
estado    ejecutada  rechazada
lp
LP-Atlas        4.0        4.0
LP-Borea       41.0       47.0
LP-Cirro       58.5       79.5
LP-Delta      145.0      173.0
LP-Eris        32.0       38.0
```

Las rechazadas tardan más en los cuatro proveedores con last look: 173 ms frente a 145 en `LP-Delta`, 79,5 frente a 58,5
en `LP-Cirro`. En estos datos no hay ninguna retención extra plantada para rechazar: la diferencia sale sola, porque
una retención más larga deja más tiempo para que el precio se mueva y la orden falle la comprobación. Es un buen
recordatorio de que los indicios del informe son indicios: una diferencia de tiempos pide una explicación al proveedor,
no demuestra un abuso. Lo que sí sería una señal clara es que la retención fuera más larga que la que el proveedor
declara.

## Lo que cuesta de verdad: horquilla más rechazos

Para comparar proveedores hace falta una medida de coste que incluya los rechazos. Una orden ejecutada cuesta el
semi-spread: la distancia entre el precio solicitado y el medio en el momento del envío. Una orden rechazada no cuesta
nada en el acto, pero deja al cliente sin la posición y con el precio movido: si el precio subió mientras el proveedor
miraba una compra, el cliente tiene que volver a pedir precio desde el nuevo nivel. Ese movimiento perdido es el
**coste del rechazo**. Si el rechazo llega con el precio movido en contra del cliente, el coste es negativo: el
proveedor le ha ahorrado una operación mala.

$$
c = \underbrace{s \cdot \frac{p_{\text{sol}} - m_{\text{env}}}{\text{pip}}}_{\text{semi-spread}}
  + \underbrace{\mathbb{1}_{\text{rechazo}} \cdot \Delta m}_{\text{coste del rechazo}}
$$

con $s = +1$ en las compras y $-1$ en las ventas y $\Delta m$ el movimiento del medio durante la retención a favor
del cliente, en pips. La medida supone que, tras un rechazo, el cliente vuelve a pagar el mismo semi-spread; ignora
el tiempo perdido y el riesgo de que el precio siga moviéndose mientras pide otro precio, así que es una cota inferior
del coste real.

```python
ords["semi_spread"] = signo * (ords["precio_solicitado"]
                               - ords["mid_envio"]) / PIP
ords["coste_rechazo"] = ords["rechazada"] * ords["mov"]
coste = ords.groupby("lp")[["semi_spread", "coste_rechazo"]].mean()
coste["efectivo"] = coste.sum(axis=1)
coste["usd_por_millon"] = 100 * coste["efectivo"]
coste.round(3)
```
```output
          semi_spread  coste_rechazo  efectivo  usd_por_millon
lp
LP-Atlas        0.571          0.000     0.571          57.052
LP-Borea        0.502         -0.004     0.499          49.866
LP-Cirro        0.250          0.030     0.280          28.020
LP-Delta        0.199          0.079     0.278          27.807
LP-Eris         0.370          0.024     0.394          39.361
```

Es la figura de la cabecera. El last look de `LP-Delta` le cuesta al cliente 0,079 pips por orden, un 40 % de su
semi-spread de 0,199. Aun así, su coste efectivo (0,278 pips, unos 28 USD por millón) es la mitad que el de `LP-Atlas`
(0,571), que no rechaza nada pero cotiza tres veces más ancho. `LP-Cirro` y `LP-Delta` quedan empatados, y `LP-Eris`
paga 0,024 de rechazos sobre un semi-spread de 0,370. La comprobación simétrica de `LP-Borea` sale gratis de media
(−0,004): rechaza tanto a favor como en contra del cliente.

La conclusión no es «el last look no importa»: es que hay que medirlo. Con estos parámetros y este cliente, la
horquilla estrecha compensa los rechazos. Con un cliente distinto, no (ejercicio 2).

### El markout: la misma cuenta desde las ejecuciones

Un cliente real no conoce el precio de referencia, pero puede medir lo que se movió el precio de mercado después de
cada ejecución: el **markout**. Si el proveedor filtra las órdenes de forma asimétrica, las ejecuciones que quedan son
las que no se movieron o se movieron a favor del proveedor, y el markout medio de las ejecuciones es negativo para el
cliente.

```python
ejec = ords[~ords["rechazada"]]
markout = ejec.groupby("lp")["mov"].agg(["mean", "size"])
markout["mean"] = markout["mean"].round(3)
markout
```
```output
           mean  size
lp
LP-Atlas -0.002   285
LP-Borea  0.003   280
LP-Cirro -0.028   432
LP-Delta -0.097   593
LP-Eris  -0.017   299
```

`LP-Delta` tiene un markout medio de −0,097 pips en sus 593 ejecuciones: el precio se movió en contra del cliente en la
retención, porque las que se habrían movido a su favor fueron rechazadas. `LP-Atlas` tiene −0,002, como corresponde a
un proveedor que no filtra. `LP-Borea`, simétrico, tiene +0,003 porque filtra por los dos lados. Si el
precio no tiene tendencia, la suma de los movimientos de todas las órdenes es cero, así que el markout negativo de las
ejecuciones y el coste positivo de los rechazos son el mismo dinero visto desde dos lados. En un análisis de costes
(TCA) real se mide el markout a varios horizontes (100 ms, 1 s, 5 s) con el medio del libro agregado, por proveedor y
por sesión.

```mermaid
flowchart TB
  O[Órdenes propias] --> R[Rechazo por mov]
  O --> T[Tiempos]
  O --> K[Markout]
  R --> C[Coste efectivo]
  K --> C
  T --> D[Preguntas al LP]
  C --> E[Reparto del flujo]
```

## Lo que esto cambia en un sistema de trading

- **El precio del backtest no es el de la ejecución.** Un backtest de divisas con el medio y una horquilla fija supone
  órdenes firmes. Con last look, una parte de las órdenes no se ejecuta, y las que no se ejecutan son justo las que
  iban bien. Para una estrategia de baja frecuencia el efecto es pequeño; para una de minutos, puede ser toda la ventaja
  (lecciones 2.4.7 y 2.4.8).
- **La horquilla cotizada no ordena a los proveedores.** El coste efectivo, sí. Y depende del flujo de cada cliente:
  hay que medirlo con las órdenes propias, no con las de otro.
- **El libro agregado engaña en los momentos rápidos.** Un libro cruzado o una horquilla de 0,05 pips en la apertura
  de Londres son, en parte, precios atrasados que no se van a ejecutar.
- **La sesión importa.** En la hora de Tokio el coste efectivo de todos los proveedores casi se duplica (ejercicio 1).
  Un sistema que opera a cualquier hora debería saber en qué hora ejecuta.
- **Pedir los documentos.** El Código pide a los proveedores que publiquen su política de last look (simétrica o
  asimétrica, ventana mínima y máxima). El informe del GFXC propone una hoja de divulgación normalizada. Un cliente
  institucional puede, y debe, pedirla.

En la lección 3.3.3 se mandan órdenes de verdad a tres brókeres por sus APIs; allí los rechazos aparecen como códigos
de respuesta que el programa tiene que tratar.

## Ejercicios

1. Calcula el coste efectivo por proveedor y sesión. ¿Cuánto más caro es operar en la hora de Tokio que en la de
   Londres? ¿Cambia el orden de los proveedores?

**Solución.**

```python
ords["efectivo"] = ords["semi_spread"] + ords["coste_rechazo"]
ords.pivot_table(index="lp", columns="sesion",
                 values="efectivo", aggfunc="mean").round(3)
```
```output
sesion     asia  londres  nueva_york
lp
LP-Atlas  0.992    0.525       0.505
LP-Borea  0.804    0.454       0.451
LP-Cirro  0.445    0.249       0.255
LP-Delta  0.379    0.259       0.256
LP-Eris   0.636    0.356       0.393
```

En Tokio el coste efectivo es casi el doble que en Londres para todos: 0,992 frente a 0,525 pips en `LP-Atlas`, 0,379
frente a 0,259 en `LP-Delta`. El orden se mantiene: los no bancarios son los más baratos en las tres sesiones. Con
solo 312 órdenes en Tokio, las diferencias pequeñas (`LP-Cirro` frente a `LP-Delta`) no son fiables.

2. Imagina un cliente cuyas órdenes anticipan el precio: solo opera cuando el precio va a moverse a su favor en los
   milisegundos siguientes. Simúlalo quedándote con las órdenes con `mov > 0` y calcula su tasa de rechazo y su coste
   efectivo por proveedor. ¿Qué proveedor elegiría?

**Solución.**

```python
rapido = ords[ords["mov"] > 0]
t = rapido.groupby("lp").agg(
    ordenes=("rechazada", "size"),
    rechazo_pct=("rechazada", "mean"),
    efectivo=("efectivo", "mean"))
t["rechazo_pct"] *= 100
t.round(2)
```
```output
          ordenes  rechazo_pct  efectivo
lp
LP-Borea       43        44.19      0.53
LP-Cirro       81        74.07      0.45
LP-Delta      241        93.36      0.46
LP-Eris        42        73.81      0.53
```

Para ese cliente, los proveedores asimétricos rechazan casi todo: `LP-Delta` el 93,4 % y `LP-Cirro` el 74,1 %. Su
coste efectivo sube de 0,28 a 0,46 pips en `LP-Delta` y de 0,28 a 0,45 en `LP-Cirro`. `LP-Atlas` no tiene órdenes en
la muestra (con 4 ms de retención el precio no llega a moverse), pero como no rechaza, le costaría su semi-spread,
0,57 pips. Por esta medida `LP-Delta` sigue siendo algo más barato, y sin embargo este cliente no lo elegiría: solo
ejecuta el 7 % de sus órdenes, y la medida solo cuenta el movimiento durante la retención, no el que el cliente espera
capturar después. Si su ventaja es mayor que unas décimas de pip, lo que necesita es que le ejecuten, y paga con gusto
la horquilla de un precio firme; por eso los proveedores de precios firmes cotizan más ancho. Es la selección adversa
de la lección 2.3.6 vista desde el otro lado: el last look es la forma en que el proveedor se protege de este
cliente.

3. Un proveedor te dice que su comprobación de precio es simétrica, con una tolerancia de 0,05 pips y una ventana de
   hasta 50 ms. Con las órdenes que le has mandado, ¿qué tres medidas de esta lección usarías para comprobarlo?

**Solución.**

Primero, la tabla de rechazo por contexto: una comprobación simétrica rechaza en una proporción parecida cuando el
precio se mueve a favor y en contra del proveedor (como `LP-Borea`), y casi nada con el precio quieto. Segundo, la
distribución de la retención: la mediana y los percentiles altos deben estar dentro de los 50 ms declarados, y la
diferencia entre aceptadas y rechazadas debe explicarse por la duración (las retenciones largas se rechazan más) y no
por un retraso añadido a los rechazos. Tercero, el markout de las ejecuciones: con una comprobación simétrica debe ser
cercano a cero; uno claramente negativo indicaría que, en la práctica, filtra de forma asimétrica.

[^1]: Banco de España, [Encuesta trienal sobre los mercados OTC de divisas y de derivados de tipos de interés en España (2025)](https://www.bde.es/wbe/es/estadisticas/temas/mercados-financieros/encuesta-sobre-mercados-divisas-derivados-otc/encuesta-sobre-el-mercado-de-divisas-y-de-derivados-otc-2025.html) y [nota de prensa del 30 de septiembre de 2025](https://www.bde.es/f/webbe/GAP/Secciones/SalaPrensa/NotasInformativas/25/presbe2025-89.pdf), consultadas el 22/10/2026.
[^2]: GFXC, [FX Global Code](https://www.globalfxc.org/fx_global_code.htm), última actualización de diciembre de 2024, Principio 17; [versión en español](https://www.globalfxc.org/uploads/fx_global_dec24_es_es.pdf) del Banco de España, que traduce *last look* como «último vistazo».
[^3]: GFXC, [Execution Principles Working Group Report on Last Look](https://www.globalfxc.org/docs/gfxc_report_last_look.pdf), agosto de 2021, secciones 2 y 5.

## Autoevaluación

1. Según el Principio 17 del FX Global Code, ¿para qué debe usarse el last look?

   - A. Para que el proveedor decida si le interesa la operación a la vista de la intención del cliente
   - B. Como mecanismo de control de riesgos para verificar la validez de la operación y el precio
   - C. Para cubrirse en el mercado antes de aceptar la orden
   - D. Para retener la orden el tiempo que haga falta hasta que el precio mejore

   *Respuesta B: Como mecanismo de control de riesgos para verificar la validez de la operación y el precio.* El Código dice que, si se utiliza, el last look será un mecanismo de control de riesgos para verificar la validez y/o el precio. Prohíbe usarlo para obtener información sin intención de aceptar y negociar durante la ventana con la información de la solicitud; el informe del GFXC de 2021 añade que cualquier retraso adicional al necesario para las comprobaciones es contrario al Principio 17.

2. En los datos, LP-Borea rechaza el 44 % de las órdenes cuando el precio se mueve en su contra y el 60 % cuando se mueve a su favor. ¿Qué tipo de comprobación de precio aplica?

   - A. Asimétrica
   - B. Simétrica: rechaza si el precio se ha movido más que la tolerancia en cualquier dirección
   - C. Ninguna: son rechazos de fondo
   - D. Una mejora de precio

   *Respuesta B: Simétrica: rechaza si el precio se ha movido más que la tolerancia en cualquier dirección.* Una comprobación simétrica aplica la misma tolerancia alrededor del precio actual en los dos sentidos. Rechaza también cuando el movimiento perjudica al cliente, que así se libra de una ejecución que le habría costado. Su coste medio por rechazo es casi nulo (−0,004 pips en los datos).

3. LP-Delta cotiza la horquilla más estrecha (0,46 pips en Londres) pero rechaza el 27,7 % de las órdenes. ¿Cómo se compara con LP-Atlas, sin last look y con 1,26 pips?

   - A. Delta es más caro, porque rechaza mucho
   - B. Atlas es más barato, porque nunca rechaza
   - C. Delta sigue siendo más barato: su semi-spread de 0,20 pips más 0,08 de coste de los rechazos dan 0,28, frente a 0,57 de Atlas
   - D. No se pueden comparar sin saber el tamaño de las órdenes

   *Respuesta C: Delta sigue siendo más barato: su semi-spread de 0,20 pips más 0,08 de coste de los rechazos dan 0,28, frente a 0,57 de Atlas.* El coste efectivo suma el semi-spread pagado y, en las órdenes rechazadas, el movimiento a favor del cliente que se pierde al tener que volver a pedir precio. En los datos, el last look de Delta cuesta 0,08 pips por orden, un 40 % de su semi-spread, pero no basta para que Atlas sea más barato. Con un flujo que anticipa el precio (ejercicio 2) la cuenta cambia.

4. El libro agregado de Londres está cruzado (mejor bid por encima del mejor ask) el 20 % del tiempo. ¿Qué indica?

   - A. Una oportunidad de arbitraje sin riesgo
   - B. Un error en los datos
   - C. Que las cotizaciones de cada proveedor llegan con latencias distintas: unas reflejan el precio de hace 5 ms y otras el de hace 60 ms, y en un mercado rápido se cruzan
   - D. Que los proveedores coluden

   *Respuesta C: Que las cotizaciones de cada proveedor llegan con latencias distintas: unas reflejan el precio de hace 5 ms y otras el de hace 60 ms, y en un mercado rápido se cruzan.* Cada LP publica su precio con su retraso. Cuando el precio de referencia se mueve, el más lento queda atrasado y su cotización puede cruzar la de otro más rápido. Quien intente cruzar las dos se encuentra con el last look del proveedor atrasado: un precio que no es firme no es un arbitraje.

5. Las ejecuciones de un proveedor con last look asimétrico tienen un markout medio negativo para el cliente. ¿Por qué?

   - A. Porque cobra comisiones ocultas
   - B. Porque las órdenes en las que el precio se movió a favor del cliente se rechazaron: solo se ejecutan las que no se movieron o se movieron a favor del proveedor
   - C. Porque las ejecuciones se hacen a un precio peor que el solicitado
   - D. Por azar

   *Respuesta B: Porque las órdenes en las que el precio se movió a favor del cliente se rechazaron: solo se ejecutan las que no se movieron o se movieron a favor del proveedor.* En los datos las ejecuciones son siempre al precio solicitado. El sesgo viene de qué se ejecuta: el filtro asimétrico quita las órdenes que habrían ganado y deja las demás. El markout de las ejecuciones y el coste de los rechazos son dos caras del mismo dinero.

## Ideas clave

- Las divisas se negocian OTC: cada proveedor de liquidez publica su precio por separado y el libro que ve el cliente lo construye él o su agregador. En España, según el Banco de España, el 94 % del volumen de abril de 2025 fue con contrapartidas extranjeras.
- El libro agregado es más estrecho que cualquier proveedor (0,19 pips de media en Londres frente a 0,46 del mejor), pero está cruzado el 20 % del tiempo por las distintas latencias: no todo precio visible es firme.
- El last look es una última oportunidad del proveedor para aceptar o rechazar. El FX Global Code lo admite como control de validez y de precio, exige explicar si se aplica de forma simétrica o asimétrica y prohíbe usarlo para obtener información.
- En los datos se recuperan los parámetros plantados: retención mediana de 4, 43, 59, 152 y 33 ms (2, 40, 60, 150 y 30 más 1–3 ms de red), rechazo del 93 % en LP-Delta cuando el precio va en su contra y del 0,4 % cuando va a su favor.
- La comparación correcta entre proveedores es el coste efectivo: semi-spread más coste de los rechazos. LP-Delta, con la horquilla más estrecha, cuesta 0,28 pips por orden; LP-Atlas, sin last look, 0,57.
- El resultado depende del flujo: un cliente cuyas órdenes anticipan el precio ve rechazos mucho más frecuentes y caros. El análisis de costes (TCA) por proveedor, con markouts y rechazos, es la única forma de saberlo.

## Recursos

### Research

- [Liquidez Order Book - Trading Liquidez Agregada](https://pythonparatrading.com/limit-order-book-y-liquidez-de-mercado/) · Research-0008 · 11 min
- [Modelos Cuantitativos y Ejecución Algorítmica](https://pythonparatrading.com/modelos-cuantitativos-ejecucion-algoritmica/) · Research-0009 · 11 min

### Documentación

- [FX Global Code (diciembre de 2024), versión en español del Banco de España: Principio 17](https://www.globalfxc.org/uploads/fx_global_dec24_es_es.pdf) · globalfxc.org
- [GFXC (2021): Execution Principles Working Group Report on Last Look](https://www.globalfxc.org/docs/gfxc_report_last_look.pdf) · globalfxc.org
- [Banco de España: encuesta trienal sobre los mercados OTC de divisas y derivados (2025)](https://www.bde.es/wbe/es/estadisticas/temas/mercados-financieros/encuesta-sobre-mercados-divisas-derivados-otc/encuesta-sobre-el-mercado-de-divisas-y-de-derivados-otc-2025.html) · bde.es
- [Oomen (2017): Last look, Quantitative Finance 17(7)](https://doi.org/10.1080/14697688.2016.1262545) · doi.org

### Datos

- [Datos del nivel 3: órdenes de un cliente a cinco proveedores de QEVUSD (Parquet)](https://pythonparatrading.com/curso/datos/nivel-3/fx3_ecn_ordenes.parquet) · Descarga · fx3_ecn_ordenes.parquet

Lección anterior: [3.2.6 · Estacionalidad de spreads: análisis multi-año, patrones y trampas](https://pythonparatrading.com/curso-python-trading/nivel-3/estacionalidad-de-spreads/)

Lección siguiente: [3.3.2 · Carry y riesgo de crash, momentum y value en divisas; cestas](https://pythonparatrading.com/curso-python-trading/nivel-3/carry-momentum-y-value-en-divisas/)
