Repository navigation
Expand file tree
/
Copy pathgestion_riesgo.py
More file actions
174 lines (150 loc) · 6.98 KB
/
Copy pathgestion_riesgo.py
File metadata and controls
174 lines (150 loc) · 6.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# gestion_riesgo.py
# Módulo avanzado de gestión de capital y riesgo.
# Estrategias integradas del manual "Gestión de Capital":
# * Stop Loss diario
# * Take Profit diario
# * Reducción adaptativa tras rachas de pérdidas
# * Criterio de Kelly simplificado
# * Martingale limitado
# * Drawdown máximo permitido
# * Protección de capital
from typing import Dict
class GestorRiesgo:
"""
Gestor de riesgo con criterios profesionales.
Basado en los principios de gestión de capital del manual avanzado.
"""
def __init__(self, saldo_inicial: float, max_riesgo_operacion: float = 0.02,
max_perdida_diaria: float = 0.10, meta_ganancia_diaria: float = 0.15,
max_martingale_niveles: int = 3, max_drawdown: float = 0.30):
self.saldo_inicial = saldo_inicial
self.saldo_actual = saldo_inicial
self.max_riesgo_operacion = max_riesgo_operacion
self.max_perdida_diaria = max_perdida_diaria
self.meta_ganancia_diaria = meta_ganancia_diaria
self.max_martingale_niveles = max_martingale_niveles
self.max_drawdown = max_drawdown # drawdown máximo desde el peak
# Contadores
self.operaciones_perdidas_consecutivas = 0
self.operaciones_ganadas_consecutivas = 0
self.total_operaciones = 0
self.total_ganadas = 0
self.total_perdidas = 0
self.nivel_martingale_actual = 0
self.peak_saldo = saldo_inicial
self.bot_bloqueado = False
self.motivo_bloqueo = ""
self.ultima_actualizacion = None
def calcular_monto_entrada(self, fuerza_senal: float = 1.0,
usar_kelly: bool = False, win_rate: float = 0.55) -> float:
"""
Calcula el monto para la siguiente entrada.
Ajusta por rachas, drawdown y criterio de Kelly opcional.
"""
if self.bot_bloqueado:
return 0.0
# Base: porcentaje de riesgo sobre saldo actual
if usar_kelly and win_rate > 0:
# Kelly simplificado: f* = (p*b - q) / b
# b = ratio payout (0.8), p = win_rate, q = 1 - win_rate
b = 0.80
kelly_pct = (win_rate * b - (1 - win_rate)) / b
kelly_pct = max(0, min(kelly_pct, 0.10)) # cap a 10%
monto_base = self.saldo_actual * kelly_pct
else:
monto_base = self.saldo_actual * self.max_riesgo_operacion
# Ajuste por fuerza de la señal (si es muy fuerte, arriesgar un poco más)
ajuste_fuerza = 0.7 + (fuerza_senal * 0.6) # 0.7x a 1.3x
monto_base *= ajuste_fuerza
# Reducción adaptativa por rachas
if self.operaciones_perdidas_consecutivas >= 5:
monto_base *= 0.25 # reducir a 1/4 tras 5 pérdidas
elif self.operaciones_perdidas_consecutivas >= 3:
monto_base *= 0.5 # reducir a 1/2 tras 3 pérdidas
# Reducción por drawdown
if self.peak_saldo > 0:
drawdown = (self.peak_saldo - self.saldo_actual) / self.peak_saldo
if drawdown > 0.20:
monto_base *= 0.5
elif drawdown > 0.10:
monto_base *= 0.75
return round(max(monto_base, 0.50), 2) # mínimo $0.50
def registrar_resultado(self, ganancia_o_perdida: float) -> Dict:
"""
Actualiza contadores y evalúa límites.
Retorna un dict con el estado actual y si debe detenerse.
"""
self.saldo_actual += ganancia_o_perdida
self.total_operaciones += 1
if self.saldo_actual > self.peak_saldo:
self.peak_saldo = self.saldo_actual
if ganancia_o_perdida < 0:
self.operaciones_perdidas_consecutivas += 1
self.operaciones_ganadas_consecutivas = 0
self.total_perdidas += 1
self.nivel_martingale_actual += 1
else:
self.operaciones_ganadas_consecutivas += 1
self.operaciones_perdidas_consecutivas = 0
self.total_ganadas += 1
self.nivel_martingale_actual = 0 # reset martingale
self.evaluar_estado_cuenta()
return {
'saldo': self.saldo_actual,
'total_ops': self.total_operaciones,
'racha_actual': (self.operaciones_ganadas_consecutivas
if ganancia_o_perdida > 0
else -self.operaciones_perdidas_consecutivas),
'bot_bloqueado': self.bot_bloqueado,
'motivo': self.motivo_bloqueo,
}
def evaluar_estado_cuenta(self) -> bool:
"""Evalúa si se ha alcanzado algún límite. Retorna True si está bloqueado."""
perdida_acumulada = self.saldo_inicial - self.saldo_actual
ganancia_acumulada = self.saldo_actual - self.saldo_inicial
if perdida_acumulada >= (self.saldo_inicial * self.max_perdida_diaria):
self.bot_bloqueado = True
self.motivo_bloqueo = f"STOP LOSS DIARIO (perdida {perdida_acumulada:.2f})"
return True
if ganancia_acumulada >= (self.saldo_inicial * self.meta_ganancia_diaria):
self.bot_bloqueado = True
self.motivo_bloqueo = f"TAKE PROFIT DIARIO (ganancia {ganancia_acumulada:.2f})"
return True
# Drawdown máximo
if self.peak_saldo > 0:
dd = (self.peak_saldo - self.saldo_actual) / self.peak_saldo
if dd >= self.max_drawdown:
self.bot_bloqueado = True
self.motivo_bloqueo = f"DRAWDOWN MAXIMO ALCANZADO ({dd:.1%})"
return True
return False
def puede_operar(self) -> bool:
"""True si se puede continuar operando."""
return not self.bot_bloqueado
def reiniciar_sesion(self, nuevo_saldo_inicial: float):
"""Reinicia el gestor para una nueva sesión de trading."""
self.saldo_inicial = nuevo_saldo_inicial
self.saldo_actual = nuevo_saldo_inicial
self.peak_saldo = nuevo_saldo_inicial
self.operaciones_perdidas_consecutivas = 0
self.operaciones_ganadas_consecutivas = 0
self.nivel_martingale_actual = 0
self.bot_bloqueado = False
self.motivo_bloqueo = ""
def obtener_estadisticas(self) -> Dict:
"""Retorna un resumen del estado del gestor."""
return {
'saldo_actual': self.saldo_actual,
'saldo_inicial': self.saldo_inicial,
'ganancia_neta': self.saldo_actual - self.saldo_inicial,
'total_ops': self.total_operaciones,
'ganadas': self.total_ganadas,
'perdidas': self.total_perdidas,
'win_rate': self.total_ganadas / max(self.total_operaciones, 1),
'racha_perdidas': self.operaciones_perdidas_consecutivas,
'racha_ganadas': self.operaciones_ganadas_consecutivas,
'drawdown_actual': ((self.peak_saldo - self.saldo_actual) / self.peak_saldo
if self.peak_saldo > 0 else 0),
'bot_bloqueado': self.bot_bloqueado,
'motivo_bloqueo': self.motivo_bloqueo,
}