- Implemented portfolio engine with risk-based allocation (50/50) - Added equity-based metrics for system-level evaluation - Validated portfolio against standalone strategies - Reduced max drawdown and volatility at system level - Quantitative decision closed before paper trading phase
161 lines
5.1 KiB
Python
161 lines
5.1 KiB
Python
# tests/test_walkforward.py
|
|
"""
|
|
Script para probar Walk-Forward Validation
|
|
Guarda resultados en CSV para análisis posterior
|
|
"""
|
|
import os
|
|
import sys
|
|
from dotenv import load_dotenv
|
|
from pathlib import Path
|
|
import pandas as pd
|
|
|
|
# Añadir raíz del proyecto al path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
from src.utils.logger import log
|
|
from src.data.storage import StorageManager
|
|
from src.strategies import MovingAverageCrossover
|
|
from src.core.walk_forward import WalkForwardValidator
|
|
|
|
|
|
def setup_environment():
|
|
"""Carga variables de entorno"""
|
|
env_path = Path(__file__).parent.parent / "config" / "secrets.env"
|
|
load_dotenv(dotenv_path=env_path)
|
|
log.success("✓ Variables de entorno cargadas")
|
|
|
|
|
|
def test_walkforward():
|
|
"""
|
|
Test de Walk-Forward Validation con múltiples configuraciones
|
|
"""
|
|
log.info("=" * 70)
|
|
log.info("🪜 TEST: WALK-FORWARD VALIDATION (MULTI CONFIG)")
|
|
log.info("=" * 70)
|
|
|
|
# --------------------------------------------------
|
|
# Setup
|
|
# --------------------------------------------------
|
|
setup_environment()
|
|
|
|
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"),
|
|
)
|
|
|
|
log.info("\n📥 Cargando datos...")
|
|
data = storage.load_ohlcv(
|
|
symbol="BTC/USDT",
|
|
timeframe="1h",
|
|
start_date=None,
|
|
end_date=None,
|
|
use_cache=False,
|
|
)
|
|
|
|
log.success(f"✓ Datos cargados: {len(data)} velas")
|
|
|
|
# --------------------------------------------------
|
|
# Grid de parámetros de la estrategia
|
|
# --------------------------------------------------
|
|
param_grid = {
|
|
"fast_period": [10, 15],
|
|
"slow_period": [30, 50],
|
|
"ma_type": ["sma"],
|
|
"use_adx": [True],
|
|
"adx_threshold": [20, 25, 30],
|
|
}
|
|
|
|
# --------------------------------------------------
|
|
# Configuraciones Walk-Forward a comparar
|
|
# --------------------------------------------------
|
|
wf_configs = [
|
|
{"name": "WF_12_3", "train_days": 365, "test_days": 90},
|
|
{"name": "WF_24_3", "train_days": 365 * 2, "test_days": 90},
|
|
{"name": "WF_24_6", "train_days": 365 * 2, "test_days": 180},
|
|
]
|
|
|
|
all_windows = []
|
|
summary_rows = []
|
|
|
|
# --------------------------------------------------
|
|
# Ejecutar Walk-Forward por configuración
|
|
# --------------------------------------------------
|
|
for cfg in wf_configs:
|
|
log.info("\n" + "=" * 70)
|
|
log.info(f"🧪 EJECUTANDO {cfg['name']}")
|
|
log.info("=" * 70)
|
|
|
|
wf = WalkForwardValidator(
|
|
strategy_class=MovingAverageCrossover,
|
|
param_grid=param_grid,
|
|
data=data,
|
|
train_window=pd.Timedelta(days=cfg["train_days"]),
|
|
test_window=pd.Timedelta(days=cfg["test_days"]),
|
|
initial_capital=10_000,
|
|
commission=0.001,
|
|
slippage=0.0005,
|
|
position_size=0.95,
|
|
optimizer_metric="sharpe_ratio",
|
|
)
|
|
|
|
wf_result = wf.run()
|
|
|
|
# -------------------------------
|
|
# Validaciones básicas
|
|
# -------------------------------
|
|
assert isinstance(wf_result, dict), "wf_result debe ser dict"
|
|
assert "windows" in wf_result, "wf_result debe contener 'windows'"
|
|
|
|
df_windows = wf_result["windows"].copy()
|
|
df_windows["wf_name"] = cfg["name"]
|
|
|
|
all_windows.append(df_windows)
|
|
|
|
# -------------------------------
|
|
# Métricas agregadas por WF
|
|
# -------------------------------
|
|
summary_rows.append({
|
|
"wf_name": cfg["name"],
|
|
"train_days": cfg["train_days"],
|
|
"test_days": cfg["test_days"],
|
|
"windows": len(df_windows),
|
|
"avg_return_pct": df_windows["return_pct"].mean(),
|
|
"avg_sharpe": df_windows["sharpe"].mean(),
|
|
"avg_max_dd_pct": df_windows["max_dd_pct"].mean(),
|
|
"avg_trades": df_windows["trades"].mean(),
|
|
})
|
|
|
|
# --------------------------------------------------
|
|
# Consolidar resultados
|
|
# --------------------------------------------------
|
|
df_all_windows = pd.concat(all_windows, ignore_index=True)
|
|
df_summary = pd.DataFrame(summary_rows)
|
|
|
|
# --------------------------------------------------
|
|
# Guardar CSVs
|
|
# --------------------------------------------------
|
|
output_dir = Path("backtest_results/walkforward")
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
windows_path = output_dir / "walkforward_windows.csv"
|
|
summary_path = output_dir / "walkforward_summary.csv"
|
|
|
|
df_all_windows.to_csv(windows_path, index=False)
|
|
df_summary.to_csv(summary_path, index=False)
|
|
|
|
log.success(f"💾 CSV ventanas guardado: {windows_path}")
|
|
log.success(f"💾 CSV resumen guardado: {summary_path}")
|
|
|
|
print("\n📊 RESUMEN WALK-FORWARD:")
|
|
print(df_summary.to_string(index=False))
|
|
|
|
storage.close()
|
|
log.success("\n✅ TEST WALK-FORWARD COMPLETADO")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test_walkforward()
|