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:
192
scripts/research/compare_systems.py
Normal file
192
scripts/research/compare_systems.py
Normal 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()
|
||||
Reference in New Issue
Block a user