Diseñar arquitectura del bot con módulos separados

Lectura
20 min~4 min lectura

Concepto clave

La arquitectura modular en bots de trading es similar a un equipo de especialistas en una sala de operaciones financiera. En lugar de tener una sola persona haciendo todo (desde analizar datos hasta ejecutar órdenes), divides las responsabilidades en módulos especializados que trabajan juntos. Cada módulo tiene una función específica y se comunica con los demás a través de interfaces claras.

Esta separación permite mantenibilidad (puedes actualizar un módulo sin afectar los demás), escalabilidad (puedes añadir nuevas estrategias o APIs fácilmente) y testabilidad (puedes probar cada componente por separado). Imagina un coche: el motor, la transmisión y los frenos son módulos separados que funcionan juntos; si falla uno, no tienes que desmontar todo el vehículo.

Cómo funciona en la práctica

Vamos a diseñar un bot con cinco módulos principales que se comunican en un flujo secuencial:

  1. DataFetcher: Obtiene datos de mercado de Binance API (ej., precios, volumen).
  2. Strategy: Analiza los datos y genera señales de compra/venta basadas en reglas predefinidas.
  3. RiskManager: Evalúa el riesgo de cada señal (ej., límites de posición, stop-loss).
  4. OrderExecutor: Envía órdenes a Binance si la señal pasa el filtro de riesgo.
  5. Logger: Registra todas las acciones y resultados para análisis posterior.

Estos módulos se organizan en un bucle principal que se ejecuta periódicamente (ej., cada minuto). La clave es que cada módulo solo conoce su propia tarea y se comunica a través de objetos de datos simples (como diccionarios o clases).

Código en acción

Aquí tienes un ejemplo básico de la estructura modular en Python. Primero, el antes: un script monolítico que hace todo en una sola función:

# Antes: Código monolítico difícil de mantener
def run_bot():
    # Obtener datos
    data = fetch_from_binance('BTCUSDT', '1m')
    # Analizar estrategia
    signal = simple_moving_average_strategy(data)
    # Gestionar riesgo
    if check_risk(signal):
        # Ejecutar orden
        execute_order(signal)
    # Loggear
    log_to_file(signal)

Ahora, el después: refactorizado en módulos separados:

# Después: Arquitectura modular clara
class DataFetcher:
    def fetch(self, symbol, interval):
        from binance.client import Client
        client = Client(api_key, api_secret)
        klines = client.get_klines(symbol=symbol, interval=interval)
        return [{"close": float(k[4])} for k in klines]

class Strategy:
    def analyze(self, data):
        closes = [d["close"] for d in data]
        sma = sum(closes[-20:]) / 20
        current_price = closes[-1]
        return "BUY" if current_price > sma else "SELL"

class RiskManager:
    def validate(self, signal, current_positions):
        max_position = 0.1  # Máximo 10% del capital
        return len(current_positions) < max_position

class OrderExecutor:
    def execute(self, signal, symbol, quantity):
        from binance.client import Client
        client = Client(api_key, api_secret)
        if signal == "BUY":
            order = client.order_market_buy(symbol=symbol, quantity=quantity)
        else:
            order = client.order_market_sell(symbol=symbol, quantity=quantity)
        return order

class Logger:
    def log(self, event):
        with open('bot_log.txt', 'a') as f:
            f.write(f"{event}\n")

# Bucle principal que integra los módulos
def main():
    fetcher = DataFetcher()
    strategy = Strategy()
    risk = RiskManager()
    executor = OrderExecutor()
    logger = Logger()
    
    while True:
        data = fetcher.fetch('BTCUSDT', '1m')
        signal = strategy.analyze(data)
        if risk.validate(signal, []):
            order = executor.execute(signal, 'BTCUSDT', 0.001)
            logger.log(f"Orden ejecutada: {order}")
        time.sleep(60)

Errores comunes

  • Acoplamiento fuerte entre módulos: Evita que un módulo dependa de detalles internos de otro. Usa interfaces abstractas o clases base.
  • Falta de manejo de errores: Cada módulo debe gestionar sus propias excepciones (ej., fallos de API en DataFetcher) y propagar errores claros.
  • Comunicación excesiva: No pases datos innecesarios entre módulos; solo lo esencial para reducir complejidad.
  • Ignorar la concurrencia: En bots avanzados, múltiples módulos pueden ejecutarse en paralelo; usa threading o asyncio con cuidado.
  • Olvidar el logging: Sin un Logger robusto, será imposible depurar o auditar el bot en producción.

Checklist de dominio

  1. ¿Puedes añadir una nueva estrategia sin modificar el módulo OrderExecutor?
  2. ¿Cada módulo tiene una responsabilidad única y clara?
  3. ¿La comunicación entre módulos usa estructuras de datos simples (ej., dicts, dataclasses)?
  4. ¿Hay manejo de errores específico en cada módulo?
  5. ¿El bot puede escalar para manejar múltiples símbolos o intervalos?
  6. ¿El logging captura suficientes detalles para análisis post-ejecución?
  7. ¿Los módulos son testables de forma aislada (ej., con datos mock)?

Refactorizar un bot monolítico en arquitectura modular

En este ejercicio, tomarás un script de bot de trading existente (monolítico) y lo refactorizarás en una arquitectura modular. Sigue estos pasos:

  1. Descarga el script inicial desde https://ejemplo.com/bot_monolitico.py (simula un bot que obtiene datos, aplica una estrategia RSI, y ejecuta órdenes en una sola función).
  2. Analiza el código y identifica las responsabilidades que pueden separarse en módulos (ej., DataFetcher, Strategy, RiskManager, OrderExecutor, Logger).
  3. Crea un nuevo archivo bot_modular.py y define clases para cada módulo, basándote en el ejemplo de la lección.
  4. Implementa la lógica de cada módulo, asegurándote de que solo expongan métodos públicos necesarios (ej., fetch(), analyze()).
  5. Diseña un bucle principal que instancie los módulos y los coordine en secuencia (obtener datos → analizar → gestionar riesgo → ejecutar → loggear).
  6. Prueba tu bot modular con datos mock (usa una lista de precios falsos) para verificar que los módulos funcionan juntos sin errores.
  7. Documenta los cambios en un README breve, explicando las ventajas de la nueva arquitectura.
Pistas
  • Comienza por extraer la lógica de obtención de datos en una clase DataFetcher; usa la biblioteca requests o binance-python si es necesario.
  • Asegúrate de que los módulos se comuniquen a través de parámetros y return values, no variables globales.
  • Para probar sin conexión a Binance, mockea la API en DataFetcher devolviendo datos estáticos.

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.