Estacionalidad Intradiaria IV - Busqueda de campo

Estacionalidad Intradiaria IV - Busqueda de campo

Table of contents

En esta nueva entrega de la estacionalidad intradia, vamos a realizar una busqueda de campo. Consiste en realizar el backtest que hemos programado anteriormente en diferentes dias, diferentes horas y diferente intervalod entro de la operacion, para mapear el activo, y buscar las zonas donde podrian ocurrir las anomalias estadisticas, y tuvieran algun tipo de alpha.

Para poder seguir este articulo, es necesario haber leido los tres primeros articulos, que te dejo los enlaces aqui abajo

Estacionalidad en Python. Buscando Patrones Intradia
Tras un largo periodo totalmente desconectados, volvemos a escribir algunos apuntes para vosotros. En esta ocasion, vamos a programar desde cero, todo un proceso de research, y en lugar de ir a por un activo concreto, vamos a crear un metodo completo, que se reutilizable en el futuro, para poder
Estacionalidad en Python II - Backtesting
Descubre cómo identificar patrones estacionales intradía en futuros y validar estrategias de trading mediante backtesting. Aprende a utilizar Python para analizar datos históricos, optimizar parámetros y evaluar el rendimiento de tus algoritmos antes de aplicarlos en el mercado real
Estacionalidad en Python III - Estadisticas sobre la operativa
En este articulo, analizamos el ratio de sharpe, el value at risk (VaR) el Conditional Value At Risk (CVaR), el System Quality Number (SQN) y otros ratios relevantes para analizar el backtest.

Plan de Accion

Vamos a ampliar la funcionalidad de la clase, creando una funcion denominada optimize donde, a partir de un rango efectivo de parametros como

  • Dias de la semana
  • Hora de Entrada
  • Minuto de Entrada
  • Velas Intrade

Vamos a recorrer todo el espectro asignado, y almacenando los resultados en un dataframe, que posteriormente exportaremos a CSV, y sera la base para poder encontrar ventajas estadisticas dentro del activo.

Por simplificacion, no vamos a paralizar los procesos, se hara unicamente un unico proceso de forma concurrente. Pero el codigo puede acelerado sin demasiada complicacion, pero el objetivo de esta serie es el research, no la optimizacion operativa de los procesos.

Programando la busqueda de campo

Vamos a crear una nueva funcion dentro de la clase, denominada optimize, que realizara la funcion de busqueda de alpha de forma estacional dentro del activo. Esta busqueda de campo corresponde en la realizacion de un backtest por cada uno de los parametros del rango introducido, y almacenar los resultados.

La clase deberia quedar tal que asi:

Defininendo los parametros de la busqueda de campo

    def optimize(self, weekday_range=range(0, 7), hour_range=range(0, 24), 
                minute_range=range(0, 60), bars_range=range(1, 11), 
                csv_path='optimization_results.csv'):

Los parametros que necesitaremos para lanzar la funcion son :

  • Weekday range: Un rango numerico de los dias que se quieren incluir dentro de la optimizacion. La optimizacion filtra por dias, considerando cada dia de la semana como un caso independiente.
  • Hour range: Rango horario que se ejecutara la busqueda, debe estar ajustado a los horarios y apertura y cierre del activo para evitar perder tiempo con optimizaciones donde el mercado no esta cotizando, y por consecuencia dara resultados 0 en todos los campos
  • Minute range: Este parametro es una lista fija de 0,15,30,45 por que nuestros datos estan resampleados en intervalos de 15 minutos. Pero si se trabaja con intervalos superiores o inferiores, deberia ser reajustado.
  • Bars Range: Hace referencia al rango de velas que estara el trade abierto. Se introducen en rango por que probara todos los valores del rango, haciendo la salida mas larga o mas corta, de uno en uno. Es la forma mas sencilla de manejar las salidas, y facilmente transformable a tiempo.

Cargando las librerias necesarias

        from itertools import product
        from tqdm import tqdm
        
        all_results = []

El primer paso es cargar las librerias necesarias. En este caso mediante tqdm vamos a crear un progressbar que nos muestre de forma visual el estado de la optimizacion, desde la funcion itertools.product vamos a crear todas las combinaciones posibles desde los parametros introducidos, para ir backtesteando uno a uno y guardando los resultados.

Del mismo modo, inializamos una lista denominada all_results, donde vamos a ir guardando los parametros y ratios mas relevantes de la actualizacion.

        total_combinations = len(weekday_range) * len(hour_range) * len(minute_range) * len(bars_range)
        
        print(f"Iniciando optimización con {total_combinations} combinaciones...")
        

Posterirmente vamos a calcular el numero toftal de combinatorias posibles, multiplicando la longitud del rango por cada uno de los parametros, y lo mostramos antes de empezar la busqueda de campo para que el usario tenga consciencia de la dificultad de la tarea.

        parameter_combinations = product(weekday_range, hour_range, minute_range, bars_range)
        progress_bar = tqdm(parameter_combinations, total=total_combinations, 
                            desc="Optimizando", unit="combinación")

Posteriormente vamos a crear todas las combinanciones posibles entre los argumentos introducidos en el buscador de amplio espectro utilizando el product de itertools, definimos la progress bar, que acompanara al usuario mediante toda la optimizacion.

La función product() del módulo itertools en Python es una herramienta poderosa que genera el producto cartesiano de los iterables que le pasamos como argumentos. En términos simples, crea todas las combinaciones posibles de los elementos de las listas que le proporcionamos.

Programando la busqueda de amplio espectro

      for weekday, hour, minute, bars in progress_bar:
            backtester = IntradayBacktester(
                df=self.df.copy(),
                entry_weekday=weekday,
                entry_hour=hour,
                entry_minute=minute,
                bars_to_exit=bars)
            backtester.run_backtest()
            summary = backtester.get_summary()

La busqueda de amplio espectro, se trata de, para cada uno de los parametros de los rangos introducidos, los colocamos en una llamada al backtester IntradayBacktester, lo ejecutamos, y guardamos el sumary. Para posteriormente


            result = {
                'entry_weekday': weekday if weekday is not None else 'All',
                'entry_hour': hour,
                'entry_minute': minute,
                'bars_to_exit': bars,
            }
            
            # Si hay trades, añadir todas las métricas
            if "message" not in summary:
                result.update(summary)  # Desempaquetar el diccionario summary
            else:
                # Si no hay trades, establecer valores predeterminados para todas las métricas
                for key in ['total_trades', 'winning_trades', 'losing_trades', 'win_rate',
                            'avg_profit', 'total_profit', 'max_profit', 'max_loss', 
                            'variance', 'profit_factor', 'sharpe', 'sqn', 'mdd',
                            'VaR', 'CVaR', 'beta', 'alpha']:
                    result[key] = 0
                result['total_trades'] = 0
            
            # Añadir el resultado a la lista
            all_results.append(result)

Crea un diccionario 'result' con los parámetros básicos de entrada (día de la semana, hora, minuto y número de barras para salir) y luego lo enriquece con métricas de rendimiento si se han realizado operaciones.

Si no hay operaciones, establece valores predeterminados de cero para todas las métricas importantes como número total de operaciones, tasa de éxito, beneficio promedio, factor de beneficio, ratio de Sharpe, entre otros.

Finalmente, este resultado completo se añade a una lista 'all_results', que presumiblemente se utilizará para análisis posteriores o para generar informes sobre el rendimiento de la estrategia en diferentes escenarios temporales.

Ademas mediante el siguiente codigo, si se han producido resultados, se actualiza la barra de progreso, para proporcionar feedback al usuario de lo que esta ocurriendo por detras

if result['total_trades'] > 0:
                progress_bar.set_postfix(trades=result['total_trades'], win_rate=f"{result['win_rate']:.2f}")

Para Finalizar

Una vez backtesteadas todas las posibilidades posibles, guardamos los resultados en un dataframe, exportamos el CSV y mostramos un print en pantalla, anunciando que hemos acabado la busqueda. De esta forma unicamente se escriben los resultados una vez acabados. Para optimizaciones extremadamente largas, o donde la probabilidad de error es mayor, deberia actualizarse el csv, cada N optimizaciones realizadas con exito incluso cada una, pero para nuestra labor, es mas que suficiente.

        # Crear DataFrame con todos los resultados
        results_df = pd.DataFrame(all_results)
        
        # Guardar el DataFrame completo en CSV al final
        results_df.to_csv(csv_path, index=False)
        
        print(f"Optimización completada. Resultados guardados en {csv_path}")
        return results_df

Pseudocodigo

Por si alguien no ha entendido muy bien lo que se pretende, o quiere hacerlo a su manera, le proporciono tambien el pseudocodigo de la funcion completa

función optimize(weekday_range, hour_range, minute_range, bars_range, csv_path):
    inicializar lista all_results
    calcular total_combinations
    crear parameter_combinations usando product()
    crear barra de progreso

    para cada combinación de (weekday, hour, minute, bars) en parameter_combinations:
        crear nueva instancia de IntradayBacktester con parámetros actuales
        ejecutar backtest
        obtener resumen estadístico
        crear diccionario result con parámetros básicos
        
        si hay trades:
            añadir todas las métricas al diccionario result
        si no:
            establecer valores predeterminados para todas las métricas
        
        añadir result a all_results
        actualizar barra de progreso

    crear DataFrame results_df con all_results
    guardar results_df en CSV
    devolver results_df

Ejecutando desde el Notebook

Para ejecutar desde un notebook o como cualquier script, unicamente se necesita seguir la siguiente metodologia

  • Cargar las librerias necesarias, en este caso pandas y el IntradayBacktester
  • Cargar el dataframe preprocesado a utilizar
  • Lanzar el model.optimize con los rangos ajustados. En este caso buscara de lunes a miercoles, de 5 a 10 en los minutos 15,30,45 y 00 y una ventana de salida de 1 a 3 barras.

Una vez ejecutada la actualizacion, nos devolvera el dataframe final, que podria ser asignado a una variable mediante

resultados = model.optimize(parametros)

Pero lo a guardado en un .csv, que posteriormente cargaremos. En este caso el archivo csv que corresponde a los resultados de la optimizacion los encontramos es LE_optimization_results.csv

Conclusiones

En este nuevo articulo, hemos visto como llegar hasta poder buscar ventajas estacionales en todo el espectro del activo. En el proximo y ultimo articulo, veremos como interpretar los datos de la optimizacion de una forma sencilla, mediante diferentes plots, diferentes estadisticos, etc..

Nos vemos en la proxima entrega, ante cualquier pregunta, no dudes en contactarme en jcx[a]quantarmy.com

Un saludo, y hasta la proxima!

Jesús Cuesta

Odesa (Ucrania)
Inversor desde 2014. Research desde 2017. He trabjado en diferentes gestoras de capital, y Hedgefunds Crypto. Apasasionado en el codigo, los datos y las finanzas. Acualmente localizado en Ucrania.