✅ 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
210 lines
6.3 KiB
Python
210 lines
6.3 KiB
Python
# 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") |