Pagar sobre el promedio
Un call estándar depende del precio de un solo día, el último. Una opción asiática depende del precio medio en un conjunto de fechas: un call asiático paga max(promedio - K, 0). Las empresas las usan para cubrir un coste que soportan de forma continua, por ejemplo una compra mensual de petróleo, y son difíciles de manipular al vencimiento. Son *dependientes de la trayectoria*, así que no hay una fórmula simple de Black–Scholes para el promedio aritmético, y la herramienta habitual es la simulación Monte Carlo.
import numpy as np
# Two simulated paths: the starting price, then monthly prices; the last column is expiry
paths = np.array([[100.0, 90.0, 110.0, 130.0],
[100.0, 70.0, 80.0, 90.0]])
average = paths[:, 1:].mean(axis=1) # the monitoring dates only, not the starting price
print(average) # [110. 80.]
print(np.maximum(average - 100.0, 0.0)) # Asian call payoffs: [10. 0.]
print(np.maximum(paths[:, -1] - 100.0, 0.0)) # European call payoffs on the same paths: [30. 0.]Simular las trayectorias
Las trayectorias salen del movimiento browniano geométrico, con la deriva neutral al riesgo: el tipo r sustituye a mu. Se saca una matriz de normales estándar con una fila por trayectoria y una columna por fecha de observación, se convierte en movimientos del logaritmo del precio y se hace una suma acumulada a lo largo de cada fila. Un solo array contiene entonces todas las trayectorias, y cada paso posterior (promedios, pagos) es una operación vectorizada por filas, sin bucle sobre trayectorias.
import numpy as np
def simulate_paths(s0, rate, sigma, t, steps, n_paths, rng):
dt = t / steps
z = rng.standard_normal((n_paths, steps))
log_moves = (rate - 0.5 * sigma ** 2) * dt + sigma * np.sqrt(dt) * z
return s0 * np.exp(np.hstack([np.zeros((n_paths, 1)), np.cumsum(log_moves, axis=1)]))
rng = np.random.default_rng(1)
paths = simulate_paths(100.0, 0.05, 0.2, 1.0, 12, 50000, rng) # monthly monitoring for a year
print(paths.shape) # (50000, 13)
print(round(float(paths[:, -1].mean()), 2), round(100 * np.exp(0.05), 2)) # the risk-neutral mean of the final priceEl precio y su barra de error
El precio de la opción es el pago medio descontado: exp(-r * T) * mean(payoffs). Un precio Monte Carlo es una estimación, así que informa siempre su error estándar, std(payoffs) / sqrt(número de trayectorias) (descontado), que dice cuánto puede alejarse el número de la verdad: cuatro veces más trayectorias lo reducen a la mitad. Un call asiático es más barato que uno europeo sobre la misma acción, porque el promedio suaviza el precio y reduce la volatilidad efectiva.
import numpy as np
def simulate_paths(s0, rate, sigma, t, steps, n_paths, rng):
dt = t / steps
z = rng.standard_normal((n_paths, steps))
log_moves = (rate - 0.5 * sigma ** 2) * dt + sigma * np.sqrt(dt) * z
return s0 * np.exp(np.hstack([np.zeros((n_paths, 1)), np.cumsum(log_moves, axis=1)]))
def asian_price(paths, strike, rate, t):
payoff = np.maximum(paths[:, 1:].mean(axis=1) - strike, 0.0)
discount = np.exp(-rate * t)
return discount * payoff.mean(), discount * payoff.std(ddof=1) / np.sqrt(len(payoff))
rng = np.random.default_rng(1)
paths = simulate_paths(100.0, 0.05, 0.2, 1.0, 12, 100000, rng)
price, error = asian_price(paths, 100.0, 0.05, 1.0)
european = np.exp(-0.05) * np.maximum(paths[:, -1] - 100.0, 0.0).mean()
print(round(float(price), 3), "+/-", round(float(error), 3))
print(round(float(european), 3), "European call on the same paths")# Geometric and arithmetic averages of the same monitoring prices
import numpy as np
prices = np.array([90.0, 110.0, 130.0])
arithmetic = prices.mean()
geometric = np.exp(np.log(prices).mean())
print(round(float(arithmetic), 3), round(float(geometric), 3)) # 110.0 108.774: the geometric average is never larger- Una opción asiática paga sobre el promedio de las fechas de observación:
max(promedio - K, 0); depende de la trayectoria, así que no hay fórmula simple y Monte Carlo es la herramienta. - Simule todas las trayectorias en un array (filas = trayectorias, columnas = fechas) con la deriva neutral al riesgo; promedios y pagos se vectorizan por filas.
- Precio =
exp(-r * T) * mean(payoffs)con error estándarstd / sqrt(paths); el promedio lo abarata frente a un call europeo, y la versión geométrica es una variable de control.
A continuación: las opciones con barrera, que nacen o mueren cuando el precio toca un nivel.
Lab: qué se califica
- Promediar a lo largo de cada trayectoria con
average_price25 pts - Probar el promedio geométrico con
geometric_average25 pts - Pagar sobre el promedio con
asian_call_payoff25 pts - Valorarla, con barra de error, con
asian_price25 pts