
用 Python 揭秘均值回归策略:你的收益从何而来?
量化交易的生死线:用 Python 算出你的最优下注比例
2026年重磅升级已全面落地!欢迎加入专注财经数据与量化投研的【数据科学实战】知识星球!您将获取持续更新的《财经数据宝典》与《量化投研宝典》,双典协同提供系统化指引;星球内含 500 篇以上独有高质量文章,深度覆盖策略开发、因子分析、风险管理等核心领域,内容基本每日更新;同步推出的「量化因子专题教程」系列(含完整可运行代码与实战案例),系统详解因子构建、回测与优化全流程,并实现日更迭代。我们持续扩充独家内容资源,全方位赋能您的投研效率与专业成长。无论您是量化新手还是资深研究者,这里都是助您少走弯路、事半功倍的理想伙伴,携手共探数据驱动的投资未来!
很多人学习投资组合优化,都是从一行 scipy.optimize.minimize 加一张 matplotlib 散点图开始的。这确实是理解 Markowitz 理论的好方法,但它和真正在市场上「跑真金白银」的软件相去甚远。
真正的量化组合系统不是脚本,而是库:它有分层、有抽象、有可测试的接缝;它把协方差矩阵当作一个可以用五种方式估计的对象;它默认经验协方差矩阵经常不是正定的,会悄悄毒害你的优化器;它还严格区分三件事——估计输入、求解问题、度量结果。
本文将带你完整走一遍这样一个模块化量化库的设计与实现思路:约 60 个 Python 模块、383 个测试、mypy --strict 零报错,覆盖均值 - 方差优化、有效前沿、风险平价(含层次风险平价)、Black-Litterman、CVaR 优化、因子模型、蒙特卡洛模拟,以及完整的绘图层。对于学习 Python 的量化爱好者来说,把工程搭在数学之上,往往比数学本身更能让你成长。
Harry Markowitz 在 1952 年提出的核心洞见是:一个投资组合不是「一袋资产」,而是一个分布。假设资产收益的期望为 μ、协方差矩阵为 Σ,权重为 w 的组合有:
有效前沿就是在每个风险水平下能给出最高收益的组合集合。最小方差组合、最大夏普(切点)组合以及两者之间的所有组合,都落在这条前沿上。
理论到这里很简单,真正的问题也从这里开始:
一个生产级的库必须同时回答这四个问题,这就是「Markowitz 演示」和「软件」之间的分水岭。
在写第一个优化器之前,最值得投入的其实是依赖关系图。它决定了哪些东西可以被隔离测试,哪些会退化成意大利面条式代码。
一条核心原则:依赖指向内部,朝向纯数值计算。 I/O 和展示层坐在边缘,依赖核心,绝不反向。整个库大致分为这些松耦合子包:
utils:配置(Pydantic)、日志、异常体系、PSD 投影线性代数statistics:收益与协方差估计器(样本、EWMA、Ledoit-Wolf 收缩)portfolio:Portfolio 值对象risk:风险贡献、VaR/ES、回撤、跟踪误差optimizer:均值 - 方差、前沿、约束、风险平价、CVaRperformance:Sharpe/Sortino/Calmar、CAPM alpha/beta、滚动指标models:Black-Litterman、因子模型data:CSV / Yahoo / Polygon / Alpha Vantage 数据源simulation:蒙特卡洛与自助法visualization:出版级图表utils 不依赖任何内部模块;statistics 只依赖 utils;optimizer 依赖 statistics 和 risk;visualization 依赖所有模块,但不被任何模块导入。整个图没有循环依赖。
支撑这套架构的三个设计模式:
第一个模块并不起眼,却是「机构级」水准悄悄体现的地方。这里最重要的是保证协方差矩阵始终有效的线性代数工具。
前面提过:经验协方差矩阵经常是不定的,修复方法是把它投影到最近的半正定矩阵上。这里有个漂亮的结论——Higham(1988)证明了:Frobenius 范数意义下最近的 PSD 矩阵,就是把原矩阵的负特征值截断为零后得到的矩阵。
import numpy as np
def nearest_psd(matrix: np.ndarray) -> np.ndarray:
"""把一个对称矩阵投影到最近的半正定矩阵(Higham 1988)。"""
# 先做对称化,消除浮点误差带来的微小不对称
sym = (matrix + matrix.T) / 2.0
# 特征分解:得到特征值和特征向量
eigvals, eigvecs = np.linalg.eigh(sym)
# 把所有负特征值截断为 0(关键一步)
eigvals_clipped = np.clip(eigvals, a_min=0.0, a_max=None)
# 用修正后的特征值重建矩阵
return eigvecs @ np.diag(eigvals_clipped) @ eigvecs.T关键点:每一个协方差估计器的输出都会跑一遍 PSD 检查与投影。因为这一个决定,下游的优化器、风险模型、模拟器都可以放心假设它们拿到的输入是合法的。这就是地基的意义。
💡 易错点补充:直接用
np.linalg.eig处理对称矩阵可能返回复数结果,务必用np.linalg.eigh(专门针对对称/厄米矩阵),既更快也数值更稳定。
优化器需要两个输入:期望收益和协方差矩阵。但怎么估计它们是一个有实际后果的建模选择,所以把估计做成了一个通过 Protocol 定义的扩展点:
from typing import Protocol
import numpy as np
class CovarianceEstimator(Protocol):
"""协方差估计器协议:任何实现该方法的类都能被优化器接受。"""
def estimate(self, returns: np.ndarray) -> np.ndarray:
...
class SampleCovariance:
"""样本协方差:无偏但噪声大。"""
def estimate(self, returns: np.ndarray) -> np.ndarray:
cov = np.cov(returns, rowvar=False) # 计算样本协方差
return nearest_psd(cov) # 别忘了 PSD 投影一共有三个具体策略实现它:SampleCovariance、EWMACovariance(RiskMetrics 式指数加权)、LedoitWolfShrinkage。因为它们都满足同一个协议,优化器接受其中任意一个——把样本换成收缩估计,其他代码一行都不用改。
我最喜欢的是 Ledoit-Wolf 收缩。样本协方差无偏但噪声大,而一个结构化目标(比如常相关矩阵)虽有偏但更稳定。Ledoit 和 Wolf(2004)给出了两者的解析最优混合:
其中收缩强度 δ 有闭式解。有一个漂亮的自检落地了:在由常相关过程生成的数据上,估计器返回 δ → 1.0——因为此时目标就是真相,所以它一路收缩到底。这恰恰是单元测试应该钉死的行为。
这是整个库的心脏,建立在 cvxpy 之上,让凸问题拿到全局最优解。三个想法让它既灵活又不乱:
每个目标返回一个待最小化的凸表达式(最大化目标在内部取负)。这种统一性让引擎可以给任何目标叠加 L2 正则化和交易成本惩罚,而无需关心底层目标是什么。
约束是一个个小对象:FullyInvested、LongOnly、WeightBounds、SectorConstraint、PositionLimit、LeverageLimit、TargetReturn、TargetVolatility……你把它们丢进一个列表,就拼出了组合的委托规则:
# 组合式约束:只做多 + 满仓 + 单资产权重上限 30%
constraints = [
FullyInvested(), # 权重之和为 1
LongOnly(), # 权重全部非负
WeightBounds(upper=0.30), # 单个资产最多 30%
]最大化夏普比率 (μ−r_f)ᵀw / √(wᵀΣw) 不是凸问题,很多库干脆用扫描前沿的方式来近似。但它有一个精确的凸重构——来自 Cornuéjols 和 Tütüncü 的 Schur(齐次化)技巧。引入一个缩放变量 κ ≥ 0,令 w = y/κ,整个问题就坍缩成一个干净的二次规划:
最小化 yᵀΣy,约束为 (μ−r_f)ᵀy = 1,且每个线性约束作用在 y 上、其常数项按 κ 缩放。
最后这一句正是约束为什么要携带 scale的原因:同一批约束对象既服务于普通问题(scale=1.0),也服务于最大夏普变换(scale=κ),零重复逻辑。最后用 w = y/κ 还原权重。实测下来,这个精确切点的夏普和前沿扫描的最优格点在小数点后第四位吻合——没有近似。
等风险贡献组合让每个资产对总波动率的贡献相等。Spinu(2013)证明这是一个凸对数障碍问题的解:
用 cvxpy 求解、归一化即可,实测风险贡献落在目标预算的 1e-4 之内。库里还加了层次风险平价(HRP)(López de Prado,2016),它按相关性聚类资产、递归二分配权,全程无需对协方差矩阵求逆。
方差不是尾部风险。Rockafellar-Uryasev(2000)的公式把条件 VaR 最小化变成了在历史情景上的一个线性规划:
一个调用 min_cvar_portfolio(returns, confidence=0.95) 就能得到针对 5% 尾部优化的组合,而且复用完全相同的约束对象。
有了组合,就要度量它。risk 模块提供 Euler 风险分解、回撤分析(曲线、最大回撤、Ulcer 指数、峰/谷/恢复),以及通过策略枚举切换尾部风险估计技术。
其中 Cornish-Fisher 方法会根据偏度和超额峰度修正正态分位数,因此在肥尾(Student-t)数据上,它报告的尾部损失会大于朴素高斯假设的结果。
performance 模块提供常见比率(Sharpe、Sortino、Calmar、Treynor、Omega),外加用于 alpha/beta 的 CAPM 回归。一个我很喜欢的正确性检验:在已知 β=1.1 的合成数据上拟合市场模型,回归恢复出 β ≈ 1.08、R² = 0.855。当你的代码能恢复出真相时,你才开始信任它。
还记得第一个问题「输入有噪声」吗?models 包正面迎击它。
Black-Litterman 从市场均衡(反向优化)收益出发作为先验,再融合你自己的观点,产出一个远比原始历史均值稳定的后验。实测中,对新兴市场一个看涨 10% 的观点,把 EM 的后验收益从约 2.7% 抬升到 7.4%,并让最优组合向其倾斜——这正是你想要的直觉行为。
因子模型(CAPM、Fama-French 或任意多因子集)把资产收益对因子收益做回归,重建一个低秩、良态的协方差 BΣ_fBᵀ + diag(σ²_ε)。对大规模资产池而言,这是比原始样本矩阵稳定得多的输入。
数据源都藏在同一个协议(get_prices / get_returns)之后,有四种实现:CSV、Yahoo Finance、Polygon、Alpha Vantage。这里最得意的工程选择是:每个网络调用都通过一个可注入的 fetch 可调用对象。于是三个网络数据源的解析逻辑都能用「罐装响应」做单元测试——不打真实请求、不需要 API key、完全确定性。能在没有网络的情况下测试网络逻辑,是生产级代码的标志。
模拟提供基于 Cholesky 分解的蒙特卡洛(高斯和肥尾 Student-t 扰动,并带一个尺度修正让模拟收益匹配目标协方差),以及平稳自助法(Politis-Romano)来给出尊重自相关性的置信区间。
import numpy as np
def monte_carlo_paths(mu, cov, n_periods=252, n_paths=1000, seed=42):
"""基于 Cholesky 分解生成多条蒙特卡洛财富路径。"""
rng = np.random.default_rng(seed)
L = np.linalg.cholesky(cov) # 对协方差做 Cholesky 分解
n_assets = len(mu)
# 生成相互独立的标准正态噪声
z = rng.standard_normal((n_paths, n_periods, n_assets))
# 通过 L 引入资产间相关性,再叠加漂移项 mu
returns = z @ L.T + mu
# 累积成财富曲线(从 1 开始)
wealth = np.cumprod(1 + returns.mean(axis=2), axis=1)
return wealth可视化层提供 14 个绘图函数,采用了经过验证的、色盲友好的分类调色板,每个函数都接收一个可选的 ax 并返回它,因此图表可以自由组合成仪表盘。
# 1. 估计输入
cov = LedoitWolfShrinkage().estimate(returns) # 收缩协方差,更稳定
mu = returns.mean(axis=0) # 期望收益
# 2. 求解:最大夏普 + 一组约束
portfolio = max_sharpe_portfolio(
mu, cov,
constraints=[FullyInvested(), LongOnly(), WeightBounds(upper=0.30)],
)
# 3. 度量:一键生成风险报告
report = RiskModel().report(portfolio)
print(report.annualized_vol, report.var_95, report.max_drawdown)每个对象都流入下一个。优化器返回一个 Portfolio,它知道如何计算自己的收益,并委托给风险层和业绩层。这种可组合性,正是一开始就把依赖图理顺的回报。
一个你无法信任的库毫无价值。好的测试不是「它能跑吗」,而是闭式解检验和性质检验:
再加上 mypy --strict、ruff 静态检查、每个公开函数的 Google 风格文档字符串,你得到的就是一个行为像真正基础设施、而不是像一个 notebook 的东西。
有意思的是,作者靠测试抓到的 bug 里有几个其实在测试本身:曾断言组合从回撤中「恢复」了,但一次 −19% 下跌后 +20% 的反弹并不能真正收复前高。实现是对的,脑子里的算术错了——这正是测试的意义。
从一行 scipy.optimize.minimize 到一个能放心托付真金白银的量化库,中间隔着的不是数学,而是工程。回顾整个旅程,有五条经验值得每一个学 Python 的量化人记住:
Markowitz 理论只是一页代数,把它变成你敢用真钱去跑的软件,才是更有价值的修炼。如果你正在学量化,不妨也从「把工程搭在数学之上」开始。
2026年全面升级已落地!【数据科学实战】知识星球核心权益如下:
星球已沉淀丰富内容生态——涵盖量化文章专题教程库、因子日更系列、高频数据集、PyBroker实战课程、专家深度分享与实时答疑服务。无论您是初探量化的学习者,还是深耕领域的从业者,这里都是助您少走弯路、高效成长的理想平台。诚邀加入,共探数据驱动的投资未来!
好文推荐
1. 用 Python 打造股票预测系统:Transformer 模型教程(一)
2. 用 Python 打造股票预测系统:Transformer 模型教程(二)
3. 用 Python 打造股票预测系统:Transformer 模型教程(三)
4. 用 Python 打造股票预测系统:Transformer 模型教程(完结)
6. YOLO 也能预测股市涨跌?计算机视觉在股票市场预测中的应用
9. Python 量化投资利器:Ridge、Lasso 和 Elastic Net 回归详解
好书推荐