σμ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 AcademyHojas de rutaRutas
Ctrl K
AO
+1,2 k estudiantes
Iniciar sesiónUnirse ↗
Hojas de rutaRutas
AO
+1,2 k estudiantes
Iniciar sesión
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
σμ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 AcademyHojas de rutaRutas
Ctrl K
AO
+1,2 k estudiantes
Iniciar sesiónUnirse ↗
Hojas de rutaRutas
AO
+1,2 k estudiantes
Iniciar sesión
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Ruta de Cache locality›Módulo 3 · La máquina en conjunto›Tema 3.6·~55 minAvanzado

Predicción de saltos

Posición en la rutaMódulo 3 de 3 · Tema 6 de 7
3.6 Predicción de saltos
—Puntuación del lab— / 100XP total—Racha—

6 min de lectura3 ejemplos de código

  1. 01Por qué el procesador adivina
  2. 02Predictores que recuerdan
  3. 03Cuánto cuesta un fallo y por qué ordenar ayuda
  4. 04Lo que va a construir

Un procesador no espera a saber hacia dónde va un if: adivina y empieza a trabajar. Una buena apuesta es gratis y una mala tira el trabajo, unos 15 ciclos perdidos. Con qué frecuencia acierta depende de los datos: el mismo bucle sobre los mismos rendimientos de SPY cuesta 8,3 veces menos ciclos una vez ordenados los rendimientos.

01

Por qué el procesador adivina

Un procesador funciona como una cadena de montaje: mientras se ejecuta una instrucción, las siguientes ya se están leyendo y decodificando. Un if rompe la cadena, porque la siguiente instrucción depende de su respuesta, que aún no está lista. Así que el procesador hace una predicción de salto y avanza con ella. Si acierta no se pierde nada. Si falla, el trabajo hecho por el camino equivocado se tira y la cadena reinicia, lo que cuesta unos 15 ciclos en un núcleo moderno, tanto como una visita a la caché L2. Escribimos el resultado de un salto como True cuando se toma. En un bucle de trading un salto es, por ejemplo, if daily_return > 0, y el flujo de sus resultados es simplemente un True o False por día.

main.pyPython
def taken_sequence(values, threshold):    return [bool(value > threshold) for value in values]def always_taken_mispredicts(outcomes):    return sum(1 for outcome in outcomes if not outcome)returns = [0.004, -0.012, 0.007, 0.001, -0.003, 0.009]outcomes = taken_sequence(returns, 0)print(outcomes)print(always_taken_mispredicts(outcomes), "misses out of", len(outcomes))
[True, False, True, True, False, True]
2 misses out of 6

De seis días, cuatro tuvieron un rendimiento positivo: el salto se toma cuatro veces y no se toma dos. El predictor más simple, siempre tomado, falla exactamente en los dos resultados False. Acierta tantas veces como se toma el salto e ignora el historial.

02

Predictores que recuerdan

Los procesadores reales usan el historial. El predictor de 1 bit recuerda lo que hizo el salto la última vez y adivina lo mismo. Es bueno cuando los resultados vienen en rachas, pero paga dos veces cada resultado suelto: una cuando llega y otra cuando la racha se reanuda. El predictor de 2 bits lo arregla con un contador de 0 a 3 que se detiene en ambos extremos. Adivina «tomado» en 2 y 3, avanza un paso hacia cada resultado y por eso necesita dos resultados seguidos en contra para cambiar de opinión. Empezamos la apuesta de 1 bit en «no tomado» y el contador en 1.

main.pyPython
def one_bit_mispredicts(outcomes):    guess = False    wrong = 0    for outcome in outcomes:        if guess != outcome:            wrong += 1        guess = outcome    return wrongdef two_bit_mispredicts(outcomes):    counter = 1    wrong = 0    for outcome in outcomes:        if (counter >= 2) != outcome:            wrong += 1        counter = min(3, counter + 1) if outcome else max(0, counter - 1)1    return wrongruns = [True] * 4 + [False] * 4 + [True] * 4stray = [True] * 6 + [False] + [True] * 6for name, stream in (("runs", runs), ("one stray", stray)):    print(name, "| 1-bit", one_bit_mispredicts(stream), "| 2-bit", two_bit_mispredicts(stream))
runs | 1-bit 3 | 2-bit 5
one stray | 1-bit 3 | 2-bit 2
  1. 1El contador avanza un paso hacia el resultado y se detiene en 0 y 3: eso lo hace saturante.

En las rachas de cuatro, ambos predictores fallan tres veces (la primera apuesta y los dos giros). Con un solo False suelto entre resultados tomados, el de 1 bit falla tres veces y el de 2 bits solo una, porque el contador baja de 3 a 2 y aún adivina «tomado». Por eso los procesadores reales prefieren contadores. Tenga presente el otro lado: cuando los resultados no tienen patrón, ningún predictor puede hacerlo mejor que adivinar la respuesta más frecuente.

La predicción depende de los datos, no del código

El mismo if es barato con datos predecibles y caro con datos aleatorios. No reescriba un salto para hacerlo «más rápido» antes de saber cómo son sus resultados en los datos reales; mida primero los fallos.

03

Cuánto cuesta un fallo y por qué ordenar ayuda

Para convertir los fallos en tiempo los sumamos: cada salto cuesta un ciclo base y cada fallo añade una penalización de 15. Ahora el experimento que da nombre a esta lección: tomemos los primeros 4000 rendimientos diarios de SPY y ejecutemos el salto return > 0 dos veces, sobre los rendimientos en orden de fechas y sobre los mismos rendimientos ordenados. Ordenar no cambia nada de los números, solo su orden, pero convierte los resultados de un cara o cruz en dos rachas largas: todos los False primero y todos los True después.

main.pyPython
def cycles(n_branches, mispredicts, penalty=PENALTY, base=BASE_CYCLES):    return base * n_branches + penalty * mispredictsdef sorted_stream(values, threshold):    return taken_sequence(sorted(values), threshold)returns = [float(r) for r in load_bars()["close"].pct_change().dropna()][:4000]for name, stream in (("unsorted", taken_sequence(returns, 0)), ("sorted", sorted_stream(returns, 0))):    wrong = two_bit_mispredicts(stream)    print(name, "| always", always_taken_mispredicts(stream), "| 1-bit", one_bit_mispredicts(stream), "| 2-bit", wrong, "|", cycles(len(stream), wrong), "cycles")
unsorted | always 1800 | 1-bit 2035 | 2-bit 1950 | 33250 cycles
sorted | always 1800 | 1-bit 1 | 2-bit 2 | 4030 cycles

En orden de fechas, 2200 de los 4000 rendimientos son positivos: el salto se toma el 55 % de las veces y pasa de una respuesta a otra casi sin patrón. Siempre tomado falla 1800 veces, el de 1 bit 2035 y el de 2 bits 1950. Los predictores con memoria son incluso peores que la apuesta simple, porque siguen cada cambio. Ordenados, los resultados son 1800 False seguidos de 2200 True, y el de 2 bits falla solo 2 veces. El coste baja de 33 250 a 4030 ciclos, 8,3 veces menos, con exactamente los mismos datos y el mismo bucle.

DefiniciónPenalización por fallo de predicción

Los ciclos perdidos cuando el procesador se equivoca en un salto: descarta el trabajo hecho por el camino equivocado y vuelve a llenar la cadena, unos 15 ciclos en un núcleo moderno.

El mismo truco en NumPy y en C++

Una comparación vectorizada como returns > 0 no tiene un salto por elemento, así que no sufre esto en absoluto. En C++ un bucle con un if sobre datos aleatorios es el caso clásico en el que ordenar primero los datos, o escribir la condición como aritmética, lo hace varias veces más rápido.

Qué dicen los datos reales4000 rendimientos diarios de SPY, salto return > 0, penalización de 15 ciclos
Siempre tomado, orden de fechas1,800fallos de 4000
2 bits, orden de fechas1,950fallos, peor que siempre tomado
2 bits, ordenado2fallos: una racha y luego la otra
Ganancia de ordenar8.3×menos ciclos (33 250 → 4030)
Ciclos del predictor de 2 bits
Orden de fechas33250
Ordenado4030
04

Lo que va a construir

El laboratorio sigue la lección: los resultados de un salto, los tres predictores, el coste en ciclos, el flujo ordenado y un estudio de los rendimientos de SPY en orden de fechas y ordenados.

  • Función · preguntaSe apoya en
  • taken_sequence(values, threshold)¿Qué hace el salto con cada valor?Aritmética simple
  • always_taken_mispredicts(outcomes)¿Cuántas veces falla «siempre tomado»?Aritmética simple
  • one_bit_mispredicts(outcomes)¿Y un predictor que recuerda el último resultado?Aritmética simple
  • two_bit_mispredicts(outcomes)¿Y un contador saturante?Aritmética simple
  • cycles(n_branches, mispredicts)¿Cuánto cuesta todo?Aritmética simple
  • sorted_stream(values, threshold)¿Y si los valores se ordenaran antes?taken_sequence
  • study(bars)¿Cuánto ahorra ordenar en SPY?sorted_streamtwo_bit_mispredictscycles

Ideas clave

  • El procesador adivina cada `if`. Una buena apuesta es gratis; una mala cuesta unos 15 ciclos, y un predictor con memoria solo supera a la apuesta simple cuando los resultados vienen en rachas.
  • Un contador supera a un solo bit. El contador de 2 bits ignora un resultado suelto donde el de 1 bit paga dos veces.
  • El orden cambia el coste. Los mismos 4000 rendimientos cuestan 33 250 ciclos en orden de fechas y 4030 ordenados, porque ordenar convierte un cara o cruz en dos rachas.
Comprobación rápida
Un salto tiene los resultados True ×4, False ×4, True ×4. El predictor de 1 bit empieza en «no tomado». ¿Cuántas veces falla?
Elige una respuesta.

Siguiente tema: el proyecto final reúne toda la habilidad. Se le da una tabla de ticks y una carga que la lee; la reorganiza en filas, columnas o bloques, cuenta los fallos de caché de cada disposición con la caché que construyó, los valora con el modelo de latencia del primer tema y demuestra qué disposición gana.

Lab: qué se califica

7 tareas · 100 pts
  • Resultados de un salto5 pts
  • Siempre tomado10 pts
  • Un predictor de 1 bit15 pts
  • Un contador saturante de 2 bits20 pts
  • El coste en ciclos10 pts
  • Valores ordenados10 pts
  • El estudio con la tabla de SPY30 pts
Explorador · lab-cl-12
▾ lab-cl-12
▾ data
…
bash · en el navegador
learner@quantcraft:~/lab-cl-12$
Python aún no cargadoPythonLn 1, Col 1Spaces: 4UTF-8

¿Restablecer este tema?

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