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,192 @@
# scripts/research/compare_systems.py
"""
Comparación cuantitativa final entre:
- MA_Crossover (standalone)
- TrendFiltered_MA (standalone)
- Portfolio 50/50
Este script cierra la decisión cuantitativa antes de pasar a paper trading.
"""
import os
import sys
from pathlib import Path
from dotenv import load_dotenv
import pandas as pd
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from src.core.engine import Engine
from src.data.storage import StorageManager
from src.risk.sizing.percent_risk import PercentRiskSizer
from src.risk.stops.trailing_stop import TrailingStop
from src.strategies.moving_average import MovingAverageCrossover
from src.strategies.trend_filtered import TrendFilteredMACrossover
from src.portfolio.portfolio_engine import PortfolioEngine
from src.portfolio.allocation import Allocation
from src.metrics.equity_metrics import compute_equity_metrics
# --------------------------------------------------
# CONFIG
# --------------------------------------------------
SYMBOL = "BTC/USDT"
TIMEFRAME = "1h"
INITIAL_CAPITAL = 10_000
STOP = TrailingStop(0.02)
# --------------------------------------------------
def load_data():
load_dotenv("config/secrets.env")
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"),
)
data = storage.load_ohlcv(
symbol=SYMBOL,
timeframe=TIMEFRAME,
use_cache=True,
)
storage.close()
if data.empty:
raise RuntimeError("No data loaded")
return data
def run_engine(engine: Engine, data) -> dict:
res = engine.run(data)
equity = res["equity_curve"]
timestamps = res["timestamps"]
metrics = compute_equity_metrics(
equity_curve=equity,
timestamps=timestamps,
)
return {
"final_capital": equity[-1],
**metrics,
}
# --------------------------------------------------
def run():
data = load_data()
systems = {}
# --------------------------------------------------
# MA_Crossover standalone (1% risk)
# --------------------------------------------------
systems["MA_Crossover"] = run_engine(
Engine(
strategy=MovingAverageCrossover(30, 100, "ema", False),
initial_capital=INITIAL_CAPITAL,
position_sizer=PercentRiskSizer(0.01),
stop_loss=STOP,
commission=0.001,
slippage=0.0005,
),
data,
)
# --------------------------------------------------
# TrendFiltered_MA standalone (1% risk)
# --------------------------------------------------
systems["TrendFiltered_MA"] = run_engine(
Engine(
strategy=TrendFilteredMACrossover(30, 100, "ema", 15),
initial_capital=INITIAL_CAPITAL,
position_sizer=PercentRiskSizer(0.01),
stop_loss=STOP,
commission=0.001,
slippage=0.0005,
),
data,
)
# --------------------------------------------------
# Portfolio 50/50 (0.5% + 0.5%)
# --------------------------------------------------
engines = {
"MA_Crossover": Engine(
strategy=MovingAverageCrossover(30, 100, "ema", False),
initial_capital=INITIAL_CAPITAL,
position_sizer=PercentRiskSizer(0.005),
stop_loss=STOP,
commission=0.001,
slippage=0.0005,
),
"TrendFiltered_MA": Engine(
strategy=TrendFilteredMACrossover(30, 100, "ema", 15),
initial_capital=INITIAL_CAPITAL,
position_sizer=PercentRiskSizer(0.005),
stop_loss=STOP,
commission=0.001,
slippage=0.0005,
),
}
portfolio = PortfolioEngine(
engines=engines,
allocation=Allocation(
weights={
"MA_Crossover": 0.5,
"TrendFiltered_MA": 0.5,
}
),
initial_capital=INITIAL_CAPITAL,
)
portfolio_res = portfolio.run(data)
systems["Portfolio_50_50"] = {
"final_capital": portfolio_res.final_capital,
**compute_equity_metrics(
equity_curve=portfolio_res.equity_curve,
timestamps=data.index[: len(portfolio_res.equity_curve)],
),
}
# --------------------------------------------------
# COMPARISON TABLE
# --------------------------------------------------
df = pd.DataFrame(systems).T
df["final_capital"] = df["final_capital"].round(2)
df["cagr"] = (df["cagr"] * 100).round(2)
df["max_drawdown"] = (df["max_drawdown"] * 100).round(2)
df["volatility"] = (df["volatility"] * 100).round(2)
df["time_in_drawdown"] = (df["time_in_drawdown"] * 100).round(2)
df["calmar_ratio"] = df["calmar_ratio"].round(2)
df["ulcer_index"] = df["ulcer_index"].round(2)
print("\n" + "=" * 100)
print("📊 SYSTEM COMPARISON (FINAL DECISION)")
print("=" * 100)
print(df)
print("=" * 100)
print("\n📌 Interpretación:")
print("- El mejor sistema NO es el que tiene mayor capital final.")
print("- Prioriza menor drawdown, mejor Calmar y menor Ulcer Index.")
print("- Si el portfolio domina en riesgo/retorno → decisión cerrada.\n")
# --------------------------------------------------
if __name__ == "__main__":
run()