├── Modulo_2.py ├── Modulo_3.py ├── Modulo_4.py ├── Modulo_5.py ├── Modulo_6.py ├── Modulo_7.py └── README.md /Modulo_2.py: -------------------------------------------------------------------------------- 1 | ''' 2.1.1''' 2 | # Variables y Tipos de Datos: 3 | precio_accion = 150.50 # Esto es un número decimal 4 | cantidad = 10 # Esto es un número entero 5 | activo = "AAPL" # Esto es una cadena de texto 6 | es_rentable = True # Esto es un valor booleano 7 | 8 | print(type(precio_accion)) # Salida: 9 | 10 | # Condicionales: 11 | if precio_accion > 100: 12 | print("El precio es alto") 13 | elif precio_accion > 50: 14 | print("El precio es moderado") 15 | else: 16 | print("El precio es bajo") 17 | 18 | # Bucles: 19 | # bucle for: 20 | precios_historicos = [150, 155, 160, 158, 165] 21 | 22 | for precio in precios_historicos: 23 | print(f"El precio actual es: {precio}") 24 | 25 | # bucle while: 26 | precio_actual = 90 27 | while precio_actual < 100: 28 | precio_actual += 1 # Simula el aumento del precio 29 | print(f"Esperando a que el precio supere los 100: {precio_actual}") 30 | 31 | ''' 2.1.2''' 32 | # Listas en Python 33 | # Creación y Acceso a Elementos: 34 | precios_acciones = [100, 105, 110, 115, 120] 35 | print(precios_acciones[0]) # Imprime el primer elemento: 100 36 | print(precios_acciones[-1]) # Imprime el último elemento: 120 37 | 38 | # Agregar y Eliminar Elementos: 39 | precios_acciones.append(125) # Añade un nuevo elemento al final de la lista 40 | print(precios_acciones) # [100, 105, 110, 115, 120, 125] 41 | 42 | precios_acciones.remove(105) # Elimina el primer elemento que coincida con el valor 43 | print(precios_acciones) # [100, 110, 115, 120, 125] 44 | 45 | # Slicing (Corte de Listas): 46 | primeros_tres = precios_acciones[:3] # Obtiene los primeros tres elementos 47 | print(primeros_tres) # [100, 110, 115] 48 | 49 | ultimos_dos = precios_acciones[-2:] # Obtiene los dos últimos elementos 50 | print(ultimos_dos) # [120, 125] 51 | 52 | # List Comprehensions: 53 | # Incrementa cada precio en un 5% 54 | precios_actualizados = [precio * 1.05 for precio in precios_acciones] 55 | print(precios_actualizados) # [105.0, 115.5, 120.75, 126.0, 131.25] 56 | 57 | # Diccionarios en Python 58 | # Creación y Acceso a Elementos: 59 | precios_acciones = {'AAPL': 150, 'GOOG': 1200, 'TSLA': 700} 60 | print(precios_acciones['AAPL']) # Imprime el valor asociado a 'AAPL': 150 61 | 62 | # Agregar y Eliminar Elementos: 63 | precios_acciones['AMZN'] = 3300 # Añade un nuevo par clave-valor 64 | print(precios_acciones) # {'AAPL': 150, 'GOOG': 1200, 'TSLA': 700, 'AMZN': 3300} 65 | 66 | del precios_acciones['TSLA'] # Elimina la clave 'TSLA' y su valor asociado 67 | print(precios_acciones) # {'AAPL': 150, 'GOOG': 1200, 'AMZN': 3300} 68 | 69 | # Iterar sobre Diccionarios: 70 | for clave, valor in precios_acciones.items(): 71 | print(f"El precio de {clave} es {valor}") 72 | 73 | # Comprensiones de Diccionarios: 74 | # Incrementa cada precio en un 10% 75 | precios_actualizados = {clave: valor * 1.10 for clave, valor in precios_acciones.items()} 76 | print(precios_actualizados) # {'AAPL': 165.0, 'GOOG': 1320.0,'AMZN': 3630.0} 77 | 78 | # Pandas para Análisis de Datos 79 | # Creación de un DataFrame: 80 | import pandas as pd 81 | 82 | datos = { 83 | 'Date': ['2024-01-01', '2024-01-02', '2024-01-03'], 84 | 'AAPL': [150, 152, 151], 85 | 'GOOG': [1200, 1215, 1220] 86 | } 87 | df = pd.DataFrame(datos) 88 | print(df) 89 | 90 | # Filtrado de Datos: 91 | precios_altos = df[df['AAPL'] > 150] 92 | print(precios_altos) 93 | 94 | # Agregar Valores a un DataFrame: 95 | df['AMZN'] = [3300, 3315, 3320] # Añadir nueva columna con datos 96 | print(df) 97 | 98 | df['AMZN'] = [3300, 3315, 3320] # Añadir nueva columna con datos 99 | print(df) 100 | 101 | # Eliminar Valores del DataFrame: 102 | df = df.drop(columns=['GOOG']) 103 | print(df) 104 | 105 | df = df.drop(index=2) 106 | print(df) 107 | 108 | # Ejemplo de Cálculo de Cambio Porcentual y Media Móvil utilizando Pandas: 109 | df['AAPL_pct_change'] = df['AAPL'].pct_change() 110 | df['SMA_50'] = df['AAPL'].rolling(window=2).mean() 111 | print(df[['Date', 'AAPL', 'AAPL_pct_change', 'SMA_50']]) 112 | 113 | ''' 2.1.3''' 114 | ## Funciones 115 | # Definición y Uso de Funciones: 116 | def calcular_rendimiento(precio_inicial, precio_final): 117 | rendimiento = (precio_final - precio_inicial) / precio_inicial 118 | return rendimiento 119 | 120 | # Llamando a la función 121 | rendimiento = calcular_rendimiento(100, 150) 122 | print(f"El rendimiento es: {rendimiento}") # El rendimiento es: 0.5 123 | 124 | # Parámetros y Retornos: 125 | def saludo(nombre): 126 | return f"Hola, {nombre}!" 127 | 128 | mensaje = saludo("Isaac") 129 | print(mensaje) 130 | 131 | # Funciones Lambda (Anónimas): 132 | multiplicar = lambda x, y: x * y 133 | resultado = multiplicar(10, 5) 134 | print(resultado) # 50 135 | 136 | ## Módulos 137 | # Creación de un Módulo Propio: 138 | 139 | # Archivo: mis_funciones.py 140 | def calcular_media_movil(precios, periodo): 141 | return sum(precios[-periodo:]) / periodo 142 | 143 | def calcular_maximo(precios): 144 | return max(precios) 145 | 146 | # Archivo principal 147 | import mis_funciones 148 | 149 | precios = [100, 105, 110, 115, 120] 150 | media_movil = mis_funciones.calcular_media_movil(precios, 3) 151 | print(f"La media móvil de 3 días es: {media_movil}") 152 | 153 | maximo = mis_funciones.calcular_maximo(precios) 154 | print(f"El precio máximo es: {maximo}") 155 | 156 | # Uso de Módulos Integrados y Externos: 157 | import math 158 | 159 | # Uso del módulo math 160 | resultado = math.sqrt(16) 161 | print(resultado) # Salida: 4.0 162 | 163 | '''2.2.1''' 164 | # Lectura de Datos desde Fuentes Externas: 165 | import pandas as pd 166 | 167 | # Cargar datos desde un archivo CSV 168 | df = pd.read_csv('precios_historicos.csv') 169 | print(df.head()) # Muestra las primeras 5 filas del DataFrame 170 | 171 | ## Operaciones Avanzadas con DataFrames: 172 | 173 | # Cálculo de Indicadores Técnicos: 174 | df['SMA_20'] = df['Close'].rolling(window=20).mean() 175 | print(df[['Date', 'Close', 'SMA_20']].tail()) 176 | 177 | # Aplicación de Funciones Personalizadas con apply(): 178 | def clasificar_rendimiento(rendimiento): 179 | if rendimiento > 1: 180 | return 'Alta' 181 | elif rendimiento < -1: 182 | return 'Baja' 183 | else: 184 | return 'Neutral' 185 | 186 | df=pd.DataFrame() # Creamos un DataFrame vacío 187 | 188 | # Creamos datos de ejemplo 189 | df['Date'] = pd.date_range(start='1/1/2021', periods=10, freq='D') 190 | df['Close'] = [100, 102, 98, 105, 95, 110, 105, 100, 98, 105] 191 | 192 | # Calcular el rendimiento diario 193 | df['Daily_Return'] = df['Close'].pct_change() * 100 194 | 195 | # Aplicar la función personalizada a cada valor de la columna 'Daily_Return' 196 | df['Rendimiento_Clasificado'] = df['Daily_Return'].apply(clasificar_rendimiento) 197 | print(df[['Date', 'Close', 'Daily_Return', 'Rendimiento_Clasificado']].tail()) 198 | 199 | '''2.2.2''' 200 | # Creación de Arrays y Operaciones Básicas: 201 | import numpy as np 202 | 203 | precios = [100, 102, 101, 103, 104] 204 | np_precios = np.array(precios) 205 | print(np_precios) 206 | 207 | # Calcular el logaritmo natural de cada precio 208 | log_precios = np.log(np_precios) 209 | print(log_precios) 210 | 211 | # Generación de Datos Aleatorios: 212 | # Generar 10 números aleatorios con una distribución normal 213 | rendimientos_simulados = np.random.normal(0, 0.01, 10) # 10 números aleatorios con media 0 y desviación estándar 0.01 214 | print(rendimientos_simulados) 215 | 216 | '''2.2.3''' 217 | # matplotlib Básico: 218 | import matplotlib.pyplot as plt 219 | 220 | plt.figure(figsize=(10, 5)) 221 | plt.plot(df['Date'], df['Close'], label='Precio de Cierre') 222 | plt.xlabel('Fecha') 223 | plt.ylabel('Precio') 224 | plt.title('Precio de Cierre de la Acción') 225 | plt.legend() 226 | plt.show() 227 | 228 | # Visualización con seaborn: 229 | import matplotlib.pyplot as plt 230 | import seaborn as sns 231 | 232 | plt.figure(figsize=(10, 5)) 233 | sns.histplot(df['Daily_Return'].dropna(), bins=20, kde=True) 234 | plt.title('Distribución de Rendimientos Diarios') 235 | plt.show() 236 | 237 | '''2.2.4''' 238 | # Implementación Básica de un Modelo de Regresión Lineal: 239 | from sklearn.linear_model import LinearRegression 240 | import numpy as np 241 | 242 | # Extraer los datos para entrenamiento (eliminando filas con NaN) 243 | df = df.dropna() 244 | X = df[['Open']].values # Variable independiente (precio de apertura) 245 | y = df['Close'].values # Variable dependiente (precio de cierre) 246 | 247 | # Dividir los datos en conjuntos de entrenamiento y prueba 248 | from sklearn.model_selection import train_test_split 249 | X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) 250 | 251 | # Crear y entrenar el modelo de regresión lineal 252 | modelo = LinearRegression() 253 | modelo.fit(X_train, y_train) 254 | 255 | # Realizar predicciones 256 | predicciones = modelo.predict(X_test) 257 | print("Predicciones:", predicciones[:5]) 258 | print("Valores Reales:", y_test[:5]) 259 | 260 | # Evaluación del Modelo 261 | from sklearn.metrics import mean_squared_error, r2_score 262 | 263 | # Calcular el error cuadrático medio y el R2 264 | mse = mean_squared_error(y_test, predicciones) 265 | r2 = r2_score(y_test, predicciones) 266 | 267 | print(f"Error Cuadrático Medio (MSE): {mse}") 268 | print(f"Coeficiente de Determinación (R²): {r2}") 269 | -------------------------------------------------------------------------------- /Modulo_3.py: -------------------------------------------------------------------------------- 1 | '''3.1.1''' 2 | # yfinance 3 | import yfinance as yf 4 | # Obtener datos históricos de Apple (AAPL) 5 | datos_apple = yf.download('AAPL', start='2023-01-01', end='2023-12-31') 6 | print(datos_apple.head()) 7 | 8 | # Web Scraping 9 | import pandas as pd 10 | import requests 11 | from bs4 import BeautifulSoup 12 | 13 | # Definir el ticker de Tesla 14 | ticker = 'TSLA' 15 | 16 | # Headers para la solicitud HTTP 17 | headers = { 18 | 'authority': 'stockanalysis.com', 19 | 'accept': '*/*', 20 | 'accept-language': 'es-ES,es;q=0.9', 21 | 'referer': 'https://stockanalysis.com/', 22 | 'sec-ch-ua': '"Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"', 23 | 'sec-ch-ua-mobile': '?0', 24 | 'sec-ch-ua-platform': '"Windows"', 25 | 'sec-fetch-dest': 'empty', 26 | 'sec-fetch-mode': 'cors', 27 | 'sec-fetch-site': 'same-origin', 28 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36', 29 | } 30 | 31 | # URL del estado de resultados de Tesla 32 | url = f'https://stockanalysis.com/stocks/{ticker.lower()}/financials/' 33 | 34 | # Realizar la solicitud GET 35 | response = requests.get(url, headers=headers) 36 | 37 | # Verificar que la solicitud fue exitosa 38 | if response.status_code == 200: 39 | # Parsear el contenido HTML de la página 40 | soup = BeautifulSoup(response.text, 'html.parser') 41 | 42 | # Encontrar la tabla que contiene el estado de resultados 43 | table = soup.find('table', {"class": 'w-full'}) 44 | 45 | if table: 46 | # Extraer los datos de la tabla 47 | rows = table.find_all('tr') 48 | 49 | # Inicializar listas para almacenar los datos 50 | headers = [header.text.strip() for header in rows[0].find_all('th')] 51 | data = [] 52 | 53 | for row in rows[1:]: 54 | cols = row.find_all('td') 55 | data.append([col.text.strip() for col in cols]) 56 | 57 | # Crear un DataFrame de pandas 58 | df = pd.DataFrame(data, columns=headers) 59 | print(df.iloc[:10, :3]) # Mostrar las primeras 10 filas y 3 columnas 60 | else: 61 | print("No se encontró la tabla de resultados financieros en la página.") 62 | else: 63 | print(f'Error al realizar la solicitud. Código de estado: {response.status_code}') 64 | 65 | 66 | 67 | 68 | '''3.2.1''' 69 | # Identificación de Valores Nulos: 70 | import pandas as pd 71 | 72 | # Suponiendo que df es tu DataFrame 73 | print(df.isnull().sum()) # Muestra el número de valores nulos por columna 74 | 75 | 76 | ## Eliminación de Filas o Columnas con Valores Faltantes: 77 | 78 | # Elimina filas con cualquier valor faltante 79 | df = df.dropna() 80 | 81 | # Elimina columnas con cualquier valor faltante 82 | df = df.dropna(axis=1) 83 | 84 | 85 | 86 | ##Relleno de Valores Faltantes: 87 | 88 | # Rellenar con la media de cada columna 89 | df.fillna(df.mean(), inplace=True) 90 | 91 | # Rellenar con el valor anterior (Forward Fill) 92 | df.fillna(method='ffill', inplace=True) 93 | 94 | # Rellenar con el valor siguiente (Backward Fill) 95 | df.fillna(method='bfill', inplace=True) 96 | 97 | 98 | 99 | ## Interpolación de Datos Faltantes: 100 | 101 | # Interpolación Lineal 102 | df.interpolate(method='linear', inplace=True) 103 | 104 | # Interpolación Polinómica 105 | df.interpolate(method='polynomial', order=2, inplace=True) 106 | 107 | # Interpolación Spline 108 | df.interpolate(method='spline', order=3, inplace=True) 109 | 110 | # Interpolación por Método de Aproximación de Valores Cercanos 111 | df.interpolate(method='nearest', inplace=True) 112 | 113 | # Ejemplo de Interpolación Lineal en una Serie Temporal 114 | import pandas as pd 115 | import numpy as np 116 | 117 | # Crear un DataFrame con datos faltantes 118 | data = { 119 | 'Date': pd.date_range(start='2024-01-01', periods=10, freq='D'), 120 | 'Precio': [100, np.nan, np.nan, 105, np.nan, 110, 115, np.nan, 120, 125] 121 | } 122 | df = pd.DataFrame(data).set_index('Date') 123 | 124 | # Mostrar datos originales 125 | print("Datos originales:") 126 | print(df) 127 | 128 | # Interpolación lineal 129 | df.interpolate(method='linear', inplace=True) 130 | 131 | # Mostrar datos interpolados 132 | print("\nDatos interpolados:") 133 | print(df) 134 | 135 | # Detección y Eliminación de Duplicados 136 | # Identificar filas duplicadas 137 | duplicados = df[df.duplicated()] 138 | print(duplicados) 139 | 140 | # Eliminar Duplicados 141 | df_sin_duplicados = df.drop_duplicates() 142 | 143 | # Corrección de Errores en Datos 144 | # Identificar valores fuera de un rango específico 145 | valores_fuera_rango = df[(df['Precio'] < 0) | (df['Precio'] > 1000)] 146 | print(valores_fuera_rango) 147 | 148 | # Corrección Manual de Errores: 149 | # Reemplazar un valor específico 150 | df.at[5, 'Precio'] = 150 # Reemplaza el valor en la fila 5, columna 'Precio' 151 | 152 | '''3.2.2''' 153 | # Normalización de Datos con Escalado Min-Max 154 | from sklearn.preprocessing import MinMaxScaler 155 | import pandas as pd 156 | import yfinance as yf 157 | 158 | # Descargar del futuro del Nasdaq en Yahoo Finance 159 | df= yf.download('NQ=F', start='2020-01-01', end='2021-01-01') 160 | 161 | scaler = MinMaxScaler() 162 | 163 | # Normalizar la columna 'Close' 164 | df['Precio_Normalizado'] = scaler.fit_transform(df[['Close']]) 165 | print(df[['Close', 'Precio_Normalizado']].head()) 166 | 167 | # Estandarización de Datos 168 | from sklearn.preprocessing import StandardScaler 169 | 170 | # Crear el escalador estándar 171 | scaler = StandardScaler() 172 | 173 | # Estandarizar la columna 'Precio' 174 | df['Precio_Estandarizado'] = scaler.fit_transform(df[['Close']]) 175 | print(df[['Close', 'Precio_Estandarizado']].head()) 176 | 177 | ## Transformaciones Adicionales 178 | # Transformación Logarítmica: 179 | import numpy as np 180 | 181 | # Aplicar la transformación logarítmica 182 | df['Precio_Log'] = np.log(df['Close'] + 1) # +1 para evitar log(0) 183 | 184 | # Transformación de Potencia: 185 | from scipy import stats 186 | 187 | # Aplicar la transformación de Box-Cox 188 | df['Precio_BoxCox'], _ = stats.boxcox(df['Close'] + 1) 189 | 190 | ## Aplicación a Series Temporales 191 | # Normalización de series temporales usando ventanas deslizantes 192 | window_size = 20 193 | df['Precio_Normalizado_Ventana'] = df['Close'].rolling(window=window_size).apply( 194 | lambda x: (x[-1] - x.min()) / (x.max() - x.min()) if (x.max() - x.min()) > 0 else 0) 195 | 196 | '''3.2.3''' 197 | ## Identificación de Outliers 198 | 199 | # Método del Rango Intercuartil (IQR): 200 | # calcular el rango intercuartil 201 | Q1 = df['Close'].quantile(0.25) 202 | Q3 = df['Close'].quantile(0.75) 203 | IQR = Q3 - Q1 204 | 205 | limite_inferior = Q1 - 1.5 * IQR 206 | limite_superior = Q3 + 1.5 * IQR 207 | 208 | outliers = df[(df['Close'] < limite_inferior) | (df['Close'] > limite_superior)] 209 | print(outliers) 210 | 211 | # Z-Score (Puntuación Z): 212 | from scipy import stats 213 | import numpy as np 214 | 215 | # Calcular el Z-Score para cada valor 216 | df['Z_Score'] = np.abs(stats.zscore(df['Close'])) 217 | 218 | # Definir un umbral para identificar outliers (por ejemplo, Z > 3) 219 | outliers_z = df[df['Z_Score'] > 3] 220 | print(outliers_z) 221 | 222 | # Visualización para Detección de Outliers: 223 | import matplotlib.pyplot as plt 224 | 225 | # Crear un boxplot 226 | plt.figure(figsize=(8, 6)) 227 | plt.boxplot(df['Close'], vert=False) 228 | plt.title('Boxplot para Detección de Outliers') 229 | plt.xlabel('Close') 230 | plt.show() 231 | 232 | 233 | ## Manejo de Outliers 234 | # Eliminación de Outliers: 235 | # Eliminar outliers utilizando el método IQR 236 | df_sin_outliers = df[(df['Close'] >= limite_inferior) & (df['Close'] <= limite_superior)] 237 | print(df_sin_outliers.head(10)) 238 | 239 | # Transformación de Outliers: 240 | import numpy as np 241 | 242 | # Transformación logarítmica 243 | df['Precio_Log'] = np.log(df['Close']) 244 | 245 | # Reemplazo Outliers: 246 | # Reemplazar outliers con la mediana 247 | df.loc[(df['Close'] < limite_inferior) | (df['Close'] > limite_superior), 'Close'] = df['Close'].median() 248 | print(df) 249 | 250 | 251 | '''3.2.4''' 252 | # Visualización de Datos 253 | import yfinance as yf 254 | import pandas as pd 255 | import matplotlib.pyplot as plt 256 | 257 | df= yf.download('AAPL', start='2020-01-01', end='2021-01-01') 258 | 259 | # Histograma de la columna 'Close' 260 | plt.figure(figsize=(10, 6)) 261 | plt.hist(df['Close'], bins=30, edgecolor='k', alpha=0.7) 262 | plt.title('Distribución del Precio de Cierre') 263 | plt.xlabel('Precio de Cierre') 264 | plt.ylabel('Frecuencia') 265 | plt.show() 266 | 267 | # Boxplot 268 | # Boxplot de la columna 'Close' 269 | plt.figure(figsize=(8, 6)) 270 | plt.boxplot(df['Close'], vert=False) 271 | plt.title('Boxplot del Precio de Cierre') 272 | plt.xlabel('Precio de Cierre') 273 | plt.show() 274 | 275 | # Gráfico de Serie Temporal: 276 | # Gráfico de Serie Temporal 277 | plt.figure(figsize=(12, 6)) 278 | plt.plot(df['Close']) 279 | plt.title('Precio de Cierre a lo Largo del Tiempo') 280 | plt.xlabel('precio') 281 | plt.ylabel('Precio de Cierre') 282 | plt.show() 283 | 284 | # Análisis Estadístico Descriptivo 285 | # Resumen estadístico 286 | resumen = df.describe() 287 | print(resumen) 288 | 289 | # Análisis de Correlación 290 | import seaborn as sns 291 | # Calcular los rendimientos logarítmicos 292 | returns = df[['Open', 'High', 'Low', 'Close', 'Adj Close', 'Volume']].pct_change().dropna() 293 | # Matriz de correlación de los rendimientos 294 | plt.figure(figsize=(10, 6)) 295 | matriz_correlacion = returns.corr() 296 | sns.heatmap(matriz_correlacion, annot=True, cmap='coolwarm', linewidths=0.5) 297 | plt.title('Matriz de Correlación de los Rendimientos') 298 | plt.show() 299 | 300 | # Identificación de Patrones y Tendencias: 301 | from pandas.plotting import autocorrelation_plot 302 | 303 | # Autocorrelación de la serie de tiempo 304 | plt.figure(figsize=(10, 6)) 305 | autocorrelation_plot(df['Close']) 306 | plt.title('Autocorrelación del Precio de Cierre') 307 | plt.show() 308 | 309 | -------------------------------------------------------------------------------- /Modulo_4.py: -------------------------------------------------------------------------------- 1 | '''4.1''' 2 | # Media Móvil Simple (SMA): 3 | # Suponiendo que df es tu DataFrame con la columna 'Close' 4 | df['SMA_50'] = df['Close'].rolling(window=50).mean() 5 | df['SMA_200'] = df['Close'].rolling(window=200).mean() 6 | print(df[['Close', 'SMA_50', 'SMA_200']]) 7 | 8 | # Media Móvil Exponencial (EMA): 9 | # Calcular la EMA 10 | df['EMA_50'] = df['Close'].ewm(span=50, adjust=False).mean() 11 | df['EMA_200'] = df['Close'].ewm(span=200, adjust=False).mean() 12 | print(df[['Close', 'EMA_50', 'EMA_200']]) 13 | 14 | # MACD 15 | # Calcular el MACD 16 | df['EMA_12'] = df['Close'].ewm(span=12, adjust=False).mean() 17 | df['EMA_26'] = df['Close'].ewm(span=26, adjust=False).mean() 18 | df['MACD'] = df['EMA_12'] - df['EMA_26'] 19 | df['Señal'] = df['MACD'].ewm(span=9, adjust=False).mean() 20 | df['Histograma'] = df['MACD'] - df['Señal'] 21 | print(df[['Close', 'MACD', 'Señal', 'Histograma']]) 22 | 23 | # ADX 24 | import yfinance as yf 25 | import pandas as pd 26 | import talib as ta 27 | 28 | # Descargar del futuro del Nasdaq en Yahoo Finance 29 | df= yf.download('NQ=F', start='2020-01-01', end='2021-01-01') 30 | 31 | # Calcular el ADX 32 | df['ADX'] = ta.ADX(df['High'], df['Low'], df['Close'], timeperiod=14) 33 | print(df[['Close', 'ADX']]) 34 | 35 | # RSI 36 | import talib as ta 37 | 38 | # Calcular el RSI 39 | df['RSI'] = ta.RSI(df['Close'], timeperiod=14) 40 | print(df[['Close', 'RSI']]) 41 | 42 | # Estocástico 43 | # Calcular el Estocástico 44 | df['%K'], df['%D'] = ta.STOCH(df['High'], df['Low'], df['Close'], 45 | fastk_period=14, slowk_period=3, slowk_matype=0, 46 | slowd_period=3, slowd_matype=0) 47 | print(df[['Close', '%K', '%D']]) 48 | 49 | # Índice de Fuerza de Elder 50 | # Calcular el Índice de Fuerza de Elder 51 | df['Elder_Force'] = (df['Close'] - df['Close'].shift(1)) * df['Volume'] 52 | print(df[['Close', 'Volume', 'Elder_Force']]) 53 | 54 | # Bandas de Bollinger 55 | import pandas as pd 56 | 57 | # Calcular las Bandas de Bollinger 58 | df['SMA_20'] = df['Close'].rolling(window=20).mean() 59 | df['STD_20'] = df['Close'].rolling(window=20).std() 60 | df['Banda_Superior'] = df['SMA_20'] + (df['STD_20'] * 2) 61 | df['Banda_Inferior'] = df['SMA_20'] - (df['STD_20'] * 2) 62 | print(df[['Close', 'SMA_20', 'Banda_Superior', 'Banda_Inferior']]) 63 | 64 | # ATR 65 | # Calcular el ATR 66 | df['TR'] = df['High'] - df['Low'] 67 | df['ATR'] = df['TR'].rolling(window=14).mean() 68 | print(df[['Close', 'ATR']]) 69 | 70 | # CCI 71 | # Calcular el CCI 72 | df['CCI'] = ta.CCI(df['High'], df['Low'], df['Close'], timeperiod=20) 73 | print(df[['Close', 'CCI']]) 74 | 75 | # OBV 76 | # Calcular OBV 77 | df['OBV'] = (df['Close'].diff() > 0).astype(int) * df['Volume'] - (df['Close'].diff() < 0).astype(int) * df['Volume'] 78 | df['OBV'] = df['OBV'].cumsum() 79 | print(df[['Close', 'Volume', 'OBV']]) 80 | 81 | # Volumen Relativo 82 | # Calcular el Volumen Relativo 83 | df['Volumen_Promedio'] = df['Volume'].rolling(window=20).mean() 84 | df['Volumen_Relativo'] = df['Volume'] / df['Volumen_Promedio'] 85 | print(df[['Close', 'Volume', 'Volumen_Promedio', 'Volumen_Relativo']]) 86 | 87 | # Acumulación/Distribución 88 | # Calcular Acumulación/Distribución (A/D) 89 | df['Clv'] = ((df['Close'] - df['Low']) - (df['High'] - df['Close'])) / (df['High'] - df['Low']) 90 | df['A/D'] = (df['Clv'] * df['Volume']).cumsum() 91 | print(df[['Close', 'Volume', 'A/D']]) 92 | 93 | 94 | '''4.2''' 95 | 96 | # Regresión Lineal 97 | import yfinance as yf 98 | import pandas as pd 99 | from sklearn.model_selection import train_test_split 100 | from sklearn.linear_model import LinearRegression 101 | from sklearn.metrics import mean_squared_error, r2_score 102 | 103 | # Descargar datos del futuro del Nasdaq desde Yahoo Finance 104 | df = yf.download('NQ=F', start='2020-01-01', end='2021-01-01') 105 | 106 | # Crear la variable independiente (Días) y la dependiente (Cierre) 107 | df['Dias'] = (df.index - df.index[0]).days # Crear columna 'Días' a partir de la fecha 108 | X = df[['Dias']] # Variable independiente 109 | Y = df['Close'] # Variable dependiente (Precio de cierre) 110 | 111 | # Dividir los datos en conjunto de entrenamiento y conjunto de prueba 112 | X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=0) 113 | 114 | # Crear y ajustar el modelo de regresión lineal 115 | modelo = LinearRegression() 116 | modelo.fit(X_train, Y_train) 117 | 118 | # Hacer predicciones sobre el conjunto de prueba 119 | predicciones = modelo.predict(X_test) 120 | 121 | # Evaluar el modelo 122 | mse = mean_squared_error(Y_test, predicciones) 123 | r2 = r2_score(Y_test, predicciones) 124 | 125 | # Mostrar resultados 126 | print(f'Error Cuadrático Medio: {mse}') 127 | print(f'Coeficiente de Determinación (R^2): {r2}') 128 | 129 | # Mostrar primeras filas con predicciones 130 | df['Prediccion'] = modelo.predict(X) 131 | print(df[['Close', 'Prediccion']].head()) 132 | 133 | # Regresión Logística 134 | import yfinance as yf 135 | import pandas as pd 136 | from sklearn.model_selection import train_test_split 137 | from sklearn.linear_model import LogisticRegression 138 | from sklearn.metrics import accuracy_score, confusion_matrix, roc_auc_score 139 | 140 | # Descargar datos del futuro del Nasdaq desde Yahoo Finance 141 | df = yf.download('NQ=F', start='2020-01-01', end='2021-01-01') 142 | 143 | # Crear la variable dependiente (1 si el precio sube, 0 si baja) 144 | df['Sube'] = (df['Close'].shift(-1) > df['Close']).astype(int) 145 | 146 | # Crear la variable independiente (por ejemplo, el volumen) 147 | X = df[['Volume']] # Variable independiente 148 | Y = df['Sube'] # Variable dependiente (subida o bajada) 149 | 150 | # Dividir los datos en conjunto de entrenamiento y conjunto de prueba 151 | X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=0) 152 | 153 | # Crear y ajustar el modelo de regresión logística 154 | modelo = LogisticRegression() 155 | modelo.fit(X_train, Y_train) 156 | 157 | # Hacer predicciones sobre el conjunto de prueba 158 | predicciones = modelo.predict(X_test) 159 | 160 | # Evaluar el modelo 161 | precision = accuracy_score(Y_test, predicciones) 162 | matriz_confusion = confusion_matrix(Y_test, predicciones) 163 | roc_auc = roc_auc_score(Y_test, modelo.predict_proba(X_test)[:, 1]) 164 | 165 | # Mostrar resultados 166 | print(f'Precisión: {precision}') 167 | print(f'Matriz de Confusión:\n{matriz_confusion}') 168 | print(f'AUC-ROC: {roc_auc}') 169 | print(df[['Close', 'Sube']].tail()) 170 | 171 | # ARIMA 172 | import yfinance as yf 173 | import pandas as pd 174 | from statsmodels.tsa.arima.model import ARIMA 175 | import matplotlib.pyplot as plt 176 | 177 | # Descargar los datos 178 | df = yf.download('NQ=F', start='2020-01-01', end='2024-01-01') 179 | 180 | # Convertir el índice a columna 181 | df.reset_index(inplace=True) 182 | 183 | # Guardar las fechas en una columna separada 184 | dates = df['Date'] 185 | 186 | # Seleccionar la columna de precios de cierre y numerar el índice de 0 a x 187 | close_prices = df['Close'] 188 | close_prices.index = range(len(close_prices)) 189 | 190 | # Ajustar el modelo ARIMA 191 | model = ARIMA(close_prices, order=(5, 1, 2)) 192 | model_fit = model.fit() 193 | 194 | # Mostrar el resumen del modelo 195 | print(model_fit.summary()) 196 | 197 | # Obtener predicciones dentro de la muestra 198 | in_sample_preds = model_fit.predict(start=0, end=len(close_prices)-1) 199 | 200 | # Graficar los resultados 201 | plt.figure(figsize=(10, 6)) 202 | 203 | # Graficar los precios reales y las predicciones dentro de la muestra 204 | plt.plot(dates, close_prices, label='Historical Prices') 205 | plt.plot(dates, in_sample_preds, label='Predictions', linestyle='--', color='green') 206 | 207 | plt.title('ARIMA Predictions') 208 | plt.xlabel('Date') 209 | plt.ylabel('Close Price') 210 | plt.legend() 211 | plt.show() 212 | plt.show() 213 | 214 | # GARCH 215 | import yfinance as yf 216 | import pandas as pd 217 | from arch import arch_model 218 | import matplotlib.pyplot as plt 219 | 220 | # Descargar los datos de Yahoo Finance 221 | df = yf.download('^NDX', start='2020-01-01', end='2024-01-01') 222 | 223 | # Calcular los retornos logarítmicos 224 | df['Returns'] = 100 * df['Close'].pct_change() 225 | 226 | # Eliminar los valores NaN en la columna de retornos 227 | df = df.dropna() 228 | 229 | # Crear el modelo GARCH 230 | model = arch_model(df['Returns'], vol='Garch', p=1, q=1) 231 | garch_fit = model.fit() 232 | 233 | # Mostrar el resumen del modelo 234 | print(garch_fit.summary()) 235 | 236 | # Hacer predicciones de volatilidad in-sample y out-of-sample (OOS) 237 | # Predicciones in-sample 238 | in_sample_forecasts = garch_fit.conditional_volatility 239 | 240 | # Predicciones out-of-sample (OOS) 241 | oos_forecasts = garch_fit.forecast(horizon=10) 242 | forecasted_variance = oos_forecasts.variance.iloc[-1] 243 | forecasted_volatility_oos = forecasted_variance ** 0.5 # Volatilidad OOS 244 | 245 | # Calcular la volatilidad histórica (rolling std de los retornos) 246 | df['Historical Volatility'] = df['Returns'].rolling(window=30).std() 247 | 248 | # Crear un rango de fechas para los próximos 10 días (out-of-sample) 249 | future_dates = pd.date_range(start=df.index[-1], periods=10, freq='B') 250 | 251 | # Graficar la volatilidad histórica, in-sample y out-of-sample 252 | plt.figure(figsize=(12, 6)) 253 | 254 | # Graficar la volatilidad histórica 255 | plt.plot(df.index, df['Historical Volatility'], label='Historical Volatility', color='blue') 256 | 257 | # Graficar las predicciones in-sample 258 | plt.plot(df.index, in_sample_forecasts, label='In-Sample Forecasted Volatility', color='orange') 259 | 260 | # Graficar las predicciones out-of-sample (OOS) 261 | plt.plot(future_dates, forecasted_volatility_oos, label='Out-of-Sample Forecasted Volatility', linestyle='--', color='red') 262 | 263 | # Añadir título y etiquetas 264 | plt.title('Historical vs Forecasted Volatility (GARCH Model)') 265 | plt.xlabel('Date') 266 | plt.ylabel('Volatility') 267 | plt.legend() 268 | plt.show() 269 | 270 | # SARIMA 271 | import yfinance as yf 272 | import pandas as pd 273 | from statsmodels.tsa.statespace.sarimax import SARIMAX 274 | import matplotlib.pyplot as plt 275 | 276 | # Descargar los datos del maíz 277 | df = yf.download('ZC=F', start='2015-01-01', end='2024-01-01') 278 | 279 | # Convertir el índice a columna 280 | df.reset_index(inplace=True) 281 | 282 | # Guardar las fechas y el precio de cierre 283 | dates = df['Date'] 284 | close_prices = df['Close'] 285 | 286 | # Numerar el índice de 0 a x 287 | close_prices.index = range(len(close_prices)) 288 | 289 | # Ajustar el modelo SARIMA 290 | sarima_model = SARIMAX(close_prices, order=(1,1,1), seasonal_order=(1,1,1,12)) 291 | sarima_fit = sarima_model.fit() 292 | 293 | # Mostrar el resumen del modelo 294 | print(sarima_fit.summary()) 295 | 296 | # Obtener predicciones dentro de la muestra 297 | in_sample_preds = sarima_fit.predict(start=0, end=len(close_prices)-1) 298 | 299 | # Graficar los resultados 300 | plt.figure(figsize=(10, 6)) 301 | plt.plot(dates, close_prices, label='Historical Prices') 302 | plt.plot(dates, in_sample_preds, label='Predictions', linestyle='--', color='orange') 303 | plt.title('SARIMA Predictions - Corn Futures') 304 | plt.xlabel('Date') 305 | plt.ylabel('Close Price') 306 | plt.legend() 307 | plt.show() 308 | 309 | 310 | # Random Forests 311 | import yfinance as yf 312 | import pandas as pd 313 | from sklearn.model_selection import train_test_split 314 | from sklearn.tree import DecisionTreeClassifier 315 | from sklearn.metrics import accuracy_score, classification_report 316 | 317 | # Descargar los datos de precios del futuro del Nasdaq 318 | df = yf.download('NQ=F', start='2020-01-01', end='2021-01-01') 319 | 320 | # Crear una nueva columna 'Sube' que será nuestro target (1 si sube, 0 si baja) 321 | df['Sube'] = (df['Close'].shift(-1) > df['Close']).astype(int) 322 | 323 | # Limpiar datos eliminando NaNs 324 | df.dropna(inplace=True) 325 | 326 | # Definir las variables independientes (usaremos solo el precio de cierre para este ejemplo) 327 | X = df[['Close']] 328 | y = df['Sube'] 329 | 330 | # Dividir los datos en conjunto de entrenamiento y prueba 331 | X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) 332 | 333 | # Crear el Árbol de Decisión 334 | tree_model = DecisionTreeClassifier() 335 | tree_model.fit(X_train, y_train) 336 | 337 | # Hacer predicciones 338 | y_pred = tree_model.predict(X_test) 339 | 340 | # Evaluar el modelo 341 | accuracy = accuracy_score(y_test, y_pred) 342 | print(f'Precisión del Árbol de Decisión: {accuracy}') 343 | print(classification_report(y_test, y_pred)) 344 | 345 | 346 | # Random Forest 347 | import yfinance as yf 348 | import pandas as pd 349 | from sklearn.ensemble import RandomForestClassifier 350 | from sklearn.model_selection import train_test_split, GridSearchCV 351 | from sklearn.metrics import accuracy_score, classification_report 352 | 353 | # Descargar los datos de precios del futuro del Nasdaq 354 | df = yf.download('NQ=F', start='2020-01-01', end='2023-01-01') 355 | 356 | # Calcular indicadores técnicos adicionales 357 | df['SMA_10'] = ta.SMA(df['Close'], timeperiod=10) # Media Móvil Simple de 10 días 358 | 359 | # Crear la columna objetivo 'Sube', que indica si el precio sube al día siguiente 360 | df['Sube'] = (df['Close'].shift(-1) > df['Close']).astype(int) 361 | 362 | # Limpiar datos eliminando NaNs 363 | df.dropna(inplace=True) 364 | 365 | # Definir las variables independientes (indicadores técnicos y precio de cierre) 366 | X = df[['Close', 'SMA_10']] 367 | 368 | y = df['Sube'] 369 | 370 | # Dividir los datos en conjunto de entrenamiento y prueba 371 | X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) 372 | 373 | # Definir los hiperparámetros que queremos probar con GridSearchCV 374 | param_grid = { 375 | 'n_estimators': [100, 200, 300], # Número de árboles 376 | 'max_depth': [None, 10, 20, 30], # Profundidad máxima de los árboles 377 | 'min_samples_split': [2, 5, 10], # Mínimo de muestras para dividir un nodo 378 | 'min_samples_leaf': [1, 2, 4], # Mínimo de muestras en una hoja 379 | 'bootstrap': [True, False] # Si utilizar o no el bootstrap 380 | } 381 | 382 | # Crear el modelo Random Forest 383 | rf_model = RandomForestClassifier(random_state=42) 384 | 385 | # Crear el objeto GridSearchCV 386 | grid_search = GridSearchCV(estimator=rf_model, param_grid=param_grid, 387 | cv=5, n_jobs=-1, verbose=2) 388 | 389 | # Ajustar el modelo con los datos de entrenamiento 390 | grid_search.fit(X_train, y_train) 391 | 392 | # Imprimir los mejores hiperparámetros encontrados 393 | print(f'Mejores Hiperparámetros: {grid_search.best_params_}') 394 | 395 | # Evaluar el modelo con los mejores hiperparámetros 396 | best_rf_model = grid_search.best_estimator_ 397 | y_pred = best_rf_model.predict(X_test) 398 | 399 | # Evaluar el modelo 400 | accuracy = accuracy_score(y_test, y_pred) 401 | print(f'Precisión del Random Forest después de ajuste: {accuracy}') 402 | print(classification_report(y_test, y_pred)) 403 | 404 | 405 | # LSTM 406 | import yfinance as yf 407 | import numpy as np 408 | import pandas as pd 409 | from sklearn.preprocessing import MinMaxScaler 410 | from tensorflow.keras.models import Sequential 411 | from tensorflow.keras.layers import LSTM, Dense, Dropout 412 | from sklearn.metrics import mean_squared_error, mean_absolute_error, mean_absolute_percentage_error 413 | 414 | # Descargar los datos del Nasdaq 415 | df = yf.download('NQ=F', start='2020-01-01', end='2023-01-01') 416 | 417 | # Normalizar los datos usando MinMaxScaler 418 | scaler = MinMaxScaler(feature_range=(0,1)) 419 | df_scaled = scaler.fit_transform(df[['Close']]) 420 | 421 | # Preparar los datos para el modelo LSTM 422 | X_train = [] 423 | y_train = [] 424 | window_size = 60 # Usamos una ventana de 60 días 425 | 426 | for i in range(window_size, len(df_scaled)): 427 | X_train.append(df_scaled[i-window_size:i, 0]) 428 | y_train.append(df_scaled[i, 0]) 429 | 430 | X_train, y_train = np.array(X_train), np.array(y_train) 431 | X_train = np.reshape(X_train, (X_train.shape[0], X_train.shape[1], 1)) 432 | 433 | # Crear el modelo LSTM 434 | model = Sequential() 435 | 436 | model.add(LSTM(units=50, return_sequences=True, input_shape=(X_train.shape[1], 1))) 437 | model.add(Dropout(0.2)) 438 | model.add(LSTM(units=50, return_sequences=False)) 439 | model.add(Dropout(0.2)) 440 | model.add(Dense(units=1)) # Capa de salida con una sola unidad (precio predicho) 441 | 442 | model.compile(optimizer='adam', loss='mean_squared_error') 443 | 444 | # Entrenar el modelo 445 | model.fit(X_train, y_train, epochs=50, batch_size=32) 446 | 447 | # Hacer predicciones sobre nuevos datos 448 | df_test = yf.download('NQ=F', start='2023-01-02', end='2024-01-01') 449 | df_total = pd.concat((df['Close'], df_test['Close']), axis=0) 450 | inputs = df_total[len(df_total) - len(df_test) - window_size:].values 451 | inputs = inputs.reshape(-1,1) 452 | inputs = scaler.transform(inputs) 453 | 454 | X_test = [] 455 | for i in range(window_size, len(inputs)): 456 | X_test.append(inputs[i-window_size:i, 0]) 457 | 458 | X_test = np.array(X_test) 459 | X_test = np.reshape(X_test, (X_test.shape[0], X_test.shape[1], 1)) 460 | 461 | # Predecir los precios 462 | predicted_price = model.predict(X_test) 463 | predicted_price = scaler.inverse_transform(predicted_price) 464 | 465 | # Graficar los resultados 466 | import matplotlib.pyplot as plt 467 | 468 | plt.plot(df_test['Close'].values, color='blue', label='Precio Real') 469 | plt.plot(predicted_price, color='red', label='Precio Predicho por LSTM') 470 | plt.title('Predicción de Precio usando LSTM') 471 | plt.xlabel('Fecha') 472 | plt.ylabel('Precio de Cierre') 473 | plt.legend() 474 | plt.show() 475 | 476 | # Calcular RMSE 477 | rmse = np.sqrt(mean_squared_error(df_test['Close'].values, predicted_price)) 478 | 479 | # Calcular MAE 480 | mae = mean_absolute_error(df_test['Close'].values, predicted_price) 481 | 482 | # Calcular MAPE 483 | mape = mean_absolute_percentage_error(df_test['Close'].values, predicted_price) 484 | 485 | # Generar el resumen del performance 486 | performance_summary = f""" 487 | Evaluación del Modelo LSTM para la Predicción del Nasdaq: 488 | 489 | - RMSE (Raíz del Error Cuadrático Medio): {rmse:.2f} 490 | - MAE (Error Absoluto Medio): {mae:.2f} 491 | - MAPE (Error Porcentual Absoluto Medio): {mape * 100:.2f}% 492 | 493 | """ 494 | print(performance_summary) 495 | 496 | 497 | 498 | # K-Means 499 | import yfinance as yf 500 | import pandas as pd 501 | from sklearn.cluster import KMeans 502 | import matplotlib.pyplot as plt 503 | import seaborn as sns 504 | 505 | # Descargar los datos del índice S&P 500 506 | df = yf.download('^GSPC', start='2015-01-01', end='2023-01-01') 507 | 508 | # Calcular características adicionales 509 | df['Price Change'] = df['Close'].pct_change() * 100 #Cambio porcentual diario del precio 510 | df['Volatility'] = df['High'] - df['Low'] #Rango de precios como medida de volatilidad 511 | df['Volume'] = df['Volume'] # Volumen diario 512 | 513 | # Limpiar datos eliminando NaNs 514 | df.dropna(inplace=True) 515 | 516 | # Seleccionar las características para el clustering 517 | X = df[['Price Change', 'Volatility', 'Volume']] 518 | 519 | # Aplicar K-Means con 4 clústeres 520 | kmeans = KMeans(n_clusters=4, random_state=42) 521 | df['Market Regime'] = kmeans.fit_predict(X) 522 | 523 | # Visualizar los clústeres identificados por el modelo 524 | sns.scatterplot(x='Price Change', y='Volatility', hue='Market Regime', 525 | data=df, palette='Set1') 526 | plt.title('Identificación de Regímenes de Mercado usando K-Means') 527 | plt.xlabel('Cambio Porcentual del Precio') 528 | plt.ylabel('Volatilidad (High - Low)') 529 | plt.show() 530 | 531 | 532 | # Graficar la línea de cierre y sus regímenes de mercado 533 | plt.figure(figsize=(14, 7)) 534 | # Colores para cada clúster 535 | colors = ['#ff0000', '#0000ff', '#00ff00'] 536 | plt.plot(df.index, df['Close'], color='black', label='Precio de Cierre') 537 | 538 | # Superponer puntos de color según el régimen 539 | for regime in range(3): 540 | plt.scatter(df.index[df['Market Regime'] == regime], 541 | df['Close'][df['Market Regime'] == regime], 542 | color=colors[regime], label=f'Regimen {regime}', s=10) 543 | 544 | plt.title('Precio de Cierre del S&P 500 con Regímenes de Mercado (K-Means)') 545 | plt.xlabel('Fecha') 546 | plt.ylabel('Precio de Cierre') 547 | plt.legend(loc='best') 548 | plt.show() 549 | 550 | 551 | # DBSCAN 552 | import yfinance as yf 553 | import pandas as pd 554 | from sklearn.preprocessing import StandardScaler 555 | from sklearn.cluster import DBSCAN 556 | import matplotlib.pyplot as plt 557 | 558 | # Descargar datos de precios del S&P 500 559 | df = yf.download('^GSPC', start='2015-01-01', end='2024-01-01') 560 | 561 | # Calcular el cambio porcentual diario 562 | df['Pct_Change'] = df['Close'].pct_change() * 100 563 | 564 | # Calcular la volatilidad diaria (High - Low) 565 | df['Volatility'] = df['High'] - df['Low'] 566 | 567 | # Limpiar datos eliminando NaNs 568 | df.dropna(inplace=True) 569 | 570 | # Normalizar los datos (DBSCAN es sensible a las escalas de los datos) 571 | scaler = StandardScaler() 572 | X = scaler.fit_transform(df[['Pct_Change', 'Volatility']]) 573 | 574 | # Aplicar DBSCAN 575 | dbscan = DBSCAN(eps=0.5, min_samples=5) 576 | df['Cluster'] = dbscan.fit_predict(X) 577 | 578 | # Graficar los resultados, marcando las anomalías como 'ruido' 579 | plt.figure(figsize=(10, 6)) 580 | 581 | # Identificar los puntos de ruido (Cluster -1) 582 | noise = df[df['Cluster'] == -1] 583 | clusters = df[df['Cluster'] != -1] 584 | 585 | plt.scatter(clusters['Pct_Change'], clusters['Volatility'], c=clusters['Cluster'], cmap='Set1', label='Clustered Points', alpha=0.6) 586 | plt.scatter(noise['Pct_Change'], noise['Volatility'], c='black', label='Noise (Anomalías)', alpha=0.8) 587 | 588 | plt.title('Detección de Patrones Anómalos en el S&P 500 usando DBSCAN') 589 | plt.xlabel('Cambio Porcentual Diario (%)') 590 | plt.ylabel('Volatilidad (High - Low)') 591 | plt.legend() 592 | plt.show() 593 | 594 | 595 | -------------------------------------------------------------------------------- /Modulo_5.py: -------------------------------------------------------------------------------- 1 | # Look-Ahead Bias 2 | import yfinance as yf 3 | import pandas as pd 4 | 5 | # Descargar datos de un activo financiero 6 | df = yf.download('AAPL', start='2020-01-01', end='2023-01-01') 7 | 8 | # Supongamos que queremos basar nuestra estrategia en el precio de cierre del día anterior 9 | df['Close_Anterior'] = df['Close'].shift(1) # Usamos el cierre del día anterior 10 | 11 | # Aquí es donde aplicaríamos la lógica de nuestra estrategia 12 | # df['Signal'] sería una columna con las señales de compra o venta 13 | 14 | # Overfitting 15 | import yfinance as yf 16 | import pandas as pd 17 | import numpy as np 18 | 19 | # Descargar datos históricos del futuro del Nasdaq 20 | df = yf.download('NQ=F', start='2015-01-01', end='2024-01-01') 21 | 22 | # Crear una columna 'Returns' para los rendimientos diarios 23 | df['Returns'] = df['Close'].pct_change() 24 | 25 | # Eliminar los valores NaN que pueden surgir al calcular los rendimientos 26 | df.dropna(inplace=True) 27 | 28 | # Dividir los datos en tres conjuntos: Desarrollo, Test y Validación 29 | # Aquí vamos a dividir el conjunto de datos en 60% Desarrollo, 20% Test y 20% Validación 30 | n = len(df) 31 | train_size = int(n * 0.6) 32 | test_size = int(n * 0.2) 33 | validation_size = n - train_size - test_size 34 | 35 | # Conjunto de Desarrollo 36 | df_train = df.iloc[:train_size] 37 | 38 | # Conjunto de Test (fuera de muestra) 39 | df_test = df.iloc[train_size:train_size + test_size] 40 | 41 | # Conjunto de Validación (para validación final) 42 | df_validation = df.iloc[train_size + test_size:] 43 | 44 | # Mostramos el tamaño de cada conjunto de datos 45 | print(f"Tamaño del conjunto de Desarrollo: {len(df_train)}") 46 | print(f"Tamaño del conjunto de Test: {len(df_test)}") 47 | print(f"Tamaño del conjunto de Validación: {len(df_validation)}") 48 | 49 | -------------------------------------------------------------------------------- /Modulo_6.py: -------------------------------------------------------------------------------- 1 | '''6.1.1''' 2 | # Ejemplo básico de reglas de entrada y salida 3 | import yfinance as yf 4 | import pandas as pd 5 | 6 | # Descargar los datos de un activo 7 | df = yf.download('AAPL', start='2021-01-01', end='2024-01-01') 8 | 9 | # Calcular medias móviles 10 | df['SMA_10'] = df['Close'].rolling(window=10).mean() 11 | df['SMA_50'] = df['Close'].rolling(window=50).mean() 12 | 13 | # Definir las reglas de entrada y salida 14 | df['Signal'] = 0 # Inicializamos las señales a 0 15 | df['Signal'][df['SMA_10'] > df['SMA_50']] = 1 # Regla de entrada 16 | f['Signal'].diff() # Detectamos cambios en las señales para identificar entradas y salidas 17 | 18 | # eliminamos NaNs 19 | df = df.dropna() 20 | 21 | # Imprimir las primeras filas 22 | print(df[['Close', 'SMA_10', 'SMA_50', 'Signal', 'Position']]) 23 | 24 | '''6.1.2''' 25 | # Construcción de Señales de Trading 26 | import yfinance as yf 27 | import pandas as pd 28 | import talib as ta 29 | import matplotlib.pyplot as plt 30 | 31 | # Descargar datos históricos del futuro del Nasdaq 32 | df = yf.download('NQ=F', start='2015-01-01', end='2024-01-01') 33 | 34 | # Calcular indicadores 35 | df['SMA_50'] = ta.SMA(df['Close'], timeperiod=50) 36 | df['SMA_200'] = ta.SMA(df['Close'], timeperiod=200) 37 | df['RSI'] = ta.RSI(df['Close'], timeperiod=14) 38 | 39 | # Definir reglas de entrada y salida 40 | df['Signal'] = 0 41 | df.loc[(df['RSI'] < 30) & (df['SMA_50'] > df['SMA_200']), 'Signal'] = 1 # Compra 42 | df.loc[(df['RSI'] > 70) & (df['SMA_50'] < df['SMA_200']), 'Signal'] = -1 # Venta 43 | 44 | # graficar en un grafico de precios los puntos de compra y venta 45 | plt.figure(figsize=(10, 5)) 46 | plt.plot(df['Close'], label='Close') 47 | plt.plot(df.loc[df['Signal'] == 1, 'Close'], '^', markersize=10, color='g', lw=0, label='Buy Signal') 48 | plt.plot(df.loc[df['Signal'] == -1, 'Close'], 'v', markersize=10, color='r', lw=0, label='Sell Signal') 49 | plt.title('AAPL Close Price') 50 | plt.legend() 51 | plt.show() 52 | 53 | 54 | # Indicadores Combinados 55 | df['ATR'] = ta.ATR(df['High'], df['Low'], df['Close'], timeperiod=14) 56 | df['Signal'] = 0 # Inicializamos las señales a 0 57 | df.loc[(df['SMA_10'] > df['SMA_50']) & (df['ATR'] < 2), 'Signal'] = 1 58 | 59 | 60 | # Señales Basadas en Patrones de Velas 61 | df['Doji'] = ((df['Close'] - df['Open']).abs() < (df['High'] - df['Low']) * 0.1).astype(int) 62 | df.loc[(df['Doji'] == 1) & (df['RSI'] < 30), 'Signal'] = 1 # Comprar cuando se forma un Doji en condiciones de sobreventa 63 | 64 | 65 | # Contratendencia 66 | df['MACD'] = df['EMA_12'] - df['EMA_26'] 67 | df['Signal_Line'] = df['MACD'].ewm(span=9, adjust=False).mean() 68 | df['Signal'] = 0 # Inicializamos las señales a 0 69 | df['Signal'][df['MACD'] > df['Signal_Line']] = 1 # Compra cuando MACD > Línea de señal 70 | 71 | 72 | '''6.2.2''' 73 | # Ejemplo de Backtesting Básico en Python 74 | import yfinance as yf 75 | import pandas as pd 76 | import matplotlib.pyplot as plt 77 | 78 | # Descargar datos históricos 79 | df = yf.download('NQ=F', start='2010-01-01', end='2024-01-01') 80 | 81 | # Calcular medias móviles 82 | df['SMA_50'] = df['Close'].rolling(window=50).mean() 83 | df['SMA_200'] = df['Close'].rolling(window=200).mean() 84 | 85 | # Definir reglas de entrada y salida 86 | df['Signal'] = 0 87 | df.loc[df['SMA_50'] > df['SMA_200'], 'Signal'] = 1 # Compra 88 | df.loc[df['SMA_50'] < df['SMA_200'], 'Signal'] = -1 # Vende 89 | 90 | # Simular la ejecución de las órdenes 91 | df['Position'] = df['Signal'].shift() # Simular la ejecución al siguiente día 92 | df['Strategy_Returns'] = df['Position'] * df['Close'].pct_change() # Retornos de la estrategia 93 | 94 | # Eliminar NaNs 95 | df.dropna(inplace=True) 96 | 97 | # Graficar el rendimiento de la estrategia frente al activo 98 | (df['Strategy_Returns'] + 1).cumprod().plot(label='Strategy', figsize=(10,5)) 99 | (df['Close'].pct_change() + 1).cumprod().plot(label='AAPL') 100 | plt.legend() 101 | plt.show() 102 | 103 | 104 | # Ejemplo con Backtrader 105 | import backtrader as bt 106 | import yfinance as yf 107 | 108 | # Descargar datos usando yfinance 109 | data = bt.feeds.PandasData(dataname=yf.download('AAPL', start='2020-01-01', 110 | end='2024-01-01')) 111 | 112 | # Crear una clase de estrategia 113 | class SMACross(bt.Strategy): 114 | def __init__(self): 115 | self.sma1 = bt.indicators.SimpleMovingAverage(self.data.close, period=50) 116 | self.sma2 = bt.indicators.SimpleMovingAverage(self.data.close, period=200) 117 | 118 | def next(self): 119 | if not self.position: # No tenemos posición abierta 120 | if self.sma1 > self.sma2: # Regla de entrada 121 | self.buy() 122 | elif self.sma1 < self.sma2: # Regla de salida 123 | self.sell() 124 | 125 | # Crear el cerebro y añadir estrategia 126 | cerebro = bt.Cerebro() 127 | cerebro.addstrategy(SMACross) 128 | 129 | # Añadir datos al cerebro 130 | cerebro.adddata(data) 131 | 132 | # Configurar capital inicial 133 | cerebro.broker.setcash(10000) 134 | 135 | # Ejecutar el backtest 136 | print(f'Valor inicial: {cerebro.broker.getvalue()}') 137 | cerebro.run() 138 | print(f'Valor final: {cerebro.broker.getvalue()}') 139 | 140 | # Graficar resultados 141 | cerebro.plot() 142 | 143 | '''6.2.3''' 144 | # rendimiento anualizado 145 | total_return = (df['Strategy_Returns'] + 1).prod() - 1 146 | annualized_return = (1 + total_return) ** (252 / len(df)) - 1 # Ajustado por 252 días de trading anuales 147 | print(f'Rendimiento Total: {total_return:.2%}') 148 | print(f'Rendimiento Anualizado: {annualized_return:.2%}') 149 | 150 | 151 | # Ratio de Sharpe 152 | risk_free_rate = 0.01 # Supongamos que la tasa libre de riesgo es del 1% 153 | excess_return = df['Strategy_Returns'].mean() - (risk_free_rate / 252) # Retornos en exceso sobre la tasa libre de riesgo 154 | sharpe_ratio = excess_return / df['Strategy_Returns'].std() 155 | print(f'Ratio de Sharpe: {sharpe_ratio:.2f}') 156 | 157 | 158 | # Máximo Drawdown 159 | df['Cumulative_Returns'] = (df['Strategy_Returns'] + 1).cumprod() 160 | df['Peak'] = df['Cumulative_Returns'].cummax() 161 | df['Drawdown'] = (df['Cumulative_Returns'] - df['Peak']) / df['Peak'] 162 | max_drawdown = df['Drawdown'].min() 163 | print(f'Máximo Drawdown: {max_drawdown:.2%}') 164 | 165 | 166 | # Ratio de Ganancias/Pérdidas 167 | winning_trades = df[df['Strategy_Returns'] > 0]['Strategy_Returns'].count() 168 | losing_trades = df[df['Strategy_Returns'] <= 0]['Strategy_Returns'].count() 169 | win_loss_ratio = winning_trades / losing_trades if losing_trades != 0 else np.inf 170 | ganancia_media = df[df['Strategy_Returns'] > 0]['Strategy_Returns'].mean() 171 | perdida_media = df[df['Strategy_Returns'] <= 0]['Strategy_Returns'].mean() 172 | print(f'Operaciones Ganadoras: {winning_trades} -- Ganancia Media: {ganancia_media:.2%}') 173 | print(f'Operaciones Perdedoras: {losing_trades} -- Pérdida Media: {perdida_media:.2%}') 174 | print(f'Ratio Ganancias/Pérdidas: {win_loss_ratio:.2f}') 175 | 176 | 177 | # Visualización de Resultados 178 | import matplotlib.pyplot as plt 179 | 180 | # Graficar el Drawdown 181 | plt.figure(figsize=(10, 5)) 182 | df['Drawdown'].plot(label='Drawdown') 183 | plt.title('Drawdown de la Estrategia') 184 | plt.ylabel('Porcentaje de Drawdown') 185 | plt.legend() 186 | plt.show() 187 | 188 | 189 | # Incorporando Costos Operativos: Comisiones y Slippage 190 | # Simular comisiones y slippage (costos fijos y slippage como porcentaje del precio) 191 | commission = 0.001 # 0.1% por transacción 192 | slippage = 0.0005 # 0.05% de slippage por transacción 193 | 194 | df['Transaction_Cost'] = df['Signal'].diff().abs() * (commission + slippage) 195 | df['Net_Strategy_Returns'] = df['Strategy_Returns'] - df['Transaction_Cost'] 196 | 197 | 198 | # Ejemplo del Backtest anterior con metricas incorporadas: 199 | import yfinance as yf 200 | import pandas as pd 201 | import matplotlib.pyplot as plt 202 | import numpy as np 203 | 204 | # Descargar datos históricos 205 | df = yf.download('NQ=F', start='2010-01-01', end='2024-01-01') 206 | 207 | # Calcular medias móviles 208 | df['SMA_50'] = df['Close'].rolling(window=50).mean() 209 | df['SMA_200'] = df['Close'].rolling(window=200).mean() 210 | 211 | # Definir reglas de entrada y salida 212 | df['Signal'] = 0 213 | df.loc[df['SMA_50'] > df['SMA_200'], 'Signal'] = 1 # Compra 214 | df.loc[df['SMA_50'] < df['SMA_200'], 'Signal'] = -1 # Vende 215 | 216 | # Simular la ejecución de las órdenes 217 | df['Position'] = df['Signal'].shift() # Shift para simular la ejecución al siguiente día 218 | 219 | # Definir comisiones y slippage 220 | commission = 0.001 # 0.1% por operación 221 | slippage = 0.0005 # 0.05% de slippage en cada operación 222 | 223 | # Calcular los retornos de la estrategia con comisiones y slippage 224 | df['Returns'] = df['Close'].pct_change() # Retornos diarios del activo 225 | df['Strategy_Returns'] = df['Position'] * df['Returns'] 226 | 227 | # Aplicar comisiones y slippage 228 | # Nota: Ajusta los valores de comisiones y slippage según el broker. 229 | df['Strategy_Returns'] -= (abs(df['Position'].diff()) * (commission + slippage)) 230 | 231 | # Eliminar NaNs 232 | df.dropna(inplace=True) 233 | 234 | # Calcular rendimiento total y anualizado 235 | total_return = (df['Strategy_Returns'] + 1).prod() - 1 236 | annualized_return = (1 + total_return) ** (252 / len(df)) - 1 # Ajustado por 252 días de trading anuales 237 | print(f'Rendimiento Total: {total_return:.2%}') 238 | print(f'Rendimiento Anualizado: {annualized_return:.2%}') 239 | 240 | # Ratio de Sharpe 241 | risk_free_rate = 0.01 # Supongamos que la tasa libre de riesgo es del 1% 242 | excess_return = df['Strategy_Returns'].mean() - (risk_free_rate / 252) # Retornos en exceso sobre la tasa libre de riesgo 243 | sharpe_ratio = excess_return / df['Strategy_Returns'].std() 244 | print(f'Ratio de Sharpe: {sharpe_ratio:.2f}') 245 | 246 | # Drawdown Máximo 247 | df['Cumulative_Returns'] = (df['Strategy_Returns'] + 1).cumprod() 248 | df['Peak'] = df['Cumulative_Returns'].cummax() 249 | df['Drawdown'] = (df['Cumulative_Returns'] - df['Peak']) / df['Peak'] 250 | max_drawdown = df['Drawdown'].min() 251 | print(f'Máximo Drawdown: {max_drawdown:.2%}') 252 | 253 | # Ratio de Ganancias/Pérdidas 254 | winning_trades = df[df['Strategy_Returns'] > 0]['Strategy_Returns'].count() 255 | losing_trades = df[df['Strategy_Returns'] <= 0]['Strategy_Returns'].count() 256 | win_loss_ratio = winning_trades / losing_trades if losing_trades != 0 else np.inf 257 | ganancia_media = df[df['Strategy_Returns'] > 0]['Strategy_Returns'].mean() 258 | perdida_media = df[df['Strategy_Returns'] <= 0]['Strategy_Returns'].mean() 259 | print(f'Operaciones Ganadoras: {winning_trades} -- Ganancia Media: {ganancia_media:.2%}') 260 | print(f'Operaciones Perdedoras: {losing_trades} -- Pérdida Media: {perdida_media:.2%}') 261 | print(f'Ratio Ganancias/Pérdidas: {win_loss_ratio:.2f}') 262 | 263 | # Graficar el rendimiento de la estrategia frente al activo 264 | fig, ax1 = plt.subplots(figsize=(10, 5)) 265 | 266 | # Graficar los rendimientos acumulados de la estrategia 267 | ax1.plot(df.index, df['Cumulative_Returns'], label='Strategy', color='blue', lw=2) 268 | 269 | # Graficar los rendimientos acumulados del activo directamente usando Close 270 | ax1.plot(df.index, (df['Close'] / df['Close'].iloc[0]), label='Asset', color='green', lw=2) 271 | 272 | ax1.set_ylabel('Cumulative Returns') 273 | 274 | # Graficar el drawdown coloreado 275 | ax2 = ax1.twinx() 276 | ax2.fill_between(df.index, df['Drawdown'], 0, color='red', alpha=0.3, label='Drawdown') 277 | ax2.set_ylabel('Drawdown') 278 | 279 | # Configurar leyendas y títulos 280 | ax1.legend(loc='upper left') 281 | ax2.legend(loc='lower left') 282 | plt.title('Strategy vs Asset Returns with Drawdown (including commissions and slippage)') 283 | 284 | # Mostrar el gráfico 285 | plt.show() 286 | 287 | '''6.2.4''' 288 | # Cálculo incorrecto de retornos 289 | # Calcular correctamente los retornos diarios 290 | df['Returns'] = df['Close'].pct_change() 291 | 292 | 293 | # Aplicar las señales de entrada y salida de forma incorrecta 294 | # Simular la ejecución al día siguiente usando shift 295 | df['Position'] = df['Signal'].shift(1) 296 | 297 | 298 | # Costos de transacción 299 | # Simular comisiones y slippage 300 | commission = 0.001 # 0.1% por transacción 301 | slippage = 0.0005 # 0.05% de slippage 302 | df['Transaction_Cost'] = df['Signal'].diff().abs() * (commission + slippage) 303 | df['Net_Strategy_Returns'] = df['Strategy_Returns'] - df['Transaction_Cost'] 304 | 305 | 306 | # Falta de ajustes corporativos 307 | # Uso de datos ajustados para evitar errores en backtesting 308 | df = yf.download('AAPL', start='2015-01-01', end='2024-01-01', adjusted=True) 309 | 310 | '''6.3.1''' 311 | # Ejemplo de ajuste de parámetros en Python 312 | import yfinance as yf 313 | import pandas as pd 314 | import numpy as np 315 | 316 | # Descargar datos históricos 317 | df = yf.download('AAPL', start='2015-01-01', end='2024-01-01') 318 | 319 | # Definir función para calcular el rendimiento de la estrategia 320 | def backtest_strategy(short_window, long_window): 321 | df['SMA_short'] = df['Close'].rolling(window=short_window).mean() 322 | df['SMA_long'] = df['Close'].rolling(window=long_window).mean() 323 | df['Signal'] = np.where(df['SMA_short'] > df['SMA_long'], 1, -1) 324 | df['Position'] = df['Signal'].shift() 325 | df['Strategy_Returns'] = df['Position'] * df['Close'].pct_change() 326 | return (df['Strategy_Returns'] + 1).cumprod()[-1] - 1 # Retorno total 327 | 328 | # Definir el rango de parámetros a probar 329 | short_windows = range(10, 100, 10) # De 10 a 90 días 330 | long_windows = range(50, 200, 10) # De 50 a 190 días 331 | 332 | # Probar todas las combinaciones de parámetros 333 | best_performance = -np.inf 334 | best_params = (None, None) 335 | 336 | for short_window in short_windows: 337 | for long_window in long_windows: 338 | if short_window >= long_window: 339 | continue # Evitar combinaciones donde la media corta sea mayor o igual a la larga 340 | performance = backtest_strategy(short_window, long_window) 341 | if performance > best_performance: 342 | best_performance = performance 343 | best_params = (short_window, long_window) 344 | 345 | print(f"Mejores parámetros: SMA corta={best_params[0]}, SMA larga={best_params[1]}") 346 | print(f"Mejor rendimiento: {best_performance:.2%}") 347 | 348 | '''6.3.2''' 349 | # Grid Search 350 | import yfinance as yf 351 | import pandas as pd 352 | from sklearn.model_selection import ParameterGrid 353 | import numpy as np 354 | 355 | # Descargar datos históricos 356 | df = yf.download('NQ=F', start='2010-01-01', end='2024-01-01') 357 | 358 | # Rango de valores para las medias móviles 359 | param_grid = { 360 | 'SMA_1': [10, 20, 50], 361 | 'SMA_2': [100, 150, 200] 362 | } 363 | 364 | # Función de backtest para cada combinación de parámetros 365 | def backtest_strategy(sma_1, sma_2): 366 | df['SMA_50'] = df['Close'].rolling(window=sma_1).mean() 367 | df['SMA_200'] = df['Close'].rolling(window=sma_2).mean() 368 | 369 | df['Signal'] = 0 370 | df.loc[df['SMA_50'] > df['SMA_200'], 'Signal'] = 1 371 | df.loc[df['SMA_50'] < df['SMA_200'], 'Signal'] = -1 372 | 373 | df['Position'] = df['Signal'].shift() 374 | df['Strategy_Returns'] = df['Position'] * df['Close'].pct_change() 375 | df.dropna(inplace=True) 376 | 377 | # Rendimiento total de la estrategia 378 | return (df['Strategy_Returns'] + 1).prod() - 1 379 | 380 | # Iteramos sobre todas las combinaciones posibles en la grid 381 | best_params = None 382 | best_return = -np.inf 383 | 384 | for params in ParameterGrid(param_grid): 385 | sma_1 = params['SMA_1'] 386 | sma_2 = params['SMA_2'] 387 | total_return = backtest_strategy(sma_1, sma_2) 388 | print(f'Probando SMA_1: {sma_1}, SMA_2: {sma_2} - Rendimiento Total: {total_return:.2%}') 389 | 390 | if total_return > best_return: 391 | best_return = total_return 392 | best_params = params 393 | 394 | print(f'Los mejores parámetros son: {best_params} con un rendimiento total de {best_return:.2%}') 395 | 396 | 397 | 398 | # Random Search 399 | import yfinance as yf 400 | import pandas as pd 401 | from sklearn.model_selection import ParameterGrid 402 | import numpy as np 403 | 404 | # Descargar datos históricos 405 | df = yf.download('NQ=F', start='2010-01-01', end='2024-01-01') 406 | 407 | # Definimos los rangos de parámetros 408 | sma_1_range = np.arange(10, 100, 10) 409 | sma_2_range = np.arange(100, 300, 50) 410 | 411 | # Función de backtest para cada combinación de parámetros 412 | def backtest_strategy(sma_1, sma_2): 413 | df['SMA_50'] = df['Close'].rolling(window=sma_1).mean() 414 | df['SMA_200'] = df['Close'].rolling(window=sma_2).mean() 415 | 416 | df['Signal'] = 0 417 | df.loc[df['SMA_50'] > df['SMA_200'], 'Signal'] = 1 418 | df.loc[df['SMA_50'] < df['SMA_200'], 'Signal'] = -1 419 | 420 | df['Position'] = df['Signal'].shift() 421 | df['Strategy_Returns'] = df['Position'] * df['Close'].pct_change() 422 | df.dropna(inplace=True) 423 | 424 | return (df['Strategy_Returns'] + 1).prod() - 1 425 | 426 | # Seleccionamos aleatoriamente combinaciones de parámetros 427 | best_params = None 428 | best_return = -np.inf 429 | 430 | for _ in range(5): # 5 pruebas aleatorias 431 | sma_1 = np.random.choice(sma_1_range) 432 | sma_2 = np.random.choice(sma_2_range) 433 | 434 | total_return = backtest_strategy(sma_1, sma_2) 435 | print(f'Probando SMA_1: {sma_1}, SMA_2: {sma_2} - Rendimiento Total: {total_return:.2%}') 436 | 437 | if total_return > best_return: 438 | best_return = total_return 439 | best_params = {'SMA_1': sma_1, 'SMA_2': sma_2} 440 | 441 | print(f'Los mejores parámetros son: {best_params} con un rendimiento total de {best_return:.2%}') 442 | 443 | 444 | # Algoritmos Genéticos 445 | import yfinance as yf 446 | import pandas as pd 447 | from deap import base, creator, tools, algorithms 448 | import random 449 | 450 | # Descargar datos históricos 451 | df_original = yf.download('NQ=F', start='2010-01-01', end='2024-01-01') 452 | 453 | # Función de backtest para cada combinación de parámetros 454 | def backtest_strategy(sma_1, sma_2): 455 | # Asegurarse de que sean enteros 456 | sma_1 = int(sma_1) 457 | sma_2 = int(sma_2) 458 | 459 | # Hacer una copia del DataFrame original para cada evaluación 460 | df = df_original.copy() 461 | 462 | # Calcular medias móviles 463 | df['SMA_50'] = df['Close'].rolling(window=sma_1).mean() 464 | df['SMA_200'] = df['Close'].rolling(window=sma_2).mean() 465 | 466 | # Definir reglas de entrada y salida 467 | df['Signal'] = 0 468 | df.loc[df['SMA_50'] > df['SMA_200'], 'Signal'] = 1 469 | df.loc[df['SMA_50'] < df['SMA_200'], 'Signal'] = -1 470 | 471 | # Simular la ejecución de las órdenes 472 | df['Position'] = df['Signal'].shift() 473 | df['Strategy_Returns'] = df['Position'] * df['Close'].pct_change() 474 | 475 | # Eliminar NaNs 476 | df.dropna(inplace=True) 477 | 478 | # Rendimiento total de la estrategia 479 | return (df['Strategy_Returns'] + 1).prod() - 1 480 | 481 | # Función de evaluación 482 | def evaluate(individual): 483 | sma_1, sma_2 = individual 484 | # Validar que ambos valores sean mayores que 0 y que SMA_1 < SMA_2 485 | if sma_1 <= 0 or sma_2 <= 0 or sma_1 >= sma_2: 486 | return -np.inf, # Penalización por una configuración inválida 487 | total_return = backtest_strategy(sma_1, sma_2) 488 | return (total_return,) 489 | 490 | # Crear individuo y configuración genética 491 | creator.create("FitnessMax", base.Fitness, weights=(1.0,)) 492 | creator.create("Individual", list, fitness=creator.FitnessMax) 493 | 494 | toolbox = base.Toolbox() 495 | 496 | # Registro de los atributos de las medias móviles como enteros 497 | toolbox.register("attr_sma1", random.randint, 10, 100) 498 | toolbox.register("attr_sma2", random.randint, 100, 300) 499 | toolbox.register("individual", tools.initCycle, creator.Individual, (toolbox.attr_sma1, toolbox.attr_sma2), n=1) 500 | toolbox.register("population", tools.initRepeat, list, toolbox.individual) 501 | 502 | # Registro de la función de evaluación 503 | toolbox.register("evaluate", evaluate) 504 | 505 | # Operadores genéticos: cruza y mutación ajustados para enteros 506 | toolbox.register("mate", tools.cxUniform, indpb=0.5) # cxUniform mezcla valores enteros 507 | toolbox.register("mutate", tools.mutUniformInt, low=[10, 100], up=[100, 300], indpb=0.2) # mutUniformInt para mantener enteros 508 | toolbox.register("select", tools.selTournament, tournsize=3) 509 | 510 | # Ejecutar algoritmo genético 511 | population = toolbox.population(n=10) 512 | 513 | # Algoritmo evolutivo simple 514 | algorithms.eaSimple(population, toolbox, cxpb=0.7, mutpb=0.2, ngen=10, verbose=True) 515 | 516 | # Imprimir los mejores resultados 517 | best_individual = tools.selBest(population, k=1)[0] 518 | best_return = evaluate(best_individual)[0] # Calcular el mejor rendimiento 519 | 520 | print(f'Los mejores parámetros son SMA_1: {int(best_individual[0])}, SMA_2: {int(best_individual[1])}') 521 | print(f'El mejor rendimiento total es: {best_return:.2%}') 522 | 523 | 524 | # Simulated Annealing 525 | import yfinance as yf 526 | import pandas as pd 527 | import numpy as np 528 | import random 529 | import matplotlib.pyplot as plt 530 | 531 | # Descargar datos históricos 532 | df_original = yf.download('NQ=F', start='2010-01-01', end='2024-01-01') 533 | 534 | # Función de backtest para cada combinación de parámetros 535 | def backtest_strategy(sma_1, sma_2): 536 | df = df_original.copy() 537 | df['SMA_50'] = df['Close'].rolling(window=sma_1).mean() 538 | df['SMA_200'] = df['Close'].rolling(window=sma_2).mean() 539 | 540 | df['Signal'] = 0 541 | df.loc[df['SMA_50'] > df['SMA_200'], 'Signal'] = 1 542 | df.loc[df['SMA_50'] < df['SMA_200'], 'Signal'] = -1 543 | 544 | df['Position'] = df['Signal'].shift() 545 | df['Strategy_Returns'] = df['Position'] * df['Close'].pct_change() 546 | 547 | df.dropna(inplace=True) 548 | 549 | # Rendimiento total de la estrategia 550 | return (df['Strategy_Returns'] + 1).prod() - 1 551 | 552 | # Función de aceptación de Simulated Annealing 553 | def acceptance_probability(old_cost, new_cost, temperature): 554 | if new_cost > old_cost: 555 | return 1.0 556 | else: 557 | return np.exp((new_cost - old_cost) / temperature) 558 | 559 | # Función de Simulated Annealing 560 | def simulated_annealing(initial_sma_1, initial_sma_2, initial_temp, cooling_rate, max_iter): 561 | current_sma_1 = initial_sma_1 562 | current_sma_2 = initial_sma_2 563 | current_cost = backtest_strategy(current_sma_1, current_sma_2) 564 | best_sma_1, best_sma_2 = current_sma_1, current_sma_2 565 | best_cost = current_cost 566 | temp = initial_temp 567 | 568 | for i in range(max_iter): 569 | # Generar nuevos parámetros vecinos (cercanos a los actuales) 570 | new_sma_1 = current_sma_1 + random.randint(-5, 5) 571 | new_sma_2 = current_sma_2 + random.randint(-5, 5) 572 | 573 | # Asegurarse de que los parámetros tengan sentido (evitar valores negativos o cruces imposibles) 574 | new_sma_1 = max(10, new_sma_1) 575 | new_sma_2 = max(20, new_sma_2) 576 | new_sma_2 = max(new_sma_1 + 10, new_sma_2) # Asegurar que SMA_2 sea mayor que SMA_1 577 | 578 | # Calcular el nuevo costo (rendimiento) con los nuevos parámetros 579 | new_cost = backtest_strategy(new_sma_1, new_sma_2) 580 | 581 | # Decidir si aceptamos la nueva solución o no 582 | if acceptance_probability(current_cost, new_cost, temp) > random.random(): 583 | current_sma_1 = new_sma_1 584 | current_sma_2 = new_sma_2 585 | current_cost = new_cost 586 | 587 | # Actualizar la mejor solución encontrada 588 | if new_cost > best_cost: 589 | best_sma_1, best_sma_2 = new_sma_1, new_sma_2 590 | best_cost = new_cost 591 | 592 | # Enfriar la temperatura 593 | temp *= cooling_rate 594 | 595 | print(f'Iteración {i+1}: Mejor SMA_1 = {best_sma_1}, Mejor SMA_2 = {best_sma_2}, Mejor Rendimiento = {best_cost:.2%}') 596 | 597 | return best_sma_1, best_sma_2, best_cost 598 | 599 | # Parámetros iniciales para Simulated Annealing 600 | initial_sma_1 = 100 601 | initial_sma_2 = 300 602 | initial_temp = 1000 603 | cooling_rate = 0.95 604 | max_iter = 100 605 | 606 | # Ejecutar el algoritmo de Simulated Annealing 607 | best_sma_1, best_sma_2, best_return = simulated_annealing(initial_sma_1, initial_sma_2, initial_temp, cooling_rate, max_iter) 608 | 609 | print(f'Los mejores parámetros son SMA_1: {best_sma_1}, SMA_2: {best_sma_2} con un rendimiento total de {best_return:.2%}') 610 | 611 | 612 | # Optimización Basada en Múltiples Métricas 613 | 614 | # Ratio de Sharpe 615 | import yfinance as yf 616 | import pandas as pd 617 | import numpy as np 618 | 619 | 620 | # Descargar datos históricos 621 | df = yf.download('NQ=F', start='2010-01-01', end='2024-01-01') 622 | 623 | def backtest_strategy(sma_1, sma_2): 624 | df['SMA_50'] = df['Close'].rolling(window=sma_1).mean() 625 | df['SMA_200'] = df['Close'].rolling(window=sma_2).mean() 626 | 627 | df['Signal'] = 0 628 | df.loc[df['SMA_50'] > df['SMA_200'], 'Signal'] = 1 629 | df.loc[df['SMA_50'] < df['SMA_200'], 'Signal'] = -1 630 | 631 | df['Position'] = df['Signal'].shift() 632 | df['Strategy_Returns'] = df['Position'] * df['Close'].pct_change() 633 | df.dropna(inplace=True) 634 | 635 | # Calcular el ratio de Sharpe 636 | risk_free_rate = 0.01 # 1% tasa libre de riesgo 637 | excess_return = df['Strategy_Returns'].mean() - (risk_free_rate / 252) 638 | sharpe_ratio = excess_return / df['Strategy_Returns'].std() 639 | 640 | return sharpe_ratio 641 | 642 | # Optimización mediante Grid Search 643 | best_params = None 644 | best_sharpe = -np.inf 645 | 646 | for sma_1 in range(10, 100, 10): 647 | for sma_2 in range(100, 300, 50): 648 | sharpe = backtest_strategy(sma_1, sma_2) 649 | print(f'Probando SMA_1: {sma_1}, SMA_2: {sma_2} - Ratio de Sharpe: {sharpe:.2f}') 650 | 651 | if sharpe > best_sharpe: 652 | best_sharpe = sharpe 653 | best_params = (sma_1, sma_2) 654 | 655 | print(f'Los mejores parámetros son: {best_params} con un Ratio de Sharpe de {best_sharpe:.2f}') 656 | 657 | 658 | # Drawdown Máximo 659 | import yfinance as yf 660 | import pandas as pd 661 | import numpy as np 662 | 663 | 664 | # Descargar datos históricos 665 | df = yf.download('NQ=F', start='2010-01-01', end='2024-01-01') 666 | 667 | def backtest_strategy(sma_1, sma_2): 668 | df['SMA_50'] = df['Close'].rolling(window=sma_1).mean() 669 | df['SMA_200'] = df['Close'].rolling(window=sma_2).mean() 670 | 671 | df['Signal'] = 0 672 | df.loc[df['SMA_50'] > df['SMA_200'], 'Signal'] = 1 673 | df.loc[df['SMA_50'] < df['SMA_200'], 'Signal'] = -1 674 | 675 | df['Position'] = df['Signal'].shift() 676 | df['Strategy_Returns'] = df['Position'] * df['Close'].pct_change() 677 | df.dropna(inplace=True) 678 | 679 | # Calcular drawdown máximo 680 | df['Cumulative_Returns'] = (df['Strategy_Returns'] + 1).cumprod() 681 | df['Peak'] = df['Cumulative_Returns'].cummax() 682 | df['Drawdown'] = (df['Cumulative_Returns'] - df['Peak']) / df['Peak'] 683 | max_drawdown = df['Drawdown'].min() 684 | 685 | return max_drawdown 686 | 687 | # Optimización mediante Grid Search 688 | best_params = None 689 | best_drawdown = np.inf 690 | 691 | for sma_1 in range(10, 100, 10): 692 | for sma_2 in range(100, 300, 50): 693 | drawdown = backtest_strategy(sma_1, sma_2) 694 | print(f'Probando SMA_1: {sma_1}, SMA_2: {sma_2} - Máximo Drawdown: {drawdown:.2%}') 695 | 696 | if abs(drawdown) < abs(best_drawdown): # Compara la magnitud del drawdown 697 | best_drawdown = drawdown 698 | best_params = (sma_1, sma_2) 699 | 700 | print(f'Los mejores parámetros son: {best_params} con un Máximo Drawdown de {best_drawdown:.2%}') 701 | 702 | 703 | # Ratio de Calmar 704 | import yfinance as yf 705 | import pandas as pd 706 | import numpy as np 707 | 708 | # Descargar datos históricos 709 | df = yf.download('NQ=F', start='2000-01-01', end='2024-01-01') 710 | 711 | def backtest_strategy(sma_1, sma_2): 712 | # Cálculo de SMAs 713 | df['SMA_50'] = df['Close'].rolling(window=sma_1).mean() 714 | df['SMA_200'] = df['Close'].rolling(window=sma_2).mean() 715 | 716 | # Señales de trading 717 | df['Signal'] = 0 718 | df.loc[df['SMA_50'] > df['SMA_200'], 'Signal'] = 1 719 | df.loc[df['SMA_50'] < df['SMA_200'], 'Signal'] = -1 720 | 721 | # Posición basada en la señal 722 | df['Position'] = df['Signal'].shift() 723 | 724 | # Comprobar que se generaron suficientes señales 725 | num_signals = df['Position'].diff().abs().sum() # Número de cambios de señal 726 | if num_signals < 10: # Ajusta este umbral según lo que consideres relevante 727 | return np.nan 728 | 729 | # Retornos de la estrategia 730 | df['Strategy_Returns'] = df['Position'] * df['Close'].pct_change() 731 | df.dropna(inplace=True) # Eliminar filas con NaN 732 | 733 | # Asegurarse de que hay suficientes datos 734 | if len(df) == 0: 735 | return np.nan 736 | 737 | # Calcular drawdown máximo y rendimiento anualizado 738 | df['Cumulative_Returns'] = (df['Strategy_Returns'] + 1).cumprod() 739 | df['Peak'] = df['Cumulative_Returns'].cummax() 740 | df['Drawdown'] = (df['Cumulative_Returns'] - df['Peak']) / df['Peak'] 741 | 742 | max_drawdown = df['Drawdown'].min() 743 | total_return = (df['Strategy_Returns'] + 1).prod() - 1 744 | annualized_return = (1 + total_return) ** (252 / len(df)) - 1 745 | 746 | # Controlar retornos anómalos 747 | if total_return > 10: # Si el retorno acumulado es absurdo 748 | total_return = 10 # Ajustar a un valor razonable 749 | 750 | # Evitar división por cero en el Ratio de Calmar 751 | if max_drawdown == 0 or np.isnan(max_drawdown): 752 | return np.nan # Retornar NaN si no hay drawdown o es cero 753 | 754 | calmar_ratio = annualized_return / abs(max_drawdown) 755 | 756 | # Limitar valores desproporcionados de Calmar Ratio 757 | if calmar_ratio > 10: # Puedes ajustar este umbral 758 | calmar_ratio = 10 # Limitar a un máximo razonable 759 | 760 | return calmar_ratio 761 | 762 | # Optimización mediante Grid Search 763 | best_params = None 764 | best_calmar = -np.inf 765 | 766 | for sma_1 in range(10, 100, 10): 767 | for sma_2 in range(100, 300, 50): 768 | calmar_ratio = backtest_strategy(sma_1, sma_2) 769 | if not np.isnan(calmar_ratio): # Evitar comparar con NaN 770 | if calmar_ratio > best_calmar: 771 | best_calmar = calmar_ratio 772 | best_params = (sma_1, sma_2) 773 | 774 | print(f'Los mejores parámetros son: {best_params} con un Ratio de Calmar de {best_calmar:.2f}') 775 | 776 | 777 | '''6.4.1''' 778 | # Ejemplo práctico: Separación de datos 779 | import yfinance as yf 780 | import pandas as pd 781 | import matplotlib.pyplot as plt 782 | 783 | # Descargar datos históricos del futuro del Nasdaq 784 | df = yf.download('NQ=F', start='2010-01-01', end='2024-01-01') 785 | 786 | # División de datos en entrenamiento (60%), test (20%) y validación (20%) 787 | train_size = int(len(df) * 0.6) 788 | test_size = int(len(df) * 0.2) 789 | 790 | train_data = df[:train_size] 791 | test_data = df[train_size:train_size+test_size] 792 | validation_data = df[train_size+test_size:] 793 | 794 | # Verificar las dimensiones de los conjuntos 795 | print("Tamaño del conjunto de Entrenamiento:", train_data.shape) 796 | print("Tamaño del conjunto de Test:", test_data.shape) 797 | print("Tamaño del conjunto de Validación:", validation_data.shape) 798 | 799 | # Graficar los diferentes conjuntos de datos 800 | plt.figure(figsize=(10,5)) 801 | plt.plot(train_data['Close'], label='Entrenamiento', color='blue') 802 | plt.plot(test_data['Close'], label='Test', color='orange') 803 | plt.plot(validation_data['Close'], label='Validación', color='green') 804 | plt.title('Separación de Datos: Entrenamiento, Test y Validación') 805 | plt.legend() 806 | plt.show() 807 | 808 | 809 | '''6.4.2''' 810 | # Ejemplo: Evaluación con datos fuera de muestra 811 | import yfinance as yf 812 | import pandas as pd 813 | import numpy as np 814 | import matplotlib.pyplot as plt 815 | 816 | # Descargar datos históricos 817 | df_original = yf.download('NQ=F', start='2010-01-01', end='2024-01-01') 818 | 819 | # Separar los datos en entrenamiento (80%) y validación (20%) 820 | train_size = int(len(df_original) * 0.8) 821 | train_data = df_original[:train_size] 822 | validation_data = df_original[train_size:] 823 | 824 | # Función de backtest para cada combinación de parámetros 825 | def backtest_strategy(data, sma_1, sma_2): 826 | df = data.copy() 827 | df['SMA_1'] = df['Close'].rolling(window=sma_1).mean() 828 | df['SMA_2'] = df['Close'].rolling(window=sma_2).mean() 829 | 830 | df['Signal'] = 0 831 | df.loc[df['SMA_1'] > df['SMA_2'], 'Signal'] = 1 832 | df.loc[df['SMA_1'] < df['SMA_2'], 'Signal'] = -1 833 | 834 | df['Position'] = df['Signal'].shift() 835 | df['Strategy_Returns'] = df['Position'] * df['Close'].pct_change() 836 | 837 | df.dropna(inplace=True) 838 | 839 | # Retornar el DataFrame con los cálculos 840 | return df 841 | 842 | # Rango de valores para la optimización 843 | sma_1_range = range(10, 50, 5) 844 | sma_2_range = range(100, 300, 25) 845 | 846 | # Optimización con Grid Search en el conjunto de entrenamiento 847 | best_params = None 848 | best_return_train = -np.inf 849 | 850 | for sma_1 in sma_1_range: 851 | for sma_2 in sma_2_range: 852 | df_train_result = backtest_strategy(train_data, sma_1, sma_2) 853 | total_return_train = (df_train_result['Strategy_Returns'] + 1).prod() - 1 854 | print(f'Probando SMA_1: {sma_1}, SMA_2: {sma_2} - Rendimiento en Entrenamiento: {total_return_train:.2%}') 855 | 856 | if total_return_train > best_return_train: 857 | best_return_train = total_return_train 858 | best_params = (sma_1, sma_2) 859 | 860 | print(f'\nMejores parámetros en entrenamiento: SMA_1: {best_params[0]}, SMA_2: {best_params[1]} con un rendimiento total de {best_return_train:.2%}') 861 | 862 | # Evaluar en el conjunto de validación con los mejores parámetros 863 | df_validation_result = backtest_strategy(validation_data, best_params[0], best_params[1]) 864 | best_return_validation = (df_validation_result['Strategy_Returns'] + 1).prod() - 1 865 | print(f'Rendimiento en validación con los mejores parámetros: {best_return_validation:.2%}') 866 | 867 | # Calcular rendimientos acumulados de entrenamiento 868 | train_cumulative_returns = np.cumprod(df_train_result['Strategy_Returns'] + 1) 869 | 870 | # Calcular rendimientos acumulados de validación, comenzando desde el último valor de entrenamiento 871 | last_train_value = train_cumulative_returns.iloc[-1] 872 | validation_cumulative_returns = np.cumprod(df_validation_result['Strategy_Returns'] + 1) * last_train_value 873 | 874 | # Graficar resultados de entrenamiento y validación solapados 875 | plt.figure(figsize=(14, 7)) 876 | plt.plot(train_cumulative_returns, label='Entrenamiento', color='blue') 877 | plt.plot(validation_cumulative_returns, label='Validación', color='orange') 878 | plt.title('Rendimiento Acumulado en Entrenamiento vs Validación (solapados)') 879 | plt.xlabel('Fecha') 880 | plt.ylabel('Rendimiento acumulado') 881 | plt.legend() 882 | plt.show() 883 | 884 | 885 | '''6.4.3''' 886 | # Ejemplo de Walk-Forward Optimization 887 | import yfinance as yf 888 | import pandas as pd 889 | import numpy as np 890 | import matplotlib.pyplot as plt 891 | 892 | # Descargar datos históricos 893 | df_original = yf.download('NQ=F', start='2010-01-01', end='2024-01-01') 894 | 895 | # Función de backtest para cada combinación de parámetros 896 | def backtest_strategy(data, sma_1, sma_2): 897 | df = data.copy() 898 | df['SMA_1'] = df['Close'].rolling(window=sma_1).mean() 899 | df['SMA_2'] = df['Close'].rolling(window=sma_2).mean() 900 | 901 | df['Signal'] = 0 902 | df.loc[df['SMA_1'] > df['SMA_2'], 'Signal'] = 1 903 | df.loc[df['SMA_1'] < df['SMA_2'], 'Signal'] = -1 904 | 905 | df['Position'] = df['Signal'].shift() 906 | df['Strategy_Returns'] = df['Position'] * df['Close'].pct_change() 907 | 908 | df.dropna(inplace=True) 909 | 910 | # Retornar el DataFrame con los cálculos 911 | return df 912 | 913 | # Configuración de la optimización walk-forward 914 | train_window = 3 * 252 # 3 años de datos para entrenamiento (252 días de trading por año) 915 | validation_window = 1 * 252 # 1 año de datos para validación 916 | total_window = train_window + validation_window 917 | 918 | # Rango de valores para optimización de medias móviles 919 | sma_1_range = range(10, 50, 5) 920 | sma_2_range = range(100, 300, 25) 921 | 922 | # Listas para acumular resultados 923 | validation_returns = [] 924 | train_returns = [] 925 | 926 | # Realizar la optimización Walk-Forward 927 | for start in range(0, len(df_original) - total_window, validation_window): 928 | # Separar los datos en entrenamiento y validación 929 | train_data = df_original[start:start + train_window] 930 | validation_data = df_original[start + train_window:start + total_window] 931 | 932 | # Optimización en el conjunto de entrenamiento 933 | best_params = None 934 | best_return_train = -np.inf 935 | 936 | for sma_1 in sma_1_range: 937 | for sma_2 in sma_2_range: 938 | df_train_result = backtest_strategy(train_data, sma_1, sma_2) 939 | total_return_train = (df_train_result['Strategy_Returns'] + 1).prod() - 1 940 | 941 | if total_return_train > best_return_train: 942 | best_return_train = total_return_train 943 | best_params = (sma_1, sma_2) 944 | 945 | train_returns.append(best_return_train) 946 | 947 | # Evaluar en el conjunto de validación con los mejores parámetros 948 | df_validation_result = backtest_strategy(validation_data, best_params[0], best_params[1]) 949 | total_return_validation = (df_validation_result['Strategy_Returns'] + 1).prod() - 1 950 | validation_returns.append(total_return_validation) 951 | 952 | print(f'Periodo {start // validation_window + 1}: Mejores parámetros en entrenamiento: SMA_1: {best_params[0]}, SMA_2: {best_params[1]}') 953 | print(f'Rendimiento en entrenamiento: {best_return_train:.2%}, Rendimiento en validación: {total_return_validation:.2%}\n') 954 | 955 | # Evaluar el rendimiento total en validación fuera de muestra 956 | print(f'Rendimiento promedio en validación fuera de muestra: {np.mean(validation_returns):.2%}') 957 | 958 | 959 | '''6.4.4''' 960 | # Ejemplo de Análisis de Sensibilidad 961 | import yfinance as yf 962 | import pandas as pd 963 | import numpy as np 964 | import matplotlib.pyplot as plt 965 | from scipy.stats import gaussian_kde 966 | 967 | # Descargar datos históricos 968 | df_original = yf.download('NQ=F', start='2010-01-01', end='2024-01-01') 969 | 970 | # Función de backtest para la estrategia de medias móviles con cálculo de Sharpe 971 | def backtest_strategy(data, sma_1, sma_2): 972 | df = data.copy() 973 | df['SMA_1'] = df['Close'].rolling(window=sma_1).mean() 974 | df['SMA_2'] = df['Close'].rolling(window=sma_2).mean() 975 | 976 | df['Signal'] = 0 977 | df.loc[df['SMA_1'] > df['SMA_2'], 'Signal'] = 1 978 | df.loc[df['SMA_1'] < df['SMA_2'], 'Signal'] = -1 979 | 980 | df['Position'] = df['Signal'].shift() 981 | df['Strategy_Returns'] = df['Position'] * df['Close'].pct_change() 982 | 983 | df.dropna(inplace=True) 984 | 985 | # Calcular el rendimiento total 986 | total_return = (df['Strategy_Returns'] + 1).prod() - 1 987 | 988 | # Calcular el ratio de Sharpe (asumimos rendimiento libre de riesgo = 0) 989 | avg_return = df['Strategy_Returns'].mean() 990 | risk = df['Strategy_Returns'].std() 991 | sharpe_ratio = avg_return / risk if risk != 0 else np.nan 992 | 993 | return total_return, sharpe_ratio 994 | 995 | # Parámetros base optimizados 996 | base_sma_1 = 50 997 | base_sma_2 = 200 998 | 999 | # Rango de variación para el análisis de sensibilidad 1000 | sma_1_range = [45, 50, 55] 1001 | sma_2_range = [195, 200, 205] 1002 | 1003 | # Realizar el análisis de sensibilidad 1004 | sens_results = [] 1005 | 1006 | for sma_1 in sma_1_range: 1007 | for sma_2 in sma_2_range: 1008 | total_return, sharpe_ratio = backtest_strategy(df_original, sma_1, sma_2) 1009 | sens_results.append((sma_1, sma_2, total_return, sharpe_ratio)) 1010 | print(f'SMA_1: {sma_1}, SMA_2: {sma_2} - Rendimiento Total: {total_return:.2%}, Sharpe Ratio: {sharpe_ratio:.2f}') 1011 | 1012 | # Ordenar los resultados por rendimiento 1013 | sens_results.sort(key=lambda x: x[2], reverse=True) 1014 | 1015 | # Calcular la desviación estándar de los Sharpe Ratios 1016 | sharpe_ratios = [res[3] for res in sens_results if not np.isnan(res[3])] 1017 | sharpe_std = np.std(sharpe_ratios) 1018 | 1019 | # Mostrar los resultados 1020 | print("\nResultados del análisis de sensibilidad (ordenados por rendimiento):") 1021 | for res in sens_results: 1022 | print(f'SMA_1: {res[0]}, SMA_2: {res[1]} - Rendimiento Total: {res[2]:.2%}, Sharpe Ratio: {res[3]:.2f}') 1023 | 1024 | print(f"\nDesviación estándar de los Sharpe Ratios: {sharpe_std:.4f}") 1025 | 1026 | # Calcular el Sharpe Ratio original 1027 | _, sharpe_original = backtest_strategy(df_original, base_sma_1, base_sma_2) 1028 | 1029 | # Graficar la curva de densidad del Sharpe Ratio 1030 | kde = gaussian_kde(sharpe_ratios) 1031 | x_range = np.linspace(min(sharpe_ratios), max(sharpe_ratios), 100) 1032 | plt.plot(x_range, kde(x_range), label='Curva de Distribución de Sharpe Ratios', color='b') 1033 | 1034 | # Añadir la línea del Sharpe Ratio original 1035 | plt.axvline(x=sharpe_original, color='r', linestyle='--', label=f'Sharpe Original ({sharpe_original:.2f})') 1036 | 1037 | plt.title('Distribución de Sharpe Ratios vs Sharpe Original') 1038 | plt.xlabel('Sharpe Ratio') 1039 | plt.ylabel('Densidad') 1040 | plt.legend() 1041 | plt.show() 1042 | 1043 | 1044 | # Evaluación de la Robustez 1045 | import yfinance as yf 1046 | import pandas as pd 1047 | import numpy as np 1048 | import matplotlib.pyplot as plt 1049 | 1050 | # Descargar datos históricos para el análisis de sensibilidad y robustez 1051 | df_original = yf.download('NQ=F', start='2010-01-01', end='2024-01-01') 1052 | 1053 | # Función de backtest para la estrategia de medias móviles con cálculo de Sharpe y retorno total 1054 | def backtest_strategy(data, sma_1, sma_2): 1055 | df = data.copy() 1056 | df['SMA_1'] = df['Close'].rolling(window=sma_1).mean() 1057 | df['SMA_2'] = df['Close'].rolling(window=sma_2).mean() 1058 | 1059 | df['Signal'] = 0 1060 | df.loc[df['SMA_1'] > df['SMA_2'], 'Signal'] = 1 1061 | df.loc[df['SMA_1'] < df['SMA_2'], 'Signal'] = -1 1062 | 1063 | df['Position'] = df['Signal'].shift() 1064 | df['Strategy_Returns'] = df['Position'] * df['Close'].pct_change() 1065 | 1066 | df.dropna(inplace=True) 1067 | 1068 | # Calcular el rendimiento total 1069 | total_return = (df['Strategy_Returns'] + 1).prod() - 1 1070 | 1071 | # Calcular el ratio de Sharpe (asumimos rendimiento libre de riesgo = 0) 1072 | avg_return = df['Strategy_Returns'].mean() 1073 | risk = df['Strategy_Returns'].std() 1074 | sharpe_ratio = avg_return / risk if risk != 0 else np.nan 1075 | 1076 | return total_return, sharpe_ratio 1077 | 1078 | # Parámetros base optimizados 1079 | base_sma_1 = 50 1080 | base_sma_2 = 200 1081 | 1082 | # Definir diferentes periodos para pruebas de robustez 1083 | periods = [ 1084 | ('2010-01-01', '2013-01-01'), 1085 | ('2013-01-01', '2016-01-01'), 1086 | ('2016-01-01', '2019-01-01'), 1087 | ('2019-01-01', '2024-01-01') 1088 | ] 1089 | 1090 | # Evaluar la estrategia en diferentes periodos 1091 | print("Evaluación de Robustez - Resultados en Diferentes Periodos de Tiempo") 1092 | for start, end in periods: 1093 | df_period = yf.download('NQ=F', start=start, end=end) 1094 | total_return, sharpe_ratio = backtest_strategy(df_period, base_sma_1, base_sma_2) 1095 | print(f'Periodo {start} a {end} - Rendimiento Total: {total_return:.2%}, Sharpe Ratio: {sharpe_ratio:.2f}') 1096 | 1097 | # Visualizar resultados para robustez en cada periodo (rendimiento acumulado) 1098 | plt.figure(figsize=(12, 6)) 1099 | 1100 | for start, end in periods: 1101 | df_period = yf.download('NQ=F', start=start, end=end) 1102 | df_period_result = backtest_strategy(df_period, base_sma_1, base_sma_2) 1103 | cumulative_returns = np.cumprod(df_period['Close'].pct_change() + 1) 1104 | plt.plot(cumulative_returns, label=f'Periodo {start} a {end}') 1105 | 1106 | plt.title('Rendimiento Acumulado en Diferentes Periodos de Tiempo (Prueba de Robustez)') 1107 | plt.xlabel('Fecha') 1108 | plt.ylabel('Rendimiento acumulado') 1109 | plt.legend() 1110 | plt.show() 1111 | 1112 | 1113 | -------------------------------------------------------------------------------- /Modulo_7.py: -------------------------------------------------------------------------------- 1 | '''7.1.1''' 2 | # Iniciar la conexión con MetaTrader5 3 | import MetaTrader5 as mt5 4 | 5 | # iniciar conexión en MetaTrader 5 6 | if not mt5.initialize(login=123456, password='password', server='server'): 7 | print('Error al inicializar MetaTrader5') 8 | mt5.shutdown() 9 | print('Error al inicializar MetaTrader5') 10 | quit() 11 | else: 12 | print(f'Conexión establecida con MetaTrader5') 13 | 14 | 15 | '''7.1.2''' 16 | # Monitoreo del Timeframe 17 | import MetaTrader5 as mt5 18 | from datetime import datetime 19 | import time 20 | 21 | # iniciar conexión en MetaTrader 5 22 | if not mt5.initialize(login=123456, password='password', server='server'): 23 | print('Error al inicializar MetaTrader5') 24 | mt5.shutdown() 25 | print('Error al inicializar MetaTrader5') 26 | quit() 27 | else: 28 | print(f'Conexión establecida con MetaTrader5') 29 | 30 | # Función que ejecuta la estrategia de trading 31 | def execute_trading_strategy(): 32 | print("Estrategia ejecutada a las 22:55") 33 | # Aquí iría la lógica de trading (adquisición de datos, señales, órdenes, etc.) 34 | 35 | # Bucle continuo que monitorea la hora 36 | while True: 37 | # Obtener la hora actual 38 | current_time = datetime.now() 39 | 40 | # Comprobar si es la hora de ejecución (22:55:00) 41 | if current_time.hour == 22 and current_time.minute == 55: 42 | execute_trading_strategy() 43 | 44 | # Dormir por un minuto para evitar ejecutar múltiples veces en el mismo minuto 45 | time.sleep(60) 46 | 47 | # Esperar un segundo antes de volver a comprobar 48 | time.sleep(60) 49 | 50 | 51 | # ejemplo, para un timeframe de 1 hora 52 | # Bucle continuo que monitorea la hora 53 | while True: 54 | current_time = datetime.now() 55 | 56 | # Comprobar si es el cierre de la hora (cuando el minuto sea 00) 57 | if current_time.minute == 0: 58 | execute_trading_strategy() 59 | time.sleep(60) # Evitar múltiples ejecuciones en el mismo minuto 60 | 61 | time.sleep(1) 62 | 63 | 64 | '''7.1.3''' 65 | # Función de adquisición de datos 66 | def get_data(ticker, interval, start, end): 67 | data = mt5.copy_rates_range(ticker, interval, start, end) 68 | data = pd.DataFrame(data) 69 | data['time'] = pd.to_datetime(data['time'], unit='s') 70 | data.set_index('time', inplace=True) 71 | return data 72 | 73 | 74 | # como nos va quedando el bot: 75 | import MetaTrader5 as mt5 76 | from datetime import datetime 77 | import time 78 | import pandas as pd 79 | 80 | # iniciar conexión en MetaTrader 5 81 | if not mt5.initialize(login=123456, password='password', server='server'): 82 | print('Error al inicializar MetaTrader5') 83 | mt5.shutdown() 84 | print('Error al inicializar MetaTrader5') 85 | quit() 86 | else: 87 | print(f'Conexión establecida con MetaTrader5') 88 | 89 | # Función para obtener los datos de precios 90 | def get_data(ticker, interval, start, end): 91 | data = mt5.copy_rates_range(ticker, interval, start, end) 92 | data = pd.DataFrame(data) 93 | data['time'] = pd.to_datetime(data['time'], unit='s') 94 | data.set_index('time', inplace=True) 95 | return data 96 | 97 | # Función que ejecuta la estrategia de trading 98 | def execute_trading_strategy(): 99 | print("Estrategia ejecutada a las 22:55") 100 | 101 | '''Aquí iría la lógica de trading (adquisición de datos, señales, órdenes, etc.)''' 102 | 103 | # Adquirir los datos necesarios 104 | df = get_data('NDX', mt5.TIMEFRAME_D1, datetime(2024, 1, 1), datetime.now()) 105 | 106 | # Bucle continuo que monitorea la hora 107 | while True: 108 | current_time = datetime.now() 109 | 110 | # Comprobar si es la hora de ejecución (22:55:00) 111 | if current_time.hour == 22 and current_time.minute == 55: 112 | execute_trading_strategy() 113 | time.sleep(60) # Evitar múltiples ejecuciones en el mismo minuto 114 | 115 | time.sleep(1) # Esperar un segundo antes de volver a comprobar 116 | 117 | 118 | '''7.1.4''' 119 | # Ejemplo de lógica de decisión basada en medias móviles 120 | def generate_signal(df): 121 | # Calcular las medias móviles 122 | df['SMA_50'] = df['close'].rolling(window=50).mean() 123 | df['SMA_200'] = df['close'].rolling(window=200).mean() 124 | 125 | # Generar señal 126 | if df['SMA_50'].iloc[-1] > df['SMA_200'].iloc[-1]: # Señal de compra 127 | return 1 128 | elif df['SMA_50'].iloc[-1] < df['SMA_200'].iloc[-1]: # Señal de venta 129 | return -1 130 | else: 131 | return 0 # No hacer nada 132 | 133 | 134 | 135 | # Integración de la toma de decisiones en el bot 136 | import MetaTrader5 as mt5 137 | from datetime import datetime 138 | import time 139 | import pandas as pd 140 | 141 | # iniciar conexión en MetaTrader 5 142 | if not mt5.initialize(login=123456, password='password', server='server'): 143 | print('Error al inicializar MetaTrader5') 144 | mt5.shutdown() 145 | print('Error al inicializar MetaTrader5') 146 | quit() 147 | else: 148 | print(f'Conexión establecida con MetaTrader5') 149 | 150 | # Función para obtener los datos de precios 151 | def get_data(ticker, interval, start, end): 152 | data = mt5.copy_rates_range(ticker, interval, start, end) 153 | data = pd.DataFrame(data) 154 | data['time'] = pd.to_datetime(data['time'], unit='s') 155 | data.set_index('time', inplace=True) 156 | return data 157 | 158 | # Función que genera la señal de compra o venta 159 | def generate_signal(df): 160 | df['SMA_50'] = df['close'].rolling(window=50).mean() 161 | df['SMA_200'] = df['close'].rolling(window=200).mean() 162 | 163 | if df['SMA_50'].iloc[-1] > df['SMA_200'].iloc[-1]: 164 | return 1 # Señal de compra 165 | elif df['SMA_50'].iloc[-1] < df['SMA_200'].iloc[-1]: 166 | return -1 # Señal de venta 167 | else: 168 | return 0 # No hacer nada 169 | 170 | # Función que ejecuta la estrategia de trading 171 | def execute_trading_strategy(): 172 | print("Estrategia ejecutada a las 22:55") 173 | 174 | # Adquirir los datos 175 | df = get_data('NDX', mt5.TIMEFRAME_D1, datetime(2024, 1, 1), datetime.now()) 176 | 177 | # Generar la señal 178 | signal = generate_signal(df) 179 | 180 | # Tomar decisiones según la señal generada 181 | if signal == 1: 182 | print("Señal de compra generada.") 183 | # Aquí iría la ejecución de la orden de compra 184 | elif signal == -1: 185 | print("Señal de venta generada.") 186 | # Aquí iría la ejecución de la orden de venta 187 | else: 188 | print("No se ha generado ninguna señal.") 189 | 190 | # Bucle continuo que monitorea la hora 191 | while True: 192 | current_time = datetime.now() 193 | 194 | # Comprobar si es la hora de ejecución (22:55:00) 195 | if current_time.hour == 22 and current_time.minute == 55: 196 | execute_trading_strategy() 197 | time.sleep(60) # Evitar múltiples ejecuciones en el mismo minuto 198 | 199 | time.sleep(1) # Esperar un segundo antes de volver a comprobar 200 | 201 | 202 | '''7.1.5''' 203 | # Ejecución de órdenes en MetaTrader5 204 | # Función para ejecutar una orden de compra o venta 205 | def place_order(symbol, order_type, volume): 206 | # Definir los detalles de la orden 207 | if order_type == 'buy': 208 | order_type_mt5 = mt5.ORDER_TYPE_BUY 209 | elif order_type == 'sell': 210 | order_type_mt5 = mt5.ORDER_TYPE_SELL 211 | else: 212 | print("Tipo de orden no válido.") 213 | return False 214 | 215 | # Obtener el precio actual 216 | symbol_info = mt5.symbol_info(symbol) 217 | if symbol_info is None: 218 | print(f"Símbolo {symbol} no encontrado.") 219 | return False 220 | 221 | price = mt5.symbol_info_tick(symbol).ask if order_type == 'buy' else mt5.symbol_info_tick(symbol).bid 222 | 223 | # Crear la orden 224 | order = { 225 | 'action': mt5.TRADE_ACTION_DEAL, 226 | 'symbol': symbol, 227 | 'volume': volume, 228 | 'type': order_type_mt5, 229 | 'price': price, 230 | 'deviation': 10, 231 | 'magic': 123456, 232 | 'comment': 'Trade ejecutado por bot', 233 | 'type_time': mt5.ORDER_TIME_GTC, 234 | 'type_filling': mt5.ORDER_FILLING_IOC, 235 | } 236 | 237 | # Enviar la orden 238 | result = mt5.order_send(order) 239 | if result.retcode != mt5.TRADE_RETCODE_DONE: 240 | print(f"Error al ejecutar la orden: {result.retcode}") 241 | return False 242 | 243 | print(f"Orden {order_type} de {symbol} ejecutada correctamente. Volumen: {volume}, Precio: {price}") 244 | return True 245 | 246 | 247 | # También hemos modificado la función execute_trading_strategy(): 248 | def execute_trading_strategy(): 249 | print("Estrategia ejecutada a las 22:55") 250 | 251 | # Adquirir los datos 252 | df = get_data('NDX', mt5.TIMEFRAME_D1, datetime(2024, 1, 1), datetime.now()) 253 | 254 | # Generar la señal 255 | signal = generate_signal(df) 256 | 257 | # Ejecutar órdenes en función de la señal 258 | if signal == 1: 259 | print("Señal de compra generada.") 260 | place_order('NDX', 'buy', 1.0) # Orden de compra con un volumen de 1 lote 261 | elif signal == -1: 262 | print("Señal de venta generada.") 263 | place_order('NDX', 'sell', 1.0) # Orden de venta con un volumen de 1 lote 264 | else: 265 | print("No se ha generado ninguna señal.") 266 | 267 | 268 | 269 | # Integración en el bot 270 | import MetaTrader5 as mt5 271 | from datetime import datetime 272 | import time 273 | import pandas as pd 274 | 275 | # iniciar conexión en MetaTrader 5 276 | if not mt5.initialize(login=123456, password='password', server='server'): 277 | print('Error al inicializar MetaTrader5') 278 | mt5.shutdown() 279 | print('Error al inicializar MetaTrader5') 280 | else: 281 | print(f'Conexión establecida con MetaTrader5') 282 | quit() 283 | 284 | # Función para obtener los datos de precios 285 | def get_data(ticker, interval, start, end): 286 | data = mt5.copy_rates_range(ticker, interval, start, end) 287 | data = pd.DataFrame(data) 288 | data['time'] = pd.to_datetime(data['time'], unit='s') 289 | data.set_index('time', inplace=True) 290 | return data 291 | 292 | # Función que genera la señal de compra o venta 293 | def generate_signal(df): 294 | df['SMA_50'] = df['close'].rolling(window=50).mean() 295 | df['SMA_200'] = df['close'].rolling(window=200).mean() 296 | 297 | if df['SMA_50'].iloc[-1] > df['SMA_200'].iloc[-1]: 298 | return 1 # Señal de compra 299 | elif df['SMA_50'].iloc[-1] < df['SMA_200'].iloc[-1]: 300 | return -1 # Señal de venta 301 | else: 302 | return 0 # No hacer nada 303 | 304 | # Función para colocar una orden 305 | def place_order(ticker, order_type, volume): 306 | if order_type == 'buy': 307 | request = { 308 | 'action': mt5.TRADE_ACTION_DEAL, 309 | 'symbol': ticker, 310 | 'volume': volume, 311 | 'type': mt5.ORDER_TYPE_BUY, 312 | 'price': mt5.symbol_info_tick(ticker).ask, 313 | 'magic': 123456, 314 | 'comment': 'Estrategia de cruce de medias' 315 | } 316 | elif order_type == 'sell': 317 | request = { 318 | 'action': mt5.TRADE_ACTION_DEAL, 319 | 'symbol': ticker, 320 | 'volume': volume, 321 | 'type': mt5.ORDER_TYPE_SELL, 322 | 'price': mt5.symbol_info_tick(ticker).bid, 323 | 'magic': 123456, 324 | 'comment': 'Estrategia de cruce de medias' 325 | } 326 | 327 | result = mt5.order_send(request) 328 | print(result) 329 | if result.retcode != mt5.TRADE_RETCODE_DONE: 330 | print("Error al colocar la orden: ", result.comment) 331 | else: 332 | print("Orden colocada con éxito.") 333 | 334 | # Función para ejecutar la estrategia 335 | def execute_trading_strategy(): 336 | print("Estrategia ejecutada a las 22:55") 337 | 338 | # Adquirir los datos 339 | df = get_data('NDX', mt5.TIMEFRAME_D1, datetime(2024, 1, 1), datetime.now()) 340 | 341 | # Generar la señal 342 | signal = generate_signal(df) 343 | 344 | # Ejecutar órdenes en función de la señal 345 | if signal == 1: 346 | print("Señal de compra generada.") 347 | place_order('NDX', 'buy', 1.0) # Orden de compra con un volumen de 1 lote 348 | elif signal == -1: 349 | print("Señal de venta generada.") 350 | place_order('NDX', 'sell', 1.0) # Orden de venta con un volumen de 1 lote 351 | else: 352 | print("No se ha generado ninguna señal.") 353 | 354 | # Bucle continuo que monitorea la hora 355 | while True: 356 | current_time = datetime.now() 357 | 358 | # Comprobar si es la hora de ejecución (22:55:00) 359 | if current_time.hour == 22 and current_time.minute == 55: 360 | execute_trading_strategy() 361 | time.sleep(60) # Evitar múltiples ejecuciones en el mismo minuto 362 | 363 | time.sleep(1) # Esperar un segundo antes de volver a comprobar 364 | 365 | 366 | 367 | # Ejemplo de Módulo de Alertas por Email usando GMail 368 | import smtplib 369 | from email.mime.text import MIMEText 370 | from email.mime.multipart import MIMEMultipart 371 | 372 | # Función para enviar alertas por correo 373 | def send_email_alert(subject, body): 374 | sender_email = "tu_email@gmail.com" 375 | receiver_email = "destinatario@gmail.com" # Puede ser el mismo que sender_email 376 | app_password = "tu_app_password" 377 | 378 | # Crear mensaje 379 | msg = MIMEMultipart() 380 | msg['From'] = sender_email 381 | msg['To'] = receiver_email 382 | msg['Subject'] = subject 383 | 384 | # Cuerpo del correo 385 | msg.attach(MIMEText(body, 'plain')) 386 | 387 | # Configuración del servidor SMTP 388 | server = smtplib.SMTP('smtp.gmail.com', 587) 389 | server.starttls() 390 | server.login(sender_email, app_password) 391 | 392 | # Enviar correo 393 | server.sendmail(sender_email, receiver_email, msg.as_string()) 394 | server.quit() 395 | 396 | # Modificar la ejecución de la estrategia para incluir alertas 397 | def execute_trading_strategy(): 398 | print("Estrategia ejecutada a las 22:55") 399 | 400 | # Lógica de la estrategia (adquisición de datos, señales, órdenes, etc.) 401 | ... 402 | # Enviar alerta por correo 403 | send_email_alert("Alerta de Trading", "Se ha ejecutado una operación en el bot.") 404 | 405 | 406 | 407 | # Ejemplo de Módulo de Control de Festivos 408 | import datetime 409 | import time 410 | 411 | # Lista manual de festivos adicionales 412 | manual_holidays = [ 413 | "2024-01-01", # Año Nuevo 414 | "2024-04-05", # Viernes Santo 415 | "2024-12-25", # Navidad 416 | ] 417 | 418 | # Función para comprobar si hoy es festivo (fin de semana o festivo manual) 419 | def is_holiday(): 420 | today = datetime.datetime.now().strftime('%Y-%m-%d') 421 | 422 | # Comprobar si hoy es fin de semana (sábado o domingo) 423 | if datetime.datetime.now().weekday() >= 5: 424 | print(f"Hoy es fin de semana ({today}), no se ejecutarán operaciones.") 425 | return True 426 | 427 | # Comprobar si hoy es un festivo manual 428 | if today in manual_holidays: 429 | print(f"Hoy es festivo manual: {today}") 430 | return True 431 | 432 | return False 433 | 434 | # Función para gestionar la operativa del bot en caso de ser festivo 435 | def manage_trading_on_holiday(): 436 | if is_holiday(): 437 | return False # No operar si es festivo o fin de semana 438 | return True # Seguir con la operativa si no es festivo 439 | 440 | # Función principal del bot 441 | def execute_trading_strategy(): 442 | if manage_trading_on_holiday(): 443 | print("Ejecutando estrategia de trading...") 444 | # Aquí iría el resto de la lógica del bot (adquisición de datos, señales, ejecución de órdenes, etc.) 445 | 446 | # Bucle continuo para monitorizar y ejecutar la estrategia 447 | while True: 448 | current_time = datetime.datetime.now() 449 | 450 | # Comprobar si es la hora de ejecución (por ejemplo, 22:55:00) 451 | if current_time.hour == 22 and current_time.minute == 55: 452 | execute_trading_strategy() 453 | 454 | # Esperar un minuto antes de volver a comprobar 455 | time.sleep(60) 456 | 457 | 458 | 459 | '''7.2.1''' 460 | # Ejemplo: Gestión de capital basada en el riesgo por operación 461 | # Función para calcular el tamaño de la posición basado en el riesgo por operación 462 | def calculate_position_size(capital, risk_per_trade, stop_loss_distance, price): 463 | 464 | # El riesgo total es el porcentaje del capital que estamos dispuestos a perder 465 | risk_amount = capital * risk_per_trade 466 | 467 | # Calculamos el tamaño de la posición según la distancia al stop-loss 468 | position_size = risk_amount / stop_loss_distance 469 | 470 | # Convertimos el tamaño de la posición a número de unidades (acciones, contratos, etc.) 471 | units = position_size / price 472 | 473 | return units 474 | 475 | # Ejemplo 476 | capital = 100000 # Capital total disponible 477 | risk_per_trade = 0.02 # 2% de riesgo por operación 478 | stop_loss_distance = 50 # Distancia al stop-loss en pips 479 | price = 1500 # Precio actual del activo 480 | 481 | # Calcular el tamaño de la posición 482 | units = calculate_position_size(capital, risk_per_trade, stop_loss_distance, price) 483 | print(f"Tamaño de la posición: {units:.2f} unidades") 484 | 485 | 486 | # Implementación en nuestro Bot 487 | import MetaTrader5 as mt5 488 | from datetime import datetime 489 | import time 490 | import pandas as pd 491 | 492 | # iniciar conexión en MetaTrader 5 493 | if not mt5.initialize(login=123456, password='password', server='server'): 494 | print('Error al inicializar MetaTrader5') 495 | mt5.shutdown() 496 | print('Error al inicializar MetaTrader5') 497 | else: 498 | print(f'Conexión establecida con MetaTrader5') 499 | quit() 500 | 501 | # Función para obtener los datos de precios 502 | def get_data(ticker, interval, start, end): 503 | data = mt5.copy_rates_range(ticker, interval, start, end) 504 | data = pd.DataFrame(data) 505 | data['time'] = pd.to_datetime(data['time'], unit='s') 506 | data.set_index('time', inplace=True) 507 | return data 508 | 509 | # Función que genera la señal de compra o venta 510 | def generate_signal(df): 511 | df['SMA_50'] = df['close'].rolling(window=50).mean() 512 | df['SMA_200'] = df['close'].rolling(window=200).mean() 513 | 514 | if df['SMA_50'].iloc[-1] > df['SMA_200'].iloc[-1]: 515 | return 1 # Señal de compra 516 | elif df['SMA_50'].iloc[-1] < df['SMA_200'].iloc[-1]: 517 | return -1 # Señal de venta 518 | else: 519 | return 0 # No hacer nada 520 | 521 | # Función para colocar una orden (compra o venta) 522 | def place_order(ticker, order_type, volume, sl=0, tp=0): 523 | # Seleccionar el símbolo en MetaTrader 5 524 | if not mt5.symbol_select(ticker, True): 525 | print(f"Error al seleccionar el símbolo {ticker}") 526 | return None 527 | 528 | # Determinar si la orden es de compra o venta 529 | if order_type == 'buy': 530 | order_type_mt5 = mt5.ORDER_TYPE_BUY 531 | price = mt5.symbol_info_tick(ticker).ask 532 | elif order_type == 'sell': 533 | order_type_mt5 = mt5.ORDER_TYPE_SELL 534 | price = mt5.symbol_info_tick(ticker).bid 535 | else: 536 | print("Tipo de orden no reconocido. Debe ser 'buy' o 'sell'") 537 | return None 538 | 539 | # Crear la solicitud de orden 540 | request = { 541 | "action": mt5.TRADE_ACTION_DEAL, 542 | "symbol": ticker, 543 | "volume": volume, 544 | "type": order_type_mt5, 545 | "price": price, 546 | "sl": price - sl if order_type == 'buy' else price + sl, # SL en función del tipo de orden 547 | "tp": price + tp if order_type == 'buy' else price - tp, # TP en función del tipo de orden 548 | "deviation": 20, # Desviación permitida 549 | "magic": 123456, # Número mágico para identificar la operación 550 | "comment": f"Python Script {order_type.capitalize()}", 551 | "type_time": mt5.ORDER_TIME_GTC, # Good till Cancelled 552 | "type_filling": mt5.ORDER_FILLING_IOC, # Immediate or Cancel 553 | } 554 | 555 | # Comprobar la orden antes de enviarla 556 | check_result = mt5.order_check(request) 557 | if check_result is None: 558 | print("Error al comprobar la orden. El resultado es None.") 559 | return None 560 | elif check_result.retcode != 0: 561 | print(f"No se puede enviar la orden. Código de error: {check_result.retcode}") 562 | return None 563 | 564 | # Enviar la orden 565 | result = mt5.order_send(request) 566 | if result is None: 567 | print("Error al enviar la orden. El resultado es None.") 568 | return None 569 | elif result.retcode != mt5.TRADE_RETCODE_DONE: 570 | print(f"No se ha podido colocar la orden. Código de error: {result.retcode}") 571 | return None 572 | else: 573 | print("Orden colocada con éxito.") 574 | return result 575 | 576 | # Función para calcular el tamaño de lote en función del sl y el riesgo 577 | def lot_calc(ticker, sl, risk): 578 | # Obtener el equity de la cuenta, que incluye las posiciones abiertas 579 | equity = mt5.account_info().equity 580 | 581 | # Obtener la información del símbolo 582 | symbol_info = mt5.symbol_info(ticker) 583 | 584 | # Calcular la cantidad de riesgo en términos monetarios (basado en el equity) 585 | risk_amount = equity * risk 586 | 587 | # Obtener el valor de un pip (en USD) 588 | pip_value = symbol_info.trade_contract_size * symbol_info.point # El valor de un pip es el tamaño del contrato por cada punto de movimiento 589 | 590 | # Calcular la distancia al stop-loss en pips 591 | sl_distance_usd = sl * pip_value # SL en pips convertido a USD 592 | 593 | # Comprobar que la distancia al SL no sea cero 594 | if sl_distance_usd == 0: 595 | print("Error: La distancia al stop-loss no puede ser cero.") 596 | return None 597 | 598 | # Calcular el tamaño de la posición basado en la distancia al stop-loss 599 | position_size = risk_amount / sl_distance_usd 600 | 601 | # Ajustar el tamaño de la posición a los lotes permitidos por el broker 602 | lot_min = symbol_info.volume_min 603 | lot_max = symbol_info.volume_max 604 | lot_step = symbol_info.volume_step 605 | 606 | # Redondear el tamaño de la posición al lote permitido más cercano 607 | position_size = max(lot_min, min(round(position_size / lot_step) * lot_step, lot_max)) 608 | 609 | return position_size 610 | 611 | # Función para ejecutar la estrategia 612 | def execute_trading_strategy(): 613 | print("Estrategia ejecutada a las 22:55") 614 | 615 | # Adquirir los datos 616 | df = get_data('NDX', mt5.TIMEFRAME_D1, datetime(2024, 1, 1), datetime.now()) 617 | 618 | # Generar la señal 619 | signal = generate_signal(df) 620 | 621 | #Puntos para sl y tp 622 | sl_point = 1000 # en pips! 623 | tp_point = 2000 # en pips! 624 | # Riesgo por operación 625 | risk = 0.02 626 | 627 | # Ejecutar órdenes en función de la señal 628 | if signal == 1: 629 | print("Señal de compra generada.") 630 | place_order('NDX', 'buy', lot_calc('NDX', sl_point, risk), sl_point, tp_point) #Buy 631 | elif signal == -1: 632 | print("Señal de venta generada.") 633 | place_order('NDX', 'sell', lot_calc('NDX', sl_point, risk), sl_point, tp_point) #Sell 634 | else: 635 | print("No se ha generado ninguna señal.") 636 | 637 | # Bucle continuo que monitorea la hora 638 | while True: 639 | current_time = datetime.now() 640 | 641 | # Comprobar si es la hora de ejecución (22:55:00) 642 | if current_time.hour == 22 and current_time.minute == 55: 643 | execute_trading_strategy() 644 | time.sleep(60) # Evitar múltiples ejecuciones en el mismo minuto 645 | 646 | time.sleep(1) # Esperar un segundo antes de volver a comprobar 647 | 648 | 649 | 650 | '''7.2.2''' 651 | # Ejemplo: Implementación de Control de Volatilidad con ATR 652 | def adjust_position_based_on_normalized_atr(ticker, sl, risk, lookback=14): 653 | # Obtener los datos históricos del activo 654 | df = get_data(ticker, mt5.TIMEFRAME_D1, datetime(2024, 1, 1), datetime.now()) 655 | 656 | # Calcular el ATR 657 | df['H-L'] = df['high'] - df['low'] 658 | df['H-PC'] = abs(df['high'] - df['close'].shift(1)) 659 | df['L-PC'] = abs(df['low'] - df['close'].shift(1)) 660 | 661 | true_range = df[['H-L', 'H-PC', 'L-PC']].max(axis=1) 662 | atr = true_range.rolling(window=lookback).mean() 663 | 664 | # Calcular el ATR mínimo y máximo de los últimos x días 665 | atr_min = atr[-lookback:].min() 666 | atr_max = atr[-lookback:].max() 667 | 668 | # Normalizar el ATR actual entre 0 y 1 669 | current_atr = atr.iloc[-1] 670 | normalized_atr = (current_atr - atr_min) / (atr_max - atr_min) if atr_max != atr_min else 0.5 671 | 672 | # Llamar a la función lot_calc() para calcular el lotaje basado en el SL y el riesgo 673 | base_lot_size = lot_calc(ticker, sl, risk) 674 | 675 | # Ajustar el lotaje en función del ATR normalizado 676 | adjusted_lot_size = base_lot_size * (1 - 0.5 * normalized_atr) 677 | 678 | # Ajustar el tamaño de la posición a los lotes permitidos por el broker 679 | symbol_info = mt5.symbol_info(ticker) 680 | lot_min = symbol_info.volume_min 681 | lot_max = symbol_info.volume_max 682 | lot_step = symbol_info.volume_step 683 | 684 | # Redondear el tamaño de la posición al lote permitido más cercano 685 | adjusted_lot_size = max(lot_min, min(round(adjusted_lot_size / lot_step) * lot_step, lot_max)) 686 | 687 | return adjusted_lot_size 688 | 689 | 690 | # Función para ejecutar la estrategia 691 | def execute_trading_strategy(): 692 | print("Estrategia ejecutada a las 22:55") 693 | 694 | # Tickers a operar 695 | ticker = 'NDX' 696 | 697 | # Adquirir los datos 698 | df = get_data(ticker, mt5.TIMEFRAME_D1, datetime(2024, 1, 1), datetime.now()) 699 | 700 | # Generar la señal 701 | signal = generate_signal(df) 702 | 703 | #Puntos para sl y tp 704 | sl_point = 1000 # en pips! 705 | tp_point = 2000 # en pips! 706 | # Riesgo por operación 707 | risk = 0.02 708 | 709 | # calculamos el tamaño de la posición 710 | lot_size = adjust_position_based_on_normalized_atr(ticker, sl_point, risk) 711 | 712 | # Ejecutar órdenes en función de la señal 713 | if signal == 1: 714 | print("Señal de compra generada.") 715 | place_order('NDX', 'buy', lot_size, sl_point, tp_point) #Buy 716 | elif signal == -1: 717 | print("Señal de venta generada.") 718 | place_order('NDX', 'sell', lot_size, sl_point, tp_point) #Sell 719 | else: 720 | print("No se ha generado ninguna señal.") 721 | 722 | 723 | 724 | # Ejemplo: Detección de eventos extremos y protección del bot 725 | def detect_extreme_event(df, threshold=5): 726 | """ 727 | Detecta si el mercado ha experimentado un movimiento extremo (gap) superior al umbral definido. 728 | threshold: porcentaje de cambio en el precio que consideramos extremo. 729 | """ 730 | last_close = df['close'].iloc[-1] 731 | previous_close = df['close'].iloc[-2] 732 | 733 | price_change = abs((last_close - previous_close) / previous_close) * 100 734 | 735 | if price_change > threshold: 736 | print(f"Movimiento extremo detectado: {price_change:.2f}%") 737 | return True 738 | return False 739 | 740 | 741 | 742 | '''7.2.3''' 743 | # Ejemplo: Protección contra Drawdowns 744 | # Función para calcular el drawdown semanal 745 | def calculate_weekly_drawdown(account_balance, peak_balance): 746 | return (peak_balance - account_balance) / peak_balance 747 | 748 | # Función que implementa la protección contra drawdowns semanales 749 | def weekly_drawdown_protection(account_balance, peak_balance, max_weekly_drawdown): 750 | current_drawdown = calculate_weekly_drawdown(account_balance, peak_balance) 751 | 752 | if current_drawdown >= max_weekly_drawdown: 753 | print(f"Drawdown semanal excede el límite permitido del {max_weekly_drawdown * 100}%. Operaciones detenidas hasta la próxima semana.") 754 | return False # Detener el bot por el resto de la semana 755 | return True # Continuar operando 756 | 757 | # Variables 758 | max_weekly_drawdown = 0.10 # Máximo drawdown semanal permitido (10%) 759 | peak_balance = 100000 # Balance pico de la cuenta 760 | account_balance = 90000 # Balance actual de la cuenta 761 | 762 | # Ejemplo de uso 763 | if weekly_drawdown_protection(account_balance, peak_balance, max_weekly_drawdown): 764 | print("El bot continúa operando.") 765 | else: 766 | print("El bot se ha detenido debido al drawdown semanal.") 767 | 768 | 769 | 770 | # Reducción del Lotaje Basada en Drawdowns 771 | # Ajustar el tamaño de las posiciones en función del drawdown semanal 772 | def adjust_position_based_on_weekly_drawdown(account_balance, peak_balance, max_weekly_drawdown, base_lot_size): 773 | current_drawdown = calculate_weekly_drawdown(account_balance, peak_balance) 774 | 775 | # Reducir el lotaje en función del drawdown semanal 776 | if current_drawdown >= max_weekly_drawdown: 777 | adjusted_lot_size = base_lot_size * (1 - current_drawdown) 778 | else: 779 | adjusted_lot_size = base_lot_size 780 | 781 | return max(0, adjusted_lot_size) 782 | 783 | # Ejemplo de uso 784 | base_lot_size = 1 # Tamaño de lote inicial 785 | adjusted_lot_size = adjust_position_based_on_weekly_drawdown(account_balance, peak_balance, max_weekly_drawdown, base_lot_size) 786 | print(f"Nuevo tamaño de lote ajustado: {adjusted_lot_size:.2f} lotes") 787 | 788 | 789 | 790 | '''7.3.1''' 791 | # DEBUG (10) 792 | logging.debug('El valor de SMA_50 es 14500 y SMA_200 es 14250.') 793 | 794 | # INFO (20) 795 | logging.info('Orden de compra ejecutada a 15000 para el par EUR/USD.') 796 | 797 | # WARNING (30) 798 | logging.warning('La volatilidad actual es muy alta. Podría ser riesgoso operar.') 799 | 800 | # ERROR (40) 801 | logging.error('Error al ejecutar la orden de venta: conexión fallida.') 802 | 803 | # CRITICAL (50) 804 | logging.critical('Desconexión completa de MetaTrader5. Bot detenido.') 805 | 806 | 807 | # Cómo configurar los niveles de logging en tu bot 808 | import logging 809 | 810 | # Configurar el sistema de logging con nivel INFO (no incluirá mensajes DEBUG) 811 | logging.basicConfig(filename='bot_trading.log', 812 | level=logging.INFO, 813 | format='%(asctime)s - %(levelname)s - %(message)s') 814 | 815 | # Ejemplos de diferentes niveles de logging 816 | logging.debug('Este es un mensaje DEBUG. Solo se verá si el nivel es DEBUG.') 817 | logging.info('Este es un mensaje INFO. Se verá en niveles INFO o superiores.') 818 | logging.warning('Este es un mensaje WARNING. Se verá en niveles WARNING o superiores.') 819 | logging.error('Este es un mensaje ERROR. Se verá en niveles ERROR o superiores.') 820 | logging.critical('Este es un mensaje CRITICAL. Se verá en niveles CRITICAL o superiores.') 821 | 822 | 823 | # Ejemplo: Log de operaciones con detalles 824 | import logging 825 | 826 | # Configurar el sistema de logging con diferentes niveles 827 | logging.basicConfig(filename='bot_trading.log', 828 | level=logging.DEBUG, 829 | format='%(asctime)s - %(levelname)s - %(message)s') 830 | 831 | # Función para registrar la ejecución de la estrategia 832 | def log_strategy_execution(signal, lot_size, close_price, sma_50, sma_200): 833 | if signal == 1: 834 | logging.info(f'Se ejecuta una orden de compra de {lot_size} lotes a un precio de cierre de {close_price}.') 835 | logging.debug(f'Valor del SMA_50: {sma_50}, Valor del SMA_200: {sma_200}') 836 | elif signal == -1: 837 | logging.info(f'Se ejecuta una orden de venta de {lot_size} lotes a un precio de cierre de {close_price}.') 838 | logging.debug(f'Valor del SMA_50: {sma_50}, Valor del SMA_200: {sma_200}') 839 | else: 840 | logging.info('No se ha generado ninguna señal.') 841 | 842 | # Función para registrar errores 843 | def log_error(error_message): 844 | logging.error(f"Error: {error_message}") 845 | 846 | # Función que ejecuta la estrategia de trading y genera logs avanzados 847 | def execute_trading_strategy(): 848 | print("Estrategia ejecutada a las 22:55") 849 | 850 | # Datos de ejemplo para el log 851 | signal = 1 # 1 para compra, -1 para venta, 0 para no hacer nada 852 | lot_size = 0.1 853 | close_price = 15000 # Precio actual del activo 854 | sma_50 = 14800 # Valor de la media móvil de 50 días 855 | sma_200 = 14000 # Valor de la media móvil de 200 días 856 | 857 | try: 858 | # Registrar la ejecución de la estrategia con detalles 859 | log_strategy_execution(signal, lot_size, close_price, sma_50, sma_200) 860 | except Exception as e: 861 | # Registrar cualquier error que ocurra 862 | log_error(str(e)) 863 | 864 | 865 | 866 | # Ejemplo de Rotación de Logs Basada en Tamaño 867 | import logging 868 | from logging.handlers import RotatingFileHandler 869 | 870 | # Configuración de la rotación de logs 871 | handler = RotatingFileHandler('bot_trading.log', maxBytes=5*1024*1024, backupCount=5) 872 | logging.basicConfig(level=logging.INFO, 873 | format='%(asctime)s - %(levelname)s - %(message)s', 874 | handlers=[handler]) 875 | 876 | # Ejemplo de uso del logging 877 | logging.info('Bot iniciado correctamente.') 878 | 879 | 880 | 881 | # Rotación Basada en Tiempo 882 | from logging.handlers import TimedRotatingFileHandler 883 | import logging 884 | 885 | # Configurar rotación semanal de logs 886 | handler = TimedRotatingFileHandler('bot_trading.log', when='W0', interval=1, backupCount=5) 887 | logging.basicConfig(level=logging.INFO, 888 | format='%(asctime)s - %(levelname)s - %(message)s', 889 | handlers=[handler]) 890 | 891 | # Ejemplo de uso del logging 892 | logging.info('Bot iniciado correctamente.') 893 | 894 | 895 | 896 | # Ejemplo básico: Enviar una alerta a Telegram 897 | import requests 898 | 899 | # Configuración del bot de Telegram 900 | telegram_token = 'your_telegram_bot_token' 901 | telegram_chat_id = 'your_chat_id' 902 | 903 | def send_telegram_alert(message): 904 | url = f'https://api.telegram.org/bot{telegram_token}/sendMessage' 905 | payload = { 906 | 'chat_id': telegram_chat_id, 907 | 'text': message 908 | } 909 | requests.post(url, data=payload) 910 | 911 | # Ejemplo de uso 912 | def execute_trading_strategy(): 913 | print("Estrategia ejecutada a las 22:55") 914 | 915 | # Datos de ejemplo 916 | signal = 1 # 1 para compra, -1 para venta, 0 para no hacer nada 917 | lot_size = 0.1 918 | close_price = 15000 # Precio actual del activo 919 | 920 | # Enviar alerta a Telegram 921 | if signal == 1: 922 | message = f"Se ha ejecutado una orden de compra de {lot_size} lotes a un precio de cierre de {close_price}." 923 | elif signal == -1: 924 | message = f"Se ha ejecutado una orden de venta de {lot_size} lotes a un precio de cierre de {close_price}." 925 | else: 926 | message = "No se ha generado ninguna señal." 927 | 928 | send_telegram_alert(message) 929 | 930 | 931 | 932 | '''7.3.2''' 933 | # Ejemplo de Actualización de Logs y Alertas 934 | import logging 935 | from logging.handlers import TimedRotatingFileHandler 936 | import smtplib 937 | 938 | # Configurar rotación semanal de logs 939 | handler = TimedRotatingFileHandler('bot_trading.log', when='W0', interval=1, backupCount=5) 940 | logging.basicConfig(level=logging.INFO, 941 | format='%(asctime)s - %(levelname)s - %(message)s', 942 | handlers=[handler]) 943 | 944 | # Función para actualizar los logs con nuevos eventos 945 | def log_new_strategy_updates(signal, rsi, var, kelly_fraction): 946 | if signal == 1: 947 | logging.info(f'Nueva estrategia ejecutada: Compra con RSI={rsi} y VaR={var}. Fracción de Kelly={kelly_fraction:.2f}') 948 | elif signal == -1: 949 | logging.info(f'Nueva estrategia ejecutada: Venta con RSI={rsi} y VaR={var}. Fracción de Kelly={kelly_fraction:.2f}') 950 | else: 951 | logging.info(f'Sin señal generada, RSI={rsi}, VaR={var}, Kelly={kelly_fraction:.2f}') 952 | 953 | # Función para enviar alertas basadas en KPIs 954 | def send_kpi_alert(email, subject, message): 955 | try: 956 | server = smtplib.SMTP('smtp.gmail.com', 587) 957 | server.starttls() 958 | server.login('tu_email@gmail.com', 'tu_password_app') 959 | message = f'Subject: {subject}\n\n{message}' 960 | server.sendmail('tu_email@gmail.com', email, message) 961 | server.quit() 962 | logging.info(f'Alerta enviada a {email} con el asunto: {subject}') 963 | except Exception as e: 964 | logging.error(f'Error al enviar alerta: {str(e)}') 965 | 966 | # Ejemplo de ejecución de la estrategia y envío de alertas 967 | def execute_updated_strategy(): 968 | signal = 1 # Supongamos que es una señal de compra 969 | rsi = 55.3 970 | var = 0.02 971 | kelly_fraction = 0.25 972 | 973 | # Registrar los nuevos eventos en el log 974 | log_new_strategy_updates(signal, rsi, var, kelly_fraction) 975 | 976 | # Enviar alerta si se supera un umbral 977 | if var > 0.015: 978 | send_kpi_alert('mi_email@gmail.com', 'Alerta: VaR superado', f'El VaR ha alcanzado {var:.2f}, revisa el bot.') 979 | 980 | 981 | 982 | '''7.3.3''' 983 | # Ejemplo de Monitoreo en Tiempo Real con Logs y Gráficos 984 | import MetaTrader5 as mt5 985 | import logging 986 | import pandas as pd 987 | import matplotlib.pyplot as plt 988 | 989 | # Configurar el sistema de logging 990 | logging.basicConfig(filename='bot_realtime.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') 991 | 992 | # Inicializar conexión a MetaTrader 5 993 | mt5.initialize() 994 | 995 | # Variables de rendimiento 996 | balance_inicial = 100000 997 | balances = [] 998 | drawdowns = [] 999 | 1000 | # Función para monitorear rendimiento en tiempo real 1001 | def monitor_performance(current_balance, peak_balance): 1002 | # Calcular la rentabilidad 1003 | rentabilidad = (current_balance - balance_inicial) / balance_inicial * 100 1004 | balances.append(current_balance) 1005 | 1006 | # Calcular el drawdown 1007 | drawdown = (peak_balance - current_balance) / peak_balance * 100 1008 | drawdowns.append(drawdown) 1009 | 1010 | # Registrar la información en los logs 1011 | logging.info(f'Rentabilidad acumulada: {rentabilidad:.2f}%. Drawdown actual: {drawdown:.2f}%.') 1012 | 1013 | # Visualización del rendimiento y drawdown en tiempo real 1014 | plt.figure(figsize=(10, 5)) 1015 | plt.subplot(2, 1, 1) 1016 | plt.plot(balances, label='Balance') 1017 | plt.title('Balance en tiempo real') 1018 | plt.grid(True) 1019 | 1020 | plt.subplot(2, 1, 2) 1021 | plt.plot(drawdowns, label='Drawdown', color='red') 1022 | plt.title('Drawdown en tiempo real') 1023 | plt.grid(True) 1024 | 1025 | plt.tight_layout() 1026 | plt.show() 1027 | 1028 | # Ejemplo de simulación de operación 1029 | peak_balance = balance_inicial 1030 | for i in range(1, 11): # Simulamos 10 operaciones 1031 | current_balance = balance_inicial + i * 1000 - i * 200 # Supongamos que hay una ganancia neta 1032 | peak_balance = max(peak_balance, current_balance) # Actualizamos el pico 1033 | monitor_performance(current_balance, peak_balance) 1034 | 1035 | 1036 | 1037 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Trading_Algoritmico_con_python 2 | Repositorio de código del libro Trading Algorítmico con Python 3 | --------------------------------------------------------------------------------