Simulación de Monte Carlo en finanzas con Python, paso a paso | Quantum Club

Etapa 2 de 4 · Fundamentos en Python

Simulación de Monte Carlo en finanzas con Python

Qué es la simulación de Monte Carlo y cómo usarla en finanzas: el movimiento browniano geométrico, simular miles de trayectorias de un precio en Python, probabilidades de pérdida y valorar una opción.

En este artículo
  1. Qué es el método de Monte Carlo
  2. El modelo: movimiento browniano geométrico
  3. Simular trayectorias en Python
  4. Responder preguntas con las simulaciones
  5. Valorar una opción con Monte Carlo
  6. La precisión de Monte Carlo
  7. Limitaciones que debes conocer
  8. Resumen

En física de partículas usamos Monte Carlo a diario: cuando un problema tiene demasiadas piezas aleatorias para resolverlo con papel y lápiz, lo simulamos miles de veces y miramos la estadística del resultado. En finanzas pasa exactamente lo mismo, y por eso es una de las herramientas más útiles que puede aprender un quant.

En este artículo vas a entender la idea, el modelo de precio que hay detrás y a programarlo en Python para responder preguntas reales: ¿qué probabilidad hay de perder más de un 20% en un año? o ¿cuánto vale una opción?

Qué es el método de Monte Carlo

La idea cabe en una línea: si no sabes calcular algo, simúlalo muchas veces y promedia. La ley de los grandes números garantiza que el promedio de muchas simulaciones se acerca al valor verdadero.

Un ejemplo sin finanzas: ¿cuál es la probabilidad de sacar al menos un seis en cuatro tiradas de un dado? Se puede calcular (\( 1 - (5/6)^4 \approx 0{,}518 \)), pero también simular:

Python
import numpy as np

rng = np.random.default_rng(42)
tiradas = rng.integers(1, 7, size=(1_000_000, 4))
prob = (tiradas == 6).any(axis=1).mean()
print(f"Simulado: {prob:.4f}   Exacto: {1 - (5/6)**4:.4f}")

Con un millón de simulaciones el error es de unas milésimas. En finanzas el «dado» es el precio de un activo, y lo que simulamos son sus posibles trayectorias futuras.

El modelo: movimiento browniano geométrico

Para simular un precio necesitamos un modelo de cómo se mueve. El estándar, el mismo que usa Black-Scholes, es el movimiento browniano geométrico (GBM):

\[ dS_t = \mu S_t\,dt + \sigma S_t\,dW_t \]

Se lee así: en cada instante el precio tiene una tendencia \( \mu \) (la deriva) y un componente aleatorio de tamaño \( \sigma \) (la volatilidad), ambos proporcionales al precio. Por eso es «geométrico»: se mueve en porcentajes, no en euros, y el precio nunca puede ser negativo.

La ecuación tiene solución exacta, que es la que usaremos para simular:

\[ S_{t+\Delta t} = S_t \exp\!\left[\left(\mu - \tfrac{1}{2}\sigma^2\right)\Delta t + \sigma\sqrt{\Delta t}\,Z\right], \qquad Z \sim \mathcal{N}(0,1) \]

¿De dónde sale el \( -\tfrac12\sigma^2 \)? Del lema de Itô: al pasar a logaritmos, la volatilidad «resta» rentabilidad. Intuitivamente, perder un 50% y ganar después un 50% no te deja igual, te deja en un 75%. Cuanta más volatilidad, más se nota ese efecto.

Simular trayectorias en Python

Simulemos una acción que hoy vale 100 €, con una rentabilidad esperada del 8% anual y una volatilidad del 25%, durante un año de 252 sesiones. En lugar de un bucle, generamos todos los números aleatorios de golpe con numpy, que es cientos de veces más rápido:

Python
S0, mu, sigma = 100.0, 0.08, 0.25    # precio inicial, deriva y volatilidad anuales
T, pasos, n_sim = 1.0, 252, 10_000   # horizonte (años), pasos y simulaciones
dt = T / pasos

rng = np.random.default_rng(7)
Z = rng.standard_normal((n_sim, pasos))
incrementos = (mu - 0.5 * sigma**2) * dt + sigma * np.sqrt(dt) * Z
log_trayectorias = np.cumsum(incrementos, axis=1)
S = S0 * np.exp(np.hstack([np.zeros((n_sim, 1)), log_trayectorias]))

print(S.shape)          # (10000, 253): 10.000 caminos de 253 precios

Cada fila de S es un futuro posible. Dibujemos unos cuantos:

hoy 3 meses 6 meses 9 meses 1 año 50 75 100 125 150 175 200 Precio (€) percentil 95 mediana percentil 5 90 % de los escenarios Mediana
60 de las 2.000 trayectorias simuladas (gris), la mediana y la banda donde cae el 90 % de los escenarios. La banda se abre con el tiempo: la incertidumbre crece con la raíz del plazo.
Python
import matplotlib.pyplot as plt

plt.figure(figsize=(10, 5))
plt.plot(S[:50].T, lw=0.8, alpha=0.7)
plt.title("50 trayectorias simuladas (GBM)")
plt.xlabel("Días"); plt.ylabel("Precio (€)")
plt.show()

Responder preguntas con las simulaciones

Aquí está la potencia del método: una vez tienes los 10.000 futuros, cualquier pregunta se responde contando.

Python
final = S[:, -1]

print(f"Precio medio a 1 año:          {final.mean():.2f} €")
print(f"Mediana:                       {np.median(final):.2f} €")
print(f"Prob. de acabar en pérdidas:   {(final < S0).mean():.1%}")
print(f"Prob. de perder más de un 20%: {(final < 0.8 * S0).mean():.1%}")
print(f"Intervalo del 90%:             {np.percentile(final, 5):.1f} € – {np.percentile(final, 95):.1f} €")
50 100 150 200 250 Precio a un año (€) Mediana: 105 € Media: 108 €
Distribución del precio a un año en 20.000 simulaciones. Es asimétrica (lognormal): la cola derecha tira de la media por encima de la mediana.

Fíjate en que la media es mayor que la mediana: la distribución del precio final es lognormal, con una cola larga hacia arriba. Unos pocos caminos muy buenos tiran de la media, pero el caso «típico» es más modesto. Es una lección importante para cualquier inversor.

También podemos mirar la trayectoria entera, no solo el final. Por ejemplo, la probabilidad de que en algún momento del año el precio caiga por debajo de 80 €, aunque luego se recupere:

Python
toca_80 = (S.min(axis=1) < 80).mean()
print(f"Prob. de tocar 80 € en algún momento: {toca_80:.1%}")

Esta probabilidad es mucho mayor que la de acabar por debajo de 80 €. Preguntas así (dependientes del camino) son difíciles de resolver con fórmulas y triviales con Monte Carlo.

Valorar una opción con Monte Carlo

Una opción call europea da derecho a comprar la acción a un precio fijo \( K \) en la fecha \( T \). Si al vencimiento la acción vale \( S_T \), la opción paga \( \max(S_T - K, 0) \).

La teoría de valoración dice que su precio es el valor esperado del pago, descontado, pero calculado en el llamado mundo neutral al riesgo: se sustituye la deriva \( \mu \) por el tipo sin riesgo \( r \). Veremos por qué en el siguiente artículo; de momento, apliquémoslo:

\[ C = e^{-rT}\,\mathbb{E}\!\left[\max(S_T - K, 0)\right] \]

Python
def call_montecarlo(S0, K, T, r, sigma, n=1_000_000, seed=0):
    rng = np.random.default_rng(seed)
    Z = rng.standard_normal(n)
    ST = S0 * np.exp((r - 0.5 * sigma**2) * T + sigma * np.sqrt(T) * Z)
    pagos = np.maximum(ST - K, 0)
    precio = np.exp(-r * T) * pagos.mean()
    error = np.exp(-r * T) * pagos.std() / np.sqrt(n)
    return precio, error

precio, error = call_montecarlo(S0=100, K=105, T=1, r=0.03, sigma=0.25)
print(f"Call: {precio:.3f} € ± {1.96 * error:.3f} (95%)")

Como solo nos importa el precio final, no hace falta simular la trayectoria día a día: un único paso basta. El resultado debería salir en torno a 9,1 €, y en el artículo de Black-Scholes comprobarás que coincide con la fórmula cerrada.

La precisión de Monte Carlo

El error estándar de un estimador de Monte Carlo decrece como \( 1/\sqrt{N} \). Eso tiene una consecuencia práctica muy clara: para ganar un decimal de precisión necesitas 100 veces más simulaciones.

Python
for n in [1_000, 10_000, 100_000, 1_000_000]:
    p, e = call_montecarlo(100, 105, 1, 0.03, 0.25, n=n)
    print(f"N = {n:>9,}   precio = {p:.4f}   error = {e:.4f}")
100 1.000 10.000 100.000 1.000.000 Número de simulaciones N 1 0 − 2 1 0 − 1 1 0 0 Error típico del precio (€) 100 veces más simulaciones, 10 veces menos error Pendiente teórica 1/√N Error medido (Monte Carlo)
Error del precio de la call frente al número de simulaciones, en escala logarítmica. Los puntos siguen la pendiente teórica 1/√N.

Por eso existen las técnicas de reducción de varianza. La más sencilla son las variables antitéticas: por cada número aleatorio \( Z \) se usa también \( -Z \). Las dos trayectorias están negativamente correlacionadas y sus errores se compensan en parte:

Python
def call_antitetica(S0, K, T, r, sigma, n=500_000, seed=0):
    rng = np.random.default_rng(seed)
    Z = rng.standard_normal(n)
    a = (r - 0.5 * sigma**2) * T
    b = sigma * np.sqrt(T)
    pagos = 0.5 * (np.maximum(S0 * np.exp(a + b * Z) - K, 0)
                   + np.maximum(S0 * np.exp(a - b * Z) - K, 0))
    return np.exp(-r * T) * pagos.mean(), np.exp(-r * T) * pagos.std() / np.sqrt(n)

p, e = call_antitetica(100, 105, 1, 0.03, 0.25)
print(f"Antitética: {p:.4f} ± {1.96 * e:.4f}")

Con el mismo número de números aleatorios, el error baja de forma apreciable. En los bancos se combinan varias técnicas de este tipo, porque cada simulación cuesta tiempo de cálculo.

Limitaciones que debes conocer

  • Monte Carlo es tan bueno como su modelo. El GBM supone volatilidad constante y rentabilidades normales. Los mercados reales tienen colas más gruesas y volatilidad que cambia con el tiempo (lo trata el modelo GARCH).
  • La deriva \( \mu \) es muy difícil de estimar. Con diez años de datos, el error en la rentabilidad media es enorme. Para valorar opciones no importa (se usa \( r \)), pero para planificar inversiones, sí.
  • Converge despacio. Para problemas con pocas dimensiones suele haber métodos numéricos más rápidos; Monte Carlo brilla cuando hay muchos activos o el pago depende del camino.

Resumen

  • Monte Carlo = simular muchos escenarios y hacer estadística del resultado.
  • El precio se modela con un movimiento browniano geométrico, que se simula con su solución exacta.
  • Con las trayectorias puedes calcular probabilidades de pérdida, intervalos y precios de opciones.
  • El error cae como \( 1/\sqrt{N} \); las variables antitéticas ayudan gratis.

Siguiente paso: entender por qué una opción se valora con el tipo sin riesgo y de dónde sale la fórmula más famosa de las finanzas en Black-Scholes explicado desde la intuición.

Quantum Pablo

Sobre Quantum Pablo

Físico de partículas y quant. Enseño finanzas cuantitativas en español desde la intuición y con código: más de 400 alumnos en más de 25 países se han formado en Quantum Club. También en YouTube e Instagram.

Etapa 2 de 4 · Fundamentos en Python

  1. 06Python para finanzas: rentabilidad, volatilidad, Sharpe y drawdown
  2. 07Simulación de Monte Carlo en finanzas con Python
  3. 08El modelo de Black-Scholes explicado desde la intuición (con Python)
  4. 09Value at Risk (VaR) y Expected Shortfall: los 3 métodos en Python
Ver el recorrido completo →