# tests/backtest/test_engine_trailing_stop.py import sys from pathlib import Path import pandas as pd from datetime import datetime 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.stops.trailing_stop import TrailingStop class AlwaysBuyStrategy(Strategy): """ Estrategia dummy: - Compra en la primera vela - Nunca vende """ 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 def test_trailing_stop_moves_and_closes_position(): """ El trailing stop: - se mueve cuando el precio sube - cierra la posición cuando el precio cae """ timestamps = pd.date_range( start=datetime(2024, 1, 1), periods=7, freq="1h" ) data = pd.DataFrame( { "open": [100, 102, 105, 108, 110, 107, 103], "high": [101, 103, 106, 109, 111, 108, 104], "low": [99, 101, 104, 107, 109, 106, 102], "close": [100, 102, 105, 108, 110, 107, 103], "volume": [1, 1, 1, 1, 1, 1, 1], }, index=timestamps ) strategy = AlwaysBuyStrategy() engine = Engine( strategy=strategy, initial_capital=10000, commission=0.0, slippage=0.0, position_size=1.0, stop_loss=TrailingStop(0.05), # 5% trailing ) engine.run(data) # Solo debe haber un trade assert len(engine.trades) == 1 trade = engine.trades[0] # El trade debe cerrarse por stop assert trade.status == TradeStatus.CLOSED assert trade.exit_reason == "Stop Loss" # El cierre no debe ser al final del backtest assert trade.exit_time <= data.index[-1]