# 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