Von einem Asset zu vielen
Bisher ging es um eine einzige Renditereihe. Ein Portfolio enthält viele Assets, und sein Risiko hängt nicht nur davon ab, wie riskant jedes einzelne ist, sondern davon, wie sie sich *gemeinsam* bewegen. Die Kovarianz zweier Assets ist das mittlere Produkt ihrer Abweichungen vom Mittelwert: positiv, wenn sie tendenziell zusammen steigen und fallen, negativ, wenn eines steigt, während das andere fällt. Bei n Assets gibt es n * n Kovarianzen, angeordnet als Kovarianzmatrix, die symmetrisch ist und die Varianzen auf der Diagonale hat.
import numpy as np
# Five days of returns for two assets: shape (days, assets)
returns = np.array([[0.01, 0.02], [-0.02, -0.01], [0.03, 0.01], [0.00, -0.02], [0.01, 0.03]])
centered = returns - returns.mean(axis=0) # subtract each column's mean
cov = centered.T @ centered / (len(returns) - 1) # (assets x days) @ (days x assets) -> (assets x assets)
print(np.round(cov, 6))
print(np.allclose(cov, np.cov(returns, rowvar=False))) # TrueDie empirische Kovarianzmatrix
Mit einer Renditetabelle der Form (days, assets) ist die ganze Matrix eine Zeile lineare Algebra. Man zieht von jeder Spalte den Mittelwert ab (axis=0, Mittel über die Tage) und multipliziert die Transponierte der zentrierten Tabelle mit ihr selbst: Eintrag (i, j) von centered.T @ centered ist die Summe über die Tage von x_i * x_j. Dann teilt man durch days - 1, wie bei einer Stichprobenvarianz. Das berechnen np.cov(returns, rowvar=False) und DataFrame.cov(); beachten Sie rowvar=False, denn np.cov erwartet die Assets in den Zeilen.
Korrelation: Kovarianz ohne Einheiten
Eine Kovarianz von 0.0004 sagt wenig: Sie hängt davon ab, wie volatil die beiden Assets sind. Teilt man durch das Produkt der beiden Standardabweichungen, verschwindet die Skala, und man erhält die Korrelation, immer zwischen -1 und 1: corr[i, j] = cov[i, j] / (std[i] * std[j]), mit std als Wurzel der Diagonale. Im Code baut np.outer(std, std) die Tabelle der Produkte, und eine Division skaliert die ganze Matrix, mit Einsen auf der Diagonale. Die Korrelation zeigt, wie eng zwei Assets zusammenlaufen; die Kovarianz, die die Skala behält, brauchen die Risikoformeln.
# From covariance to correlation and annualised volatilities
import numpy as np
returns = np.array([[0.01, 0.02], [-0.02, -0.01], [0.03, 0.01], [0.00, -0.02], [0.01, 0.03]])
cov = np.cov(returns, rowvar=False)
std = np.sqrt(np.diag(cov))
corr = cov / np.outer(std, std)
print(np.round(corr, 4))
print(np.round(np.sqrt(np.diag(cov) * 252), 4)) # daily variances scaled to a year: volatility grows with sqrt(252)Kann diese Matrix echt sein?
Eine beliebige Zahlentabelle ist keine Kovarianzmatrix. Sie muss symmetrisch und positiv semidefinit sein: Für beliebige Gewichte w ist w' C w die Varianz eines Portfolios und kann nicht negativ sein. Gleichwertig: Kein Eigenwert liegt unter null. Geschätzte Matrizen bestehen, von Hand bearbeitete oder ein Flickwerk aus Korrelationen verschiedener Quellen (etwa A~B = 0.9, B~C = 0.9, A~C = -0.9) nicht. Man prüft mit np.linalg.eigvalsh für symmetrische Matrizen, das reelle Eigenwerte liefert, mit kleiner Toleranz für Rundung.
# A validity check: symmetry and no negative eigenvalue
import numpy as np
def is_valid_cov(matrix, tol=1e-10):
symmetric = bool(np.all(np.abs(matrix - matrix.T) <= tol))
return symmetric and bool(np.linalg.eigvalsh(matrix).min() >= -tol)
good = np.array([[1.0, 0.5, 0.2], [0.5, 1.0, 0.3], [0.2, 0.3, 1.0]])
bad = np.array([[1.0, 0.9, -0.9], [0.9, 1.0, 0.9], [-0.9, 0.9, 1.0]])
print(is_valid_cov(good), np.round(np.linalg.eigvalsh(good), 3))
print(is_valid_cov(bad), np.round(np.linalg.eigvalsh(bad), 3))# Few days, many assets: the sample covariance matrix is singular
import numpy as np
rng = np.random.default_rng(0)
returns = rng.normal(0, 0.01, size=(5, 8)) # 5 days, 8 assets
cov = np.cov(returns, rowvar=False)
print(np.linalg.matrix_rank(cov), "of", cov.shape[0]) # 4 of 8: some portfolios seem to have zero variance- Die Kovarianzmatrix einer
(days, assets)-Tabelle istcentered.T @ centered / (days - 1): symmetrisch, Varianzen auf der Diagonale. - Die Korrelation ist
cov / np.outer(std, std): einheitenlos, zwischen -1 und 1, Einsen auf der Diagonale; Varianzen annualisiert man mitperiods, Volatilitäten mit dessen Wurzel. - Eine gültige Kovarianzmatrix ist symmetrisch ohne negativen Eigenwert (
np.linalg.eigvalsh); Schätzungen aus zu wenigen Tagen oder von Hand angepasste können das verletzen.
Als Nächstes: Portfoliorendite und -varianz, aus Gewichten und dieser Matrix das Risiko eines Portfolios machen.
Lab: Was bewertet wird
- Mit
cov_matrixdie Kovarianztabelle aufbauen25 Pkt. - Mit
corr_from_covauf Korrelationen skalieren25 Pkt. - Mit
annualised_volsdie Volatilität jedes Assets ablesen25 Pkt. - Mit
is_valid_covprüfen, ob sie echt sein kann25 Pkt.