σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft AcademyRutas
Ctrl K
AO
+1,2 k estudiantes
Iniciar sesiónUnirse ↗
Rutas
AO
+1,2 k estudiantes
Iniciar sesión
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Ruta de Python›Módulo 10 · Ingeniería Python de alto rendimiento›Tema 10.12·~50 minAvanzado

Parquet y Arrow

Posición en la rutaMódulo 10 de 11 · Tema 12 de 13
10.12 Parquet y Arrow
—Puntuación del lab— / 100XP total—Racha—

¿Filas o columnas?

Un archivo CSV o una base orientada a filas guarda cada registro junto: todos los campos de la operación 1, luego los de la 2, etc. El análisis rara vez lo quiere. Calcular el precio medio de diez millones de operaciones solo necesita la columna price, pero una disposición por filas obliga al lector a saltar todos los demás campos. Una disposición columnar mantiene cada columna contigua: una consulta lee solo las columnas que nombra, los valores parecidos quedan juntos y se comprimen muy bien, y la aritmética sobre una columna recorre un único bloque compacto de memoria. Por eso los lagos de datos cuantitativos usan Parquet en disco y Arrow en memoria.

# The same table as rows and as columns
rows = [{"symbol": "AAPL", "price": 184.25, "qty": 100},
        {"symbol": "MSFT", "price": 410.5, "qty": 50},
        {"symbol": "AAPL", "price": 184.5, "qty": 25}]

columns = {name: [row[name] for row in rows] for name in rows[0]}
print(columns)                                       # {'symbol': ['AAPL', 'MSFT', 'AAPL'], 'price': [184.25, 410.5, 184.5], 'qty': [100, 50, 25]}
print(sum(columns["price"]) / len(rows))             # 259.75   the average only touched one column

back = [{name: columns[name][i] for name in columns} for i in range(len(rows))]
print(back == rows)                                  # True  nothing is lost either way

Parquet: grupos de filas, estadísticas y codificaciones

Un archivo Parquet divide la tabla en grupos de filas (digamos un millón de filas cada uno); dentro de un grupo cada columna se guarda como un fragmento propio y el archivo registra el mínimo y el máximo de cada fragmento. Una consulta de price entre 180 y 190 lee primero las estadísticas y salta todo grupo cuyo rango no puede contener tal precio (predicate pushdown), y lee solo las columnas pedidas (projection pushdown). Las columnas también se codifican: una columna con pocos valores distintos, como el símbolo, se guarda como un pequeño diccionario más un diminuto código entero por fila, y el resultado se comprime. Un archivo de datos de mercado repetitivos suele ser diez veces menor que el CSV.

# Dictionary encoding turns a few distinct values into a small dictionary plus integer codes
def dictionary_encode(values):
    positions, dictionary, codes = {}, [], []
    for value in values:
        if value not in positions:
            positions[value] = len(dictionary)
            dictionary.append(value)
        codes.append(positions[value])
    return dictionary, codes

symbols = ["AAPL", "MSFT", "AAPL", "AAPL", "MSFT", "NVDA"] * 1000
dictionary, codes = dictionary_encode(symbols)
print(dictionary, codes[:6])                         # ['AAPL', 'MSFT', 'NVDA'] [0, 1, 0, 0, 1, 2]
print([dictionary[c] for c in codes] == symbols)     # True  decoding restores the column
print(len(dictionary), "distinct values for", len(symbols), "rows")      # 3 distinct values for 6000 rows
# Statistics let a reader skip whole row groups without opening them
def groups_to_read(stats, low, high):
    return [i for i, (minimum, maximum) in enumerate(stats) if maximum >= low and minimum <= high]

stats = [(180.0, 183.5), (183.0, 186.0), (185.5, 190.0), (189.0, 195.0)]        # (min, max) of price per row group
print(groups_to_read(stats, 184.0, 185.0))           # [1]        only the second group can hold a price in this range
print(groups_to_read(stats, 189.5, 192.0))           # [2, 3]
print(groups_to_read(stats, 200.0, 210.0))           # []         no group needs to be read at all

Arrow, pandas y leer menos

Apache Arrow define la misma disposición columnar para la memoria, de modo que programas y lenguajes pueden pasarse una tabla sin convertirla: pandas, Polars, DuckDB y Spark pueden ver una misma tabla Arrow sin copia. En la práctica se usa pandas.read_parquet(path, columns=["symbol", "price"], filters=[("price", ">", 180)]), que empuja la elección de columnas y el filtro al lector, o pyarrow.parquet.read_table. Escribe los datos particionados por una columna como la fecha (df.to_parquet(path, partition_cols=["date"])) para que una consulta de un día abra un solo directorio. Mantén grupos de filas lo bastante grandes para comprimir bien pero lo bastante pequeños para que las estadísticas descarten muchos, y ordena los datos por la columna de filtro para que cada grupo cubra un rango estrecho.

# Sorting by the filter column makes the row groups cover narrow, non-overlapping ranges
import random

rng = random.Random(7)
prices = [round(rng.uniform(100, 200), 2) for _ in range(10_000)]

def group_stats(values, size):
    groups = [values[i : i + size] for i in range(0, len(values), size)]
    return [(min(g), max(g)) for g in groups]

def groups_to_read(stats, low, high):
    return [i for i, (a, b) in enumerate(stats) if b >= low and a <= high]

unsorted_groups = len(groups_to_read(group_stats(prices, 1000), 150.0, 151.0))
sorted_groups = len(groups_to_read(group_stats(sorted(prices), 1000), 150.0, 151.0))
print(unsorted_groups, sorted_groups)                # 10 1   without sorting every group's range overlaps the query
Un archivo columnar es para analizar, no para editarLos archivos Parquet se escriben una vez y se leen muchas. Cambiar una fila obliga a reescribir un archivo, así que sirven para el histórico (ticks, barras, resultados de backtests) y no para una tabla de órdenes en vivo, que pertenece a una base de datos. Añade datos nuevos como archivos o particiones nuevos en vez de editar los viejos. Mantén el esquema estable y las marcas de tiempo en UTC, y recuerda que las columnas de flotantes conservan el redondeo que viste antes.
  • El almacenamiento columnar mantiene cada columna junta: las consultas leen solo las columnas necesarias, los valores parecidos comprimen bien y la aritmética por columna es rápida.
  • Parquet divide los datos en grupos de filas con estadísticas mín/máx y codifica con diccionario los valores repetidos; los lectores saltan los grupos y columnas que no necesitan.
  • Arrow es el gemelo en memoria que comparten pandas, Polars y DuckDB; particiona por fecha, ordena por la columna de filtro y añade en vez de editar.

A continuación: bases de datos de series temporales, almacenes pensados para datos con marca de tiempo como ticks y métricas.

Lab: qué se califica

  • Convertir filas en columnas con to_columns25 pts
  • Reconstruir las filas con to_rows25 pts
  • Reducir repeticiones con dictionary_encode25 pts
  • Saltar lo que se pueda con groups_to_read25 pts
Explorador · lab-128-1
▾ lab-128-1
…
bash · en el navegador
learner@quantcraft:~/lab-128-1$
Python aún no cargadoPythonLn 1, Col 1Spaces: 4UTF-8

¿Restablecer este tema?

Todo lo que hay en lab-128-1 vuelve a los archivos iniciales y se limpia el terminal. Tu solución actual se perderá: no se puede deshacer.

Se restablecerá
  • main.py → código inicial
  • Historial del terminal y salida de pruebas
  • Cambios sin enviar
Se conservará
  • Mejor puntuación del lab
  • Tu XP y tu racha
  • Progreso de la lección y notas

Inicia sesión para ejecutar tu código.

Todas las clases se pueden leer gratis. Crea una cuenta o inicia sesión para ejecutar código, lanzar las pruebas y enviar este lab.

  • IDE en el navegador con terminal bash — sin instalar nada
  • Corrección instantánea, XP y tu lugar en la ruta
Crear una cuentaIniciar sesión
O
Continuar con GitHub