- 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
71 lines
1.9 KiB
Python
71 lines
1.9 KiB
Python
# src/risk/stops/trailing_stop.py
|
|
import pandas as pd
|
|
from src.risk.stops.base import StopLoss
|
|
from src.core.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
|