Uma carteira é um vetor de pesos
Uma carteira é descrita por um peso por ativo: a fração do seu valor investida ali. Os pesos somam 1, e um peso negativo é uma posição vendida que financia as demais. Seu retorno num período é a média ponderada dos retornos dos ativos, de modo que o retorno esperado é um produto escalar w @ mu. O retorno é a metade fácil: é linear, sem interação entre ativos. O risco não.
import numpy as np
weights = np.array([0.6, 0.4]) # 60% in asset A, 40% in asset B
mu = np.array([0.08, 0.05]) # expected annual returns
print(weights.sum()) # 1.0
print(weights @ mu) # 0.068 = 0.6 * 0.08 + 0.4 * 0.05A variância é uma forma quadrática
Para dois ativos, a variância de w1 * A + w2 * B é w1**2 * var(A) + w2**2 * var(B) + 2 * w1 * w2 * cov(A, B): a variância própria de cada ativo mais um termo cruzado. Com n ativos há n * n termos, w_i * w_j * cov[i, j], e a soma inteira é a forma quadrática w @ cov @ w, uma linha em NumPy. Os termos cruzados são o essencial: quando dois ativos não são perfeitamente correlacionados, eles se compensam em parte, e a carteira é menos arriscada que a média ponderada dos riscos.
# The variance two ways: the explicit formula and the matrix form
import numpy as np
weights = np.array([0.6, 0.4])
cov = np.array([[0.04, 0.006], [0.006, 0.01]]) # variances 0.04 and 0.01 (volatilities 20% and 10%), covariance 0.006
explicit = weights[0] ** 2 * cov[0, 0] + weights[1] ** 2 * cov[1, 1] + 2 * weights[0] * weights[1] * cov[0, 1]
matrix_form = weights @ cov @ weights
print(round(explicit, 6), round(matrix_form, 6)) # 0.01888 0.01888
print(round(float(np.sqrt(matrix_form)), 4)) # 0.1374 a 13.7% volatility, below 0.6*20% + 0.4*10% = 16%# Diversification: 50/50 in two assets of 20% volatility, for several correlations
import numpy as np
weights = np.array([0.5, 0.5])
for rho in (1.0, 0.5, 0.0, -0.5):
cov = 0.2 * 0.2 * np.array([[1.0, rho], [rho, 1.0]])
print(rho, round(float(np.sqrt(weights @ cov @ weights)), 4)) # 1.0: 0.2, 0.5: 0.1732, 0.0: 0.1414, -0.5: 0.1De onde vem o risco?
Um gestor quer saber quais posições geram o risco. A contribuição ao risco do ativo i é w_i * (cov @ w)_i / sigma_p: seu peso vezes o quanto ele se move com a carteira inteira, dividido pela volatilidade sigma_p da carteira. As contribuições somam exatamente sigma_p, dividindo o total em partes. Um ativo de peso pequeno mas alta covariância com o resto pode contribuir bem mais do que seu peso sugere, e um hedge pode contribuir negativamente.
# Risk contributions of a three-asset portfolio: they add up to the volatility
import numpy as np
weights = np.array([0.5, 0.3, 0.2])
cov = np.array([[0.0400, 0.0060, 0.0100],
[0.0060, 0.0100, 0.0020],
[0.0100, 0.0020, 0.0225]])
variance = weights @ cov @ weights
contributions = weights * (cov @ weights) / np.sqrt(variance)
print(np.round(contributions, 4), round(float(contributions.sum()), 4))
print(round(float(np.sqrt(variance)), 4))mu e cov são estimativas, e a variância é a mais confiável das duas: os retornos esperados são muito ruidosos, as covariâncias bem menos. Pesos, mu e cov também devem partilhar o período: uma covariância diária com retornos anuais é um erro comum; escala-se a variância pelo número de períodos e a volatilidade pela sua raiz.- O retorno da carteira é linear:
w @ mu. A variância é a forma quadráticaw @ cov @ w, comn * ntermos e termos cruzados que fazem uma correlação abaixo de 1 reduzir o risco. - A volatilidade é
sqrt(variance * periods); a anualização deve corresponder ao período decov. - As contribuições ao risco
w_i * (cov @ w)_i / sigma_pdividem exatamente a volatilidade por ativo; um hedge pode contribuir negativamente.
A seguir: a carteira de variância mínima, escolher os pesos que tornam esse risco o menor possível.
Lab: o que é avaliado
- Somar o retorno esperado com
portfolio_return25 pts - Medir o risco com
portfolio_variance25 pts - Expressar a volatilidade com
portfolio_vol25 pts - Repartir o risco por ativo com
risk_contributions25 pts