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



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_dfPseudocodigo
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_dfEjecutando 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!


