Engine: add stop loss integration (fixed & trailing) with tests

This commit is contained in:
DaM
2026-01-30 17:05:47 +01:00
parent af7b862f60
commit c569170fcc
24 changed files with 1121 additions and 137 deletions

View File

@@ -0,0 +1,70 @@
# src/risk/stops/trailing_stop.py
import pandas as pd
from src.risk.stops.base import StopLoss
from src.backtest.trade import TradeType, Position
class TrailingStop(StopLoss):
"""
Trailing Stop porcentual basado en el precio de cierre.
- LONG: el stop solo sube
- SHORT: el stop solo baja
"""
def __init__(self, trailing_fraction: float):
"""
Args:
trailing_fraction: ej 0.05 = 5%
"""
if trailing_fraction <= 0 or trailing_fraction >= 1:
raise ValueError("trailing_fraction debe estar entre 0 y 1")
self.trailing_fraction = trailing_fraction
def get_stop_price(
self,
*,
data: pd.DataFrame,
idx: int,
entry_price: float,
trade_type: TradeType
) -> float:
"""
Stop inicial al abrir la posición
"""
if trade_type == TradeType.LONG:
return entry_price * (1 - self.trailing_fraction)
elif trade_type == TradeType.SHORT:
return entry_price * (1 + self.trailing_fraction)
else:
raise ValueError(f"TradeType no soportado: {trade_type}")
def update_stop(
self,
*,
data: pd.DataFrame,
idx: int,
position: Position
) -> float | None:
"""
Calcula un nuevo stop si el precio ha mejorado.
Devuelve None si el stop no debe moverse.
"""
current_price = data.iloc[idx]["close"]
if position.trade_type == TradeType.LONG:
candidate = current_price * (1 - self.trailing_fraction)
if position.stop_price is None or candidate > position.stop_price:
return candidate
elif position.trade_type == TradeType.SHORT:
candidate = current_price * (1 + self.trailing_fraction)
if position.stop_price is None or candidate < position.stop_price:
return candidate
return None