Integración con Brokers
Comparativa de brokers recomendados para trading cuantitativo y sus casos de uso específicos.
📊 Comparativa de Brokers
| Broker | Mejor Para | API Quality | Comisiones | Data Quality |
|---|---|---|---|---|
| IBKR | All-around, institucional | ⭐⭐⭐⭐⭐ | Muy bajas | ⭐⭐⭐⭐⭐ |
| Alpaca | Desarrollo, algoritmos | ⭐⭐⭐⭐⭐ | Sin comisiones | ⭐⭐⭐⭐ |
| Charles Schwab | Retail, 401k | ⭐⭐⭐ | Sin comisiones | ⭐⭐⭐⭐ |
| TD Ameritrade | Retail avanzado | ⭐⭐⭐⭐ | Sin comisiones | ⭐⭐⭐⭐ |
💻 Plataformas de Trading
| Plataforma | Compatible con | Mejor Para | Características Clave |
|---|---|---|---|
| DAS Trader Pro | Charles Schwab, Zimtra, otros | Day trading, small caps | Level 2, routing avanzado, hotkeys |
| TWS (IBKR) | Interactive Brokers | Trading institucional | API robusta, datos globales |
| Think or Swim | TD Ameritrade | Análisis técnico | Charting avanzado, paper trading |
| Webull Desktop | Webull | Trading retail | Gratis, análisis básico |
Interactive Brokers (IBKR) - Recomendado Principal
Ventajas de IBKR
- Comisiones bajas: $0.005 por acción, mínimo $1 por trade
- API robusta: TWS API con múltiples lenguajes
- Datos en tiempo real: Level 1 y Level 2 market data
- Short selling: Amplio inventario para locates
- Margin requirements: Competitive margin rates
- Global access: Acceso a múltiples mercados
Setup de TWS (Trader Workstation)
# config/ibkr_setup.py
from ib_insync import IB, Stock, MarketOrder, LimitOrder
import asyncio
import logging
class IBKRConnection:
def __init__(self, host='localhost', port=7497, client_id=1):
self.ib = IB()
self.host = host
self.port = port
self.client_id = client_id
self.connected = False
def connect(self):
"""Conectar a TWS"""
try:
self.ib.connect(self.host, self.port, clientId=self.client_id)
self.connected = True
logging.info(f"✅ Conectado a IBKR TWS en {self.host}:{self.port}")
return True
except Exception as e:
logging.error(f"❌ Error conectando a IBKR: {e}")
return False
def disconnect(self):
"""Desconectar de TWS"""
if self.connected:
self.ib.disconnect()
self.connected = False
logging.info("🔌 Desconectado de IBKR TWS")
def get_account_info(self):
"""Obtener información de cuenta"""
if not self.connected:
return None
account_values = self.ib.accountValues()
portfolio = self.ib.portfolio()
positions = self.ib.positions()
return {
'account_values': account_values,
'portfolio': portfolio,
'positions': positions,
'buying_power': self.get_buying_power(),
'total_liquidity': self.get_total_liquidity()
}
def get_buying_power(self):
"""Obtener buying power disponible"""
account_values = self.ib.accountValues()
for item in account_values:
if item.tag == 'BuyingPower':
return float(item.value)
return 0
def get_total_liquidity(self):
"""Obtener liquidez total"""
account_values = self.ib.accountValues()
for item in account_values:
if item.tag == 'TotalCashValue':
return float(item.value)
return 0
def get_market_data(self, symbol, exchange='SMART'):
"""Obtener market data en tiempo real"""
contract = Stock(symbol, exchange)
self.ib.reqMktData(contract)
# Esperar a que lleguen los datos
self.ib.sleep(2)
ticker = self.ib.ticker(contract)
return {
'symbol': symbol,
'bid': ticker.bid,
'ask': ticker.ask,
'last': ticker.last,
'volume': ticker.volume,
'bid_size': ticker.bidSize,
'ask_size': ticker.askSize
}
def place_market_order(self, symbol, quantity, action='BUY'):
"""Colocar orden de mercado"""
contract = Stock(symbol, 'SMART')
order = MarketOrder(action, abs(quantity))
trade = self.ib.placeOrder(contract, order)
return {
'trade': trade,
'order_id': trade.order.orderId,
'status': trade.orderStatus.status
}
def place_limit_order(self, symbol, quantity, price, action='BUY'):
"""Colocar orden límite"""
contract = Stock(symbol, 'SMART')
order = LimitOrder(action, abs(quantity), price)
trade = self.ib.placeOrder(contract, order)
return {
'trade': trade,
'order_id': trade.order.orderId,
'status': trade.orderStatus.status
}
def cancel_order(self, order_id):
"""Cancelar orden"""
try:
self.ib.cancelOrder(order_id)
return True
except Exception as e:
logging.error(f"Error cancelando orden {order_id}: {e}")
return False
def get_historical_data(self, symbol, duration='1 Y', bar_size='1 day'):
"""Obtener datos históricos"""
contract = Stock(symbol, 'SMART')
bars = self.ib.reqHistoricalData(
contract,
endDateTime='',
durationStr=duration,
barSizeSetting=bar_size,
whatToShow='TRADES',
useRTH=True
)
# Convertir a DataFrame
if bars:
df = pd.DataFrame([{
'date': bar.date,
'open': bar.open,
'high': bar.high,
'low': bar.low,
'close': bar.close,
'volume': bar.volume
} for bar in bars])
df.set_index('date', inplace=True)
return df
else:
return pd.DataFrame() # Return empty DataFrame if no data
# Ejemplo de uso
def test_ibkr_connection():
"""Test de conexión IBKR"""
ibkr = IBKRConnection()
if ibkr.connect():
# Test account info
account_info = ibkr.get_account_info()
print(f"💰 Buying Power: ${account_info['buying_power']:,.2f}")
# Test market data
spy_data = ibkr.get_market_data('SPY')
print(f"📊 SPY: Bid={spy_data['bid']}, Ask={spy_data['ask']}")
# Test historical data
historical = ibkr.get_historical_data('SPY', '1 M', '1 hour')
print(f"📈 Historical data: {len(historical)} bars")
ibkr.disconnect()
return True
return False
DAS Trader Pro - Plataforma para Day Trading
🔥 Nuevo: Integración disponible a través del das-bridge
Nota importante: DAS Trader Pro es una plataforma de trading, no un broker. Se conecta a brokers como Charles Schwab, Zimtra, Lightspeed, y otros que ofrecen compatibilidad con DAS.
¿Por Qué Usar DAS como Plataforma?
- Borrows amplios para short selling de small caps
- Routing avanzado (ARCA, EDGX, BATS) para mejor fill
- Level 2 premium con Times & Sales profundo
- Hotkeys configurables para ejecución ultra-rápida
- Comunidad activa de day traders profesionales
Casos de Uso Ideales
- Day trading agresivo en small caps
- Short selling de pump & dumps
- Scalping con volumen alto
- Trading con buying power 4:1 o 6:1
Setup Básico DAS Bridge
# Instalar el bridge
git clone https://github.com/jefrnc/das-bridge.git
cd das-bridge
pip install -r requirements.txt
pip install -e .
# config/das_config.py
import os
DAS_CONFIG = {
'host': os.getenv('DAS_HOST', 'localhost'),
'port': int(os.getenv('DAS_PORT', '9910')),
'username': os.getenv('DAS_USERNAME'),
'password': os.getenv('DAS_PASSWORD'),
'account': os.getenv('DAS_ACCOUNT'),
'paper_trading': True # Cambiar a False para live trading
}
# Límites de riesgo específicos para DAS
DAS_RISK_LIMITS = {
'max_position_size': 10000, # $10k máximo por posición
'max_daily_loss': 1000, # $1k pérdida máxima diaria
'max_buying_power_usage': 0.8, # 80% del buying power máximo
'max_trades_per_minute': 5 # Límite de velocidad
}
Ejemplo de Integración
# examples/das_trading_basic.py
import asyncio
from das_trader import DASTraderClient
async def das_example():
"""Ejemplo básico con DAS"""
client = DASTraderClient(host="localhost", port=9910)
try:
# Conectar
success = await client.connect("usuario", "password", "cuenta")
if not success:
print("❌ Error conectando a DAS")
return
print("✅ Conectado a DAS Trader")
# Obtener buying power
bp = await client.get_buying_power()
print(f"💰 Buying Power: ${bp:,.2f}")
# Suscribirse a quote
await client.subscribe_quote("AAPL")
# Ejemplo de orden (comentado para seguridad)
# order = await client.send_order("AAPL", "BUY", 100, "MARKET")
# print(f"📝 Orden enviada: {order.order_id}")
finally:
await client.disconnect()
if __name__ == "__main__":
asyncio.run(das_example())
📖 Documentación completa: Ver DAS Trader Integration para setup avanzado.
Configuración TWS
# scripts/setup_tws.py
"""
Script para configurar TWS para API trading
"""
TWS_CONFIGURATION = """
⚙️ Configuración Manual de TWS:
1. 📥 Descargar TWS:
- Ir a https://www.interactivebrokers.com/en/trading/tws.php
- Descargar Trader Workstation
2. 🔧 Configurar API:
- Abrir TWS
- Ir a Configure → API → Settings
- ✅ Enable ActiveX and Socket Clients
- ✅ Read-Only API
- Socket port: 7497 (paper) / 7496 (live)
- Master API client ID: 0
- ✅ Download open orders on connection
3. 🔐 Configurar Paper Trading:
- Ir a Configure → API → Settings
- ✅ Enable API
- Puerto: 7497
- IP permitidas: 127.0.0.1
4. ⚡ Market Data:
- Ir a Configure → Market Data Subscriptions
- Activar US Securities Snapshot and Futures Value Bundle (gratis)
- Para Level 2: US Equity and Options Add-On Streaming Bundle
5. 🚨 Configurar Alertas:
- Configure → Alerts
- ✅ Enable Popup alerts
- ✅ Email alerts (opcional)
⚠️ IMPORTANTE:
- Usar Paper Trading account inicialmente
- TWS debe estar abierto y conectado para usar API
- Verificar que el puerto esté correcto (7497 paper / 7496 live)
"""
print(TWS_CONFIGURATION)
Alpaca - Alternative Broker
Ventajas de Alpaca
- Commission-free: Sin comisiones en stocks
- API-first: Diseñado para trading algorítmico
- Paper trading: Sandbox environment robusto
- Modern REST API: Fácil de usar
- Real-time data: WebSocket feeds
Setup Alpaca
# config/alpaca_setup.py
import alpaca_trade_api as tradeapi
from datetime import datetime, timedelta
import pandas as pd
class AlpacaConnection:
def __init__(self, api_key, secret_key, base_url='https://paper-api.alpaca.markets'):
self.api = tradeapi.REST(api_key, secret_key, base_url, api_version='v2')
self.base_url = base_url
def get_account(self):
"""Obtener información de cuenta"""
account = self.api.get_account()
return {
'account_id': account.id,
'buying_power': float(account.buying_power),
'cash': float(account.cash),
'portfolio_value': float(account.portfolio_value),
'equity': float(account.equity),
'day_trade_count': account.daytrade_count,
'pattern_day_trader': account.pattern_day_trader
}
def get_positions(self):
"""Obtener posiciones actuales"""
positions = self.api.list_positions()
position_data = []
for pos in positions:
position_data.append({
'symbol': pos.symbol,
'qty': int(pos.qty),
'side': 'long' if int(pos.qty) > 0 else 'short',
'market_value': float(pos.market_value),
'cost_basis': float(pos.cost_basis),
'unrealized_pl': float(pos.unrealized_pl),
'unrealized_plpc': float(pos.unrealized_plpc),
'avg_entry_price': float(pos.avg_entry_price)
})
return position_data
def get_orders(self, status='all', limit=100):
"""Obtener órdenes"""
orders = self.api.list_orders(status=status, limit=limit)
order_data = []
for order in orders:
order_data.append({
'id': order.id,
'symbol': order.symbol,
'qty': int(order.qty),
'side': order.side,
'order_type': order.order_type,
'time_in_force': order.time_in_force,
'status': order.status,
'filled_qty': int(order.filled_qty or 0),
'limit_price': float(order.limit_price) if order.limit_price else None,
'stop_price': float(order.stop_price) if order.stop_price else None,
'submitted_at': order.submitted_at
})
return order_data
def place_order(self, symbol, qty, side, order_type='market',
limit_price=None, stop_price=None, time_in_force='day'):
"""Colocar orden"""
order_params = {
'symbol': symbol,
'qty': abs(qty),
'side': side,
'type': order_type,
'time_in_force': time_in_force
}
if order_type == 'limit' and limit_price:
order_params['limit_price'] = limit_price
elif order_type == 'stop' and stop_price:
order_params['stop_price'] = stop_price
elif order_type == 'stop_limit' and limit_price and stop_price:
order_params['limit_price'] = limit_price
order_params['stop_price'] = stop_price
try:
order = self.api.submit_order(**order_params)
return {
'success': True,
'order_id': order.id,
'status': order.status,
'symbol': order.symbol,
'qty': int(order.qty),
'side': order.side
}
except Exception as e:
return {
'success': False,
'error': str(e)
}
def cancel_order(self, order_id):
"""Cancelar orden"""
try:
self.api.cancel_order(order_id)
return True
except Exception as e:
print(f"Error cancelando orden: {e}")
return False
def get_historical_data(self, symbol, timeframe='1Day', start=None, end=None):
"""Obtener datos históricos"""
if not start:
start = datetime.now() - timedelta(days=365)
if not end:
end = datetime.now()
# Convertir a formato Alpaca
start_str = start.strftime('%Y-%m-%d')
end_str = end.strftime('%Y-%m-%d')
barset = self.api.get_bars(
symbol,
timeframe,
start=start_str,
end=end_str,
adjustment='raw'
)
# Convertir a DataFrame
data = []
for bar in barset:
data.append({
'timestamp': bar.t,
'open': bar.o,
'high': bar.h,
'low': bar.l,
'close': bar.c,
'volume': bar.v
})
df = pd.DataFrame(data)
df.set_index('timestamp', inplace=True)
return df
def get_quote(self, symbol):
"""Obtener quote actual"""
try:
quote = self.api.get_latest_quote(symbol)
return {
'symbol': symbol,
'bid': quote.bid_price,
'ask': quote.ask_price,
'bid_size': quote.bid_size,
'ask_size': quote.ask_size,
'timestamp': quote.timestamp
}
except Exception as e:
print(f"Error obteniendo quote para {symbol}: {e}")
return None
# Test de conexión Alpaca
def test_alpaca_connection():
"""Test de conexión Alpaca"""
from config.api_keys import APIKeys
if not APIKeys.ALPACA_API_KEY or not APIKeys.ALPACA_SECRET_KEY:
print("❌ API Keys de Alpaca no configuradas")
return False
alpaca = AlpacaConnection(
APIKeys.ALPACA_API_KEY,
APIKeys.ALPACA_SECRET_KEY,
APIKeys.ALPACA_BASE_URL
)
try:
# Test account
account = alpaca.get_account()
print(f"✅ Cuenta Alpaca conectada")
print(f"💰 Buying Power: ${account['buying_power']:,.2f}")
print(f"📊 Portfolio Value: ${account['portfolio_value']:,.2f}")
# Test quote
quote = alpaca.get_quote('SPY')
if quote:
print(f"📈 SPY Quote: ${quote['bid']} x ${quote['ask']}")
# Test historical data
historical = alpaca.get_historical_data('SPY', '1Day')
print(f"📊 Historical data: {len(historical)} days")
return True
except Exception as e:
print(f"❌ Error en conexión Alpaca: {e}")
return False
TD Ameritrade - Backup Option
Setup TD Ameritrade
# config/td_setup.py
import requests
import json
from datetime import datetime, timedelta
class TDAConnection:
def __init__(self, api_key, refresh_token=None):
self.api_key = api_key
self.refresh_token = refresh_token
self.access_token = None
self.base_url = 'https://api.tdameritrade.com/v1'
def authenticate(self):
"""Autenticar con TD Ameritrade"""
if self.refresh_token:
return self._refresh_access_token()
else:
print("⚠️ Necesitas configurar OAuth flow para TD Ameritrade")
return False
def _refresh_access_token(self):
"""Renovar access token"""
url = f"{self.base_url}/oauth2/token"
data = {
'grant_type': 'refresh_token',
'refresh_token': self.refresh_token,
'client_id': self.api_key
}
response = requests.post(url, data=data)
if response.status_code == 200:
token_data = response.json()
self.access_token = token_data['access_token']
return True
else:
print(f"❌ Error renovando token: {response.status_code}")
return False
def get_quote(self, symbol):
"""Obtener quote"""
if not self.access_token:
return None
url = f"{self.base_url}/marketdata/quotes"
headers = {'Authorization': f'Bearer {self.access_token}'}
params = {'symbol': symbol}
response = requests.get(url, headers=headers, params=params)
if response.status_code == 200:
data = response.json()[symbol]
return {
'symbol': symbol,
'bid': data['bidPrice'],
'ask': data['askPrice'],
'last': data['lastPrice'],
'volume': data['totalVolume']
}
return None
def get_historical_data(self, symbol, period_type='year', period=1,
frequency_type='daily', frequency=1):
"""Obtener datos históricos"""
if not self.access_token:
return None
url = f"{self.base_url}/marketdata/{symbol}/pricehistory"
headers = {'Authorization': f'Bearer {self.access_token}'}
params = {
'periodType': period_type,
'period': period,
'frequencyType': frequency_type,
'frequency': frequency
}
response = requests.get(url, headers=headers, params=params)
if response.status_code == 200:
data = response.json()
candles = data['candles']
# Convertir a DataFrame
df_data = []
for candle in candles:
df_data.append({
'timestamp': pd.to_datetime(candle['datetime'], unit='ms'),
'open': candle['open'],
'high': candle['high'],
'low': candle['low'],
'close': candle['close'],
'volume': candle['volume']
})
df = pd.DataFrame(df_data)
df.set_index('timestamp', inplace=True)
return df
return None
Unified Broker Interface
Abstraction Layer
# src/execution/broker_interface.py
from abc import ABC, abstractmethod
from typing import Dict, List, Optional
from dataclasses import dataclass
@dataclass
class OrderRequest:
"""Estructura de orden unificada"""
symbol: str
quantity: int
side: str # 'buy' or 'sell'
order_type: str # 'market', 'limit', 'stop'
limit_price: Optional[float] = None
stop_price: Optional[float] = None
time_in_force: str = 'day'
@dataclass
class Position:
"""Estructura de posición unificada"""
symbol: str
quantity: int
avg_price: float
market_value: float
unrealized_pnl: float
class BrokerInterface(ABC):
"""Interface abstracta para brokers"""
@abstractmethod
def connect(self) -> bool:
"""Conectar al broker"""
pass
@abstractmethod
def disconnect(self):
"""Desconectar del broker"""
pass
@abstractmethod
def get_account_info(self) -> Dict:
"""Obtener información de cuenta"""
pass
@abstractmethod
def get_positions(self) -> List[Position]:
"""Obtener posiciones actuales"""
pass
@abstractmethod
def place_order(self, order: OrderRequest) -> Dict:
"""Colocar orden"""
pass
@abstractmethod
def cancel_order(self, order_id: str) -> bool:
"""Cancelar orden"""
pass
@abstractmethod
def get_market_data(self, symbol: str) -> Dict:
"""Obtener market data"""
pass
class UnifiedBroker:
"""Broker unificado que maneja múltiples brokers"""
def __init__(self):
self.brokers = {}
self.primary_broker = None
def add_broker(self, name: str, broker: BrokerInterface, is_primary=False):
"""Agregar broker"""
self.brokers[name] = broker
if is_primary:
self.primary_broker = name
def execute_order(self, order: OrderRequest, broker_name=None):
"""Ejecutar orden en broker específico o primario"""
broker_name = broker_name or self.primary_broker
if broker_name not in self.brokers:
return {'success': False, 'error': f'Broker {broker_name} no encontrado'}
try:
result = self.brokers[broker_name].place_order(order)
return result
except Exception as e:
# Fallback a otro broker si falla
for backup_name, backup_broker in self.brokers.items():
if backup_name != broker_name:
try:
result = backup_broker.place_order(order)
result['executed_on_backup'] = backup_name
return result
except:
continue
return {'success': False, 'error': str(e)}
def get_consolidated_positions(self):
"""Obtener posiciones consolidadas de todos los brokers"""
all_positions = {}
for broker_name, broker in self.brokers.items():
try:
positions = broker.get_positions()
all_positions[broker_name] = positions
except Exception as e:
print(f"Error obteniendo posiciones de {broker_name}: {e}")
return all_positions
Esta infraestructura de brokers te permitirá comenzar con paper trading en IBKR o Alpaca, y luego expandir a trading real con múltiples brokers según tus necesidades.