Estoy tratando de estimar el riesgo de densidad neutra positivo a través de la convolución de la aproximación (introducido por Bondarenko 2002: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=375781). Actualmente estoy luchando para implementar un algoritmo computacional en Python solucionar el siguiente problema de optimización:
$$ \hat{f}(u) := \sum_{j}a_j \phi(u - z_j) $$
$$ \min_{un}\sum_{i=1}^n\left(P_i - \int_{-\infty}^{x_i}\left(\int_{-\infty}^y \hat{f}(u) du\derecho)dy\derecho)^2 \\ s.t. \sum_{j}a_j=1\\ \forall a_j\geq0 $$
donde: $P_i$ es el observado ponga el precio con la huelga de $x_i$, $\phi$ referirse a un reescalado distribución normal estándar y $z_j$ representa los puntos en un igualmente espaciado de cuadrícula entre la más pequeña y la más grande observado huelgas de $x$.
Es fácil que uno debe usar un estándar cuadrática programa para resolver el problema. Sin embargo, no sé cómo manejar el hecho de que el $a$'s están dentro de una función de $u$, que a su vez está dentro de una integral doble.
¿Alguien ya implementado positivo de convolución aproximación para estimar el riesgo de densidad neutra en Python?
De lo contrario, es posible que alguien me muestre cómo el código de un problema de optimización con una integral doble, como por ejemplo:
$$ \min_a\int_{-\infty}^{x}\left(\int_{-\infty}^y \hat{f}(u) du\derecho)dy \\ \hat{f}(u) := \sum_{j}a_j (u - z_j)^2 $$
Gracias por la ayuda!
EDITAR
Actualización: Gracias a los comentarios y la respuesta de Attack68. Yo era capaz de implementar el siguiente código:
import numpy as np
from scipy.optimize import minimize
from scipy.integrate import dblquad
from scipy.stats import norm
# Compute f_hat
def f(u, y, *args):
a = args[0]
z = args[1]
h = args[2]
j = len(a)
# print(np.sum(a * norm.pdf(np.tile(u, [j,1]).transpose(), z, h), axis=1))
return np.sum(a * norm.pdf(np.tile(u, [j,1]).transpose(), z, h), axis=1)
# Compute double integral
def DI(a, b, z, h):
# print(dblquad(f, -10000, b, lambda x: -10000, lambda x: x, args=(a, z, h))[0])
return dblquad(f, -np.inf, b, lambda x: -np.inf, lambda x: x, args=(a, z, h))[0]
def sum_squared_pricing_diff(a, P, X, z, h):
total = 0
for i in range(0, len(P)):
p = P[i]
x = X[i]
total += (p - DI(a, x, z, h)) ** 2
return total
# P is an array of vector put option prices
P = [0.249999283, 0.43750315, 0.572923413, 0.760408034, 0.94790493, 1.14584317,
1.458335038, 1.77083305, 2.624999786, 3.812499791, 5.377596753, 8.06065865,
10.74376984, 14.88873497, 19.88822895]
# X is the vector of the corresponding strikes of the put options
X = [560, 570, 575, 580, 585, 590, 595, 600, 605, 610, 615, 620, 625, 630, 635]
# z is the equally-spaced grid
z = np.linspace(0, 1000, 20)
# h arbitrarily chosen
h = 0.5
# initial guess of a
a_0 = np.ones(len(z)) / len(z)
constraints = ({'type': 'eq', 'fun': lambda a: 1 - np.sum(a)},)
bounds = (((0,None),)*len(z))
sol = minimize(sum_squared_pricing_diff, a_0, args=(P, X, z, h), method='SLSQP', constraints=constraints, bounds=bounds)
print(sol)
que devuelve la siguiente advertencia y tiene dificultad para convergen:
IntegrationWarning: The maximum number of subdivisions (50) has been achieved.
If increasing the limit yields no improvement it is advised to analyze
the integrand in order to determine the difficulties. If the position of a
local difficulty can be determined (singularity, discontinuity) one will probably gain from splitting up the interval and calling the integrator on the subranges. Perhaps a special-purpose integrator should be used.
warnings.warn(msg, IntegrationWarning)
Tras un desbordamiento de pila post voy a tratar de utilizar nquad en lugar de dblquad. Voy a publicar más progreso.
EDIT 2 Actualización: el Uso de las pistas de Attack68 la segunda respuesta, que fue capaz de estimar la RND en una "eficaz" de forma (probablemente puede ser mejorado):
import pandas as pd
import numpy as np
from scipy.optimize import minimize
from scipy.stats import norm
import matplotlib.pyplot as plt
import math
###############################################################################
# Define required functions to describe the optimization problem
###############################################################################
# Double integral transformed
def sum_j_aK(a, x, z, h):
j = len(a)
loc = z
scale = h
x_normalized = (np.ones(j)*x - loc) / scale
K_j = (x_normalized*norm.cdf(x_normalized) + np.exp(-0.5*x_normalized**2)/((2*np.pi)**0.5)) * scale
return np.sum(a*K_j)
# Minimization problem
def sum_squared_pricing_diff(a, P, X, z, h):
total = 0
for i in range(0, len(P)):
p = P[i]
x = X[i]
total += abs(p - sum_j_aK(a, x, z, h))
return total
###############################################################################
# Input required to solve the optimization problem
###############################################################################
# P is an array of vector put option prices
P = [0.249999283, 0.43750315, 0.572923413, 0.760408034, 0.94790493, 1.14584317,
1.458335038, 1.77083305, 2.624999786, 3.812499791, 5.377596753, 8.06065865,
10.74376984, 14.88873497, 19.88822895]
# X is the vector of the corresponding strikes of the put options
X = [560, 570, 575, 580, 585, 590, 595, 600, 605, 610, 615, 620, 625, 630, 635]
# h and j can be chosen arbitrarily
h = 4 # the higher h the smoother the estimated risk-neutral density
j = 50 # the higher the slower the optimization process
###############################################################################
# Solving the optimization problem
###############################################################################
# z is the equally-spaced grid
z = np.linspace((int(math.floor(min(X) / 100.0)) * 100), (int(math.ceil(max(X) / 100.0)) * 100), num=j)
# initial guess of a
a_0 = np.ones(j) / j
# The a vector has to sum up to 1
constraints = ({'type': 'eq', 'fun': lambda a: 1 - np.sum(a)},)
# Each a has to be larger or equal than 0
bounds = (((0,None),)*j)
sol = minimize(sum_squared_pricing_diff, a_0, args=(P, X, z, h), method='SLSQP', constraints=constraints, bounds=bounds)
print(sol)
###############################################################################
# Visualize obtained risk-neutral density (rnd)
###############################################################################
n = 500
a_sol = sol.x
s = np.linspace(min(X)*0.8, max(X)*1.2, num=n)
rnd = pd.DataFrame(np.sum(a_sol * norm.pdf(np.tile(s, [len(a_sol),1]).transpose(), z, h), axis=1))
rnd.index = s
plt.figure()
plt.plot(rnd)