每个收益水平对应的风险
上一节为一个目标收益求出了方差最小的权重。对每个目标都这样做,以波动率为横轴、收益为纵轴作图,这些点连成一条曲线,即有效前沿。这些资产构成的任何组合都在曲线上或曲线内(右侧)。曲线内部的组合是浪费的,因为另有组合在相同收益下风险更低,或在相同风险下收益更高。马科维茨的洞见是:投资者只需要看这条曲线。
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),用的仍是之前的 a, b, c 四个数。它的最低点是全局最小方差组合,收益为 b / a:这就是曲线的顶点,也就是两节之前的最小方差组合。只有从顶点向上的部分才是有效前沿;下面的分支是它的镜像,同样风险下收益更低。
# 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))有效点与无效点
曲线上同一个风险对应两个收益,每个分支各一个,只有较高的那个值得持有。所以一个目标收益当且仅当不低于最小方差组合的收益时才是有效的。这个检验只是一次比较,却决定了哪些目标应出现在选择图中。在前沿上寻找最佳权衡就变成选哪个点的问题,常见的答案是:每单位风险(高于无风险利率的部分)所获收益最多的那个点。
# 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 画成,所以它本身就是估计,而且是偏乐观的估计:优化器从所有组合中挑出的,恰恰是估计收益碰巧偏高、风险碰巧偏低的那些。真实结果会落在你所见曲线的右下方。对数据重新抽样,或用样本前半段画出的前沿与后半段的实际表现对比,就能看出曲线有多少是运气。请把前沿当作权衡的地图,而不是承诺。- 有效前沿给出每个目标收益对应的最低波动率:
sqrt(w @ cov @ w),其中w = target_return_weights(...);内部的组合是浪费的。 - 顶点是全局最小方差组合,收益为
b / a;只有不低于该收益的目标才是有效的。 - 按某个准则选点,例如夏普比率
(target - rf) / volatility最高;记住曲线由估计值构成,偏乐观。
下一节:向量化回测器,用历史价格检验交易规则。
实验:评分内容
- 用
frontier_vols描出前沿25 分 - 用
min_variance_return找到曲线的顶点25 分 - 用
is_efficient区分好坏两半25 分 - 用
max_sharpe_target选出最佳点25 分