リターンとリスクを1つの数に
最小分散はリターンを無視します。平均・分散最適化(マーコウィッツ)は両方を使います。投資家は高い期待リターン w @ mu と低い分散 w @ cov @ w を望みます。両者を天秤にかける簡単な方法が効用 w @ mu - 0.5 * lambda * w @ cov @ w で、lambda はリスク回避度です。大きいほど、分散1単位のコストが重くなります。同じ見通しでも lambda が違う2人の投資家は違うポートフォリオを選び、それぞれの最良のポートフォリオは滑らかな凹関数の最大点です。
import numpy as np
mu = np.array([0.08, 0.05, 0.10]) # expected annual returns
cov = np.array([[0.0400, 0.0060, 0.0100],
[0.0060, 0.0100, 0.0020],
[0.0100, 0.0020, 0.0225]])
def utility(weights, mu, cov, risk_aversion):
return weights @ mu - 0.5 * risk_aversion * weights @ cov @ weights
equal = np.full(3, 1 / 3)
print(round(float(equal @ mu), 4), round(float(equal @ cov @ equal), 4)) # 0.0767 0.0121
print(round(float(utility(equal, mu, cov, 4.0)), 4)) # 0.0526与えられたリスク回避度での最良ポジション
予算制約がない場合(残りの資金は無リスク金利を稼ぎ、ここでは超過リターンでゼロとみなす)、最良のポジションは勾配 mu - lambda * cov @ w がゼロになる点で、w = cov^-1 * mu / lambda です。コードでは np.linalg.solve(cov, mu) / lambda です。常識どおりにスケールし、リスク回避度が2倍ならポジションは半分です。ウェイトの合計は必要なだけの値になり、残りの 1 - w.sum() は現金です。
# The best position for several risk aversions: more aversion, smaller bets
import numpy as np
mu = np.array([0.08, 0.05, 0.10])
cov = np.array([[0.0400, 0.0060, 0.0100],
[0.0060, 0.0100, 0.0020],
[0.0100, 0.0020, 0.0225]])
for aversion in (5.0, 10.0, 20.0):
w = np.linalg.solve(cov, mu) / aversion
print(aversion, np.round(w, 3), "invested", round(float(w.sum()), 3))接点ポートフォリオ
無リスク資産があるとき、すべての投資家は同じリスク資産の組み合わせを、量だけ変えて持ちます。その組み合わせはシャープ・レシオ (w @ mu - rf) / sqrt(w @ cov @ w) が最大のもの、すなわち接点ポートフォリオで、w は cov^-1 * (mu - rf) に比例し、合計1に直したものです。慎重な投資家は資金の一部だけをそこに入れて残りを現金にし、大胆な投資家は借り入れてもっと持ちます。どれだけのリスクを取るかと、どのリスク資産を持つかは別の問題です。
# The tangency portfolio: no other mix of the three assets has a higher Sharpe ratio
import numpy as np
mu = np.array([0.08, 0.05, 0.10])
cov = np.array([[0.0400, 0.0060, 0.0100],
[0.0060, 0.0100, 0.0020],
[0.0100, 0.0020, 0.0225]])
rf = 0.02
x = np.linalg.solve(cov, mu - rf)
best = x / x.sum()
def sharpe(w):
return (w @ mu - rf) / np.sqrt(w @ cov @ w)
rng = np.random.default_rng(1)
trials = rng.normal(size=(2000, 3))
trials = trials / trials.sum(axis=1, keepdims=True)
print(np.round(best, 4), round(float(sharpe(best)), 4))
print(round(max(float(sharpe(w)) for w in trials), 4)) # the best of 2000 random mixes comes close, never above目標リターンを達成する
別の問い方として、合計が1で期待リターンが決まっているポートフォリオのうち、分散が最小のものはどれでしょうか。制約が2つあるので、解は解いた2つのベクトル s1 = cov^-1 * 1 と sm = cov^-1 * mu の組み合わせで、係数は市場を要約する4つの数 a, b, c, d から決まります。目標リターンごとに1つのポートフォリオが得られ、それらすべてのリスクを描いたものが次のトピックの効率的フロンティアです。
# Minimum-variance portfolios for three target returns
import numpy as np
mu = np.array([0.08, 0.05, 0.10])
cov = np.array([[0.0400, 0.0060, 0.0100],
[0.0060, 0.0100, 0.0020],
[0.0100, 0.0020, 0.0225]])
ones = np.ones(3)
s1, sm = np.linalg.solve(cov, ones), np.linalg.solve(cov, mu)
a, b, c = ones @ s1, ones @ sm, mu @ sm
d = a * c - b ** 2
for target in (0.06, 0.08, 0.09):
w = ((c - b * target) * s1 + (a * target - b) * sm) / d
print(target, np.round(w, 3), round(float(w @ mu), 4), round(float(np.sqrt(w @ cov @ w)), 4))mu に非常に敏感で、1つの期待リターンがわずかに変わるだけで、ポートフォリオがある資産から別の資産へ振れることがあります。オプティマイザは入力を完全に信じるので、過大評価されたリターンは大きなポジションになります。実務ではリターンを縮小し、ウェイトに上限を設け、入力は過去の生の平均ではなくモデルやビューから得ます。平均・分散は考えるための良い道具ですが、盲目的に使うと危険です。- 効用は
w @ mu - 0.5 * lambda * w @ cov @ w。予算なしの最良ポジションはsolve(cov, mu) / lambdaで、lambdaを2倍にすると半分になる - 無リスク資産があるとき最良のリスク資産の組み合わせは接点ポートフォリオ
solve(cov, mu - rf)を合計1に直したもので、シャープ・レシオが最大 - 目標リターンに対する最小分散のウェイトは
solve(cov, ones)とsolve(cov, mu)の組み合わせ。結果の質は推定したmuの質で決まる
次は効率的フロンティアです。リターンの水準ごとに最良のリスクを結んだ曲線です。
ラボ:採点される内容
utilityでポートフォリオを採点する25点utility_weightsで最良のポジションを見つける25点tangency_weightsでシャープ・レシオを最大化する25点target_return_weightsでリターン目標を達成する25点