σμ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.6·~1 hAvanzado

Numba

Posición en la rutaMódulo 10 de 11 · Tema 6 de 13
10.6 Numba
—Puntuación del lab— / 100XP total—Racha—

Compilar los bucles que escribiste

Algunos cálculos son naturalmente un bucle: cada paso depende del anterior, como en un promedio ponderado exponencialmente, un drawdown o el pago de una opción dependiente de la trayectoria. NumPy no puede vectorizarlos, y un bucle de Python sobre un millón de elementos es lento porque cada paso pasa por el intérprete. Numba es un compilador justo a tiempo: pones @njit sobre una función y, en la primera llamada, Numba traduce el bucle de Python a código máquina para los tipos exactos recibidos. Las llamadas posteriores corren a velocidad casi de C, a menudo de cincuenta a cien veces más rápido que el mismo bucle en Python puro, sin cambiar el código.

# An exponentially weighted average is a loop: each value depends on the one before
import numpy as np

try:
    from numba import njit                           # one decorator compiles the function when it is installed
except ImportError:
    def njit(func):                                  # without Numba the same code simply runs as Python
        return func

@njit
def ewma(values, alpha):
    out = np.empty(len(values))
    out[0] = values[0]
    for i in range(1, len(values)):
        out[i] = alpha * values[i] + (1.0 - alpha) * out[i - 1]
    return out

print(ewma(np.array([1.0, 2.0, 3.0]), 0.5))          # [1.   1.5  2.25]

El estilo que le gusta a Numba

En modo nopython, que selecciona @njit, Numba compila solo un subconjunto de Python y rechaza el resto en lugar de recaer en silencio en código lento. Entiende números, arrays de NumPy, tuplas, bucles simples, if y gran parte de las funciones de NumPy. No entiende pandas, objetos arbitrarios de Python ni diccionarios de tipos mezclados. Así que escribe la función crítica en estilo simple: recibe arrays y escalares, reserva la salida con np.empty o np.zeros, itera con for i in range(n) y mantén acumuladores escalares (total, peak). Llámala siempre con los mismos tipos de argumentos, porque una nueva combinación de tipos dispara una nueva compilación.

# A drawdown is a running peak and a running worst case: two scalars and one loop
import numpy as np

try:
    from numba import njit
except ImportError:
    def njit(func):
        return func

@njit
def max_drawdown(equity):
    peak = 0.0
    worst = 0.0
    for i in range(len(equity)):
        value = equity[i]
        if value > peak:
            peak = value
        drawdown = (peak - value) / peak
        if drawdown > worst:
            worst = drawdown
    return worst

curve = np.array([100.0, 120.0, 90.0, 130.0, 117.0])
print(max_drawdown(curve))                           # 0.25   from 120 down to 90
print(max_drawdown(np.array([1.0, 2.0, 3.0])))       # 0.0    a curve that only rises

Tiempo de compilación, caché y dónde Numba no ayuda

La primera llamada paga la compilación, a menudo un segundo o más, así que cronometra una función de Numba tras llamarla una vez con pocos datos, y usa @njit(cache=True) para conservar el código compilado entre ejecuciones. La aceleración necesita suficiente trabajo: una función llamada un millón de veces con tres números gasta su tiempo en la sobrecarga de la llamada, no en el bucle. Numba ayuda con bucles sobre arrays; no hace nada por código que ya es una llamada de NumPy, por esperar una base de datos ni por un mal algoritmo. @njit(parallel=True) con prange puede repartir iteraciones independientes entre núcleos, y fastmath=True cambia algo de rigor en coma flotante por velocidad, que solo debes activar tras comprobar que los resultados siguen dentro de la tolerancia.

# Counting crossings: Numba compiles any loop like this, so keep the logic in scalars
import numpy as np

try:
    from numba import njit
except ImportError:
    def njit(func):
        return func

@njit(cache=False)
def count_crossovers(fast, slow):
    count = 0
    previous = 0.0
    for i in range(len(fast)):
        gap = fast[i] - slow[i]
        if i > 0 and previous * gap < 0:
            count += 1
        previous = gap
    return count

fast = np.array([1.0, 3.0, 2.0, 1.0, 4.0])
slow = np.full(5, 2.0)
print(count_crossovers(fast, slow))                  # 2     below, above, touch, below, above: two strict sign changes
# Options change how a function is compiled; here they are ignored if Numba is not installed
import numpy as np

try:
    from numba import njit, prange
except ImportError:
    prange = range

    def njit(*args, **kwargs):
        if args and callable(args[0]):
            return args[0]
        return lambda func: func

@njit(cache=True)                                    # keep the compiled code on disk between runs
def total_notional(prices, quantities):
    total = 0.0
    for i in prange(len(prices)):                    # prange runs in parallel only with parallel=True
        total += prices[i] * quantities[i]
    return total

prices = np.array([184.25, 410.5, 99.0])
quantities = np.array([100.0, 50.0, 200.0])
print(total_notional(prices, quantities))            # 58750.0
print(total_notional(prices, quantities) == float(prices @ quantities))     # True  same answer as the vectorised dot product
Comprueba las respuestas, no solo la velocidadUn bucle compilado puede fallar de formas nuevas: desbordamiento de enteros en lugar de los enteros ilimitados de Python, un array float32 usado en silencio donde esperabas float64, un índice fuera de rango que lee basura en lugar de lanzar un error. Compara el resultado compilado con la versión Python simple con entradas aleatorias (con fn.py_func, que Numba conserva), como hacen las pruebas de estos laboratorios, y activa la comprobación de límites durante el desarrollo.
  • @njit compila una función de Python a código máquina en la primera llamada; los bucles que dependen de pasos anteriores se vuelven rápidos sin vectorizar.
  • Escribe para el modo nopython: arrays y escalares, salida reservada, for i in range(n), acumuladores escalares; sin pandas ni contenedores de tipos mezclados.
  • Cronometra tras una llamada de calentamiento, usa cache=True, no esperes ganancia en código que ya es una llamada de NumPy y comprueba los resultados con la versión Python simple.

A continuación: Cython, otra forma de compilar bucles de Python, añadiendo tipos y construyendo un módulo de extensión.

Lab: qué se califica

  • Mantener un total corriente en rolling_mean25 pts
  • Seguir el máximo en max_drawdown25 pts
  • Contar los cruces en count_crossovers25 pts
  • Rellenar un array de salida en ewma25 pts
Explorador · lab-122-1
▾ lab-122-1
…
bash · en el navegador
learner@quantcraft:~/lab-122-1$
Python aún no cargadoPythonLn 1, Col 1Spaces: 4UTF-8

¿Restablecer este tema?

Todo lo que hay en lab-122-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