feat: finalize portfolio system and quantitative validation- Finalized MA_Crossover(30,100) and TrendFiltered_MA(30,100,ADX=15)

- 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
This commit is contained in:
DaM
2026-02-02 14:38:05 +01:00
parent c569170fcc
commit f85c522f22
53 changed files with 2389 additions and 104 deletions

View File

@@ -0,0 +1,186 @@
# scripts/research/compare_stops.py
import os
import sys
from pathlib import Path
from dotenv import load_dotenv
from datetime import datetime, timedelta
import pandas as pd
import matplotlib.pyplot as plt
from loguru import logger
# Añadir raíz del proyecto al path
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from src.utils.logger import log
from src.data.storage import StorageManager
from src.core.engine import Engine
from src.strategies import MovingAverageCrossover
from src.risk.sizing.fixed import FixedPositionSizer
from src.risk.stops.fixed_stop import FixedStop
from src.risk.stops.trailing_stop import TrailingStop
from src.risk.stops.atr_stop import ATRStop
# --------------------------------------------------
# Configuración común
# --------------------------------------------------
SYMBOL = "BTC/USDT"
TIMEFRAME = "1d"
DAYS_BACK = 180
INITIAL_CAPITAL = 10_000
COMISSION = 0.001
SLIPPAGE = 0.0005
# Estrategia
STRATEGY = MovingAverageCrossover(
fast_period=10,
slow_period=30,
ma_type='ema',
use_adx=False,
adx_threshold=20
)
# Position sizing fijo (para aislar el efecto del stop)
POSITION_SIZER = FixedPositionSizer(0.95)
# Stops a comparar
STOPS = {
"Fixed 2%": FixedStop(0.02),
"Trailing 2%": TrailingStop(0.02),
"ATR 14 x 2.0": ATRStop(atr_period=14, multiplier=2.0),
}
def setup_environment():
"""Carga variables de entorno"""
env_path = Path(__file__).parent.parent.parent / 'config' / 'secrets.env'
load_dotenv(dotenv_path=env_path)
def load_data():
"""
Carga datos desde la base de datos
"""
# Setup
setup_environment()
# 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_BACK)
data = storage.load_ohlcv(
symbol=SYMBOL,
timeframe=TIMEFRAME,
start_date=None,
end_date=None,
use_cache=False,
)
storage.close()
if data.empty:
raise RuntimeError("No se cargaron datos")
return data
def run():
log.info("=" * 80)
log.info("🧪 COMPARACIÓN DE STOPS (Research)")
log.info("=" * 80)
data = load_data()
log.info(f"Símbolo: {SYMBOL}")
log.info(f"Timeframe: {TIMEFRAME}")
log.info(f"Velas: {len(data)}")
log.info(f"Periodo: {data.index[0]}{data.index[-1]}")
print()
results = {}
# --------------------------------------------------
# Ejecutar backtest por cada stop
# --------------------------------------------------
for name, stop in STOPS.items():
log.info(f"▶️ Ejecutando con stop: {name}")
# Silenciar logs del engine (solo warnings o errores)
logger.disable("src.backtest.engine")
engine = Engine(
strategy=STRATEGY,
initial_capital=INITIAL_CAPITAL,
commission=COMISSION,
slippage=SLIPPAGE,
position_sizer=POSITION_SIZER,
stop_loss=stop,
)
res = engine.run(data)
results[name] = res
logger.enable("src.backtest.engine")
log.info(
f"Trades: {res['total_trades']:>4} | "
f"Max DD: {res['max_drawdown_pct']:>7.2f}% | "
f"Return: {res['total_return_pct']:>7.2f}%"
)
print()
print("\n" + "=" * 70)
print("📊 RESUMEN COMPARATIVO DE STOPS")
print("=" * 70)
for name, res in results.items():
print(
f"{name:<15} | "
f"Trades: {res['total_trades']:>4} | "
f"Max DD: {res['max_drawdown_pct']:>7.2f}% | "
f"Return: {res['total_return_pct']:>7.2f}%"
)
print("=" * 70)
# --------------------------------------------------
# Plot equity curves (comparativa visual)
# --------------------------------------------------
plt.figure(figsize=(14, 7))
for name, res in results.items():
plt.plot(
res["timestamps"],
res["equity_curve"],
label=name,
)
plt.title(f"Equity Curve Comparison {SYMBOL} {TIMEFRAME}")
plt.xlabel("Time")
plt.ylabel("Equity")
plt.legend()
plt.grid(alpha=0.3)
plt.tight_layout()
# --------------------------------------------------
# Guardar gráfico
# --------------------------------------------------
output_dir = Path(__file__).parent / "output"
output_dir.mkdir(parents=True, exist_ok=True)
output_path = output_dir / f"equity_comparison_{SYMBOL.replace('/', '_')}_{TIMEFRAME}.png"
plt.savefig(output_path, dpi=150)
plt.close()
log.success(f"📈 Equity curve guardada en: {output_path}")
if __name__ == "__main__":
run()