-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathEstrategia1.py
330 lines (235 loc) · 11.2 KB
/
Estrategia1.py
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# Librerías Estándar
import pandas as pd
import numpy as np
import mplfinance as mpf
# Librerías Propias
from IndicadoresTecnicos import RSI, SMA
# Clase Estrategia
class Estrategia1:
"""
Estrategia 1: Estrategia de Cruce de Promedios Móviles y RSI:
Descripción:
Esta estrategia combina cruces de promedios móviles simples (SMA) con el Índice de Fuerza Relativa (RSI) para
generar señales de compra y venta. La estrategia busca identificar cambios en la tendencia y validar estos
cambios con condiciones de sobrecompra y sobreventa.
Estrategia Para:
- Acciones
- Divisas
- Materias Primas
- Criptomonedas
Frecuencias (Ventanas de Tiempo):
- 1 minuto
- 1 hora
- 1 día
Periodo de Retención:
- Corto Plazo (Variable)
- Mediano Plazo (Variable)
- Largo Plazo (Variable)
Análisis Usado:
- Análisis Técnico:
* Promedios Móviles
* RSI
Descripción Detallada de la Estrategia:
La estrategia se basa en dos componentes principales: Cruces de Promedios Móviles y el RSI.
1. **Cruces de Promedios Móviles Simples (SMA):**
- Se utilizan dos SMAs, uno de corto plazo (por ejemplo, 9 periodos) y otro de largo plazo (por ejemplo, 21 periodos).
- Una señal de compra se genera cuando el SMA de corto plazo cruza por encima del SMA de largo plazo.
- Una señal de venta se genera cuando el SMA de corto plazo cruza por debajo del SMA de largo plazo.
2. **Índice de Fuerza Relativa (RSI):**
- El RSI se calcula usando un periodo de 14 días.
- Se definen niveles de sobrecompra (RSI > 50) y sobreventa (RSI < 50).
**Señales de Compra y Venta:**
- Señal de Compra: Se genera cuando el SMA de corto plazo cruza por encima del SMA de largo plazo y el RSI está en
nivel de sobreventa (RSI < 50).
- Señal de Venta: Se genera cuando el SMA de corto plazo cruza por debajo del SMA de largo plazo y el RSI está en
nivel de sobrecompra (RSI > 50).
Descripción de Stop Loss y Take Profit:
Take Profit:
- El objetivo de beneficios se mantiene hasta que se genere una señal contraria.
Stop Loss:
- El stop loss se mantiene hasta que se genere una señal contraria.
Co-Integración (Si se usan múltiples Indicadores):
Señales:
- Las señales se confirman cuando tanto el cruce de SMA como las condiciones de RSI coinciden en
indicar una entrada.
Salida del Trade:
- La salida se realiza cuando se genera una señal contraria.
Supuestos Generales:
- Los costos de transacción y el deslizamiento no se consideran en esta estrategia.
Notas:
- Esta estrategia maximiza la utilidad en tendencias definidas.
"""
__version__ = 1.0
# __init__
def __init__(self, df: pd.DataFrame) -> None:
"""
Constructor.
Parámetros
----------
param : pd.DataFrame : df : Datos históricos del activo.
Salida
-------
return: NoneType: None.
"""
# Atributos
self.df = df
self.periodo_rsi = 14
self.periodo_ma_rapido = 9
self.periodo_ma_lento = 21
self.sobrecompra_sobreventa = 50
# __repr__
def __repr__(self) -> str:
return self.__class__.__name__ + ".class"
# Backtest
def backtest(self):
"""
Método que obtiene el rendimiento generado por la estrategia a lo largo del tiempo
Salida
-------
return: pd.Series: Rendimientos de la estrategia a lo largo del tiempo.
"""
# Calcular
calculo_est = self.estrategia_calculo.copy()
# Rellenar señales hacia adelante para conocer la posición actual en todo momento
calculo_est["posicion_mercado"] = calculo_est["Señales"].ffill()
# Calcular rendimiento
calculo_est["Rendimientos"] = self.df["Close"].pct_change()
rendimiento = (1 + calculo_est["posicion_mercado"].shift(periods=1) * calculo_est["Rendimientos"]).cumprod()
return rendimiento
# Calcular
def calcular(self):
"""
Este método calculará la estrategia
Salida
-------
return: dict: Devuelve un diccionario si se generó una señal en la última vela, o False si no se generó nada.
"""
# Calcular indicadores
datos = pd.DataFrame(index=self.df.index)
datos[f"SMA_{self.periodo_ma_rapido}"] = SMA(self.df, periodo=self.periodo_ma_rapido)
datos[f"SMA_{self.periodo_ma_lento}"] = SMA(self.df, periodo=self.periodo_ma_lento)
datos["RSI"] = RSI(self.df, periodo=self.periodo_rsi)
# Generar cruces
datos["Cruces_MAs"] = np.where(datos[f"SMA_{self.periodo_ma_rapido}"] > datos[f"SMA_{self.periodo_ma_lento}"], 1, -1)
# Detectar niveles sobrecompra y sobreventa
datos["RSI_Señal"] = np.where(datos["RSI"] > self.sobrecompra_sobreventa, -1,
np.where(datos["RSI"] < self.sobrecompra_sobreventa, 1, np.nan))
# Detectar señales
datos["Señales"] = np.nan
datos.loc[(datos["Cruces_MAs"] == 1) & (datos["RSI_Señal"] == 1), "Señales"] = 1
datos.loc[(datos["Cruces_MAs"] == -1) & (datos["RSI_Señal"] == -1), "Señales"] = -1
# Guardar
self.estrategia_calculo = datos
# Revisar si hay una señal en la última vela
if datos["Señales"].iloc[-1] == 1:
valor = {"tendencia": "alcista"}
elif datos["Señales"].iloc[-1] == -1:
valor = {"tendencia": "bajista"}
else:
valor = False
return valor
# Optimizar
def optimizar(self, combinaciones: list):
"""
Optimiza la estrategia encontrando los mejores parámetros.
Parámetros
----------
param : list : combinaciones : Conjunto de parámetros que se probarán en la estrategia.
Salida
-------
return: pd.DataFrame: Rendimiento para cada estrategia probada.
"""
# Optimizar
# Guardar los parámetros originales
params_originales = [self.periodo_ma_rapido, self.periodo_ma_lento, self.periodo_rsi]
# Almacenar resultados
resultados = []
# Iterar en cada combinación
for parametro in combinaciones:
# Modificar parámetros de la estrategia
self.periodo_ma_rapido = parametro[0]
self.periodo_ma_lento = parametro[1]
self.periodo_rsi = parametro[2]
# Calcular estrategia
_ = self.calcular()
# Backtest
retorno_final = self.backtest().iloc[-1]
# Almacenar los resultados
resultados.append([self.periodo_ma_rapido, self.periodo_ma_lento, self.periodo_rsi, retorno_final])
# Dar estructura y ordenar
resultados = pd.DataFrame(data=resultados, columns=["MA Rap Param", "MA Len Param", "RSI Param", "Retorno"])
resultados.sort_values(by="Retorno", ascending=False, inplace=True)
# Devolver los parámetros originales
self.periodo_ma_rapido = params_originales[0]
self.periodo_ma_lento = params_originales[1]
self.periodo_rsi = params_originales[2]
# Devolver calculos con parámetros originales
self.calcular()
return resultados
# Plot
def plot(self):
"""
Este método realiza el gráfico de nuestros datos
Salida
-------
return: NoneType: None
"""
# Graficar
rsi_data = self.estrategia_calculo["RSI"]
nivel_sobrecompra = self.sobrecompra_sobreventa
nivel_sobreventa = self.sobrecompra_sobreventa
# Crear plot adicional para el RSI
rsi_plot = [mpf.make_addplot(rsi_data, panel=2, color="blue", ylabel="RSI")]
# Añadir las líneas divisorias
rsi_plot.append(mpf.make_addplot([nivel_sobrecompra] * len(rsi_data), panel=2, color="black", linestyle="dashed"))
# Añadir las áreas coloreadas
rsi_plot.append(mpf.make_addplot(rsi_data, panel=2, color="blue",
fill_between=dict(y1=nivel_sobrecompra, y2=rsi_data, where=rsi_data >= nivel_sobrecompra, alpha=0.5,
color="green")))
rsi_plot.append(mpf.make_addplot(rsi_data, panel=2, color="blue",
fill_between=dict(y1=nivel_sobreventa, y2=rsi_data, where=rsi_data < nivel_sobreventa, alpha=0.5,
color="red")))
mpf.plot(self.df, type="candle", style="yahoo", title="Gráfico de Velas", ylabel="Precio", volume=True, figsize=(20,10),
figscale=3.0, addplot=rsi_plot, tight_layout=True,
mav=(int(self.periodo_ma_rapido), int(self.periodo_ma_lento)), warn_too_much_data=self.df.shape[0],
savefig="estrategia1.png")
# Ejemplo
if __name__ == "__main__":
# Importar librerías adicionales
import yfinance as yf
from itertools import product
# Obtener datos
df = yf.download("AMZN", start="2014-01-01", end="2024-01-01", interval="1d")
# Generar una instancia de nuestra clase
est1 = Estrategia1(df)
# Calcular Estrategia
calculo_señal = est1.calcular()
print(calculo_señal)
print(est1.estrategia_calculo)
print(est1.estrategia_calculo["Señales"].dropna())
# Backtest
backtest = est1.backtest()
print(backtest)
# Optimizar
periodos_rapidos = np.arange(5, 14)
periodos_lentos = np.arange(14, 51)
rsi_periodos = np.arange(9, 22)
combinaciones_parametros = list(product(periodos_rapidos, periodos_lentos, rsi_periodos))
print("Total de Estrategias por Correr son:", len(combinaciones_parametros))
resultados = est1.optimizar(combinaciones_parametros)
print(resultados.head(10))
# Tomar mejores parámetros
ma_rapida = resultados["MA Rap Param"].iloc[0]
ma_lenta = resultados["MA Len Param"].iloc[0]
rsi_valor = resultados["RSI Param"].iloc[0]
# Definir nuevamente estrategia
est1 = Estrategia1(df)
est1.periodo_ma_rapido = ma_rapida
est1.periodo_ma_lento = ma_lenta
est1.periodo_rsi = rsi_valor
# Calcular
calculo_señal = est1.calcular()
# Backtest
print("Rendimiento Final:", est1.backtest().iloc[-1])
# Graficar
est1.plot()