リターンの水準ごとのリスク
前のトピックでは、1つの目標リターンに対して分散が最小のウェイトを求めました。これをすべての目標について行い、横軸にボラティリティ、縦軸にリターンを取ると、点は曲線を描きます。これが効率的フロンティアです。これらの資産で作れるどのポートフォリオも、その曲線上か、右側の内部にあります。曲線の内側にあるポートフォリオは無駄が多く、同じリターンでリスクが小さい、あるいは同じリスクでリターンが大きい別のポートフォリオが存在します。マーコウィッツの洞察は、投資家は曲線だけを見ればよいというものでした。
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]])
def target_return_weights(mu, cov, target):
ones = np.ones(len(mu))
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
return ((c - b * target) * s1 + (a * target - b) * sm) / d
for target in (0.05, 0.06, 0.07, 0.08, 0.09, 0.10):
w = target_return_weights(mu, cov, target)
print(target, round(float(np.sqrt(w @ cov @ w)), 4))その形と先端
フロンティアは(リターン、分散)の平面では放物線で、ボラティリティを横軸に取れば双曲線です。目標リターン t での分散は (a * t**2 - 2 * b * t + c) / (a * c - b**2) で、前回と同じ4つの数 a, b, c から決まります。最低点が全体の最小分散ポートフォリオで、リターンは b / a です。これが曲線の先端で、2つ前のトピックの最小分散ポートフォリオです。先端から上の部分だけが効率的フロンティアで、下の枝は同じリスクでリターンが小さい鏡像です。
# The tip of the curve: the minimum-variance portfolio and its return
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)
x = np.linalg.solve(cov, ones)
w = x / x.sum()
print(np.round(w, 4), round(float(w @ mu), 4), round(float(np.sqrt(w @ cov @ w)), 4)) # the tip: return, volatility
def closed_form_vol(target):
s1, sm = x, np.linalg.solve(cov, mu)
a, b, c = ones @ s1, ones @ sm, mu @ sm
return float(np.sqrt((a * target ** 2 - 2 * b * target + c) / (a * c - b ** 2)))
for target in (0.05, 0.0642, 0.07):
print(target, round(closed_form_vol(target), 4))効率的な点と非効率な点
曲線上には同じリスクで2つのリターンが現れ、枝ごとに1つずつありますが、持つ価値があるのは高いほうだけです。したがって目標が効率的であるのは、最小分散ポートフォリオのリターン以上のときに限られます。判定は1回の比較ですが、選択肢のグラフにどの目標が属するかを決めます。フロンティア上で最良のバランスを探すことは、どの点を選ぶかの問題になり、よくある答えは無リスク金利を超えるリターンをリスク1単位あたり最も多く得られる点です。
# The point with the highest Sharpe ratio among candidate targets
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
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
targets = np.linspace(0.05, 0.10, 11)
vols = np.sqrt((a * targets ** 2 - 2 * b * targets + c) / d)
sharpe = (targets - rf) / vols
for t, v, s in zip(targets, vols, sharpe):
print(round(float(t), 3), round(float(v), 4), round(float(s), 4))
print("best target:", round(float(targets[np.argmax(sharpe)]), 3))# Efficient or not: compare a target with the return of the minimum-variance portfolio
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]])
x = np.linalg.solve(cov, np.ones(3))
tip = float((x / x.sum()) @ mu)
for target in (0.05, 0.06, 0.07, 0.08):
print(target, "efficient" if target >= tip else "inefficient")推定したフロンティアはぼやけた帯曲線は
mu と cov から描かれ、どちらも推定値なので、曲線自体も推定であり、しかも見栄えのよい推定です。オプティマイザは全ての組み合わせから、たまたま推定リターンが高くリスクが低く出たものを選んだからです。実際の結果は、見えている線の右下に落ちます。データを再標本化したり、標本の前半で描いたフロンティアを後半の成績と比べたりすれば、曲線のどれだけが運だったか分かります。フロンティアは約束ではなく、トレードオフの地図として扱いましょう。- 効率的フロンティアは、各目標リターンに対する最小のボラティリティ(
w = target_return_weights(...)でのsqrt(w @ cov @ w))を与える。内側のポートフォリオは無駄 - 先端は全体の最小分散ポートフォリオ(リターン
b / a)。そのリターン以上の目標だけが効率的 - 点の選択は
(target - rf) / volatilityが最大のシャープ・レシオなどの基準で行う。曲線は推定から作られ、楽観的であることを忘れない
次はベクトル化バックテスターです。過去の価格で売買ルールを検証します。
ラボ:採点される内容
frontier_volsでフロンティアをたどる25点min_variance_returnで曲線の先端を見つける25点is_efficientで良い側と悪い側を見分ける25点max_sharpe_targetで最良の点を選ぶ25点