feat: Backtesting engine completo + documentación (Semanas 3-4)
✅ Motor de backtesting: - BacktestEngine con simulación de trades - Sistema de Trade y Position - Gestión de capital y comisiones - Slippage simulado ✅ Estrategias implementadas: - MovingAverageCrossover (SMA/EMA configurable) - RSIStrategy (umbrales personalizables) - BuyAndHold (baseline) ✅ Métricas de performance: - Sharpe Ratio, Sortino Ratio, Calmar Ratio - Max Drawdown, Win Rate, Profit Factor - Expectancy, Risk/Reward Ratio ✅ Scripts: - backtest.py: Ejecutar backtests individuales - backtest.py compare: Comparar múltiples estrategias ✅ Documentación: - README actualizado con sección de backtesting - Ejemplos de uso programático - Estructura de proyecto actualizada
This commit is contained in:
210
backtest.py
210
backtest.py
@@ -0,0 +1,210 @@
|
||||
# backtest.py
|
||||
"""
|
||||
Script principal para ejecutar backtests
|
||||
"""
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from src.utils.logger import log
|
||||
from src.data.storage import StorageManager
|
||||
from src.backtest.engine import BacktestEngine
|
||||
from src.backtest.metrics import print_backtest_report, calculate_all_metrics
|
||||
from src.strategies import MovingAverageCrossover, BuyAndHold, RSIStrategy
|
||||
|
||||
def setup_environment():
|
||||
"""Carga variables de entorno"""
|
||||
env_path = Path(__file__).parent / 'config' / 'secrets.env'
|
||||
load_dotenv(dotenv_path=env_path)
|
||||
log.success("✓ Variables de entorno cargadas")
|
||||
|
||||
def run_backtest_demo():
|
||||
"""
|
||||
Demo de backtesting con estrategia simple
|
||||
"""
|
||||
log.info("="*70)
|
||||
log.info("🧪 BACKTESTING - DEMO")
|
||||
log.info("="*70)
|
||||
|
||||
# Setup
|
||||
setup_environment()
|
||||
|
||||
# Configuración del backtest
|
||||
symbol = 'BTC/USDT'
|
||||
timeframe = '1h'
|
||||
days_back = 60 # 2 meses de datos
|
||||
|
||||
log.info(f"\n📊 Configuración:")
|
||||
log.info(f" Símbolo: {symbol}")
|
||||
log.info(f" Timeframe: {timeframe}")
|
||||
log.info(f" Periodo: {days_back} días")
|
||||
|
||||
# Conectar a base de datos
|
||||
storage = StorageManager(
|
||||
db_host=os.getenv('DB_HOST'),
|
||||
db_port=int(os.getenv('DB_PORT', 5432)),
|
||||
db_name=os.getenv('DB_NAME'),
|
||||
db_user=os.getenv('DB_USER'),
|
||||
db_password=os.getenv('DB_PASSWORD'),
|
||||
)
|
||||
|
||||
# Cargar datos
|
||||
log.info("\n📥 Cargando datos desde PostgreSQL...")
|
||||
end_date = datetime.now()
|
||||
start_date = end_date - timedelta(days=days_back)
|
||||
|
||||
data = storage.load_ohlcv(
|
||||
symbol=symbol,
|
||||
timeframe=timeframe,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
use_cache=False
|
||||
)
|
||||
|
||||
if data.empty:
|
||||
log.error(f"❌ No hay datos disponibles para {symbol} {timeframe}")
|
||||
log.info("💡 Ejecuta primero: python download_data.py")
|
||||
return
|
||||
|
||||
log.success(f"✓ Datos cargados: {len(data)} velas")
|
||||
log.info(f" Desde: {data.index[0]}")
|
||||
log.info(f" Hasta: {data.index[-1]}")
|
||||
|
||||
# Crear estrategia
|
||||
strategy = MovingAverageCrossover(
|
||||
fast_period=10,
|
||||
slow_period=30,
|
||||
ma_type='sma'
|
||||
)
|
||||
|
||||
# Crear motor de backtesting
|
||||
engine = BacktestEngine(
|
||||
strategy=strategy,
|
||||
initial_capital=10000,
|
||||
commission=0.001, # 0.1%
|
||||
slippage=0.0005, # 0.05%
|
||||
position_size=0.95 # Usar 95% del capital
|
||||
)
|
||||
|
||||
# Ejecutar backtest
|
||||
log.info("\n🚀 Ejecutando backtest...")
|
||||
results = engine.run(data)
|
||||
|
||||
# Mostrar resultados
|
||||
print_backtest_report(results)
|
||||
|
||||
# Calcular métricas adicionales
|
||||
additional_metrics = calculate_all_metrics(results)
|
||||
|
||||
if additional_metrics:
|
||||
print("\n📈 MÉTRICAS ADICIONALES:")
|
||||
print(f" Sortino Ratio: {additional_metrics['sortino_ratio']:>12.2f}")
|
||||
print(f" Calmar Ratio: {additional_metrics['calmar_ratio']:>12.2f}")
|
||||
print(f" Expectancy: ${additional_metrics['expectancy']:>12,.2f}")
|
||||
print(f" Risk/Reward: {additional_metrics['risk_reward_ratio']:>12.2f}")
|
||||
print(f" Recovery Factor: {additional_metrics['recovery_factor']:>12.2f}")
|
||||
|
||||
# Mostrar algunos trades de ejemplo
|
||||
if results['trades']:
|
||||
print(f"\n📋 TRADES (primeros 5):")
|
||||
for i, trade in enumerate(results['trades'][:5], 1):
|
||||
print(f"\n Trade #{i}:")
|
||||
print(f" Entrada: {trade.entry_time} @ ${trade.entry_price:.2f}")
|
||||
print(f" Salida: {trade.exit_time} @ ${trade.exit_price:.2f}")
|
||||
print(f" PnL: ${trade.pnl:>10.2f} ({trade.pnl_percentage:>6.2f}%)")
|
||||
print(f" Duración: {trade.duration:.1f}h")
|
||||
|
||||
# Cleanup
|
||||
storage.close()
|
||||
|
||||
log.info("\n" + "="*70)
|
||||
log.success("✅ DEMO COMPLETADO")
|
||||
log.info("="*70)
|
||||
|
||||
return results
|
||||
|
||||
def compare_strategies_demo():
|
||||
"""
|
||||
Compara múltiples estrategias sobre los mismos datos
|
||||
"""
|
||||
log.info("="*70)
|
||||
log.info("🔍 COMPARACIÓN DE ESTRATEGIAS")
|
||||
log.info("="*70)
|
||||
|
||||
setup_environment()
|
||||
|
||||
# Configuración
|
||||
symbol = 'BTC/USDT'
|
||||
timeframe = '1h'
|
||||
days_back = 60
|
||||
|
||||
# Conectar a base de datos y cargar datos
|
||||
storage = StorageManager(
|
||||
db_host=os.getenv('DB_HOST'),
|
||||
db_port=int(os.getenv('DB_PORT', 5432)),
|
||||
db_name=os.getenv('DB_NAME'),
|
||||
db_user=os.getenv('DB_USER'),
|
||||
db_password=os.getenv('DB_PASSWORD'),
|
||||
)
|
||||
|
||||
end_date = datetime.now()
|
||||
start_date = end_date - timedelta(days=days_back)
|
||||
|
||||
data = storage.load_ohlcv(symbol, timeframe, start_date, end_date, use_cache=False)
|
||||
|
||||
if data.empty:
|
||||
log.error(f"❌ No hay datos disponibles")
|
||||
return
|
||||
|
||||
log.success(f"✓ Datos cargados: {len(data)} velas")
|
||||
|
||||
# Definir estrategias a comparar
|
||||
strategies = [
|
||||
('Buy & Hold', BuyAndHold()),
|
||||
('MA Cross (10/30 SMA)', MovingAverageCrossover(10, 30, 'sma')),
|
||||
('MA Cross (20/50 EMA)', MovingAverageCrossover(20, 50, 'ema')),
|
||||
('RSI (30/70)', RSIStrategy(14, 30, 70)),
|
||||
]
|
||||
|
||||
# Ejecutar backtest para cada estrategia
|
||||
all_results = {}
|
||||
|
||||
for name, strategy in strategies:
|
||||
log.info(f"\n🧪 Testeando: {name}")
|
||||
|
||||
engine = BacktestEngine(
|
||||
strategy=strategy,
|
||||
initial_capital=10000,
|
||||
commission=0.001,
|
||||
position_size=0.95
|
||||
)
|
||||
|
||||
results = engine.run(data)
|
||||
all_results[name] = results
|
||||
|
||||
log.info(f" Retorno: {results['total_return_pct']:.2f}%")
|
||||
log.info(f" Trades: {results['total_trades']}")
|
||||
log.info(f" Win Rate: {results['win_rate_pct']:.2f}%")
|
||||
|
||||
# Comparar resultados
|
||||
from src.backtest.metrics import compare_strategies
|
||||
compare_strategies(all_results)
|
||||
|
||||
storage.close()
|
||||
|
||||
log.success("✅ COMPARACIÓN COMPLETADA")
|
||||
|
||||
return all_results
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
if len(sys.argv) > 1 and sys.argv[1] == 'compare':
|
||||
# Modo comparación
|
||||
compare_strategies_demo()
|
||||
else:
|
||||
# Modo demo simple
|
||||
run_backtest_demo()
|
||||
|
||||
print("\n💡 TIP: Para comparar estrategias usa: python backtest.py compare")
|
||||
Reference in New Issue
Block a user