Contenido educativo de programación, no consejo financiero. Aquí aprendes a mover datos, no a decidir qué comprar. Y una regla que repetiremos: empieza siempre en modo papel (simulación), nunca con dinero real hasta que domines cada pieza.
Si quieres construir un bot de trading en Python, el primer paso real no es la estrategia: es conectar Python a tu exchange y conseguir datos limpios. En este ccxt python tutorial en español vamos a instalar la librería ccxt, crear claves API con permisos mínimos, conectar de forma agnóstica (sin casarnos con ningún exchange concreto) y descargar velas OHLCV históricas listas para analizar.
¿Qué es ccxt y por qué trabajar de forma agnóstica?
ccxt (CryptoCurrency eXchange Trading library) es una librería que unifica bajo una misma interfaz las APIs de más de cien exchanges. En lugar de aprender la API concreta de cada plataforma, aprendes un solo conjunto de métodos (fetch_ohlcv, load_markets, fetch_balance…) que funcionan igual en todos.
Eso nos permite escribir código agnóstico: el nombre del exchange vive en una variable y lo demás no cambia. Trataremos los distintos exchanges solo como ejemplos intercambiables. La ventaja práctica es doble: no dependes de una sola plataforma y puedes cambiar de proveedor de datos sin reescribir tu bot.
1. Instalación
Trabaja siempre dentro de un entorno virtual para no ensuciar tu Python del sistema:
python -m venv .venv
source .venv/bin/activate # en Windows: .venv\Scripts\activate
pip install ccxt pandas
Añadimos pandas porque nos hará falta para limpiar los datos al final. Comprueba la versión instalada:
import ccxt
print(ccxt.__version__)
2. Crea claves API con permisos mínimos (sin retirada)
Antes de tocar código, la parte más importante de todo el proceso: las claves API. En el panel de tu exchange (busca "API" o "gestión de API") genera un par de claves y aplica el principio de mínimo privilegio:
- Solo lectura para empezar. Para descargar datos históricos no necesitas ningún permiso de trading: los datos de mercado (OHLCV) suelen ser públicos y ni siquiera requieren clave.
- Desactiva SIEMPRE el permiso de retirada de fondos. Ningún bot educativo necesita poder sacar dinero de tu cuenta. Si una clave con permiso de retirada se filtra, el daño es irreversible.
- Restringe por IP si tu exchange lo permite: la clave solo funcionará desde la IP de tu servidor.
- Nunca escribas las claves en el código. Cárgalas desde variables de entorno o un archivo
.envque esté en tu.gitignore.
export EXCHANGE_API_KEY="tu_clave_publica"
export EXCHANGE_SECRET="tu_clave_secreta"
3. Conecta Python a "tu exchange" (de forma agnóstica)
Aquí está el patrón agnóstico: el exchange_id es lo único específico. Cámbialo por el identificador del exchange que uses y el resto del script sigue igual.
import os
import ccxt
# El único punto acoplado a una plataforma concreta.
# Sustituye por el id de tu exchange (ejemplos intercambiables).
exchange_id = "tu_exchange"
exchange_class = getattr(ccxt, exchange_id)
exchange = exchange_class({
"apiKey": os.environ.get("EXCHANGE_API_KEY"),
"secret": os.environ.get("EXCHANGE_SECRET"),
"enableRateLimit": True, # respeta los límites de la API automáticamente
})
# Carga el catálogo de mercados disponibles
exchange.load_markets()
print(f"Conectado a {exchange.id}. Mercados disponibles: {len(exchange.markets)}")
El parámetro enableRateLimit: True es tu mejor amigo: hace que ccxt espere lo necesario entre peticiones para no superar el límite de llamadas del exchange y que te bloqueen temporalmente.
Antes de pedir velas, conviene comprobar que ese exchange soporta el método que vamos a usar. No todos exponen todo:
if not exchange.has["fetchOHLCV"]:
raise RuntimeError(f"{exchange.id} no soporta descarga de OHLCV vía ccxt")
4. Descarga datos OHLCV
OHLCV son las siglas de Open, High, Low, Close, Volume: la información de cada vela. Es la materia prima de casi cualquier análisis o backtest. Una descarga básica:
symbol = "BTC/USDT" # par de mercado (base/cotizada)
timeframe = "1h" # 1m, 5m, 15m, 1h, 4h, 1d...
limit = 1000 # número de velas por petición
velas = exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
# Cada fila es: [timestamp_ms, open, high, low, close, volume]
print(velas[0])
print(f"Descargadas {len(velas)} velas")
El problema: casi todos los exchanges devuelven como máximo unos cientos o mil registros por llamada. Para reconstruir meses o años de histórico hay que paginar avanzando el parámetro since.
5. Descarga histórica completa con paginación
La idea: partimos de una fecha inicial, pedimos un lote, avanzamos since justo después de la última vela recibida y repetimos hasta que no lleguen más datos.
import time
symbol = "BTC/USDT"
timeframe = "1h"
desde = exchange.parse8601("2023-01-01T00:00:00Z") # fecha inicial en ms
todas = []
while True:
lote = exchange.fetch_ohlcv(symbol, timeframe, since=desde, limit=1000)
if not lote:
break
todas += lote
# Avanzamos 1 ms más allá de la última vela para no repetirla
desde = lote[-1][0] + 1
# Cortesía con la API: espera acorde al rate limit del exchange
time.sleep(exchange.rateLimit / 1000)
# Si el lote vino incompleto, hemos llegado al presente
if len(lote) < 1000:
break
print(f"Total de velas descargadas: {len(todas)}")
Ese time.sleep(exchange.rateLimit / 1000) convierte los milisegundos que ccxt conoce de cada exchange en una pausa educada entre peticiones. Es la diferencia entre un script que funciona y uno que se gana un bloqueo temporal.
6. Limpia y prepara los datos con pandas
Los datos crudos vienen como listas de listas con timestamps en milisegundos: incómodos de analizar y, a veces, con duplicados o huecos. Los pasamos a un DataFrame ordenado y con índice temporal:
import pandas as pd
df = pd.DataFrame(
todas,
columns=["timestamp", "open", "high", "low", "close", "volume"],
)
# Timestamp de ms a fecha UTC legible, como índice
df["datetime"] = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
df = df.set_index("datetime").drop(columns="timestamp")
# Elimina duplicados por paginación y ordena por tiempo
df = df[~df.index.duplicated(keep="last")].sort_index()
# Convierte a numérico por si acaso y detecta huecos
df = df.astype(float)
print(df.head())
print("Filas:", len(df), "| Nulos:", df.isna().sum().sum())
# Guarda para reutilizar sin volver a descargar
df.to_csv("btc_usdt_1h.csv")
Dos comprobaciones de higiene que evitan sustos más adelante: duplicados (la paginación puede solapar velas) y huecos temporales (velas que faltan por caídas del exchange). Guardar el resultado en CSV o Parquet te ahorra descargar lo mismo una y otra vez mientras desarrollas.
Buenas prácticas antes de seguir
- Respeta los rate limits.
enableRateLimity las pausas no son opcionales; son lo que mantiene tus claves vivas. - Cachea en disco. Descarga una vez, analiza mil. La API no debe ser tu base de datos.
- Valida siempre los datos. Un backtest sobre datos con huecos o duplicados da resultados que parecen buenos y no lo son.
- Modo papel primero. Cuando pases de leer datos a enviar órdenes, hazlo contra el entorno de pruebas o "sandbox" del exchange (
exchange.set_sandbox_mode(True)cuando esté disponible). No arriesgues capital real hasta validar cada pieza.
El siguiente paso
Con esto ya tienes lo esencial: Python hablando con tu exchange y un histórico OHLCV limpio en disco. Es exactamente el punto de partida que trabajamos, paso a paso, en el módulo de conexión al exchange y en el módulo de descarga y limpieza de datos del curso.
Si quieres convertir estos fragmentos en un bot serio (con gestión de errores, control de riesgo y una arquitectura que no reviente en producción), profundizamos en todo ello en el curso Bot de Trading de Criptomonedas en Python: Ingeniería para No Perder, donde el foco no es "hacerse rico", sino construir con ingeniería para no perder por fallos evitables.
Aviso: este artículo es material educativo de programación. No es asesoramiento financiero ni de inversión. El trading conlleva riesgo de pérdida total del capital. Practica siempre en modo papel o entorno de pruebas antes de operar con dinero real, y decide bajo tu propia responsabilidad. Para cualquier decisión de inversión o fiscal, consulta a un profesional cualificado.