El primer paso es definir a lo que nos referimos con optimizar un portafolio de inversión. Para esto tenemos que reconocer que existe un tradeoff, un intercambio entre el riesgo y los retornos de inversión. También tenemos que recordar que existen diferentes perfiles de riesgo, pero en general vamos a adoptar un perfil de aversión al riesgo: para aceptar un nivel de riesgo mayor, los retornos deben de ser también mayores.
Por esta razón, como inversionistas nos interesa crear portafolios eficientes. Un portafolio eficiente es aquel que genera el menor nivel de riesgo para un cierto nivel de rendimiento y simultáneamente genera el mayor nivel de rendimientos para un cierto nivel de riesgo.
Llamamos a la colección de los portafolios eficientes la frontera eficiente. La frontera eficiente contiene un número infinito de portafolios eficientes y cada uno representa un nivel diferente de intercambio entre riesgo y rendimientos.
El modelo de Markowitz
En 1952 Harry Markowitz publicó un modelo matemático que, además de darle el pase a ser ganador de un premio Nobel, mostraba cómo distribuir un capital inicial en una colección de activos riesgosos para crear un portafolio eficiente. Es decir, uno con el menor riesgo dado un retorno esperado o que tenga el mayor retorno esperado dado un nivel de riesgo. En este post, mostraremos un poco de las matemáticas detrás de este modelo.
Comenzamos con un modelo sencillo. Asume que hoy t_0 usamos un capital inicial

para crear un portafolio distribuyendo el capital entre N diferentes activos que ya seleccionamos previamente. El modelo de Markowitz nos dice cómo distribuir el capital inicial entre diferentes activos. Podría ser, por ejemplo, que algunos de los activos reciban financiamiento cero, si el modelo así nos lo indica. En tal caso decimos que no tenemos posición con ese activo particular.
Supón ahora que el portafolio se debe de mantener por un periodo [t_0,t_f] en el que el portafolio no sufrirá ningún cambio.
Podemos entonces invertir una proporción w_i del capital inicial en el activo i. El total de las proporciones tiene que cumplir

Naturalmente,

Si S_i(t_0) es el precio del activo i en el momento inicial, entonces el monto de acciones que compramos de este activo es

El valor inicial del portafolio lo podemos entonces expresar por la siguiente igualdad

Un ejemplo en Python
Como primer paso, tenemos que extraer los datos. Para esto haremos uso del módulo pandas_datareader, que nos ayudará a extraer la información de Yahoo Finance de una manera extremadamente simple:
import pandas_datareader.data as getData Para extraer un activo, por ejemplo Apple:
aapl = getData.DataReader("AAPL", "yahoo") aapl.head() Esto nos muestra los precios de apertura, cierre, máximos y mínimos. Para nuestro ejemplo simplemente usaremos los precios de cierre de cada día. Podríamos usar la función drop() para eliminar las columnas que no estamos usando, o simplemente seleccionar la columna de precios de cierre con aapl['Close']. De hecho, vamos a generar una base de datos con varios precios de activos.
Para extraer múltiples activos por su ticker al mismo tiempo, usaremos el siguiente código
tickers = ["AAPL","VXRT", "BTC", "OSTK", "COIN"] assets = [getData.DataReader(ticker, "yahoo") for ticker in tickers] assets[0]["Close"].tail() 
En la primera línea de código simplemente asignamos una lista con los tickers de los que queremos extraer la información. La segunda línea asigna una lista con las bases de datos para todos los tickers. En la tercera línea simplemente estoy pidiendo que python me muestre los precios de cierre del primer activo de esta lista (recuerda que python comienza a contar desde el cero). En este caso, son los últimos precios de cierre de Apple.
Nuestro siguiente paso es extraer las columnas con los precios de cierre de cada uno de los activos y los ponga en una sola base de datos.
assets = [assets[asset_number]["Close"] for asset_number in range(0,len(assets))]
stocks = pd.concat(assets, axis=1)
stocks.columns = tickers
stocks.tail() 
La primera línea genera una lista donde cada elemento es la base de datos únicamente con el precio de cierre de cada activo. En la segunda línea concatenamos la lista en una base de datos y en la tercera usamos la lista con los tickers para nombrar a las columnas. La última línea nos muestra el resultado de nuestra tabla.
Nuestro siguiente paso es normalizar los datos. Normalizar significa que todos los datos estén en la misma escala. Para lograrlo usaremos los retornos logarítmicos: el logaritmo de los retornos diarios.
import numpy as np
log_ret = np.log(stocks/stocks.shift(1))
log_ret.tail() # Esta línea muestra la base de datos con los retornos ¡Ahora viene lo más interesante! Haremos un ciclo for para generar diferentes combinaciones de las acciones y guardaremos el ratio de Sharpe. El tamaño de la operación depende de la velocidad de procesamiento con el que cuentes. Por ejemplo, unas 10,000 combinaciones parece un buen número. Tu puedes hacerlo con menos si este cálculo es demasiado pesado para tu computadora.
np.random.seed(42)
n_portfolios = 10000
all_weights = np.zeros((n_portfolios, len(stocks.columns))) retornos = np.zeros(n_portfolios)
volatilidades = np.zeros(n_portfolios)
sharpe = np.zeros(n_portfolios)
for portafolio in range(n_portfolios):
# Calcular las proporciones
weights = np.array(np.random.random(5))
# genera un portafolio con proporciones aleatorias
weights = weights/np.sum(weights)
# Incluimos el peso de este portafolio en el conjunto de portafolios
all_weights[portafolio,:] = weights
# ¿Cuál es el retorno esperado de este portafolio
retornos[portafolio] = np.sum((log_ret.mean() * weights *252))
# ¿Cuál es la volatilidad esperada?
volatilidades[portafolio] = np.sqrt(np.dot(weights.T, np.dot(log_ret.cov()*252, weights)))
# Ratio de Sharpe
sharpe[portafolio] = retornos[portafolio]/volatilidades[portafolio] En la primera parte de este código asignamos los vectores con retornos y volatilidades con ceros. Después comenzamos a generar las simulaciones. Para imprimir los resultados podemos usar
print('El mayor ratio de Sharpe es : {}'.format(sharpe.max())) print('Está ubicado en la posición {}'.format(sharpe.argmax())) 
Para identificar el portafolio 2498 que tiene el mayor ratio de Sharpe usamos
print(all_weights[2498,:])
# Además guardamos los retornos y volatilidad máximos para usarlos más adelante
maximo_retorno = retornos[sharpe.argmax()] maxima_volatilidad = volatilidades[sharpe.argmax()] 
¡Listo! Estos resultados nos indican que tenemos que dedicar un asombroso 57% de nuestro portafolio a ¡BITCOIN! Esto es por el tamaño gigantesco del rendimiento en los últimos días y meses. Interesante, ¿no? Sin embargo, de acuerdo a la teoría, hay un rango completo de portafolios que son eficientes. Esta frontera de portafolios eficientes la podemos visualizar usando el siguiente código.
import matplotlib.pyplot as plt
plt.figure(figsize = (12,8))
plt.scatter(volatilidades, retornos, c =sharpe, cmap = 'viridis')
plt.colorbar(label = "Ratio de Sharpe") plt.xlabel('Volatilidad') plt.ylabel('Retornos') plt.scatter(maxima_volatilidad, maximo_retorno, c = 'red', s = 50)
plt.show() 
Este ejemplo muestra un conjunto de portafolios con una forma de bala muy pronunciada. En la punta de la bala se encuentra nuestro portafolio óptimo. La frontera de portafolios eficientes puede alcanzarse a visualizar, pero vamos a crearla. Para esto necesitamos algunas funciones adicionales. La primera toma las ponderaciones y para cada ponderación regresa los retornos, la volatilidad y el ratio de Sharpe. La segunda nos ayuda a obtener el ratio de Sharpe negativo para algunas ponderaciones . La tercera sirve para verificar que la suma de las ponderaciones sea cero. Esto lo hace regresando 0 si esta condición se cumple.