4. Coste, velocidad y el equilibrio que de verdad importa
Nivel 5: Guardar, actualizar y escalar
Los tres módulos anteriores han sido decisiones de forma. Este es el de los números, porque casi todas las discusiones sobre almacenamiento se ganan o se pierden con una multiplicación que nadie hizo. La pregunta de fondo no es cuánto ocupa: es cuánto cuesta cada cosa comparada con la alternativa, y qué latencia necesita tu estrategia de verdad.
Guardar es barato; volver a descargar, no siempre
El primer equilibrio es entre disco y descarga. El disco es barato y previsible. La descarga tiene tres costes que se olvidan: el precio del proveedor, el tiempo de pared que tardas en repetirla, y el hecho de que puede que ya no sea posible, porque el histórico profundo se retira, cambia de condiciones o el proveedor desaparece.
Haz siempre la cuenta del tiempo, que es la que sorprende. Supón 500 valores, veinticinco años de datos por minuto, un proveedor que devuelve un símbolo y un mes por petición y limita a cinco peticiones por minuto. Son 500 por 300 meses igual a 150.000 peticiones; a cinco por minuto, 30.000 minutos: unas 500 horas, más de veinte días seguidos descargando. El disco que evita repetir eso no se discute.
Diario, minuto, tick: los factores que multiplican
Aquí es donde la gente se equivoca por un factor grande. La sesión regular estadounidense dura seis horas y media: 390 minutos. Por tanto pasar de una barra diaria a barras de un minuto multiplica el número de filas por 390, no por 1.440, que sería el día natural completo.
Sobre los datos del módulo uno, decenas de megabytes en diario, ese factor te lleva a un almacén de decenas de gigabytes en minuto. Del minuto al tick no hay factor fijo: depende de la liquidez del instrumento, y para valores muy negociados hablamos de órdenes de magnitud adicionales. Si no lo has medido en tu instrumento y con tu proveedor, no cites un número; di que son órdenes de magnitud.
import time
import pandas as pd
from pathlib import Path
def tamano_mb(ruta):
return sum(f.stat().st_size for f in Path(ruta).rglob("*.parquet")) / 1e6
t0 = time.perf_counter()
df = pd.read_parquet("almacen/precios", columns=["fecha", "simbolo", "cierre"])
segundos = time.perf_counter() - t0
print(len(df), "filas", round(tamano_mb("almacen/precios"), 1), "MB",
round(segundos, 2), "s")
# Mide antes de decidir: la intuicion sobre estos numeros suele fallar por 10xPrecalcular o calcular al vuelo
El segundo equilibrio es entre CPU y disco. Precalcular indicadores y guardarlos convierte un cálculo repetido en una lectura; calcularlos al vuelo mantiene el almacén pequeño y evita que se queden obsoletos cuando corriges datos.
La regla es más estrecha de lo que se cree: precalcular gana cuando el mismo cálculo, con los mismos parámetros, se repite muchas veces sobre datos que no cambian. Si tu barrido de optimización mueve la ventana del indicador, no hay nada que precalcular, porque cada combinación es un cálculo distinto.
Con números: 3,1 millones de filas, veinte indicadores fijos que tardan ocho segundos en calcularse enteros, y un barrido de 2.000 combinaciones de umbrales de entrada y salida. Al vuelo son 16.000 segundos, unas cuatro horas y media solo recalculando lo mismo. Precalculado, son ocho segundos una vez y 2.000 lecturas de un fichero que cabe en memoria.
import hashlib
import json
from pathlib import Path
def firma(parametros, version_datos):
"""La firma incluye la version del almacen: si el dato cambia, la cache caduca."""
texto = json.dumps({"p": parametros, "v": version_datos}, sort_keys=True)
return hashlib.sha256(texto.encode()).hexdigest()[:12]
def indicadores(precios, parametros, version_datos):
ruta = Path("cache") / ("ind_" + firma(parametros, version_datos) + ".parquet")
if ruta.exists():
return pd.read_parquet(ruta)
salida = calcular_indicadores(precios, **parametros)
ruta.parent.mkdir(parents=True, exist_ok=True)
salida.to_parquet(ruta)
return salidaEl detalle que convierte esa caché en algo fiable es que la firma incluya la versión del almacén, no solo los parámetros. Sin eso, el día que entre una corrección seguirás leyendo indicadores calculados sobre el dato viejo, y el fallo será silencioso: números plausibles, resultados mal.
La pregunta que ordena todo lo anterior
Todas estas decisiones se simplifican cuando respondes a una sola pregunta: qué latencia necesita de verdad tu estrategia. No la que te gustaría tener; la que cambia tus decisiones.
- Si rebalanceas una vez al mes con precios de cierre, tu necesidad de latencia se mide en horas. Un feed en tiempo real no mejora una sola de tus decisiones.
- Si operas una vez al día en la apertura, se mide en minutos.
- Si tu decisión depende de lo que ocurre dentro de la sesión, entras en el terreno del minuto y del tick, y entonces sí necesitas ese volumen y esa infraestructura.
Casi siempre la latencia real es mucho menor de la que se cree, y el dato de más alta frecuencia se compra por ansiedad, no por necesidad. Es dinero, disco y complejidad que no compran ni una decisión mejor.
Caso: la estrategia mensual y el feed de veinte milisegundos
Cierre con un caso reconocible. Una estrategia rebalancea el primer día hábil de cada mes usando cierres del día anterior. Su gestor descompone la desviación entre el resultado real y el del backtest y encuentra que alrededor del 96% viene del deslizamiento en la ejecución y de la diferencia entre el precio de apertura y el cierre usado en la señal.
Con ese reparto, sustituir el feed diario gratuito con quince minutos de retardo por uno profesional de veinte milisegundos actúa sobre el 4% restante. El coste, en cambio, no sube un 4%: se multiplican la factura del proveedor, el volumen almacenado por el factor 390 y la complejidad del proceso de ingesta.
Mide antes de comprar. La secuencia correcta es: descompón de dónde viene tu error, calcula qué parte tocaría el dato nuevo, y solo entonces mira el precio.