第 10 章 · 01 绘制有效前沿与协方差热图 本节摘要:数字看累了,画张图更直观。PyPortfolioOpt 的 模块把可视化的常用诉求封装成四个函数: 画整条有效前沿(支持 EfficientFrontier 与 CLA)、 画协方差/相关性热图、 画 HRP 的聚类树、 画权重水平柱状图。本节讲清这四个函数的用法、参数( 、 、 、 )和典型组合图(前沿 + 随机组合 + 最大 Sharpe 标记)。读完本节,你能把任意优化结果变成可读的图表。 内容来源:源码 、文档 ,汉化并套用体系化模板。 学习目标 阅读完本节,你应当能够: 用 plotefficientfrontier 画整条有效前沿。 区分 efparam 三种取值(utility/risk/return)。
本节摘要:数字看累了,画张图更直观。PyPortfolioOpt 的
plotting模块把可视化的常用诉求封装成四个函数:plot_efficient_frontier画整条有效前沿(支持 EfficientFrontier 与 CLA)、plot_covariance画协方差/相关性热图、plot_dendrogram画 HRP 的聚类树、plot_weights画权重水平柱状图。本节讲清这四个函数的用法、参数(ef_param、ef_param_range、show_assets、interactive)和典型组合图(前沿 + 随机组合 + 最大 Sharpe 标记)。读完本节,你能把任意优化结果变成可读的图表。
内容来源:源码
pypfopt/plotting.py、文档docs/Plotting.rst,汉化并套用体系化模板。
阅读完本节,你应当能够:
源码顶部明确列出四个公开函数:
""" Currently implemented: - ``plot_covariance`` - plot a correlation matrix - ``plot_dendrogram`` - plot the hierarchical clusters in a portfolio - ``plot_efficient_frontier`` – plot the efficient frontier from an EfficientFrontier or CLA object - ``plot_weights`` - bar chart of weights """
💡 延迟导入 matplotlib/plotly:源码用
_import_matplotlib()和_get_plotly()在函数内部导入,只在真正画图时才 require 这些可选依赖。所以pip install pypfopt默认不带 matplotlib,画图前需pip install matplotlib。
这是最常用的函数。注意——传入的是「未优化」的 ef 对象,plotting 内部会循环跑多个目标值:
import matplotlib.pyplot as plt from pypfopt import EfficientFrontier, plotting ef = EfficientFrontier(mu, S, weight_bounds=(None, None)) ef.add_constraint(lambda w: w[0] >= 0.2) ef.add_constraint(lambda w: w[2] == 0.15) fig, ax = plt.subplots() plotting.plot_efficient_frontier(ef, ax=ax, show_assets=True) plt.show()
签名:
def plot_efficient_frontier( opt, # EfficientFrontier 或 CLA 实例 ef_param="return", # 'utility' / 'risk' / 'return' ef_param_range=None, # 参数扫描范围 points=100, # 默认采样点数 ax=None, show_assets=True, # 是否标出每个资产的风险/收益点 show_tickers=False, # 是否给资产点加 ticker 标签 interactive=False, # True 用 plotly,默认 matplotlib **kwargs, # filename/dpi/showfig 等 ):
源码 _plot_ef 根据 ef_param 选择循环调哪个方法:
for param_value in ef_param_range: if ef_param == "utility": ef.max_quadratic_utility(param_value) elif ef_param == "risk": ef.efficient_risk(param_value) elif ef_param == "return": ef.efficient_return(param_value) # ... ret, sigma, _ = ef.portfolio_performance() mus.append(ret); sigmas.append(sigma)
| ef_param | 含义 | 调用的方法 |
|---|---|---|
"return"(默认) |
扫描目标收益 | efficient_return(target) |
"risk" |
扫描目标风险 | efficient_risk(target_risk) |
"utility" |
扫描风险厌恶 | max_quadratic_utility(risk_aversion) |
import numpy as np risk_range = np.linspace(0.10, 0.40, 100) # 100 个 0.10~0.40 的风险目标 plotting.plot_efficient_frontier( ef, ef_param="risk", ef_param_range=risk_range, show_assets=True )
如果不传 ef_param_range,源码会自动从 GMV 收益到最大收益生成默认范围:
def _ef_default_returns_range(ef, points): ef_minvol = ef.deepcopy() ef_maxret = ef.deepcopy() ef_minvol.min_volatility() min_ret = ef_minvol.portfolio_performance()[0] max_ret = ef_maxret._max_return() return np.linspace(min_ret, max_ret - 0.0001, points)
⚠️ 关键:传入未优化的 ef:
plot_efficient_frontier内部会重复调优化方法,如果你的 ef 已经 max_sharpe 过,会报错或结果异常。要复用同一个 ef 画图又优化,用ef.deepcopy()。
plotting.plot_covariance(S, plot_correlation=False, show_tickers=True)
源码用 imshow 画矩阵:
def plot_covariance(cov_matrix, plot_correlation=False, show_tickers=True, **kwargs): if plot_correlation: matrix = risk_models.cov_to_corr(cov_matrix) else: matrix = cov_matrix fig, ax = plt.subplots() cax = ax.imshow(matrix) fig.colorbar(cax) if show_tickers: ax.set_xticks(np.arange(0, matrix.shape[0], 1)) ax.set_xticklabels(matrix.index) # ... return ax
| 参数 | 含义 |
|---|---|
plot_correlation=False |
False 画协方差,True 画相关性(对角线归一为 1) |
show_tickers=True |
是否给行列加 ticker 标签(资产多时关掉) |
cookbook 4 的典型用法:
plotting.plot_covariance(S, plot_correlation=True)
💡 相关性 vs 协方差:协方差带量纲(收益的平方),数值范围乱;相关性归一到 [-1, 1],热图颜色对比更明显。日常诊断资产关系优先看相关性热图。
from pypfopt import HRPOpt, plotting hrp = HRPOpt(returns) hrp.optimize() plotting.plot_dendrogram(hrp)
源码:
def plot_dendrogram(hrp, ax=None, show_tickers=True, **kwargs): if hrp.clusters is None: warnings.warn("hrp param has not been optimized. Attempting optimization.") hrp.optimize() if show_tickers: sch.dendrogram(hrp.clusters, labels=hrp.tickers, ax=ax, orientation="top") else: sch.dendrogram(hrp.clusters, no_labels=True, ax=ax)
hrp.clusters 是 scipy.cluster.hierarchy.linkage 返回的链接矩阵,直接喂给 sch.dendrogram 画树。
💡 dendrogram 的解读:横轴是资产,纵轴是合并距离(越低越相关)。距离短的资产先合并,说明高度相关。如 AMD 和 NVDA 同属半导体,会先聚到一起。这图不仅是可视化,还是「剔除冗余资产」的工具。
ef.max_sharpe() weights = ef.clean_weights() plotting.plot_weights(weights)
源码用水平柱状图(barh):
def plot_weights(weights, ax=None, **kwargs): desc = sorted(weights.items(), key=lambda x: x[1], reverse=True) # 按权重降序 labels = [i[0] for i in desc] vals = [i[1] for i in desc] y_pos = np.arange(len(labels)) ax.barh(y_pos, vals) ax.set_xlabel("Weight") ax.set_yticks(y_pos) ax.set_yticklabels(labels) ax.invert_yaxis() # 大权重在上 return ax
权重自动按降序排,大权重在上,直观看出「谁占大头」。
Plotting.rst 里的经典示例——前沿 + 随机点云 + 最大 Sharpe 标记:
fig, ax = plt.subplots() ef_max_sharpe = ef.deepcopy() plotting.plot_efficient_frontier(ef, ax=ax, show_assets=False) # 找切点 ef_max_sharpe.max_sharpe() ret_tangent, std_tangent, _ = ef_max_sharpe.portfolio_performance() ax.scatter(std_tangent, ret_tangent, marker="*", s=100, c="r", label="Max Sharpe") # 生成 10000 个随机组合,按 Sharpe 着色 n_samples = 10000 w = np.random.dirichlet(np.ones(ef.n_assets), n_samples) rets = w.dot(ef.expected_returns) stds = np.sqrt(np.diag(w @ ef.cov_matrix @ w.T)) sharpes = rets / stds ax.scatter(stds, rets, marker=".", c=sharpes, cmap="viridis_r") ax.set_title("Efficient Frontier with random portfolios") ax.legend() plt.tight_layout() plt.savefig("ef_scatter.png", dpi=200) plt.show()
所有 plotting 函数都接受三个 kwargs(通过 _plot_io):
def _plot_io(**kwargs): filename = kwargs.get("filename", None) showfig = kwargs.get("showfig", False) dpi = kwargs.get("dpi", 300) plt.tight_layout() if filename: plt.savefig(fname=filename, dpi=dpi) if showfig: plt.show()
plotting.plot_efficient_frontier(ef, filename="frontier.png", dpi=200, showfig=True)
plot_efficient_frontier 还支持 interactive=True 切到 Plotly:
plotting.plot_efficient_frontier(ef, interactive=True, show_assets=True)
源码在 interactive 分支用 plotly.graph_objects.Scatter,鼠标悬停能看权重细节。需要 pip install plotly。
plot_efficient_frontier(前沿)、plot_covariance(热图)、plot_dendrogram(HRP 树)、plot_weights(权重柱)。ef_param(utility/risk/return)选择扫描方法,可手动传 ef_param_range 或自动生成。ef.deepcopy()。plot_correlation 控制,日常诊断优先用相关性(归一易读)。clusters 链接矩阵,横向看资产聚类结构。下一节,我们看 BaseOptimizer 抽象基类——继承它就能写自己的优化器,与库的其他层无缝拼接。