Automatizar una lista de seguimiento diaria: un script programado que descarga datos, calcula señales y escribe un informe, además de las trampas de las claves de API y los límites de solicitudes
Convierte el código de un notebook en un script de línea de comandos robusto con configuración, registro, reintentos con espera exponencial, gestión segura de la clave de API y un programador de tareas, y escribe un archivo de informe diario.
Qué aprenderás
- Cómo reestructurar las celdas de un notebook en un script con funciones, un punto de entrada
main()y un archivo de configuración. - Cómo mantener las claves de API fuera del código y gestionar los límites de solicitudes con esperas y espera exponencial (back-off).
- Cómo escribir un informe útil a las 07:30 de la mañana: una tabla ordenada, unas pocas alertas y una marca temporal.
- Cómo programar el script con cron o con el Programador de tareas de Windows, y qué registrar para que los fallos silenciosos no pasen desapercibidos.
Explicación
Un notebook sirve para explorar; un script repite el mismo trabajo cada día sin que tengas que vigilarlo. Los indicadores (lección 6) y la lógica de señales (lecciones 7 y 8) siguen siendo los mismos; todo lo que los rodea cambia.
Estructura del script
Divide el trabajo en funciones con una sola tarea cada una: load_config(), fetch(ticker), compute_signals(df), build_report(rows) y main(). La protección if __name__ == '__main__': main() te permite ejecutar el archivo y también importar sus funciones en las pruebas. Guarda la lista de seguimiento, los parámetros y la carpeta de salida en config.yaml o config.json para que puedan cambiar sin editar el código.
Claves de API y secretos
Nunca escribas una clave en el código fuente ni en un notebook que puedas compartir o subir a Git. Léela de una variable de entorno o de un archivo .env con os.environ['DATA_API_KEY'] (el paquete python-dotenv carga .env), e incluye .env en .gitignore. Trata la clave como una contraseña y renuévala si se filtra.
Límites de solicitudes, reintentos y espera exponencial
Un límite de solicitudes (rate limit) es el número máximo de peticiones que un proveedor permite por minuto o por día; superarlo devuelve un error HTTP 429 o, peor aún, datos vacíos sin ningún error. Marca el ritmo del bucle con time.sleep(60 / max_per_minute). Ante fallos temporales (429, 500, tiempos de espera agotados), reintenta con espera exponencial: espera 1 s, 2 s, 4 s, 8 s, y después abandona y registra el ticker como faltante. Nunca reintentes en un bucle cerrado sin pausas; así es como se bloquean las claves.
def fetch_with_retry(ticker, tries=4, base=1.0):for n in range(tries):try:return fetch(ticker)except TransientError as e:wait = base * 2 ** nlog.warning('%s: %s, reintento en %.0f s', ticker, e, wait)time.sleep(wait)log.error('%s: se abandona', ticker)return None
Guarda en caché las descargas brutas (data/raw/TICKER_YYYY-MM-DD.parquet): una segunda ejecución el mismo día lee el archivo en lugar de gastar cuota, y conservas un registro auditable de lo que vio el script.
Validación antes de las señales
Los datos llegan defectuosos más a menudo de lo que esperas: falta la última barra, una fecha está desfasada, hay un cierre de 0, un split sin ajustar. Comprueba que la última fecha coincide con el día de negociación esperado, que las últimas 20 filas no tienen NaN y que la rentabilidad absoluta de hoy está por debajo de un umbral de cordura, como el 50 %. Los fallos van a una sección de «problemas de datos» del informe, en lugar de producir una señal de compra a partir de una cotización errónea.
El informe
Escribe un archivo Markdown o HTML con la marca temporal de la ejecución y la fecha de los datos, una tabla ordenada según tu clasificación (por ejemplo, la distancia a la SMA de 50 días en unidades de ATR), las señales nuevas desde ayer (comparadas con el archivo de señales guardado el día anterior) y los problemas de datos. Guárdalo como reports/watchlist_2026-10-30.md para conservar el historial; los gráficos de cada ticker señalado (lección 10) son opcionales.
Programación y registro
En Linux y macOS, cron ejecuta comandos según un horario: 30 6 * * 1-5 /path/venv/bin/python /path/watchlist.py >> /path/logs/cron.log 2>&1 se ejecuta a las 06:30 los días laborables; en Windows, el Programador de tareas hace lo mismo a través de un cuadro de diálogo. Usa la ruta completa al Python del entorno virtual (lección 1), porque los programadores de tareas no activan entornos. Registra con el módulo logging en un archivo: inicio, tickers descargados, señales encontradas, fin. Si no hay entrada de hoy, el programador ha fallado. Los ordenadores entran en reposo, las redes se caen, los endpoints cambian; un script que no compruebas no es automatización.
Ejemplo práctico
Ejemplo hipotético: una lista de seguimiento de 40 acciones europeas y estadounidenses, un plan de datos gratuito con un límite de 5 solicitudes por minuto y 500 por día (cifras inventadas; consulta las condiciones vigentes de tu proveedor).
- Ritmo. 40 tickers a 5 por minuto = 40 / 5 = 8 minutos como mínimo. El script espera 12 segundos entre llamadas. Programarlo a las 06:30 significa que los datos están listos hacia las 06:40.
- Cuota. Una ejecución usa 40 de las 500 llamadas diarias (880 al mes en 22 días de negociación). Un error que repitiera la ejecución cada 10 minutos consumiría 240 llamadas por hora y alcanzaría el límite en unas dos horas; una caché en disco elimina ese riesgo.
- Espera exponencial. Dos tickers devuelven HTTP 429 en el primer intento. Los reintentos tras 1, 2 y 4 segundos tienen éxito en el segundo intento; el tiempo añadido en el peor caso es de 1 + 2 + 4 + 8 = 15 segundos por ticker, así que la ejecución sigue terminando dentro del presupuesto de 10 minutos.
- Informe. Regla: «el cierre cruza por encima de la SMA de 50 días con el RSI(14) por debajo de 60». El archivo de ayer tenía 3 señales activas; la ejecución de hoy encuentra 5, de las cuales 2 son nuevas. El informe enumera primero los 2 valores nuevos, después los 3 que continúan y 1 ticker en «problemas de datos» porque su última barra tiene dos días de antigüedad.
Excluido: esto no dice nada sobre si la señal es rentable; solo hace que la preparación diaria sea fiable y repetible.
Error frecuente
Actividad práctica
En papel: tu proveedor permite 30 solicitudes por minuto y 2,000 por día. Quieres ejecutar una lista de seguimiento de 150 tickers una vez a las 06:00 y actualizar 20 tickers prioritarios cada 30 minutos entre las 09:00 y las 17:30 (CET). (a) ¿Qué espera mínima entre llamadas respeta el límite por minuto? (b) ¿Cuántas llamadas consume un día completo? ¿Está dentro de la cuota diaria? (c) La ejecución de las 06:00 informa de 150 tickers descargados, pero todas las señales son idénticas a las de ayer. Indica dos comprobaciones que te dirían si los datos están actualizados.
Mostrar solución
(a) 60 / 30 = 2 segundos entre llamadas (usa 2.1 s por seguridad).
(b) Ejecución matinal: 150 llamadas. Intradía: de 09:00 a 17:30 son 8.5 horas = 17 franjas de media hora, más la propia franja de las 09:00 = 18 actualizaciones × 20 tickers = 360 llamadas. Total de 510 llamadas, muy por debajo de 2,000. Incluso con un reintento completo de cada llamada (1,020), la cuota se mantiene.
(c) Comprueba que la última fecha del índice de cada DataFrame es el día de negociación anterior esperado (teniendo en cuenta los festivos bursátiles), y compara un hash o el último cierre de la descarga de hoy con el archivo en caché de ayer; si los 150 últimos cierres son idénticos a los de ayer, el proveedor sirvió datos desfasados.
Limitaciones: las cuotas, los endpoints y las reglas de los planes gratuitos cambian; las cifras de aquí son ilustrativas y debes leer la documentación vigente de tu proveedor.
Ideas clave
- Los scripts necesitan funciones, un archivo de configuración, una protección
main()y registro; los notebooks no. - Guarda las claves en variables de entorno o en
.env; ajusta el ritmo de las solicitudes al límite y reintenta con espera exponencial, y después abandona y regístralo. - Valida las fechas y el número de filas antes de calcular señales; guarda en caché las descargas brutas.
- Programa con cron o con el Programador de tareas usando la ruta completa al Python del entorno virtual, y revisa el registro a diario.
Siguiente paso
La lección 13 une todo en un proyecto completo de notebook de investigación, de los datos al informe, y lo revisa con una lista de comprobación frente al sesgo de anticipación y el sobreajuste.
Autoevaluación
Responde a las preguntas y luego comprueba tus respuestas. Puedes volver a intentarlo tantas veces como quieras.
Autoevaluación automática para tu propio repaso. Tus respuestas se comprueban en tu navegador y no las vemos ni evaluamos. No es un examen ni una titulación.
Tu progreso
«Completada» solo significa que marcaste la lección como terminada. No es una prueba de tu comprensión. El progreso y las notas se guardan solo en este navegador y en este dispositivo, no en una cuenta.