Siguiendo los comentarios y las ediciones de la pregunta, intentaré mostrar cómo el El valor en riesgo condicional (también conocido como pérdida de cola esperada) puede ser minimizado para una cartera. Comenzamos con la implementación sugerida por Rockafellar/Uryasev:
@ARTICLE{,
author = {R. Tyrrell Rockafellar and Stanislav Uryasev},
title = {Optimization of Conditional Value-at-Risk},
journal = {Journal of Risk},
year = 2000,
volume = 2,
number = 3,
pages = {21--41},
doi = {10.21314/JOR.2000.038}
}
Su descripción del modelo como programa lineal (LP) se da después de la ecuación 17 en su documento. Nosotros comenzamos con un conjunto de escenarios, es decir, una muestra de posibles de posibles realizaciones de la rentabilidad. Obsérvese que en el documento Rockafellar/Uryasev trabajan con pérdidas, es decir, con rendimientos negativos. Por lo tanto, un cuantil "malo" es uno alto, por ejemplo el 90º. El cuantil elegido se denomina $\beta$ en el papel. Almacenamos los escenarios en una matriz $R$ . En tiene $n_{\mathrm{A}}$ columnas (una para cada activo) y $n_{\mathrm{S}}$ filas (una fila para cada escenario).
Las variables del modelo son las ponderaciones reales de la cartera pesos de la cartera $x$ más las variables auxiliares $u$ (uno para cada escenario), más el nivel de VaR $\alpha$ . El solucionador puede elegir el nivel de VaR y las ponderaciones de forma que el CVaR se minimice. La función objetivo sólo tiene variables auxiliares y el nivel de VaR en ella; las ponderaciones reales de la cartera entran en el modelo sólo a través de las restricciones.
Los pesos de la función objetivo son los siguientes:
\begin {array}{ccccc} \underbrace { \begin {matriz} \phantom {000}1 \phantom {000} \end {matrix}}_{ \alpha }& \underbrace { \begin {matriz}0 & \cdots & 0 \phantom {0} \end {matrix}}_{x} & \underbrace { \begin {matriz} \frac {1}{(1- \beta ) n_{ \mathrm {S}}} & \cdots & \frac {1}{(1- \beta ) n_{ \mathrm {S}}} \end {matrix}}_{u} \\ \end {array}
Es decir, un vector de un 1, seguido de $n_{\mathrm{A}}$ ceros, y luego $n_{\mathrm{S}}$ por una constante.
La matriz de restricciones tiene el siguiente aspecto:
\begin {array}{ccccccc} 0 & 1 & \cdots & 1 & 0 & 0 & \cdots & 0 & = &1 \\ 1 & r_{1,1} & \cdots & r_{1, n_ \mathrm {A}} & 1 & 0 & \cdots & 0 & \geq & 0 \\ 1 & r_{2,1} & \cdots & r_{2, n_ \mathrm {A}} & 0 & 1 & \cdots & 0 & \geq & 0 \\ \vdots \\ \underbrace { \phantom {00}1 \phantom {00}}_{ \alpha } & \underbrace { \phantom {00}r_{n_{ \mathrm {S}},1} \phantom {00}}_{x_1} & \cdots & \underbrace {r_{n_ \mathrm {S}, n_ \mathrm {A}}}_{x_{n_} \mathrm {A}}} & \underbrace {0}_{u_1} & \underbrace {0}_{u_2} & \cdots & \underbrace {1}_{u_{n_n_} \mathrm {S}}} & \geq & 0 \\ \end {array}
Obsérvese que la primera línea es la restricción presupuestaria presupuesto. Aparte de esa línea, la matriz consta de una columna de unos (para la variable VaR), la matriz de escenarios matriz $R$ y una matriz de identidad de dimensión $n_\mathrm{S}$ . Existen restricciones de no negatividad para todos los $x$ y $u$ pero no se muestran explícitamente explícitamente. Muchos solucionadores los aplican automáticamente.
Podemos probar una implementación en R. Cargamos los paquetes paquetes necesarios para los ejemplos.
library("Rglpk")
library("NMOF") ## https://github.com/enricoschumann/NMOF
library("neighbours") ## https://github.com/enricoschumann/neighbours
(Revelación: soy el encargado de mantener los paquetes NMOF
y neighbours
.)
Comenzamos con un pequeño conjunto de datos, para poder observar la función objetivo y la matriz de restricciones: sólo 3 activos y 10 escenarios.
ns <- 10 ## number of scenarios
na <- 3 ## number of assets
R <- randomReturns(na, ns, sd = 0.01) ## an array of size ns x na
b <- 0.8 ## beta in the original paper
La función objetivo da una ponderación nula a los x
.
f.obj <- c(alpha = 1,
x = rep(0, na),
u = 1/rep((1 - b)*ns, ns))
f.obj
## alpha x1 x2 x3 u1 u2 u3 ... u9 u10
## 1.0 0.0 0.0 0.0 0.5 0.5 0.5 ... 0.5 0.5
La matriz de restricciones gana una columna para cada escenario.
C <- cbind(1, R, diag(nrow(R)))
C <- rbind(c(alpha = 0, x = rep(1, na), u = rep(0, nrow(R))), C)
C
alpha x1 x2 x3 u1 u2 u3 u4 u5 u6 u7 u8 u9 u10
[1,] 0 1.000000 1.000000 1.00000 0 0 0 0 0 0 0 0 0 0
[2,] 1 0.000183 0.029174 0.00293 1 0 0 0 0 0 0 0 0 0
[3,] 1 -0.001776 -0.001673 0.00225 0 1 0 0 0 0 0 0 0 0
[4,] 1 -0.009948 -0.007892 0.01129 0 0 1 0 0 0 0 0 0 0
[5,] 1 0.008299 -0.005601 -0.00144 0 0 0 1 0 0 0 0 0 0
[6,] 1 0.005766 0.000521 -0.00940 0 0 0 0 1 0 0 0 0 0
[7,] 1 -0.017110 0.016782 -0.00122 0 0 0 0 0 1 0 0 0 0
[8,] 1 -0.008334 0.017317 0.00498 0 0 0 0 0 0 1 0 0 0
[9,] 1 -0.004077 -0.009600 0.01568 0 0 0 0 0 0 0 1 0 0
[10,] 1 -0.000532 -0.000201 0.00267 0 0 0 0 0 0 0 0 1 0
[11,] 1 -0.005090 0.002318 0.00368 0 0 0 0 0 0 0 0 0 1
Vamos a ejecutarlo en un modelo más grande.
ns <- 5000
na <- 20
R <- randomReturns(na, ns, sd = 0.01, rho = 0.5)
b <- 0.75
f.obj <- c(alpha = 1,
x = rep(0, na),
u = 1/rep(( 1 - b)*ns, ns))
C <- cbind(1, R, diag(nrow(R)))
C <- rbind(c(alpha = 0, x = rep(1, na), u = rep(0, nrow(R))), C)
const.dir <- c("==", rep(">=", nrow(C) - 1))
const.rhs <- c(1, rep(0, nrow(C) - 1))
sol.lp <- Rglpk_solve_LP(f.obj,
C,
const.dir,
rhs = const.rhs,
control = list(verbose = TRUE, presolve = TRUE))
## GLPK Simplex Optimizer, v4.65
## 5001 rows, 5021 columns, 110020 non-zeros
## ...
## OPTIMAL LP SOLUTION FOUND
Almacenamos los pesos resultantes en una variable lp.weights
.
lp.weights <- sol.lp$solution[2:(1+na)]
Ahora vamos a resolver el mismo modelo con una heurística. Podemos podemos utilizar uno de los métodos más sencillos, una Búsqueda local. Y también dejaré la implementación aquí simple (podría ser mejorada para ganar velocidad).
La búsqueda local es sencilla: partimos de una cartera, por ejemplo, una con pesos iguales para todos los activos. A continuación, evaluamos esta cartera. buena es. Así que escribimos una función objetivo que que, dados los datos, asigna una cartera a un número - el CVaR.
CVaR <- function(w, R, b) {
Rw <- R %*% w ## compute portfolio loss under scenarios
mean(Rw[Rw >= quantile(Rw, b)])
}
Y ahora ejecutamos la heurística: hará un bucle a través del espacio de posibles carteras y aceptará las carteras que son mejores que la actual, pero rechaza aquellas carteras que son peores. Para este bucle, necesitamos un segundo ingrediente (el primero era la función objetivo): la función de vecindad. La función de vecindad función de vecindad toma una cartera como entrada y devuelve una copia ligeramente modificada de esta cartera.
nb <- neighbourfun(0, 1,
type = "numeric",
stepsize = 0.05)
Para dar una idea, supongamos que tenemos una cartera formada por sólo tres activos, y que cada activo tiene un peso de un tercio. Entonces podríamos calcular los vecinos de la siguiente manera:
nb(rep(1/3, 3))
## 0.3122 0.3544 0.3333
nb(rep(1/3, 3))
## [1] 0.3272 0.3333 0.3394
Ahora que tenemos un objetivo y una función de vecindad, podemos ejecutar la búsqueda local.
sol.ls <- LSopt(CVaR,
list(x0 = rep(1/na, na),
neighbour = nb,
nI = 1000),
R = R, b = b)
Podemos comparar los valores de la función objetivo. Obsérvese que las definiciones de la función objetivo no son exactamente equivalentes, ya que el LP puede elegir el VaR y las pesos juntos, mientras que la Búsqueda Local sólo varía las ponderaciones y luego, en la función objetivo impone una forma de calcular el VaR.
CVaR(sol.ls$xbest, R, b)
CVaR(lp.weights, R, b)
## [1] 0.00946
## [1] 0.00955
Así que ambas implementaciones dan resultados muy similares.
En cuanto a la inestabilidad: Podemos ejecutar la heurística varias veces (siempre deberíamos hacerlo; véase http://enricoschumann.net/R/remarks.htm ). Vamos a ejecutarlo 20 veces.
sols.ls <- restartOpt(LSopt,
n = 20,
OF = CVaR,
list(x0 = rep(1/na, na),
neighbour = nb,
nI = 1000),
R = R, b = b)
summary(sapply(sols.ls, `[[`, "OFvalue"))
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.00946 0.00946 0.00946 0.00946 0.00946 0.00946
Así que obtenemos resultados muy similares en todas las ejecuciones.
Lo bueno de la heurística es que podemos fácilmente cambiarla. Supongamos que ya no queremos el CVaR; pero ahora preferimos minimizar un momento parcial, digamos. Entonces todo lo que tendríamos que hacer es escribir otra función objetivo objetivo.
PM <- function(w, R, exp = 2, ...) {
Rw <- R %*% w ## compute portfolio loss under scenarios
pm(Rw, xp = exp, lower = FALSE) ## we work with losses
}
sol.ls <- LSopt(PM,
list(x0 = rep(1/na, na),
neighbour = nb,
nI = 1000),
R = R,
exp = 2)
2 votos
IIRC, CVaR se puede implementar como un programa lineal. Busque 'Optimization of conditional value-at-risk' de Rockafellar/Uryasev. Pero, en general, hay que diferenciar entre el modelo que se quiere resolver (en su caso, la función objetivo específica) y los métodos para resolver el modelo. Puede haber varios métodos, y algunos son más sencillos/rápidos/lo que sea que otros.
0 votos
@EnricoSchumann Sí, CVaR se puede implementar como un programa lineal, sin embargo no he sido capaz de hacerlo porque el modelo matemático subyacente es demasiado complejo para mí, por eso estaba buscando alternativas o aproximaciones. Busco encontrar el óptimo global de alguna función objetivo, de ahí que sugiriera la programación lineal, ya que sospecho que probablemente implique esto. Pero si no es así, otras soluciones también son bienvenidas.
0 votos
Se podría abordar esencialmente cualquier modelo con heurística. Estos métodos son muy sencillos, pero pueden requerir algo de programación. Hemos utilizado un único método, el Umbral de Aceptación, para resolver todo tipo de modelos. Véase, por ejemplo papers.ssrn.com/sol3/papers.cfm?abstract_id=1365167 para diferentes modelos; y papers.ssrn.com/sol3/papers.cfm?abstract_id=2551745 para el tipo de métodos.
0 votos
@EnricoSchumann Sí, la heurística también fue mi primera opción después de que LP me fallara, y más bien esperaba algunas soluciones exactas, debería haberlo mencionado. Las heurísticas son buenas, pero como mencionas en tu artículo, pueden ser bastante sensibles a los cambios, o en mi caso, devolver soluciones bastante diferentes si intento minimizar el riesgo y maximizar el beneficio a la vez y especialmente si aumento el número de variables (dimensiones). Echaré un vistazo a tus artículos, sin duda podré incluir algunos pasos en mi algoritmo heurístico, probablemente más sencillo.
1 votos
En general, la sensibilidad de las soluciones no es resultado del uso de la heurística, sino de las propiedades de los datos financieros. En papers.ssrn.com/sol3/papers.cfm?abstract_id=2698114 lo examinamos explícitamente; véanse las figuras 3 y 4. La aleatoriedad procedente de una heurística (que funcione correctamente) es insignificante en comparación con la aleatoriedad causada por cambios en la configuración de los datos, como el uso de diferentes longitudes de ventana o la adición de algunas observaciones a la muestra.
2 votos
Si algún experto en optimización tiene mucho tiempo libre hoy en día, un claro "cómo" escribir sobre la aplicación de Rockafellar / Uryasev CvaR optimización por LP sería muy útil, tal vez con algunos pseudocódigo o descripción paso a paso de la algo ;). Tal como se presenta ahora no es muy claro, y ha habido otras preguntas al respecto en este foro.
2 votos
@noob2: He redactado algunas notas sobre la aplicación del modelo CVaR: enricoschumann.net/notas/minimizar-var-condicional.html . Pero supongo que eso estaría fuera del tema de la pregunta. Pero si el OP lo considera on-topic, podría publicar esto aquí también.
0 votos
@EnricoSchumann Una explicación fácil de entender de cómo realizar la optimización del CVaR -incluso mejor con un ejemplo sencillo- sería la mejor opción, así queda en tema. Además si es en R puntos extra por ello.