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,108 @@
# tests/backtest/test_engine_percent_risk.py
import sys
from pathlib import Path
import pandas as pd
from datetime import datetime
# Añadir raíz del proyecto al path
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from src.core.engine import Engine
from src.core.strategy import Strategy, Signal
from src.core.trade import TradeStatus
from src.risk.sizing.percent_risk import PercentRiskSizer
from src.risk.stops.fixed_stop import FixedStop
# --------------------------------------------------
# Estrategia dummy
# --------------------------------------------------
class AlwaysBuyStrategy(Strategy):
"""
Compra en la primera vela y nunca vende.
El stop debe cerrar la posición.
"""
def __init__(self):
super().__init__(name="AlwaysBuy", params={})
def init_indicators(self, data):
return data
def generate_signal(self, idx: int):
if idx == 0:
return Signal.BUY
return Signal.HOLD
# --------------------------------------------------
# Test de integración real
# --------------------------------------------------
def test_engine_percent_risk_with_fixed_stop():
"""
Verifica que:
- El size se calcula usando el stop
- El riesgo por trade ≈ % configurado
- El stop cierra la posición
"""
# -----------------------------
# Datos simulados
# -----------------------------
timestamps = pd.date_range(
start=datetime(2024, 1, 1),
periods=5,
freq="1h",
)
data = pd.DataFrame(
{
"open": [100, 100, 100, 100, 100],
"high": [101, 101, 101, 101, 101],
"low": [99, 97, 95, 93, 90],
"close": [100, 98, 96, 94, 91], # rompe stop
"volume": [1, 1, 1, 1, 1],
},
index=timestamps,
)
# -----------------------------
# Configuración
# -----------------------------
initial_capital = 10_000
risk_fraction = 0.01 # 1% por trade
stop_fraction = 0.02 # stop al 2%
strategy = AlwaysBuyStrategy()
engine = Engine(
strategy=strategy,
initial_capital=initial_capital,
commission=0.0,
slippage=0.0,
position_sizer=PercentRiskSizer(risk_fraction),
stop_loss=FixedStop(stop_fraction),
)
# -----------------------------
# Ejecutar backtest
# -----------------------------
engine.run(data)
# -----------------------------
# Assertions
# -----------------------------
assert len(engine.trades) == 1
trade = engine.trades[0]
# Trade cerrado por stop
assert trade.status == TradeStatus.CLOSED
assert trade.exit_reason == "Stop Loss"
# Riesgo real ≈ riesgo esperado
expected_risk = initial_capital * risk_fraction
actual_loss = abs(trade.pnl)
# Permitimos pequeño error numérico
assert abs(actual_loss - expected_risk) / expected_risk < 0.05

View File

@@ -9,8 +9,8 @@ from datetime import datetime, timedelta
# Añadir raíz del proyecto al path
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from src.backtest.engine import BacktestEngine
from src.backtest.strategy import Strategy, Signal
from src.core.engine import Engine
from src.core.strategy import Strategy, Signal
from src.risk.sizing.fixed import FixedPositionSizer
class BuyOnceStrategy(Strategy):
@@ -66,7 +66,7 @@ def test_engine_uses_fixed_position_sizer():
sizer = FixedPositionSizer(capital_fraction=0.5)
engine = BacktestEngine(
engine = Engine(
strategy=strategy,
initial_capital=10000,
commission=0.0,

View File

@@ -6,9 +6,9 @@ from datetime import datetime
# Añadir raíz del proyecto al path
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from src.backtest.engine import BacktestEngine
from src.backtest.strategy import Strategy, Signal
from src.backtest.trade import TradeStatus
from src.core.engine import Engine
from src.core.strategy import Strategy, Signal
from src.core.trade import TradeStatus
from src.risk.stops.fixed_stop import FixedStop
@@ -57,7 +57,7 @@ def test_engine_closes_position_on_stop_hit():
data = _build_test_data()
strategy = AlwaysBuyStrategy()
engine = BacktestEngine(
engine = Engine(
strategy=strategy,
initial_capital=10_000,
commission=0.0,
@@ -82,7 +82,7 @@ def test_engine_closes_position_at_end_without_stop():
data = _build_test_data()
strategy = AlwaysBuyStrategy()
engine = BacktestEngine(
engine = Engine(
strategy=strategy,
initial_capital=10_000,
commission=0.0,

View File

@@ -7,9 +7,9 @@ from datetime import datetime
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from src.backtest.engine import BacktestEngine
from src.backtest.strategy import Strategy, Signal
from src.backtest.trade import TradeStatus
from src.core.engine import Engine
from src.core.strategy import Strategy, Signal
from src.core.trade import TradeStatus
from src.risk.stops.trailing_stop import TrailingStop
@@ -58,7 +58,7 @@ def test_trailing_stop_moves_and_closes_position():
strategy = AlwaysBuyStrategy()
engine = BacktestEngine(
engine = Engine(
strategy=strategy,
initial_capital=10000,
commission=0.0,