Engine: add stop loss integration (fixed & trailing) with tests
This commit is contained in:
70
src/risk/stops/trailing_stop.py
Normal file
70
src/risk/stops/trailing_stop.py
Normal 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
|
||||
Reference in New Issue
Block a user