σμ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 11 · Sistemas de producción y modelos de vanguardia›Tema 11.13·~4 hExperto

Proyecto final: una biblioteca de valoración o estrategia de extremo a extremo

Posición en la rutaMódulo 11 de 11 · Tema 13 de 13
11.13 Proyecto final: una biblioteca de valoración o estrategia de extremo a extremo
—Puntuación del lab— / 100XP total—Racha—

De ejercicios a una biblioteca

Has escrito un centenar de funciones pequeñas. Una biblioteca es esas funciones organizadas para que otra persona (incluido tú dentro de seis meses) pueda usarlas con confianza: una interfaz pública clara, cada pieza probada por separado, las piezas compuestas en una canalización y todo instalable, documentado y comprobado automáticamente. Este proyecto final es el patrón en miniatura. Una biblioteca de estrategias es el lugar más fácil para practicarlo: la señal convierte precios en posiciones, el backtest convierte posiciones en rendimientos tras costes, las métricas resumen los rendimientos y un ejecutor lo conecta todo. Cada etapa recibe arrays sencillos y devuelve arrays sencillos, así que cada una se puede probar sola con números que se comprueban a mano.

# The shape of a small library: a package, its tests, its metadata and its automated checks
tree = """\
quantlib/
├── pyproject.toml            name, version, dependencies (numpy)
├── src/quantlib/
│   ├── __init__.py           the public API: sma_positions, strategy_returns, performance_summary, run_strategy
│   ├── signals.py            prices -> positions
│   ├── backtest.py           positions -> returns after costs
│   └── metrics.py            returns -> total return, volatility, Sharpe, drawdown
├── tests/                    one test file per module, fixed seeds, hand-checked cases
└── .github/workflows/ci.yml  lint, types, tests on every push
"""
print(tree.splitlines()[0])                          # quantlib/
print(sum(1 for line in tree.splitlines() if ".py" in line))     # 4   the four module files

Señal y backtest: sin anticipación y con costes

La señal es aquí un cruce de medias móviles: estar largo cuando la media rápida de precios está por encima de la lenta, corto cuando está por debajo, plano cuando son iguales o la lenta aún no existe. Las dos reglas que deciden si un backtest es honesto se ven en el código. Primero, sin anticipación: la posición decidida al cierre del día t solo puede usar precios hasta el día t, y gana el rendimiento del día t + 1, por eso el rendimiento del paso t usa positions[t - 1]. Un test lo prueba: quitar los últimos precios no debe cambiar las posiciones anteriores. Segundo, los costes: cada cambio de posición paga cost * |cambio|, así que una estrategia que cambia sin parar puede ganar antes de costes y perder después.

# The signal and the honest backtest: positions decided at the close are earned on the NEXT step, and trading costs money
import numpy as np

def sma_positions(prices, fast, slow):
    prices = np.asarray(prices, dtype=float)
    positions = np.zeros(len(prices), dtype=int)
    for t in range(slow - 1, len(prices)):
        f = prices[t - fast + 1 : t + 1].mean()
        s = prices[t - slow + 1 : t + 1].mean()
        positions[t] = 1 if f > s else -1 if f < s else 0
    return positions

def strategy_returns(prices, positions, cost):
    returns = []
    for t in range(1, len(prices)):
        before = positions[t - 2] if t >= 2 else 0
        returns.append(positions[t - 1] * (prices[t] / prices[t - 1] - 1) - cost * abs(positions[t - 1] - before))
    return np.array(returns)

prices = np.array([100, 101, 103, 102, 105, 108, 107, 110, 112, 111, 115, 118], dtype=float)
positions = sma_positions(prices, 2, 4)
print(positions)                                     # [0 0 0 1 1 1 1 1 1 1 1 1]   flat until the 4-day average exists, then long
print(np.allclose(sma_positions(prices[:8], 2, 4), positions[:8]))      # True   later prices never change earlier positions
print(np.round(strategy_returns(prices, positions, 0.0).sum() - strategy_returns(prices, positions, 0.001).sum(), 3))    # 0.001   one purchase at 0.1%

Las cifras de rendimiento y el ejecutor

Una serie de rendimientos se resume con unas pocas cifras. El rendimiento total compone los periodos, prod(1 + r) - 1; el rendimiento anual lo reescala a un año, (1 + total)^(periodos_por_año / n) - 1; la volatilidad anual es la desviación típica muestral por sqrt(periodos_por_año); el ratio de Sharpe divide el rendimiento medio por esa desviación, anualizado, y es la medida principal de rendimiento por unidad de riesgo (0 para una serie plana en vez de un error de división); el drawdown máximo es la peor caída desde un máximo de la curva de capital, la cifra que dice cuánto dolor te pide soportar la estrategia. Una función ejecutor lo ata todo: posiciones, luego rendimientos, luego el resumen. Solo debe componer las demás funciones, para que un error siempre se rastree hasta una etapa.

# Performance statistics of a return series, and the runner that wires the three stages together
import math
import numpy as np

def performance_summary(returns, periods_per_year):
    returns = np.asarray(returns, dtype=float)
    n = len(returns)
    equity = np.concatenate(([1.0], np.cumprod(1 + returns)))
    total = float(equity[-1] - 1)
    deviation = float(np.std(returns, ddof=1))
    peak = np.maximum.accumulate(equity)
    return {
        "total_return": total,
        "annual_return": float((1 + total) ** (periods_per_year / n) - 1),
        "annual_vol": deviation * math.sqrt(periods_per_year),
        "sharpe": float(returns.mean() / deviation * math.sqrt(periods_per_year)) if deviation > 0 else 0.0,
        "max_drawdown": float(np.max((peak - equity) / peak)),
    }

summary = performance_summary([0.10, -0.20, 0.05], 252)
print(round(summary["total_return"], 4), round(summary["max_drawdown"], 4))     # -0.076 0.2   equity 1 -> 1.1 -> 0.88 -> 0.924: a 20% fall from the peak
print(performance_summary([0.0, 0.0, 0.0], 252)["sharpe"])                       # 0.0   no variation: a Sharpe of 0, not an error
# The runner only composes: if a number looks wrong you can test each stage on its own
def run_strategy(prices, fast, slow, cost, periods_per_year=252):
    positions = sma_positions(prices, fast, slow)
    returns = strategy_returns(prices, positions, cost)
    return performance_summary(returns, periods_per_year)

# (sma_positions, strategy_returns and performance_summary are the functions you wrote in this lab)
steps = ["sma_positions", "strategy_returns", "performance_summary"]
print(" -> ".join(steps))                           # sma_positions -> strategy_returns -> performance_summary
print(len(steps), "stages, one result: a dict of five numbers")        # 3 stages, one result: a dict of five numbers

Tu proyecto final

Ahora construye el tuyo, en tu propio repositorio. Elige una de dos direcciones. Una biblioteca de valoración: Black-Scholes y las griegas, un valorador Monte Carlo con variables antitéticas, un resolutor de volatilidad implícita y la calibración de un modelo a un conjunto de cotizaciones. O una biblioteca de estrategias: varias señales, un backtest con costes y límites de posición, evaluación walk-forward y un informe. En ambos casos entrega lo mismo: un paquete con una API pública clara y anotaciones de tipo; pruebas con semillas fijas y casos comprobados a mano; un pyproject.toml y un README que muestre cómo instalar y ejecutar un ejemplo; un flujo de CI con lint, tipos y pruebas; una nota de rendimiento con un cuello de botella perfilado; y un breve texto sobre los supuestos y lo que rompería el modelo. Reparte tu tiempo: diseña primero la interfaz, construye la canalización funcional más pequeña y luego amplíala.

Un buen resultado es aburrido de reproducirOtra persona debería poder clonar tu repositorio, ejecutar un comando y obtener exactamente tus cifras. Fija semillas, congela versiones, conserva la instantánea de datos o un script que la obtenga y muestra la curva de capital junto a una referencia simple. Informa de lo que la estrategia cuesta en drawdown y rotación además de lo que gana, y di con claridad qué partes no probaste. Esa honestidad es lo que hace confiable el trabajo de un quant.
  • Una biblioteca son pequeñas funciones probadas compuestas en una canalización tras una interfaz clara: señal -> posiciones -> rendimientos tras costes -> métricas, conectadas por un ejecutor.
  • Un backtest honesto no tiene anticipación (la posición en t usa precios hasta t y gana el rendimiento siguiente) y cobra costes por cada cambio de posición.
  • Resume con rendimiento total y anual, volatilidad, Sharpe y drawdown máximo; luego entrega un paquete con pruebas, CI, un README y un texto honesto.

Con esto termina la ruta. Ahora tienes el lenguaje, las herramientas numéricas, las piezas de datos de mercado y de ejecución, los modelos y los hábitos de ingeniería de un desarrollador cuantitativo en activo. Conserva tu repositorio final: es la mejor prueba de las habilidades que has construido aquí.

Lab: qué se califica

  • Convertir una tendencia en posiciones con sma_positions25 pts
  • Hacer backtest con costes en strategy_returns25 pts
  • Juzgar el resultado con performance_summary25 pts
  • Conectarlo todo en run_strategy25 pts
Explorador · lab-142-1
▾ lab-142-1
…
bash · en el navegador
learner@quantcraft:~/lab-142-1$
Python aún no cargadoPythonLn 1, Col 1Spaces: 4UTF-8

¿Restablecer este tema?

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