need commit to update repository
This commit is contained in:
130
src/strategies/donchian_breakout.py
Normal file
130
src/strategies/donchian_breakout.py
Normal file
@@ -0,0 +1,130 @@
|
||||
#src/strategies/donchian_breakout.py
|
||||
"""
|
||||
Estrategia de breakout de rango Donchian.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from ..core.strategy import Strategy, Signal, calculate_donchian_channels, cross_above, cross_below
|
||||
|
||||
|
||||
class DonchianBreakout(Strategy):
|
||||
"""
|
||||
Breakout de máximos/mínimos de N barras.
|
||||
|
||||
Señales:
|
||||
- BUY: close rompe por encima del máximo Donchian previo
|
||||
- SELL: close rompe por debajo del mínimo Donchian previo
|
||||
"""
|
||||
|
||||
strategy_id = "donchian_breakout"
|
||||
strategy_family = "breakout"
|
||||
display_name = "Donchian Breakout"
|
||||
description = "Breakout de rango basado en máximos y mínimos rolling."
|
||||
|
||||
def __init__(self, donchian_window: int = 20, exit_window: int = 10):
|
||||
params = {
|
||||
"donchian_window": donchian_window,
|
||||
"exit_window": exit_window,
|
||||
}
|
||||
super().__init__(name="Donchian Breakout", params=params)
|
||||
|
||||
self.donchian_window = int(donchian_window)
|
||||
self.exit_window = int(exit_window)
|
||||
|
||||
@classmethod
|
||||
def default_parameters(cls) -> dict:
|
||||
return {
|
||||
"donchian_window": 20,
|
||||
"exit_window": 10,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def strategy_metadata(cls) -> dict:
|
||||
return {
|
||||
"strategy_id": cls.strategy_id,
|
||||
"name": cls.display_name,
|
||||
"family": cls.strategy_family,
|
||||
"direction": "long_short",
|
||||
"description": cls.description,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def strategy_definition(cls) -> dict:
|
||||
return {
|
||||
"meta": cls.strategy_metadata(),
|
||||
"defaults": cls.default_parameters(),
|
||||
"parameters_schema": cls.parameters_schema(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def parameters_schema(cls) -> dict:
|
||||
return {
|
||||
"donchian_window": {
|
||||
"type": "int",
|
||||
"min": 2,
|
||||
"max": 300,
|
||||
"default": 20,
|
||||
},
|
||||
"exit_window": {
|
||||
"type": "int",
|
||||
"min": 2,
|
||||
"max": 300,
|
||||
"default": 10,
|
||||
},
|
||||
}
|
||||
|
||||
def init_indicators(self, data: pd.DataFrame) -> pd.DataFrame:
|
||||
high = data["high"]
|
||||
low = data["low"]
|
||||
close = data["close"]
|
||||
|
||||
data["donchian_high"], data["donchian_low"] = calculate_donchian_channels(
|
||||
high,
|
||||
low,
|
||||
period=self.donchian_window,
|
||||
shift=1,
|
||||
)
|
||||
|
||||
data["exit_high"], data["exit_low"] = calculate_donchian_channels(
|
||||
high,
|
||||
low,
|
||||
period=self.exit_window,
|
||||
shift=1,
|
||||
)
|
||||
|
||||
data["breakout_up"] = cross_above(close, data["donchian_high"])
|
||||
data["breakout_down"] = cross_below(close, data["donchian_low"])
|
||||
|
||||
data["lose_exit_low"] = cross_below(close, data["exit_low"])
|
||||
data["lose_exit_high"] = cross_above(close, data["exit_high"])
|
||||
|
||||
return data
|
||||
|
||||
def generate_signal(self, idx: int) -> Signal:
|
||||
if self.data is None:
|
||||
raise ValueError("Data no establecida")
|
||||
|
||||
if idx < 1:
|
||||
return Signal.HOLD
|
||||
|
||||
row = self.data.iloc[idx]
|
||||
|
||||
needed = ["donchian_high", "donchian_low", "exit_high", "exit_low"]
|
||||
if any(pd.isna(row[c]) for c in needed):
|
||||
return Signal.HOLD
|
||||
|
||||
if bool(row["breakout_up"]) and self.current_position <= 0:
|
||||
return Signal.BUY
|
||||
|
||||
if bool(row["breakout_down"]) and self.current_position >= 0:
|
||||
return Signal.SELL
|
||||
|
||||
if self.current_position > 0 and bool(row["lose_exit_low"]):
|
||||
return Signal.SELL
|
||||
|
||||
if self.current_position < 0 and bool(row["lose_exit_high"]):
|
||||
return Signal.BUY
|
||||
|
||||
return Signal.HOLD
|
||||
Reference in New Issue
Block a user