Add walk-forward validation with optimizer, OOS evaluation and visualizer
This commit is contained in:
@@ -1,16 +1,19 @@
|
||||
# dam_test.py
|
||||
"""
|
||||
Script para probar el optimizador de parámetros
|
||||
Script para probar cositas
|
||||
"""
|
||||
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.data.storage import StorageManager
|
||||
from src.backtest.walk_forward import WalkForwardValidator
|
||||
from src.strategies import MovingAverageCrossover
|
||||
|
||||
def setup_environment():
|
||||
"""Carga variables de entorno"""
|
||||
@@ -38,9 +41,19 @@ def dam_test():
|
||||
use_cache=False
|
||||
)
|
||||
|
||||
print(data.columns)
|
||||
wf = WalkForwardValidator(
|
||||
strategy_class=MovingAverageCrossover,
|
||||
param_grid={},
|
||||
data=data,
|
||||
train_window=pd.Timedelta(days=365),
|
||||
test_window=pd.Timedelta(days=90),
|
||||
)
|
||||
|
||||
print(data[['close', 'adx']].tail(10))
|
||||
windows = wf._generate_windows()
|
||||
|
||||
print(f"Ventanas generadas: {len(windows)}")
|
||||
for w in windows[:3]:
|
||||
print(w)
|
||||
|
||||
if __name__ == "__main__":
|
||||
dam_test()
|
||||
@@ -15,7 +15,7 @@ from src.utils.logger import log
|
||||
from src.data.storage import StorageManager
|
||||
from src.strategies import MovingAverageCrossover
|
||||
from src.backtest import BacktestEngine
|
||||
from src.backtest.visualizer import BacktestVisualizer
|
||||
from src.backtest.visualizers.visualizer import BacktestVisualizer
|
||||
|
||||
def setup_environment():
|
||||
"""Carga variables de entorno"""
|
||||
@@ -83,9 +83,9 @@ def test_visualizer():
|
||||
viz = BacktestVisualizer(results, data)
|
||||
|
||||
# Generar todos los gráficos
|
||||
viz.generate_all_plots('backtest_results')
|
||||
viz.generate_all_plots()
|
||||
|
||||
log.info("\n💡 Los gráficos se guardaron en: backtest_results/")
|
||||
log.info("\n💡 Los gráficos se guardaron en: backtest_results/visualizer")
|
||||
log.info(" Archivos generados:")
|
||||
log.info(" - equity_curve.png")
|
||||
log.info(" - drawdown.png")
|
||||
|
||||
160
tests/test_walkforwad.py
Normal file
160
tests/test_walkforwad.py
Normal file
@@ -0,0 +1,160 @@
|
||||
# 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.backtest.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()
|
||||
40
tests/test_wf_visualizer.py
Normal file
40
tests/test_wf_visualizer.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# tests/test_wf_visualizer.py
|
||||
|
||||
import sys
|
||||
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.backtest.visualizers.walk_forward_visualizer import WalkForwardVisualizer
|
||||
|
||||
|
||||
def test_wf_visualizer():
|
||||
"""
|
||||
Test del WalkForwardVisualizer usando los CSV existentes
|
||||
"""
|
||||
|
||||
base_path = Path("backtest_results/walkforward")
|
||||
|
||||
summary_df = pd.read_csv(base_path / "walkforward_summary.csv")
|
||||
windows_df = pd.read_csv(base_path / "walkforward_windows.csv")
|
||||
|
||||
viz = WalkForwardVisualizer(
|
||||
summary_df=summary_df,
|
||||
windows_df=windows_df,
|
||||
name="BTC/USDT – MA + ADX"
|
||||
)
|
||||
|
||||
# 📊 Plots
|
||||
viz.plot_avg_metrics()
|
||||
viz.plot_returns_by_window()
|
||||
viz.plot_drawdown_by_window()
|
||||
viz.plot_return_distribution()
|
||||
viz.plot_parameter_stability("fast_period")
|
||||
viz.plot_parameter_stability("slow_period")
|
||||
viz.plot_parameter_stability("adx_threshold")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_wf_visualizer()
|
||||
Reference in New Issue
Block a user