Lección 1.3.6 · 50 min

Brókeres como fuente de datos

El bróker con el que operas ya te da datos: históricos, cotizaciones y, a veces, el libro. Son los precios a los que ejecutas, pero llegan con reglas propias: un programa intermedio, límites de ritmo estrictos y suscripciones de mercado. Esta lección los recorre con Interactive Brokers, MetaTrader 5 y OANDA, y simula el ritmo de descarga sin conectarse a nada.

Fig. — Un año de barras de 5 segundos de un contrato (252 peticiones) con y sin las reglas de ritmo de IBKR para barras pequeñas, en un reloj virtual
Lecciones del nivel 1
  1. 1.1 · Cómo funcionan los mercados

    1. 1.1.1 Participantes y centros de negociación
    2. 1.1.2 Sesiones, subastas, festivos y calendarios
    3. 1.1.3 Libro de órdenes: bid, ask, spread y profundidad
    4. 1.1.4 Tipos de órdenes
    5. 1.1.5 Costes, liquidación, apalancamiento y margen
  2. 1.2 · Clases de activos

    1. 1.2.1 Acciones: splits, dividendos y eventos corporativos
    2. 1.2.2 ETFs: réplica, tracking error, apalancados y UCITS
    3. 1.2.3 Futuros: especificaciones, margen, roll y contango
    4. 1.2.4 Forex: pares, pips, lotes, rollover y CFD
    5. 1.2.5 Opciones: call, put, valor intrínseco y valor temporal
    6. 1.2.6 Cripto: spot, perpetuos, funding y custodia
    7. 1.2.7 Renta fija y tipos de interés; CFDs y productos apalancados
  3. 1.3 · Fuentes de datos de mercado

    1. 1.3.1 Tipos de datos de mercado: EOD, barras, ticks, libro y referencia
    2. 1.3.2 Fuentes de datos gratuitas
    3. 1.3.3 APIs de datos retail y prosumer
    4. 1.3.4 Proveedores de datos profesionales
    5. 1.3.5 Datos de cripto y on-chain
    6. 1.3.6 Brókeres como fuente de datos
    7. 1.3.7 Licencias y términos de uso de datos
    8. 1.3.8 Cómo elegir proveedor de datos
  4. 1.4 · Ingeniería de datos financieros

    1. 1.4.1 Descarga por REST y websockets: paginación, límites y reintentos
    2. 1.4.2 Limpieza de datos: faltantes, ticks erróneos, huecos y zonas horarias
    3. 1.4.3 Ajustes por splits y dividendos: precios ajustados y sin ajustar
    4. 1.4.4 Universos sin sesgo de supervivencia: deslistados, fusiones y cambios de símbolo
    5. 1.4.5 Construcción de barras: tiempo, ticks, volumen, dólar y desequilibrio
    6. 1.4.6 Series continuas de futuros: reglas de roll, ajuste por diferencia y ajuste por ratio
    7. 1.4.7 Almacenamiento: CSV, Parquet, particionado, DuckDB, TimescaleDB y ArcticDB
    8. 1.4.8 Pipeline reproducible: descarga, validación, actualización incremental y tests de calidad
  5. 1.5 · Datos fundamentales y macro

    1. 1.5.1 Estados financieros, ratios, XBRL y SEC EDGAR
    2. 1.5.2 Datos macro, calendarios económicos, estimaciones y transcripciones
  6. 1.6 · Análisis exploratorio financiero

    1. 1.6.1 Rentabilidades, volatilidad, correlaciones móviles y drawdown
    2. 1.6.2 Hechos estilizados: colas gruesas, agrupamiento, autocorrelación y apalancamiento
    3. 1.6.3 Estacionalidad: día de la semana, mes, cambio de mes y sesiones intradía
  7. 1.7 · Análisis técnico programado

    1. 1.7.1 Indicadores técnicos en Python
    2. 1.7.2 Implementación propia frente a TA-Lib: validar resultados
    3. 1.7.3 Patrones de velas y soportes y resistencias
    4. 1.7.4 Multi-timeframe sin anticipación

El bróker también es un proveedor

Cualquier bróker que tenga API da, además de la ejecución, datos: cotizaciones en tiempo real, velas históricas y, en algunos casos, profundidad de libro. Para un sistema que opera en ese bróker tienen una ventaja que ningún otro proveedor iguala: son los precios del sitio donde se ejecuta, con su horquilla, su horario y sus comisiones. Un sistema de divisas que calcula señales con los precios de su bróker y ejecuta en él no tiene que preocuparse de que la fuente de datos y el mercado de ejecución no coincidan.

Los inconvenientes son los contrarios: historia corta, límites de peticiones pensados para operar y no para descargar años, y ningún valor deslistado (el bróker solo da lo que se puede operar hoy). Esta lección recorre tres brókeres con API muy usados en Python: Interactive Brokers, MetaTrader 5 (una plataforma que ofrecen muchos brókeres) y OANDA.

Interactive Brokers: TWS, Gateway y ib_async

La API de Interactive Brokers (IBKR) tiene una arquitectura que sorprende la primera vez: el programa no se conecta a los servidores de IBKR, sino a una aplicación que corre en la propia máquina, TWS (Trader Workstation, la plataforma de escritorio) o IB Gateway (la misma conexión sin interfaz gráfica, más ligera para un servidor). Esa aplicación mantiene la sesión con IBKR y expone un puerto local:

Tu programaib_asyncTWS o GatewayServidores IBKR
Fuente Mermaid
flowchart LR
  P[Tu programa] --> A[ib_async]
  A --> G[TWS o Gateway]
  G --> S[Servidores IBKR]
AplicaciónCuenta realCuenta de papel
TWS74967497
IB Gateway40014002

Son los puertos por defecto según la documentación de IBKR1Nota 1 · IBKR: Configure the Connecting Socket Port, consultada el 21/10/2026.; cada aplicación los permite cambiar. Cada conexión lleva además un clientId: varios programas pueden conectarse a la misma TWS a la vez si usan identificadores distintos. Conectarse a la cuenta de papel (7497 o 4002) es la forma segura de probar: los mismos datos y la misma API, sin dinero real.

La librería oficial de IBKR para Python (ibapi) es de bajo nivel: callbacks y mensajes. La que usa casi todo el mundo es ib_async, la continuación de ib_insync. Según su README, Ewald de Wit la creó en 2017 y la mantuvo hasta su fallecimiento a principios de 2024; los mantenedores la renombraron como ib_async en una organización nueva de GitHub (ib-api-reloaded) porque ya no podían modificar los repositorios originales. Requiere Python 3.10 o superior y no necesita el paquete ibapi2Nota 2 · README de ib_async, consultado el 21/10/2026; versión 2.1.0 en PyPI.. El código de ib_insync funciona casi igual cambiando el import.

IN [01]ibkr_historico.pypython
from ib_async import IB, Stock, util     # uv add ib_async

ib = IB()
ib.connect("127.0.0.1", 7497, clientId=1)   # TWS, cuenta de papel

contrato = Stock("AAPL", "SMART", "USD")
barras = ib.reqHistoricalData(
    contrato, endDateTime="", durationStr="30 D",
    barSizeSetting="1 hour", whatToShow="MIDPOINT", useRTH=True,
)
df = util.df(barras)
ib.disconnect()

durationStr es cuánto tiempo hacia atrás ("30 D"), barSizeSetting el tamaño de la barra, whatToShow qué precio (TRADES, MIDPOINT, BID, ASK, BID_ASK…) y useRTH=True limita a la sesión regular. util.df convierte la lista de barras en un DataFrame.

Suscripciones de mercado

Que la API funcione no significa que haya datos. Según la documentación de IBKR, salvo los datos diferidos, recibir datos de mercado por la API requiere una suscripción de pago al mercado correspondiente; para pedir datos diferidos sin suscripción, ib.reqMarketDataType(3) antes de pedir cotizaciones. Cada usuario tiene además un número de líneas de datos de mercado (100 por defecto) que comparten la plataforma y la API: cada símbolo que se sigue en tiempo real ocupa una3Nota 3 · IBKR: Pacing Limitations, Market Data Lines y Market Data, consultadas el 21/10/2026..

Límites de ritmo: lo que decide la duración de una descarga

IBKR limita con cuidado lo que se le pide. Hay dos tipos de límite:

  • General. El número máximo de peticiones por segundo es el de líneas de datos de mercado dividido entre dos: con las 100 líneas por defecto, 50 peticiones por segundo3.
  • Histórico de barras pequeñas. Para barras de 30 segundos o menos, la documentación de la versión anterior de la API (marcada como obsoleta, pero la única que publica las reglas) considera violación de ritmo: repetir una petición idéntica en menos de 15 segundos; hacer 6 o más peticiones del mismo contrato, bolsa y tipo de dato en 2 segundos; y hacer más de 60 peticiones en cualquier periodo de 10 minutos. Con BID_ASK, cada petición cuenta dos veces. Para barras de 1 minuto o más, esas reglas se levantaron, aunque el servidor sigue frenando a quien pide demasiado4Nota 4 · IBKR, documentación anterior de la API: Historical Data Limitations («This documentation is now deprecated»), consultada el 21/10/2026..

Además, cada petición tiene una duración máxima según el tamaño de barra: la documentación actual da, por ejemplo, 2.000 segundos para barras de 1 segundo y 86.400 segundos (un día) para barras de 5 segundos5Nota 5 · IBKR: Historical Bars (OHLC), tabla de duración máxima por tamaño de barra.. Con esas reglas se puede simular un descargador que nunca las viola, con un reloj virtual:

IN [02]python
import math
from collections import deque

class RitmoIBKR:
    """Reglas para barras de 30 s o menos (documentación anterior)."""

    def __init__(self):
        self.hechas = deque()          # (t, contrato, clave, peso)

    def espera(self, t, contrato, clave, peso=1):
        while self.hechas and self.hechas[0][0] <= t - 600:
            self.hechas.popleft()
        e = 0.0
        # más de 60 en 10 minutos (BID_ASK pesa 2)
        usadas = sum(h[3] for h in self.hechas)
        if usadas + peso > 60:
            acum = 0
            for h in self.hechas:
                acum += h[3]
                if usadas - acum + peso <= 60:
                    e = max(e, h[0] + 600 - t)
                    break
        # 6 o más del mismo contrato en 2 segundos
        mismo = [h[0] for h in self.hechas if h[1] == contrato]
        if len(mismo) >= 5:
            e = max(e, mismo[-5] + 2 - t)
        # idéntica en menos de 15 segundos
        for h in self.hechas:
            if h[2] == clave:
                e = max(e, h[0] + 15 - t)
        return max(e, 0.0)

    def registra(self, t, contrato, clave, peso=1):
        self.hechas.append((t, contrato, clave, peso))

def descarga(n_peticiones, peso=1, latencia=0.5):
    ritmo, t, horas = RitmoIBKR(), 0.0, []
    for i in range(n_peticiones):
        clave = ("NOVA", i)        # cada petición, otro tramo
        while (e := ritmo.espera(t, "NOVA", clave, peso)) > 0:
            t += e
        ritmo.registra(t, "NOVA", clave, peso)
        horas.append(t)
        t += latencia
    return horas

horas = descarga(252)                  # un día por petición
print(f"252 peticiones: {horas[-1] / 60:.1f} minutos")
print("primeras:", [round(h, 1) for h in horas[:7]])
OUT [02]Las reglas de ritmo de IBKR, en código
252 peticiones: 40.1 minutos
primeras: [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0]

La simulación supone que cada respuesta tarda medio segundo. A ese paso salen cuatro peticiones cada dos segundos, así que la regla de las 6 en 2 segundos no llega a activarse (con respuestas más rápidas sí lo haría). La que manda es la de 60 en 10 minutos: el descargador hace 60 peticiones en medio minuto y espera el resto de la ventana. Cuatro ventanas completas y 12 peticiones más: 40,1 minutos (figura de la cabecera).

Ahora la cuenta que importa: cuánto tarda un año de barras de cada tamaño para un contrato, con la duración máxima de cada petición:

IN [03]python
sesion = int(6.5 * 3600)              # 23.400 s de sesión regular
casos = {
    "5 s, TRADES": (86_400, 1),
    "5 s, BID_ASK": (86_400, 2),
    "1 s, TRADES": (2_000, 1),
    "1 s, BID_ASK": (2_000, 2),
}
for nombre, (max_seg, peso) in casos.items():
    por_dia = math.ceil(sesion / max_seg)
    n = 252 * por_dia
    h = descarga(n, peso=peso)
    print(f"{nombre:13} {n:5} peticiones  {h[-1] / 3600:5.2f} h")
OUT [03]Un año de barras pequeñas de un contrato
5 s, TRADES     252 peticiones   0.67 h
5 s, BID_ASK    252 peticiones   1.33 h
1 s, TRADES    3024 peticiones   8.34 h
1 s, BID_ASK   3024 peticiones  16.67 h
Fig. — Tiempo de descarga de un año de barras pequeñas de un contrato con las reglas de ritmo de IBKR, según el tamaño de barra y el tipo de dato

Un año de barras de 5 segundos cabe en 252 peticiones y unos 40 minutos; el mismo año en barras de 1 segundo necesita 12 peticiones por día (23.400 / 2.000, redondeado hacia arriba), 3.024 en total y más de 8 horas, o el doble si se piden bid y ask. Para cien contratos, multiplica. Por eso los históricos largos de barras pequeñas no se bajan del bróker sino de un proveedor (lecciones 1.3.3 y 1.3.4), y el bróker se reserva para lo reciente y para el tiempo real.

Qué pasa si no se respeta

Un bucle que dispara las peticiones sin limitador no sabe que está violando las reglas hasta que IBKR responde con un error de violación de ritmo, y con violaciones repetidas la conexión puede acabar cortada. Con la misma clase se puede contar cuántas peticiones de un bucle ingenuo serían violaciones:

IN [04]python
def violaciones(n, latencia=0.5):
    ritmo, t, malas = RitmoIBKR(), 0.0, 0
    for i in range(n):
        clave = ("NOVA", i)
        if ritmo.espera(t, "NOVA", clave) > 0:
            malas += 1
        ritmo.registra(t, "NOVA", clave)
        t += latencia
    return malas

print(violaciones(252), "de 252 peticiones fuera de las reglas")
OUT [04]Un bucle sin limitador
192 de 252 peticiones fuera de las reglas

Las 60 primeras caben en la ventana de 10 minutos; las 192 siguientes la violan. El bucle termina en dos minutos, pero habría recibido 192 errores (y, mucho antes, probablemente una desconexión). La solución no es reintentar más deprisa, sino esperar lo que dicen las reglas antes de pedir.

MetaTrader 5: el terminal como servidor de datos

MetaTrader 5 es una plataforma de trading que ofrecen muchos brókeres de divisas y CFD. Su paquete de Python (MetaTrader5 en PyPI) no habla con ningún servidor: se conecta a un terminal MetaTrader 5 instalado en la misma máquina, que es el que tiene la sesión con el bróker. initialize() busca el terminal o recibe la ruta de su ejecutable; las ruedas publicadas en PyPI son solo para Windows (win_amd64)6Nota 6 · MetaTrader 5: Python Integration e initialize; ruedas win_amd64 de la versión 5.0.6231 en PyPI..

IN [05]mt5_velas.pypython
from datetime import datetime, timezone

import MetaTrader5 as mt5            # solo Windows, con el terminal

if not mt5.initialize():
    raise RuntimeError(mt5.last_error())
desde = datetime(2025, 6, 2, tzinfo=timezone.utc)
velas = mt5.copy_rates_from("EURUSD", mt5.TIMEFRAME_H1, desde, 500)
ultimo = mt5.symbol_info_tick("EURUSD")
mt5.shutdown()

copy_rates_from devuelve las velas anteriores a la fecha dada como un array de numpy con campos con nombre: time, open, high, low, close, tick_volume, spread y real_volume. La documentación avisa de dos cosas: el terminal guarda la hora de apertura en UTC (por eso la fecha debe crearse en UTC) y el número de velas que puede devolver está limitado por el parámetro «Max. bars in chart» del terminal7Nota 7 · MetaTrader 5: copy_rates_from.. Con un array de esa forma, construido con el EURUSD sintético del curso:

IN [06]python
import numpy as np
import pandas as pd

eur = pd.read_parquet("eurusd_1h.parquet").set_index("ts")
h1 = eur.loc["2025-06-02", "mid"]
tipo = np.dtype([("time", "<i8"), ("open", "<f8"), ("high", "<f8"),
                 ("low", "<f8"), ("close", "<f8"),
                 ("tick_volume", "<u8"), ("spread", "<i4"),
                 ("real_volume", "<u8")])
rates = np.zeros(len(h1) - 1, dtype=tipo)
rates["time"] = h1.index[:-1].astype("int64") // 10**6   # segundos
rates["open"] = h1.values[:-1]
rates["close"] = h1.values[1:]
rates["high"] = np.maximum(rates["open"], rates["close"])
rates["low"] = np.minimum(rates["open"], rates["close"])

velas = pd.DataFrame(rates)
velas["time"] = pd.to_datetime(velas["time"], unit="s", utc=True)
velas.set_index("time")[["open", "close"]].head(3)
OUT [06]El array de copy_rates, emulado con el EURUSD del curso
                               open     close
time
2025-06-02 00:00:00+00:00  1.172300  1.172725
2025-06-02 01:00:00+00:00  1.172725  1.173615
2025-06-02 02:00:00+00:00  1.173615  1.173730

El campo time viene en segundos desde 1970 (no en milisegundos como en ccxt, lección 1.3.5), y pd.DataFrame(rates) respeta los nombres de los campos. Aquí el máximo y el mínimo salen de la apertura y el cierre porque el EURUSD del curso es una cotización por hora, no velas; en el terminal, cada vela trae los suyos. Dos detalles propios de esta fuente: tick_volume es el número de cambios de precio, no un volumen negociado (en divisas al contado no hay cinta consolidada, lección 1.2.4), y spread es la horquilla en puntos del bróker, que cambia de un bróker a otro.

OANDA: una API REST con velas bid, ask y medio

OANDA tiene una API REST, la v20, con dos entornos: práctica (api-fxpractice.oanda.com) y real (api-fxtrade.oanda.com), y servidores de streaming aparte (stream-fxpractice.oanda.com, stream-fxtrade.oanda.com). La autenticación es un token en la cabecera Authorization: Bearer. Sus límites, según la guía de desarrollo: 120 peticiones por segundo en la API REST (por encima, 429), 20 streams activos y no más de 2 conexiones nuevas por segundo; la página de buenas prácticas recomienda, en una conexión ya abierta, no pasar de 100 por segundo8Nota 8 · OANDA v20: Development Guide, Best Practices y Troubleshooting, consultadas el 21/10/2026..

Las velas se piden en GET /v3/accounts/{accountID}/instruments/{instrument}/candles, con granularity (por defecto S5, cinco segundos), count (por defecto 500, máximo 5.000) y price, que combina M (medio), B (bid) y A (ask)9Nota 9 · OANDA v20: Pricing (endpoint de velas), Instrument (esquema Candlestick) y Primitives (DateTime)..

IN [07]oanda_velas.pypython
import os
from urllib.parse import urlencode
from urllib.request import Request, urlopen

cuenta = os.environ["OANDA_ACCOUNT"]
url = (f"https://api-fxpractice.oanda.com/v3/accounts/{cuenta}"
       "/instruments/EUR_USD/candles?"
       + urlencode({"granularity": "H4", "count": 500, "price": "MBA"}))
req = Request(url, headers={
    "Authorization": f"Bearer {os.environ['OANDA_TOKEN']}"})
with urlopen(req, timeout=10) as r:
    crudo = r.read()

La respuesta trae instrument, granularity y una lista candles. Cada vela tiene time (el inicio de la vela, en RFC 3339 con nanosegundos: AAAA-MM-DDTHH:MM:SS.nnnnnnnnnZ), volume (el número de precios creados en la vela), complete y un bloque mid, bid o ask con o, h, l y c. Los precios llegan como texto, con la precisión del instrumento. Una respuesta con ese esquema, construida con el EURUSD sintético en velas de 4 horas:

IN [08]python
import json

def bloque(s):
    return {"o": f"{s.iloc[0]:.5f}", "h": f"{s.max():.5f}",
            "l": f"{s.min():.5f}", "c": f"{s.iloc[-1]:.5f}"}

dia = eur.loc["2025-06-02 00:00":"2025-06-02 15:00"]
velas_json = []
for inicio, g in dia.groupby(pd.Grouper(freq="4h")):
    velas_json.append({
        "time": inicio.strftime("%Y-%m-%dT%H:%M:%S.000000000Z"),
        "volume": len(g), "complete": len(g) == 4,
        "bid": bloque(g["bid"]), "ask": bloque(g["ask"]),
        "mid": bloque(g["mid"]),
    })
velas_json[-1]["complete"] = False          # la vela en curso
respuesta = json.dumps({"instrument": "EUR_USD",
                        "granularity": "H4", "candles": velas_json})
print(respuesta[:118], "...")
OUT [08]Una respuesta con el esquema de OANDA v20
{"instrument": "EUR_USD", "granularity": "H4", "candles": [{"time": "2025-06-02T00:00:00.000000000Z", "volume": 4, "co ...

Y su lectura: texto a número, solo velas completas y horquilla de cierre en pips.

IN [09]python
datos = json.loads(respuesta)
filas = []
for v in datos["candles"]:
    if not v["complete"]:
        continue                            # nunca al histórico
    fila = {"time": v["time"], "volume": v["volume"]}
    for lado in ("bid", "ask"):
        for k, x in v[lado].items():
            fila[f"{lado}_{k}"] = float(x)
    filas.append(fila)

oanda = pd.DataFrame(filas)
oanda["time"] = pd.to_datetime(oanda["time"], utc=True)
oanda["spread_pips"] = 1e4 * (oanda["ask_c"] - oanda["bid_c"])
oanda.set_index("time")[["bid_c", "ask_c", "spread_pips"]].round(5)
OUT [09]De la respuesta de OANDA a un DataFrame
                             bid_c    ask_c  spread_pips
time
2025-06-02 00:00:00+00:00  1.17370  1.17376          0.6
2025-06-02 04:00:00+00:00  1.17570  1.17572          0.2
2025-06-02 08:00:00+00:00  1.17466  1.17468          0.2

Tres velas completas; la cuarta, marcada como en curso, se descarta. Las velas de un bróker de divisas traen bid y ask por separado, que es justo lo que hace falta para un backtest con costes realistas: la compra al ask, la venta al bid (lección 1.1.5). Las velas de «precio medio» que dan muchas fuentes gratuitas no permiten eso.

Cuándo usar el bróker como fuente

señal en realbacktest largouniverso acciones¿Para qué?Datos del brókerProveedorContrastar
Fuente Mermaid
flowchart TB
  Q{¿Para qué?} -- señal en real --> B[Datos del bróker]
  Q -- backtest largo --> P[Proveedor]
  Q -- universo acciones --> P
  B --> C[Contrastar]
  P --> C
Uso¿Bróker?Por qué
Señales y ejecución en tiempo realSíSon los precios donde se ejecuta
Costes de un sistema de divisas o CFDSíSu horquilla y su financiación son las que se pagan
Histórico largo de barras pequeñasNoLímites de ritmo y duración máxima por petición
Universo de acciones para un backtestNoSolo tiene lo que cotiza hoy: sesgo de supervivencia (lección 1.3.8)
Validar un proveedorSí, como contrasteComparar unos días de precios de ambos detecta diferencias de horario o de ajustes

La combinación habitual es un proveedor para la historia y el research y el bróker para la operativa, con una comprobación periódica de que los dos cuentan la misma historia en los días que se solapan.

Ejercicios

  1. Con descarga, ¿cuánto tarda un año de barras de 5 segundos con BID_ASK para 10 contratos, si se piden uno detrás de otro con el mismo limitador? (Pista: las reglas de 10 minutos cuentan todas las peticiones, no por contrato.)
Solución
IN [10]python
h = descarga(10 * 252, peso=2)
print(f"{len(h)} peticiones: {h[-1] / 3600:.1f} horas")
OUT [10]Diez contratos
2520 peticiones: 13.8 horas

2.520 peticiones que cuentan doble son 5.040 unidades de las 60 por ventana de 10 minutos: unas 84 ventanas, casi 14 horas. Con diez contratos la descarga ya no es «un momento antes de empezar», sino un trabajo de noche que hay que poder reanudar si se corta (lección 1.4.8).

  1. En la respuesta de OANDA, calcula la horquilla media de las velas completas en pips usando el cierre, y compárala con la de la apertura. ¿Por qué no tienen por qué coincidir?
Solución
IN [11]python
oanda["spread_ap"] = 1e4 * (oanda["ask_o"] - oanda["bid_o"])
oanda[["spread_ap", "spread_pips"]].mean().round(2)
OUT [11]Horquilla en la apertura y en el cierre
spread_ap      0.53
spread_pips    0.33
dtype: float64

Las horquillas de apertura y de cierre son dos fotos en dos momentos distintos de la vela; la horquilla cambia con la sesión (más ancha en Asia y en el rollover, más estrecha en el solape de Londres y Nueva York, lección 1.2.4). Para un backtest, la horquilla que importa es la del momento de la orden: si el sistema opera al cierre de la vela, la de cierre.

  1. Tu sistema opera EURUSD con velas de 4 horas en un bróker con MetaTrader 5 y quieres 15 años de historia para probarlo. ¿De dónde sacarías los datos y qué comprobarías?
Solución

El histórico del terminal está limitado por «Max. bars in chart» y por lo que el bróker tenga guardado; para 15 años conviene un proveedor de divisas con historia larga (lección 1.3.3) y usar el bróker para los años recientes y la operativa. Comprobaciones: que las horas sean UTC en ambas fuentes (MT5 guarda la apertura en UTC, pero muchos brókeres muestran la hora del servidor en otra zona), que las velas de 4 horas empiecen en las mismas horas, la diferencia de precios en los días que se solapan (precio medio frente a bid), y que la horquilla del backtest sea la del bróker (spread), no la del proveedor.

Footnotes

  1. IBKR: Configure the Connecting Socket Port, consultada el 21/10/2026. ↩

  2. README de ib_async, consultado el 21/10/2026; versión 2.1.0 en PyPI. ↩

  3. IBKR: Pacing Limitations, Market Data Lines y Market Data, consultadas el 21/10/2026. ↩ ↩2

  4. IBKR, documentación anterior de la API: Historical Data Limitations («This documentation is now deprecated»), consultada el 21/10/2026. ↩

  5. IBKR: Historical Bars (OHLC), tabla de duración máxima por tamaño de barra. ↩

  6. MetaTrader 5: Python Integration e initialize; ruedas win_amd64 de la versión 5.0.6231 en PyPI. ↩

  7. MetaTrader 5: copy_rates_from. ↩

  8. OANDA v20: Development Guide, Best Practices y Troubleshooting, consultadas el 21/10/2026. ↩

  9. OANDA v20: Pricing (endpoint de velas), Instrument (esquema Candlestick) y Primitives (DateTime). ↩

Respuesta plegada en cada pregunta

Autoevaluación

  1. Conectas ib_async al puerto 7497. ¿A qué te conectas, según la tabla de puertos de IBKR?

    • AA TWS con la cuenta real
    • BA TWS con la cuenta de papel (simulada)
    • CA IB Gateway con la cuenta real
    • DDirectamente a los servidores de IBKR
    Ver respuesta

    Respuesta B. A TWS con la cuenta de papel (simulada)

    Los puertos por defecto son 7496 (TWS real), 7497 (TWS papel), 4001 (Gateway real) y 4002 (Gateway papel). La API nunca habla directamente con los servidores: siempre pasa por TWS o por IB Gateway en tu máquina.

  2. ¿Qué relación hay entre ib_async e ib_insync?

    • ASon librerías rivales sin relación
    • Bib_async es la continuación de ib_insync, renombrada en una organización nueva de GitHub tras el fallecimiento de su autor en 2024
    • Cib_async es la librería oficial de IBKR
    • Dib_insync es la versión nueva
    Ver respuesta

    Respuesta B. ib_async es la continuación de ib_insync, renombrada en una organización nueva de GitHub tras el fallecimiento de su autor en 2024

    Su README cuenta que Ewald de Wit creó ib_insync en 2017 y la mantuvo hasta su fallecimiento a principios de 2024; los mantenedores la renombraron porque ya no podían modificar el repositorio original.

  3. Con las reglas de ritmo de IBKR para barras de 30 segundos o menos, ¿cuántas peticiones de histórico puedes hacer en 10 minutos?

    • AIlimitadas
    • B60
    • C600
    • D6
    Ver respuesta

    Respuesta B. 60

    Más de 60 peticiones en cualquier periodo de 10 minutos es una violación de ritmo. Además no se puede repetir una petición idéntica en 15 segundos ni hacer 6 o más del mismo contrato en 2 segundos. Con BID_ASK, cada petición cuenta doble.

  4. OANDA devuelve una vela con "complete": false. ¿Qué significa?

    • AQue faltan precios
    • BQue es la vela en curso: su final está en el futuro y sus valores cambiarán
    • CQue el mercado estaba cerrado
    • DQue hubo un error
    Ver respuesta

    Respuesta B. Que es la vela en curso: su final está en el futuro y sus valores cambiarán

    La documentación define una vela completa como aquella cuyo final no está en el futuro. Una vela incompleta no se guarda como histórico.

  5. ¿Cuál es la principal ventaja de usar el bróker como fuente de datos?

    • AQue siempre es gratis y sin límites
    • BQue son los precios del sitio donde ejecutas, con su horquilla y su horario, y no hace falta otro contrato
    • CQue tiene más historia que cualquier proveedor
    • DQue no tiene sesgo de supervivencia
    Ver respuesta

    Respuesta B. Que son los precios del sitio donde ejecutas, con su horquilla y su horario, y no hace falta otro contrato

    Para un sistema que opera en ese bróker, sus precios son los ejecutables. A cambio, la historia suele ser más corta, los límites más estrictos y no hay valores deslistados (lección 1.3.8).

Ideas clave

  • La API de IBKR no habla con los servidores: habla con TWS o IB Gateway en tu máquina, por un puerto (7496/7497 en TWS, 4001/4002 en Gateway) y con un clientId.
  • ib_async es la continuación de ib_insync; requiere Python 3.10 o superior y no necesita el paquete ibapi de IBKR.
  • Los datos por API necesitan una suscripción de mercado de pago (salvo los diferidos), y las peticiones están limitadas: 50 por segundo en general y reglas propias para el histórico de barras pequeñas.
  • El límite de ritmo decide cuánto tarda una descarga histórica: un año de barras de 1 segundo de un contrato son unas 3.000 peticiones y más de 8 horas.
  • MetaTrader 5 entrega arrays de numpy con hora en UTC desde un terminal instalado (solo hay ruedas para Windows); OANDA, JSON con precios como texto, hora RFC 3339 y una marca complete.
  • El bróker es la mejor fuente de los precios a los que ejecutas, y una mala fuente de historia larga y de universos sin sesgo.

Recursos