σμ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 6 · Backtesting y costes de trading›Tema 6.1·~1 hAvanzado

Un backtester vectorizado

Posición en la rutaMódulo 6 de 11 · Tema 1 de 6
6.1 Un backtester vectorizado
—Puntuación del lab— / 100XP total—Racha—

Reproducir la historia

Un backtest pregunta qué habría ganado una regla de trading con datos pasados. La versión vectorizada trabaja con arrays enteros: sin bucle sobre los días, solo una señal por día, una posición por día y una multiplicación con los rendimientos. Es corta, rápida y fácil de comprobar, lo que la convierte en la primera herramienta para filtrar ideas. Sus límites (sin órdenes, sin efectivo, sin ejecuciones parciales) son justo lo que añade el siguiente tema.

import numpy as np

returns = np.array([0.01, 0.02, -0.01, 0.03])           # the asset's daily returns
signal = np.array([2.5, 0.4, -1.0, 0.0])                # any score: positive = buy, negative = sell

position = np.sign(signal)                               # +1, +1, -1, 0
print(position)

El retraso de un día

Una señal calculada con el cierre de hoy solo puede operarse después de ese cierre, así que gana el rendimiento de mañana, no el de hoy. En arrays eso es un desplazamiento: strategy[t] = position[t - 1] * returns[t], sin posición antes del primer día. Olvidarlo deja que la estrategia opere con un rendimiento que ya ha visto, y el backtest parece maravilloso. El desplazamiento es una línea, la más importante de toda la función.

# Yesterday's position earns today's return
import numpy as np

returns = np.array([0.01, 0.02, -0.01, 0.03])
position = np.array([1.0, 1.0, -1.0, 0.0])

strategy = np.zeros(len(returns))
strategy[1:] = position[:-1] * returns[1:]
print(strategy)                                    # [ 0.    0.02 -0.01 -0.03]
print(position * returns)                          # the same-day version: [ 0.01  0.02  0.01  0. ] looks better, and is cheating

Capital y estadísticas

Los rendimientos diarios se componen: el valor tras cada día es el inicial por el producto acumulado de 1 + rendimiento. De esta curva de capital, tres números resumen una prueba. El rendimiento total es el valor final menos 1. El ratio de Sharpe es el rendimiento diario medio dividido por su desviación típica, por sqrt(periods) para anualizarlo. El drawdown máximo es la peor caída desde un máximo previo, equity / running max - 1, es decir, la pérdida que habría vivido un inversor.

# Equity curve and the three statistics for a short run
import numpy as np

strategy = np.array([0.0, 0.02, -0.01, -0.03, 0.04])
equity = np.cumprod(1 + strategy)
print(np.round(equity, 5))
print(round(float(equity[-1] - 1), 5))                                        # total return
print(round(float(strategy.mean() / strategy.std(ddof=1) * np.sqrt(252)), 3))   # annualised Sharpe
print(round(float((equity / np.maximum.accumulate(equity) - 1).min()), 5))      # maximum drawdown
# A moving-average rule on a simulated price path, with its statistics
import numpy as np

rng = np.random.default_rng(7)
returns = rng.normal(0.0004, 0.01, size=500)
prices = 100 * np.cumprod(1 + returns)

fast = np.convolve(prices, np.ones(10) / 10, mode="valid")[-(len(prices) - 49):]      # 10-day mean, aligned
slow = np.convolve(prices, np.ones(50) / 50, mode="valid")                            # 50-day mean
position = np.zeros(len(prices))
position[49:] = np.sign(fast - slow)

strategy = np.zeros(len(returns))
strategy[1:] = position[:-1] * returns[1:]
equity = np.cumprod(1 + strategy)
drawdown = equity / np.maximum.accumulate(equity) - 1
print(round(float(equity[-1] - 1), 4), round(float(strategy.mean() / strategy.std(ddof=1) * np.sqrt(252)), 4), round(float(drawdown.min()), 4))
Un backtest vectorizado es un filtro, no un veredictoSupone que se opera toda la posición al cierre, al precio visto y gratis. El trading real tiene costes, deslizamiento y límites, que se ven en los temas siguientes, y probar muchas reglas sobre el mismo historial encontrará una que parece buena por azar. Un resultado prometedor es motivo para seguir probando, no para operar.
  • Posición = signo de la señal; el rendimiento de la estrategia es position[t - 1] * returns[t]: el rendimiento de hoy corresponde a la decisión de ayer.
  • El capital es start * cumprod(1 + r); rendimiento total, Sharpe anualizado y drawdown máximo lo resumen.
  • Los backtests vectorizados son filtros rápidos: ignoran costes y ejecuciones, y muchas pruebas sobre un historial producen suerte.

A continuación: un backtester dirigido por eventos, reproducir la historia barra a barra con efectivo, órdenes y ejecuciones.

Lab: qué se califica

  • Convertir una señal en posición con signal_to_position25 pts
  • Ganar con la posición de ayer con strategy_returns25 pts
  • Componer los días con equity_curve25 pts
  • Resumir la prueba con backtest_stats25 pts
Explorador · lab-87-1
▾ lab-87-1
…
bash · en el navegador
learner@quantcraft:~/lab-87-1$
Python aún no cargadoPythonLn 1, Col 1Spaces: 4UTF-8

¿Restablecer este tema?

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