σμ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.3·~50 minAvanzado

Pruebas con pytest

Posición en la rutaMódulo 10 de 11 · Tema 3 de 13
10.3 Pruebas con pytest
—Puntuación del lab— / 100XP total—Racha—

Por qué probar y cómo es una prueba

En trading, un error no es una excepción en pantalla; es un número equivocado que parece plausible: un signo cambiado en un P&L, una comisión aplicada dos veces, una señal que usa en silencio el precio de mañana. Las pruebas son pequeños programas que comprueban tu código contra respuestas conocidas, de modo que ese error se detecta en tu máquina y no en el mercado. pytest encuentra las funciones llamadas test_*, ejecuta cada una e informa de las que fallan un assert. Una prueba se lee sin framework: prepara una entrada, llama a la función y haz assert de lo que esperas.

# A test is a function with an assert; pytest runs every test_* function it finds
def net_pnl(entry, exit_price, quantity, fee):
    return (exit_price - entry) * quantity - fee

def test_net_pnl_long_winner():
    assert net_pnl(100.0, 102.0, 50, 1.0) == 99.0

def test_net_pnl_short_loser():
    assert net_pnl(100.0, 102.0, -50, 1.0) == -101.0

for test in (test_net_pnl_long_winner, test_net_pnl_short_loser):
    test()                                           # pytest does this for you and reports failures
    print(test.__name__, "passed")

Flotantes, errores y muchos casos

Los flotantes son aproximados, así que 0.1 + 0.2 == 0.3 es falso. Compara números con tolerancia: pytest.approx(expected) acepta una pequeña diferencia relativa (por defecto una parte entre un millón, más un diminuto suelo absoluto de 1e-12) y puedes añadir una tolerancia absoluta para valores cercanos a cero, donde una relativa no sirve. Para comprobar que se rechaza una entrada mala, pytest.raises(ValueError) pasa solo si ocurre esa excepción, y falla si no ocurre ninguna (u otra). Para ejecutar una prueba con muchos casos, @pytest.mark.parametrize aporta una tabla de entradas y valores esperados, informa de cada fila por separado y convierte añadir un caso en un cambio de una línea.

# Floats need a tolerance: approx accepts a tiny relative difference, == does not
import pytest

print(0.1 + 0.2 == 0.3)                              # False
print(0.1 + 0.2 == pytest.approx(0.3))               # True
print(1e-10 == pytest.approx(0.0))                   # False  near zero a relative tolerance cannot help
print(1e-10 == pytest.approx(0.0, abs=1e-9))         # True   a larger absolute tolerance does
# raises: the test passes only if the expected exception happens
import pytest

def leverage(gross, equity):
    if equity <= 0:
        raise ValueError("equity must be positive")
    return gross / equity

with pytest.raises(ValueError) as info:
    leverage(1000.0, 0.0)
print("raised:", info.value)                         # raised: equity must be positive

try:
    with pytest.raises(ValueError):
        leverage(1000.0, 500.0)                      # no exception: pytest.raises itself fails
except BaseException as outcome:
    print(type(outcome).__name__)                    # Failed
# parametrize in miniature: one function, a table of cases, each row judged on its own
def fee(notional, rate=0.001, minimum=1.0):
    return max(notional * rate, minimum)

cases = [
    ((100_000.0,), 100.0),
    ((500.0,), 1.0),                                 # the minimum fee applies
    ((2_000.0, 0.002), 4.0),
    ((0.0,), 1.0),
]
for index, (args, expected) in enumerate(cases):
    got = fee(*args)
    print(index, "ok" if abs(got - expected) <= 1e-9 * abs(expected) else f"FAIL got {got}")

Fixtures, semillas y qué probar

Una fixture es una preparación compartida que las pruebas piden por nombre: una serie de precios pequeña, un generador aleatorio con semilla, un archivo temporal. Con datos aleatorios, fija la semilla (o imprímela) para poder reproducir un fallo: una prueba que falla una vez de cada cien y no se puede repetir es peor que ninguna. Prueba el comportamiento que importa y no los detalles internos: casos conocidos a mano, casos límite (entrada vacía, cero, un elemento), invariantes que siempre deben cumplirse (una probabilidad está entre 0 y 1, comprar y vender al mismo precio pierde exactamente las comisiones) y una prueba de regresión por cada error que corrijas. Cuando puedes enunciar una regla válida para toda entrada, buscar un contraejemplo entre muchas entradas aleatorias es una prueba barata y potente.

# A seed makes a random test replayable; a property is searched for a counterexample
import random

def find_counterexample(prop, inputs):
    for value in inputs:
        if not prop(value):
            return value
    return None

rng = random.Random(42)                              # fixed seed: the same inputs on every run
returns = [round(rng.uniform(-0.05, 0.05), 4) for _ in range(1000)]

def compound_is_positive_growth(r):
    return (1 + r) > 0                               # a daily return above -100% keeps the value positive

print(find_counterexample(compound_is_positive_growth, returns))      # None  the rule holds for all 1000
print(find_counterexample(lambda r: r > -0.049, returns))             # a return that breaks a wrong rule
Una prueba que no puede fallar no prueba nadaHaz que cada prueba falle primero, rompiendo el código a propósito, para saber que puede detectar el error que debe detectar. No copies la fórmula de la propia función en la prueba: si ambas comparten un fallo, coinciden y la prueba pasa. Calcula el valor esperado de forma independiente o a mano. Y mantén las pruebas rápidas y sin red ni reloj, para ejecutarlas de verdad antes de cada cambio.
  • Una prueba prepara la entrada, llama a la función y hace assert del resultado; pytest ejecuta cada función test_* e informa de los fallos.
  • Compara flotantes con pytest.approx (añade tolerancia absoluta cerca de cero), espera errores con pytest.raises y usa parametrize para tablas de casos.
  • Fija las semillas, prueba casos límite, invariantes y errores corregidos, busca contraejemplos y haz fallar cada prueba una vez antes de fiarte de ella.

A continuación: perfilado de código Python, medir adónde va el tiempo antes de intentar acelerarlo.

Lab: qué se califica

  • Comparar flotantes con tolerancia en is_close25 pts
  • Ejecutar una tabla de casos con run_cases25 pts
  • Esperar un error con raises25 pts
  • Cazar un fallo con find_counterexample25 pts
Explorador · lab-119-1
▾ lab-119-1
…
bash · en el navegador
learner@quantcraft:~/lab-119-1$
Python aún no cargadoPythonLn 1, Col 1Spaces: 4UTF-8

¿Restablecer este tema?

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