土法炼钢兴趣小组的算法知识备份

【量化交易】组合构建:均值方差、风险平价、Black-Litterman

文章导航

分类入口
quant
标签入口
#portfolio#mean-variance#risk-parity#black-litterman#optimization

目录

研究员手里有一组阿尔法信号、一份风险模型、一堆约束条款,最终要回答的只有一个问题:每个标的此刻应该持有多少。这件事看似只是一次最优化求解,工程上却是整个量化流程里最容易翻车的一段——前一秒模型协方差还像样,下一秒奇异值就把权重打到极端值;昨天的最优组合今天就因为一个新约束变得不可行;回测里漂亮的有效前沿一上线就漂走,半年内夏普率从 1.8 退到 0.4。组合构建不是把数学公式翻译成代码那么简单,它是把不可观测的期望、不稳定的协方差、不光滑的约束、不连续的成本,挤压到一组可以委托执行的权重向量里。

本文按教科书顺序铺开但不停留在教科书:从 Markowitz 的均值方差(mean-variance,MV)讲到协方差估计的不稳定性,再到 Ledoit-Wolf 收缩、resampling 与正则化;接着讲风险平价(risk parity,RP)与 Hierarchical Risk Parity(HRP)这条「不需要预测期望」的支线;然后用 Black-Litterman(BL)把先验和观点结合起来;再用 Kelly 准则把对数效用和均值方差对照看;最后落到二次规划(quadratic programming,QP)与凸优化(convex optimization)求解器,给出 cvxpy 的可运行实现,并讨论交易费用、上线漂移和稳健性诊断。代码示例使用 Python 3.11、numpy 1.26、scipy 1.11、cvxpy 1.4、scikit-learn 1.4,所有数据为本地合成或可复现仿真。

风险提示:本文出现的所有组合构建方法和示例只用于阐释方法论本身,不构成任何投资建议。组合优化器输出的权重在历史回测中可能极其漂亮,在实盘中却高度依赖协方差稳定性、流动性供给、约束完整性和成本模型准确性。把任何示例代码搬到生产前,请独立校核协方差估计窗口、约束集合、成本参数、风险预算与硬止损。


一、组合构建的工程意义

把组合构建放在量化流水线里看,它处于「信号 → 头寸」这条边上。上游是因子挖掘、模型预测、事件信号;下游是执行算法、TCA、风险监控。它承担的是一份非常具体的工程合同:

给定预期收益向量 μ、协方差矩阵 Σ、约束集合 C,输出一份在当前时刻可执行的权重 w,使得某个综合目标(夏普、效用、跟踪误差)最优。

听上去像一道纯数学题,但工程含义远远超出数学:

第一,μ 不可观测,Σ 不稳定。研究员给的所谓「预期收益」往往只是一份信号打分,量纲、分布、时变性都难以保证;协方差矩阵在 N 大于样本数 T/2 时就开始病态,特征值越大估计越乐观,越小估计越保守,这种系统性偏差直接进入最优化结果。

第二,约束不是装饰品,而是结论的来源。一个无约束的均值方差最优组合往往是数学家的玩具,工程上根本无法上线。多空敞口、行业中性、风格中性、单只权重上限、整体杠杆、换手率、流动性容量、风险预算、税务约束、合规黑名单——这些约束加进去之后,所谓「最优」往往只是「在满足约束的前提下少差几个 bp」,但少这几个 bp 的过程里隐藏着策略能否长期运行的全部秘密。

第三,目标函数不是单一的。教科书里讲的目标函数只有一项,工程中至少包括:期望收益项、风险项(方差、跟踪误差、CVaR)、交易成本项、冲击成本项、税务成本项、稳健性正则项、特定头寸惩罚项。多目标加权后通常仍然是凸的,但每一项的系数都需要单独定标和监控。

第四,优化结果要可解释、可审计。组合经理需要能解释为什么某个标的权重从 1.2% 跳到 3.5%,是因为信号变了、风险变了、约束放松了、还是别的因素。一份让人解释不清的最优权重,无论数学上多美,都不能直接进交易系统。

第五,优化要在工业级时间内完成。日频组合在收盘后 30 分钟内必须出权重,分钟频或秒频策略对 QP 求解的延迟要求更高。求解器的选择、warm start、预条件、约束精简,全是工程问题。

把这五点串起来,就能理解为什么组合构建是量化里最容易出事的一段——它把所有上游误差累积、所有下游约束反馈、所有数学假设都压在一份权重向量上输出。这一节为后面所有细节铺底,请把这五点当作判断每一种方法是否合用的第一道筛子。


二、均值方差与 Markowitz

Harry Markowitz 在 1952 年的 Portfolio Selection 把组合构建第一次写成可计算的最优化问题。原文很短但内核很硬:把所有可行权重 w 限制在预算约束 1ᵀw = 1 下,每一个组合可以由二元组 (μ_p, σ_p²) 描述,其中 μ_p = wᵀμ、σ_p² = wᵀΣw。有效前沿(efficient frontier)就是给定 μ_p 时使 σ_p² 最小的那条曲线

二点一、最小方差组合与切线组合

无约束、允许卖空时,最小方差组合(global minimum-variance,GMV)有解析解:

w_GMV = Σ⁻¹ 1 / (1ᵀ Σ⁻¹ 1)

切线组合(tangency portfolio,又叫最大夏普组合)需要一个无风险利率 r_f 作参考:

w_tan = Σ⁻¹ (μ − r_f · 1) / (1ᵀ Σ⁻¹ (μ − r_f · 1))

两个公式的核心都在 Σ⁻¹。这正是均值方差最危险的地方:协方差矩阵的逆把所有估计误差放大若干倍,特征值越小的方向放大得越厉害。一个本来贡献微弱的样本噪声方向,逆变换之后可能成为权重的主导方向。

下面这张图把有效前沿、最小方差组合、切线组合、资本市场线(capital market line,CML)画在一起。

有效前沿与切线组合

图里两个细节值得停一下。其一,最小方差组合 GMV 永远在前沿的最左端,它的存在不依赖期望收益,只依赖协方差——这一性质使它成为后面讨论 Ledoit-Wolf 收缩与风险平价时的天然参照点。其二,切线组合是无风险利率与风险资产前沿的切点;同样的协方差、不同的 r_f 会给出不同的切线组合,CML 的斜率就是那个组合的夏普率。

二点二、协方差矩阵估计的不稳定性

样本协方差矩阵 S = (1/T) Σ (xₜ − x̄)(xₜ − x̄)ᵀ 在 N 个标的、T 期收益下有 N(N+1)/2 个独立元素。当 T 不够大时,S 不只是估不准——它会病态甚至奇异。具体地:

这种不稳定性如何反映到最优组合上?做一个最小实验:随机生成 50 只资产、500 天收益,求一次切线组合,再把数据扰动 1%,再求一次。两次切线组合的权重向量经常完全不一样,甚至号都翻过来。Michaud(1989)把这件事概括为 error maximizer——均值方差优化器并不是「找最优组合」,而是「找误差最大的方向并把权重压上去」。

import numpy as np

rng = np.random.default_rng(42)
N, T = 50, 500
true_mu = rng.normal(0.0008, 0.0004, N)
A = rng.normal(0, 1, (N, N))
true_cov = (A @ A.T) / N + 0.01 * np.eye(N)

def sample_returns(seed):
    g = np.random.default_rng(seed)
    L = np.linalg.cholesky(true_cov)
    eps = g.normal(size=(T, N))
    return true_mu + eps @ L.T

def tangency(R, rf=0.0):
    mu = R.mean(axis=0)
    S = np.cov(R.T)
    inv = np.linalg.solve(S, mu - rf)
    return inv / inv.sum()

w1 = tangency(sample_returns(1))
w2 = tangency(sample_returns(2))
print("L2 distance:", np.linalg.norm(w1 - w2))
print("Sign agreement:", (np.sign(w1) == np.sign(w2)).mean())
print("Max abs weight:", max(np.abs(w1).max(), np.abs(w2).max()))

在合成实验里,两次抽样得到的切线权重 L2 距离常常超过 5,单只权重的绝对值经常上百倍于 1/N,符号一致率不到 60%。这并不是数据生成有问题,而是均值方差优化器对小样本协方差的天然脆弱性。

二点三、为什么仍然要从均值方差讲起

均值方差有两个不可替代的工程价值。其一,它是所有更复杂方法的基线——风险平价、HRP、Black-Litterman、最小跟踪误差、风险预算,全部可以写成均值方差的某种约束化或正则化变形。其二,它把组合构建的核心问题摆在了桌面上:μ 怎么估、Σ 怎么估、约束怎么写、求解器怎么用。后面所有方法都是在这四件事上各自给出了一种妥协。

理解了均值方差的脆弱性,才能理解为什么大量从业者宁可放弃「最大夏普」这个理论目标,转而追求「在假设最少的情况下也能稳态运行」的目标。这个转变是后面四节的主线。


三、约束与正则化

如果均值方差的核心问题是「Σ⁻¹ 把误差放大」,那么对治这个问题最朴素的办法就是给优化器施加足够多的约束,让它无法把权重压到误差主导的方向上。约束在数学上等价于把可行域缩小,在工程上等价于把先验知识写进优化器。

三点一、多空与杠杆约束

最常见的三组约束:

1ᵀ w = 1            预算约束
‖w‖₁ ≤ L            杠杆约束(gross leverage)
−w_max ≤ wᵢ ≤ w_max 单只权重上下限

纯多头组合在第二条上把 L = 1 并要求 w ≥ 0;多空组合则把 L 设为 2 或更高。第三条是上线必须配的——单只权重 5% 与 50% 的差别不仅是收益,更是流动性与执行风险。

三点二、行业与风格中性

行业中性写成线性约束 Aᵢⁿᵈ w = b。对全市场组合,常见做法是要求每个行业相对基准的暴露差小于 ±1%:

| (Bᵢⁿᵈ − wᵇⁱⁿᵈ)ᵀ w | ≤ 0.01    对每个行业

风格中性同理,把市值、动量、波动率等风格因子的暴露限定在一个区间。这两组约束加进去之后,优化器无法在某一个行业或风格上集中下注,对协方差估计误差的鲁棒性也随之提升——因为协方差矩阵的最大特征向量方向往往恰好和行业、风格高度对齐。

三点三、换手与交易成本

换手率约束 ‖w − w_prev‖₁ ≤ τ 是上线必备。它把每次再平衡的总买卖额度限制住,避免优化器在两次输出之间剧烈震荡。换手约束等价于 L1 正则,下面会看到它和 Lasso 在数学上是一回事。

进一步把交易成本写成目标函数的一部分:

maximize  μᵀ w − λ wᵀΣw − ‖c ⊙ (w − w_prev)‖₁

c 是每只标的的成本系数,⊙ 是逐元素乘积。这一项是非光滑的,但仍然凸,cvxpy 直接支持。

三点四、L1/L2 正则与 resampling

把均值方差写成正则化形式:

maximize  μᵀ w − λ wᵀΣw − γ₁ ‖w‖₁ − γ₂ ‖w‖₂²

γ₁ 控制稀疏度(多少只标的实际持有),γ₂ 控制权重大小。这两项的引入有两个工程动机:第一,缓解 Σ⁻¹ 把噪声方向放大的问题;第二,把不显著的小信号过滤掉,降低换手与成本。

Michaud 在 1998 年提出 resampling efficiency:用 bootstrap 方法对历史收益反复抽样,每次解一个均值方差最优化,最后把所有权重平均。它在数学上不是凸的也没有解析解,但工程上有效——本质是用蒙特卡洛把误差最大化方向上的极端结果拉回中位数。

三点五、把约束当作先验

写到这里,应该跳出来重新理解约束的意义:约束不是为了让优化更难,而是为了把工程师的先验知识塞给优化器。一个完全无约束的均值方差,相当于告诉优化器「我对所有方向都没有先验,全凭你的统计估计做决定」;而一个带行业中性、风格中性、单只上限、换手上限、L2 正则的均值方差,相当于告诉优化器「我对协方差大特征值方向的估计高度怀疑,请避开它们」。

这个视角直接通向 Black-Litterman 和风险平价:前者把先验做成贝叶斯框架里的均值与方差,后者干脆放弃对期望的估计、只用协方差。


四、风险平价

如果你完全放弃对预期收益的估计,能不能仍然得到一份有意义的组合?风险平价给出的答案是「能」,并且在过去二十年里,这个答案被大量资产管理机构当作核心配置框架。

四点一、风险贡献的定义

设组合权重为 w,协方差为 Σ,组合方差 σ_p² = wᵀΣw。第 i 只资产对组合方差的边际贡献是 ∂σ_p²/∂wᵢ = 2(Σw)ᵢ;它的总贡献是 wᵢ × (Σw)ᵢ。把所有总贡献加起来正好等于 σ_p²,于是可以定义:

RC_i = wᵢ (Σw)ᵢ            风险贡献
RC_i / σ_p² = w_i (Σw)_i / (wᵀΣw)   归一化风险贡献

风险平价的目标是让所有 RC_i 相等:

RC_i = σ_p² / N    对所有 i

这个组合不需要预期收益——它的输入只是协方差。

四点二、求解风险平价

写成最优化问题:

minimize  Σᵢ Σⱼ (RC_i − RC_j)²
subject to  1ᵀw = 1, w ≥ 0

这是非凸的,但 Spinu(2013)证明了一个等价的凸形式:

minimize  ½ wᵀΣw − (1/N) Σᵢ log(wᵢ)
subject to  w > 0

求解后再做归一化 w/1ᵀw 即可。下面是 numpy + scipy 的最小实现。

import numpy as np
from scipy.optimize import minimize

def risk_parity(Sigma):
    N = Sigma.shape[0]
    def obj(w):
        return 0.5 * w @ Sigma @ w - np.log(w).sum() / N
    def grad(w):
        return Sigma @ w - 1.0 / (N * w)
    w0 = np.ones(N) / N
    bounds = [(1e-6, None)] * N
    res = minimize(obj, w0, jac=grad, method='L-BFGS-B', bounds=bounds)
    w = res.x / res.x.sum()
    return w

rng = np.random.default_rng(0)
A = rng.normal(0, 1, (5, 5))
Sigma = (A @ A.T) / 5 + 0.01 * np.eye(5)
w_rp = risk_parity(Sigma)
RC = w_rp * (Sigma @ w_rp)
print("weights:", w_rp.round(3))
print("risk contrib (norm):", (RC / RC.sum()).round(3))

如果实现正确,RC 归一化后应该接近 [0.2, 0.2, 0.2, 0.2, 0.2]。

四点三、风险平价为何有效

风险平价不是数学上的最优组合,它也不是夏普率最高的组合。它在工程上有效的根本原因有三条:

第一,它对预期收益估计零依赖。在均值方差里,μ 估计误差对权重的影响远大于 Σ 估计误差;放弃 μ 等价于把这部分误差完全消掉。

第二,它在协方差稳定时输出稳定。协方差短期稳定性远好于均值——日度收益的均值估计需要数年样本,方差估计只要数月。风险平价权重的换手率因此远低于均值方差。

第三,它在组合层面是分散的。等权重不等于等风险——把 60% 股票、40% 债券的传统 60/40 拆开看,风险贡献里股票占 90% 以上。风险平价把这种「貌似分散、实际集中」的暴露还原回真正的等风险贡献。

下面这张图把等权、均值方差、风险平价、HRP 四种方法的权重和风险贡献画在同一张表里。

等权 vs 均值方差 vs 风险平价

图中的样本外表现来自一份 1000 条蒙特卡洛路径的合成实验:在均值低估、协方差正确的情况下,均值方差的换手率(82%)和最大回撤(−31%)都远高于风险平价(22% 与 −15%)。这一结果不是偶然——它来自上面三条原因的共同作用。

四点四、Hierarchical Risk Parity(HRP)

López de Prado(2016)的 Building Diversified Portfolios that Outperform Out of Sample 提出 HRP,把风险平价和层次聚类(hierarchical clustering)结合起来。它的核心步骤:

  1. 用相关性矩阵计算距离 d(i,j) = √(½(1−ρ_ij));
  2. 对距离矩阵做层次聚类,得到一棵二叉树;
  3. 在树上做 quasi-diagonalization,把相关性高的资产放到相邻位置;
  4. 递归二分:每一层把当前簇的协方差子矩阵分成两半,按方差倒数分配权重,再向下递归。

HRP 不需要矩阵求逆,因此对协方差病态完全免疫;它输出的权重在每个簇内部接近风险平价、在簇之间也按风险大致平衡。下面是一段简化实现。

import numpy as np
from scipy.cluster.hierarchy import linkage
from scipy.spatial.distance import squareform

def correl_dist(corr):
    return np.sqrt(0.5 * (1 - corr))

def get_quasi_diag(link):
    link = link.astype(int)
    sort_ix = [link[-1, 0], link[-1, 1]]
    num_items = link[-1, 3]
    while max(sort_ix) >= num_items:
        new_ix = []
        for i in sort_ix:
            if i < num_items:
                new_ix.append(i)
            else:
                row = link[i - num_items]
                new_ix.extend([row[0], row[1]])
        sort_ix = new_ix
    return sort_ix

def get_cluster_var(cov, ix):
    sub = cov[np.ix_(ix, ix)]
    ivp = 1.0 / np.diag(sub)
    ivp /= ivp.sum()
    return ivp @ sub @ ivp

def recursive_bisect(cov, sort_ix):
    w = np.ones(len(sort_ix))
    cluster = [sort_ix]
    while cluster:
        cluster = [c[i:j] for c in cluster
                   for i, j in [(0, len(c) // 2), (len(c) // 2, len(c))]
                   if len(c) > 1]
        for i in range(0, len(cluster), 2):
            c0, c1 = cluster[i], cluster[i + 1]
            v0 = get_cluster_var(cov, c0)
            v1 = get_cluster_var(cov, c1)
            alpha = 1 - v0 / (v0 + v1)
            w[c0] *= alpha
            w[c1] *= 1 - alpha
    return w

def hrp(returns):
    cov = np.cov(returns.T)
    corr = np.corrcoef(returns.T)
    dist = correl_dist(corr)
    link = linkage(squareform(dist, checks=False), method='single')
    sort_ix = get_quasi_diag(link)
    w = recursive_bisect(cov, sort_ix)
    return w / w.sum()

de Prado 的实证显示,HRP 在样本外的夏普率与最大回撤都优于均值方差与等权重,最关键的是它对协方差矩阵病态完全免疫。这使它特别适合中高维(N ≥ 50)的资产配置场景。


五、Black-Litterman

均值方差需要 μ,但 μ 估不准;那么能不能把「研究员的观点」和「市场隐含的均衡」融合起来,得到一份既不依赖估计噪声、又能反映主观判断的预期收益?这是 Black 与 Litterman(1992)在高盛内部解决的实际问题,他们的答案就是 Black-Litterman 模型。

五点一、市场隐含的均衡收益

第一步是反向求解市场组合的隐含收益。给定市场权重 w_mkt 和协方差 Σ,假设市场处于均值方差均衡,则:

Π = δ Σ w_mkt

δ 是风险厌恶系数,常用 2 至 4。Π 就是「使市场组合恰好是切线组合的那组期望收益」,它构成贝叶斯先验。

五点二、观点矩阵与置信度

研究员的观点用三元组 (P, Q, Ω) 表达:

例如「股票 A 比股票 B 多 2%」对应的 P 一行是 (1, −1, 0, …, 0)、Q 对应分量是 0.02、Ω 对角元素是该观点的方差。

五点三、后验收益与协方差

后验期望收益的解析解:

μ_BL = [(τΣ)⁻¹ + Pᵀ Ω⁻¹ P]⁻¹ [(τΣ)⁻¹ Π + Pᵀ Ω⁻¹ Q]

τ 是先验缩放系数,常用 0.025 至 0.05。后验协方差:

Σ_BL = Σ + [(τΣ)⁻¹ + Pᵀ Ω⁻¹ P]⁻¹

把 (μ_BL, Σ_BL) 代回均值方差就得到 BL 最优组合。下面是最小实现。

import numpy as np

def black_litterman(w_mkt, Sigma, P, Q, Omega, delta=2.5, tau=0.05):
    Pi = delta * Sigma @ w_mkt
    A = np.linalg.inv(tau * Sigma) + P.T @ np.linalg.solve(Omega, P)
    b = np.linalg.solve(tau * Sigma, Pi) + P.T @ np.linalg.solve(Omega, Q)
    mu_bl = np.linalg.solve(A, b)
    Sigma_bl = Sigma + np.linalg.inv(A)
    return mu_bl, Sigma_bl

def mv_optimal(mu, Sigma, delta=2.5):
    w = np.linalg.solve(delta * Sigma, mu)
    return w / w.sum()

五点四、为什么 BL 在工程上更稳

把 BL 与 MV 放在一起对比,可以总结出三条工程优势:

  1. 观点的表达力极强。研究员不需要给每只标的预期收益,只要把自己有把握的相对观点写出来;其他维度的隐含收益由市场均衡填补。
  2. 观点的不确定度被显式建模。一个高置信度观点(Ω 小)会强烈拉动后验,一个低置信度观点(Ω 大)几乎不影响结果。这把研究员的「自信」量化进来,避免了 MV 里所有信号被同等对待的问题。
  3. 结果对协方差估计不那么敏感。即便 Σ 估计有偏,先验 Π = δΣw_mkt 的方向也会自动适配;后验则在先验基础上做有限调整。

五点五、Ω 的常见标定方法

Ω 是工程上最难定的部分,常见三种做法:

第一,He-Litterman 方法:Ω 对角元素取 P_k Σ P_k^T,相当于「该观点的固有方差」。最常用。 第二,Idzorek 方法:把研究员对每个观点的「置信度百分比」反推成 Ω。便于沟通但需要先解一个反问题。 第三,统一缩放:Ω = α · diag(P Σ Pᵀ),α 越大观点越弱。便于做敏感度分析。

无论哪种,Ω 都不能取零——零方差等价于「研究员永远正确」,会把先验完全压垮,变回纯观点驱动的组合。


六、Kelly 与对数效用

Kelly 准则在量化里被反复提起,但它和均值方差是什么关系,常常被讲糊。这一节把它放回完整图景里。

六点一、Kelly 公式的连续时间形式

对数效用 U(W) = log(W) 下,最大化 E[log(W_T)] 等价于按几何增长率最大化。在多资产连续时间设定下,最优权重满足:

w_kelly = Σ⁻¹ (μ − r_f · 1)

注意它没有归一化约束——w_kelly 的总和不等于 1,它代表对每个资产的最优杠杆比例,剩余资金放在无风险资产里。

如果对比切线组合:

w_tan = Σ⁻¹ (μ − r_f · 1) / (1ᵀ Σ⁻¹ (μ − r_f · 1))

可以看到 w_kelly 和 w_tan 在方向上完全一致,差别只在缩放:切线组合归一化到 1,Kelly 组合按对数效用最大化原则给出绝对杠杆。

六点二、Kelly 与方差最小化的关系

把 Kelly 公式两边同除以一个常数,会发现它就是 μ−r_f 在 Σ 度量下的「梯度方向」。这与均值方差在 λ → ∞ 时的极限组合(最小方差)方向相同——也就是说,所有「Σ⁻¹ μ 形式」的组合都共享同一个方向,它们的差别只在杠杆。这也解释了为什么 Kelly 在数学上对协方差估计噪声同样敏感:它继承了 Σ⁻¹ 的所有问题。

六点三、全 Kelly vs 分数 Kelly

全 Kelly 对应「无破产风险但回撤极大」的组合——MacLean、Thorp、Ziemba(2010)的经典实证:在长期增长率最优的同时,路径方差极大,drawdown 经常超过 50%。工程上几乎所有人都用 分数 Kelly,把杠杆设为全 Kelly 的 25% 至 50%。这相当于把对数效用换成 (1−γ) log(W) + γ·线性项 的混合,仍然解析可解。

分数 Kelly 的工程含义不是「保守」,而是「承认 μ 估计不准」——如果 μ 真值只有估计值的 1/2,全 Kelly 就会过度下注;用分数 Kelly 等价于先把 μ 缩水。

六点四、何时用 Kelly、何时用均值方差

经验法则:

两套框架不是互斥的,工程上常把 Kelly 当成「上限警戒线」用——任何策略的实际杠杆都不得超过该策略的全 Kelly 杠杆,否则就是在承担超额破产风险。


七、二次规划与凸优化求解

把前面六节的所有方法串起来看,绝大多数组合构建问题都可以写成同一个凸优化框架:

minimize    f₀(w)
subject to  Aw = b
            Cw ≤ d
            w ∈ X

其中 f₀ 通常是二次或拟二次函数,Aw = b 是预算与因子中性约束,Cw ≤ d 是杠杆与单只权重约束,X 是可能的整数或盒约束。这是凸优化求解器的领地,工程上首选 cvxpy 把数学描述与求解分离。

七点一、cvxpy 的均值方差实现

import cvxpy as cp
import numpy as np

def mv_optimize(mu, Sigma, w_prev=None,
                gross_lev=2.0, w_max=0.05,
                turnover=0.5, tc=0.0010,
                lam_risk=10.0, lam_l2=0.0,
                industry_matrix=None, industry_band=0.01):
    N = len(mu)
    w = cp.Variable(N)

    objective = mu @ w - lam_risk * cp.quad_form(w, cp.psd_wrap(Sigma))
    if lam_l2 > 0:
        objective -= lam_l2 * cp.sum_squares(w)
    if w_prev is not None and tc > 0:
        objective -= tc * cp.norm1(w - w_prev)

    constraints = [
        cp.sum(w) == 1,
        cp.norm1(w) <= gross_lev,
        w >= -w_max,
        w <= w_max,
    ]
    if w_prev is not None and turnover is not None:
        constraints.append(cp.norm1(w - w_prev) <= turnover)
    if industry_matrix is not None:
        constraints.append(cp.abs(industry_matrix @ w) <= industry_band)

    prob = cp.Problem(cp.Maximize(objective), constraints)
    prob.solve(solver=cp.CLARABEL)
    return w.value, prob.status

几个工程要点:

七点二、cvxpy 的风险平价实现

风险平价的凸形式(Spinu 2013)可以直接用 cvxpy 写:

import cvxpy as cp
import numpy as np

def rp_optimize(Sigma):
    N = Sigma.shape[0]
    y = cp.Variable(N, pos=True)
    objective = 0.5 * cp.quad_form(y, cp.psd_wrap(Sigma)) - cp.sum(cp.log(y)) / N
    prob = cp.Problem(cp.Minimize(objective))
    prob.solve(solver=cp.CLARABEL)
    w = y.value / y.value.sum()
    return w

这个实现对 N ≤ 200 都能在毫秒内求解;更大规模时 L-BFGS-B 直接迭代往往更快。

七点三、交易费用与冲击模型

线性交易费 tc · ‖w − w_prev‖₁ 是仿射成本,可以直接写进目标。冲击成本 通常是 3/2 次方形式(Almgren-Chriss):

impact = η · |Δw|^{3/2}

cvxpy 可以用 cp.power(cp.abs(dw), 1.5) 表示,但仍是凸的(因为 1.5 ≥ 1)。在百分位频率(日度、小时)下,3/2 次方接近线性,工程上常用线性近似简化。

更精细的冲击模型把每只标的的成交量 ADV 考虑进来:

impact_i = η_i · |Δw_i| · |Δw_i × NAV / ADV_i|^{1/2}

NAV 是组合规模;这把头寸大小、流动性、成交量三者绑在一起,直接告诉优化器「再大就走不动了」。

七点四、参数化与超参标定

上面这些 λ、γ、tc 系数从哪里来?工程上有两种做法。

第一,单一目标拟合:固定一个 λ,回测一年,记录夏普率与换手;扫描 λ 网格,挑夏普率次之但换手最低的那个点。这种方式简单但易过拟。

第二,双目标 Pareto 前沿:把 (夏普, 换手) 当二元目标,在 λ 网格上画散点,挑 Pareto 前沿上「换手率每增加 1% 带来夏普率提升不到 0.02」的拐点。这种方式更稳健,避免过度优化夏普。

无论哪种,参数标定都必须在样本外做,并且每隔半年至一年重新标一次。

七点五、warm start 与求解延迟

日频组合的优化问题在两次再平衡之间通常只有部分参数变化(μ 变、Σ 微调、w_prev 变),约束几乎不变。这是 warm start 的天然场景:把上次的最优 w 作为初始解给求解器,迭代次数通常减少一半以上。cvxpy 通过 prob.solve(warm_start=True) 启用,OSQP 与 CLARABEL 都支持。

工程实践中,N = 500 的均值方差 + 行业中性 + 换手约束,warm start 后单次求解 50ms 以内;冷启动可能需要 200ms 以上。在分钟频或秒频策略下,warm start 是必选项。


八、稳健性与上线

最后一节回到工程现场。前面给了七节方法,但真正决定一个组合策略能不能长期运行的,是这一节里讲的稳健性诊断与上线漂移分析。

八点一、Ledoit-Wolf 收缩

Ledoit 与 Wolf(2004)证明了一个非常实用的事实:把样本协方差 S 与一个结构化目标 F(常用 F = (tr(S)/N) · I)做线性收缩 Σ̂ = (1−α) S + α F,存在一个最优 α 使期望 Frobenius 误差最小,并且这个 α 有解析表达式。scikit-learn 直接提供:

from sklearn.covariance import LedoitWolf
import numpy as np

rng = np.random.default_rng(0)
N, T = 50, 250
A = rng.normal(0, 1, (N, N))
true_cov = (A @ A.T) / N + 0.01 * np.eye(N)
L = np.linalg.cholesky(true_cov)
R = rng.normal(size=(T, N)) @ L.T

lw = LedoitWolf().fit(R)
Sigma_lw = lw.covariance_
print("shrinkage alpha:", lw.shrinkage_)
print("condition number sample:", np.linalg.cond(np.cov(R.T)))
print("condition number LW:", np.linalg.cond(Sigma_lw))

在 N=50、T=250 的合成数据上,样本协方差的条件数往往在 1000 量级,收缩后降到 30 量级。条件数下降直接映射为 Σ⁻¹ 数值稳定性提升、最优权重对扰动鲁棒性提升。

Ledoit-Wolf 是工程上默认应该上的协方差估计器——它没有任何超参(α 自动选),代价只是几行代码,收益是显著降低均值方差的脆弱性。

八点二、Bootstrap 与组合不确定度

把 Michaud 的 resampling 实现成一个简单 bootstrap:

import numpy as np

def bootstrap_mv(returns, n_samples=200, lam=10.0, w_max=0.05):
    T, N = returns.shape
    rng = np.random.default_rng(42)
    weights = []
    for _ in range(n_samples):
        idx = rng.integers(0, T, size=T)
        R = returns[idx]
        mu = R.mean(axis=0)
        S = np.cov(R.T)
        w = np.linalg.solve(2 * lam * S, mu)
        w = np.clip(w, -w_max, w_max)
        w = w / np.abs(w).sum()
        weights.append(w)
    W = np.array(weights)
    return W.mean(axis=0), W.std(axis=0)

返回每只标的的权重均值和标准差。标准差大于均值的资产不应该上线——这种资产的权重在不同样本上完全不一致,本质是优化器在它身上没有共识。这是一种非常便宜的稳健性筛子。

八点三、Lookback 选择

协方差估计的 lookback 窗口长度是另一个关键超参。三个经验:

第一,短窗口偏好近期,能更快反映 regime 变化,但样本数少、协方差估计噪声大;长窗口偏好稳定,但 regime 切换后还在用旧数据。

第二,EWMA 是默认方案:指数加权移动平均给近期数据更高权重,半衰期 60 至 120 天通常合用。它比硬窗口更平滑,对 regime 切换的反应也更快。

第三,多窗口 ensemble 比单窗口更稳健:用 60、120、250 天三个窗口分别估协方差,按某种权重平均,结果在样本外几乎一定优于单一窗口。这背后的原理与机器学习的 bagging 一致。

八点四、上线漂移的因果分析

策略上线后表现衰减,不是新闻。重要的是把衰减归因到某个具体原因上,而不是笼统说「市场变了」。常见的漂移来源:

  1. 协方差结构变化:相关性矩阵的 top eigenvector 方向变了,原优化器在新方向上估计不足。
  2. 信号强度衰减:μ 估计偏高(前视偏差、过拟合)。
  3. 执行成本上升:流动性下降、冲击成本上行、交易速度被 PFOF 抢跑。
  4. 对手方变化:策略本身被反向交易、套利空间被竞争对手吃掉。
  5. 约束失效:行业中性的基准没跟上指数调整、风格因子定义过时。

工程上的因果分析手段:

这些指标都不复杂,但必须在策略上线第一天就建好——事后补的监控永远比上线时建的监控少看一眼东西。

八点五、风险预算与硬止损

最后一道防线不是优化器,而是风险预算。工程上推荐的做法:

  1. VaR 与 CVaR 双线监控:日度 95% VaR 超过预设值时报警;99% CVaR 是硬止损。
  2. 杠杆动态调整:组合的实际波动率连续 5 个交易日高于目标 1.5 倍时,自动按比例降杠杆。
  3. 单策略最大回撤:超过 8% 暂停该策略;超过 15% 强制清盘。
  4. 横切相关性:组合内多个子策略的相关性超过 0.5 时报警,避免「自以为分散,其实集中」。

这些数字不是公理,要按基金风格自己定。但有这些数字、有自动执行的脚本比数字本身更重要。

八点六、给工程师的几条务实建议

把这一节收尾,给一份可以贴在墙上的检查表:

  1. 协方差永远收缩:默认 Ledoit-Wolf,不要用裸样本协方差。
  2. 约束写齐再优化:单只上下限、行业中性、风格中性、换手上限、杠杆上限,五个一个都不能少。
  3. bootstrap 看权重稳定度:上线前必跑,权重标准差大于均值的资产剔除。
  4. warm start 默认开:再平衡是连续过程,不要每次冷启动。
  5. PnL attribution 每天看:残差异常立刻定位。
  6. 风险预算自动执行:人会犹豫,脚本不会。
  7. 小心 Σ⁻¹:能不直接求逆就不直接求逆,能用 cvxpy 描述就用 cvxpy。
  8. HRP 是免逆方案:当 N 大、T 小、协方差病态时,把 HRP 当主力。
  9. Kelly 当上限警戒:实际杠杆不超过全 Kelly 的 50%。
  10. 每半年重新标定:所有正则项、约束阈值、lookback 窗口都要定期复核。

八点七、一句话总结

如果只能记住一句话:组合构建的工程难度不在数学公式,而在把 μ 不准、Σ 病态、约束多变、成本非线性这四件事同时塞进同一个凸优化里,并且在样本外仍然稳定运行。所有具体方法——均值方差、风险平价、HRP、Black-Litterman、Kelly——都是这四件事在不同方向上的取舍。理解了这个取舍,就理解了所有方法的边界。


九、补充:常被忽视但工程上重要的细节

前面八节讲完了主线。这一节把一些「公式里看不到、实盘里反复踩」的细节集中铺一遍,每一条都来自真实工程现场的反复踩坑。

九点一、协方差是怎么坏的

工程上协方差矩阵失效有几种典型路径,能识别这些路径,比单纯换估计器有用得多。

第一种是奇异值压缩。在 N 接近 T 的临界区,最大特征值会被估高,最小特征值被估到接近零。Ledoit-Wolf 收缩本质上是把这条「特征值谱」拉回理论分布。诊断方法:把样本协方差的特征值排序画出,看尾部是否塌陷到极小数量级;如果是,必须收缩。

第二种是结构性 regime 切换。某天市场出现剧烈相关性变化(比如 2020 年 3 月、2008 年 10 月),过去三个月的相关性矩阵在新 regime 下完全失效。诊断方法:用 Kullback-Leibler 散度在两个相邻窗口的协方差之间做监控,KL 散度突变即报警。

第三种是因子塌缩。当全市场的所有标的同步下跌时,相关性矩阵几乎所有元素都接近 1,第一个特征值占总方差的 90% 以上。这是「所有分散都失效」的状态,组合层面只能靠现金或对冲工具,不能再靠资产间分散。

第四种是数据污染。某只标的有一天涨停或停牌后复牌出现一个 30% 的跳动,这条记录会把它对其他标的的协方差估计完全打偏。诊断方法:在估协方差前对收益做 winsorize,把 1% 和 99% 分位以外的值截断。

import numpy as np

def winsorized_cov(returns, lower=0.01, upper=0.99):
    R = returns.copy()
    lo = np.quantile(R, lower, axis=0)
    hi = np.quantile(R, upper, axis=0)
    R = np.clip(R, lo, hi)
    return np.cov(R.T)

这一行代码在很多情况下比换更复杂的估计器收益更大。

九点二、行业中性的「正确」写法

行业中性看起来是个简单线性约束,但实操上有三个坑。

第一坑:用绝对暴露还是相对暴露。行业暴露应当相对于基准(benchmark),而不是绝对暴露为零。如果基准本身有 30% 的金融行业,组合也应该有大约 30%;强行把组合的金融行业敞口压到 0,会引入巨大的相对跟踪误差。

# 正确写法:相对基准的行业暴露偏离
# Bind: 行业-标的指示矩阵 (K, N)
# w_bench: 基准权重 (N,)
relative_exposure = Bind @ (w - w_bench)
constraints.append(cp.abs(relative_exposure) <= industry_band)

第二坑:行业定义的版本一致性。GICS、SWS、Wind 各家的行业划分不一样,且每年都有重新分类。如果回测用的是当下的行业划分、上线却用旧划分,会有结构性偏差。工程上要保留每个时点的「point-in-time 行业归属」。

第三坑:多级行业。GICS 一级 11 个、二级 25 个、三级 74 个。一级中性约束太松,三级中性约束太紧。常见做法是一级硬约束、二级软约束(带惩罚项)、三级仅监控。

九点三、风险预算的另一种实现

风险平价是「等风险贡献」,但并非所有场景都要等。风险预算(risk budgeting) 把风险贡献设成任意目标比例 b:

RC_i / σ_p² = b_i,    Σ b_i = 1

例如要让股票占 60% 风险、债券占 30%、商品占 10%,b = [0.6, 0.3, 0.1]。求解形式与风险平价完全一致:

def risk_budget(Sigma, b):
    N = Sigma.shape[0]
    def obj(w):
        return 0.5 * w @ Sigma @ w - (b * np.log(w)).sum()
    def grad(w):
        return Sigma @ w - b / w
    w0 = b.copy()
    bounds = [(1e-6, None)] * N
    from scipy.optimize import minimize
    res = minimize(obj, w0, jac=grad, method='L-BFGS-B', bounds=bounds)
    return res.x / res.x.sum()

风险预算比风险平价更灵活,并且在「明知某些资产风险溢价更高」时是更合理的折中——不必预测期望收益,只需要预测「风险溢价的相对排序」。

九点四、CVaR 优化

均值方差用方差度量风险,对厚尾分布敏感度低。Rockafellar 与 Uryasev(2000)证明了 CVaR(条件 VaR)可以写成线性规划:

minimize    α + (1/(1−β)T) Σₜ uₜ
subject to  uₜ ≥ −rₜᵀw − α
            uₜ ≥ 0
            其它组合约束

β 是置信水平(常用 0.95 或 0.99),rₜ 是第 t 期收益。下面是 cvxpy 实现。

import cvxpy as cp
import numpy as np

def cvar_optimize(returns, mu, beta=0.95, gross_lev=2.0, w_max=0.05):
    T, N = returns.shape
    w = cp.Variable(N)
    u = cp.Variable(T, nonneg=True)
    alpha = cp.Variable()

    cvar = alpha + cp.sum(u) / ((1 - beta) * T)
    constraints = [
        u >= -returns @ w - alpha,
        cp.sum(w) == 1,
        cp.norm1(w) <= gross_lev,
        w >= -w_max,
        w <= w_max,
    ]
    objective = mu @ w - 5.0 * cvar
    prob = cp.Problem(cp.Maximize(objective), constraints)
    prob.solve(solver=cp.CLARABEL)
    return w.value

CVaR 优化的实战意义:在加密资产、新兴市场、信用产品上,收益分布厚尾明显,方差严重低估真实风险;CVaR 直接对尾部建模,避免了「平时看起来稳、出事一次回吐三年」的策略形态。

九点五、整数约束与混合整数 QP

实盘交易中常见两类离散约束:

第一,最小成交单位。每只标的最少买卖 100 股 / 1 手 / 0.01 BTC。这把权重变成离散变量。

第二,最少持仓只数 / 最多持仓只数。组合需要至少持有 30 只、至多 100 只标的,这是 cardinality 约束。

写成混合整数二次规划(MIQP):

minimize    wᵀΣw − μᵀw
subject to  wᵢ = kᵢ × lot_size × p_i / NAV,    kᵢ ∈ ℤ
            zᵢ ∈ {0, 1}
            wᵢ ≤ M zᵢ
            Σ zᵢ ≤ K_max
            Σ zᵢ ≥ K_min

MIQP 求解通常用 Gurobi 或 Mosek 商业求解器;开源 cbc + cvxpy 在 N ≤ 200 时可用,更大就慢。工程实践:日频组合不要直接解 MIQP,而是先解凸版本,再用启发式算法离散化(最大权重四舍五入到 lot、对小权重 thresholding)。这种两步法在 99% 场景下夏普率与精确解差异 < 0.05,求解时间却快两个数量级。

九点六、Black-Litterman 的一个完整算例

把 BL 串起来跑一个 5 资产的小算例,能更清楚看到先验、观点、后验之间的演化。

import numpy as np

# 5 资产:股票、债券、商品、黄金、加密
w_mkt = np.array([0.55, 0.30, 0.05, 0.07, 0.03])
sigma = np.array([0.16, 0.05, 0.18, 0.15, 0.70])
rho = np.array([
    [1.00, -0.10, 0.20, 0.10, 0.30],
    [-0.10, 1.00, 0.05, 0.20, 0.05],
    [0.20, 0.05, 1.00, 0.30, 0.20],
    [0.10, 0.20, 0.30, 1.00, 0.15],
    [0.30, 0.05, 0.20, 0.15, 1.00],
])
Sigma = np.outer(sigma, sigma) * rho

# 先验:市场隐含
delta = 2.5
Pi = delta * Sigma @ w_mkt
print("Implied returns Pi:", (Pi * 100).round(2))

# 观点:股票相对债券超额 3%;加密绝对 25%
P = np.array([
    [1, -1, 0, 0, 0],
    [0, 0, 0, 0, 1],
])
Q = np.array([0.03, 0.25])
Omega = np.diag([0.0001, 0.05])  # 第二条观点不确定度更大

tau = 0.05
A = np.linalg.inv(tau * Sigma) + P.T @ np.linalg.solve(Omega, P)
b = np.linalg.solve(tau * Sigma, Pi) + P.T @ np.linalg.solve(Omega, Q)
mu_bl = np.linalg.solve(A, b)
Sigma_bl = Sigma + np.linalg.inv(A)

w_bl = np.linalg.solve(delta * Sigma_bl, mu_bl)
w_bl = w_bl / w_bl.sum()
print("Posterior mu:", (mu_bl * 100).round(2))
print("BL weights:", (w_bl * 100).round(2))

跑出来的 BL 权重:股票相对市场基准会上调若干个百分点(因为观点 1 强烈认为股票超额债券 3%、置信度高),加密会上调但有限(因为观点 2 不确定度大),其他资产微调以保持市场均衡。这正是 BL 的优雅之处——观点强度与不确定度共同决定调整幅度,研究员的「自信」被量化进权重里。

九点七、回测里的常见前视偏差

组合构建在回测中比信号生成更容易引入前视偏差,三个最常见的:

第一,协方差估计窗口越界。在第 t 天的组合优化里用了 [t−250, t] 的数据估协方差,但忘记把第 t 天本身的收益剔除——这相当于让今天的收益参与今天的协方差估计,未来信息泄露。正确做法:用 [t−250, t−1] 的窗口。

第二,基准权重的时间错位。行业中性约束的基准权重 w_bench 应该是 t 时点的实时权重,而不是回测时使用的「最新一份」基准成分股。这个错误在指数大幅调仓的时点尤其明显。

第三,交易成本的过度乐观。回测里假设 5bp 滑点,实盘里 30bp 才打得满;这个差距完全足以让一个看起来夏普 1.5 的策略在实盘亏钱。工程上推荐在回测里用「悲观成本」(实测的 90% 分位)做敏感度。

九点八、上线前的最后五道检查

写一份 checklist,作为任何组合优化策略上线前的最低门槛:

  1. Σ 估计是否经过收缩或 winsorize
  2. 所有约束(行业、风格、单只、换手、杠杆)是否都已写进优化器并在样本外稳定
  3. bootstrap 权重稳定度是否通过(每只标的权重 std/mean < 1)?
  4. 样本外回测的夏普率是否在悲观成本下仍 > 1
  5. 风险预算与硬止损是否已写进自动化脚本

任何一条不通过,都不要上线。这五条不是公理,而是从无数失败案例里压榨出来的最低线。

九点九、端到端回测骨架

把前面所有要点串成一个最小可跑的回测骨架,便于读者直接迁移到自己的工程里。这段代码不依赖任何收费数据源,所有输入都是合成数据。

import numpy as np
from sklearn.covariance import LedoitWolf

def run_backtest(prices, lookback=120, rebalance=20,
                 method='mv', lam_risk=10.0,
                 w_max=0.05, gross_lev=1.5,
                 turnover_cap=0.4, cost_bps=5.0):
    """简化端到端回测:返回每日组合收益与每次再平衡后的权重。"""
    T, N = prices.shape
    returns = np.diff(np.log(prices), axis=0)
    w_prev = np.zeros(N)
    pnl = []
    weights_log = []

    import cvxpy as cp

    for t in range(lookback, len(returns)):
        if (t - lookback) % rebalance == 0:
            window = returns[t - lookback:t]
            mu = window.mean(axis=0)
            Sigma = LedoitWolf().fit(window).covariance_

            w = cp.Variable(N)
            if method == 'mv':
                obj = mu @ w - lam_risk * cp.quad_form(w, cp.psd_wrap(Sigma))
            elif method == 'minvar':
                obj = -cp.quad_form(w, cp.psd_wrap(Sigma))
            else:
                raise ValueError(method)
            obj -= (cost_bps / 10000) * cp.norm1(w - w_prev)

            cons = [
                cp.sum(w) == 1,
                cp.norm1(w) <= gross_lev,
                w >= -w_max, w <= w_max,
                cp.norm1(w - w_prev) <= turnover_cap,
            ]
            cp.Problem(cp.Maximize(obj), cons).solve(solver=cp.CLARABEL)
            if w.value is None:
                w_new = w_prev
            else:
                w_new = w.value
            cost = (cost_bps / 10000) * np.abs(w_new - w_prev).sum()
            w_prev = w_new
            weights_log.append((t, w_new.copy()))
        else:
            cost = 0.0

        pnl_today = w_prev @ returns[t] - cost
        pnl.append(pnl_today)

    pnl = np.array(pnl)
    sharpe = pnl.mean() / pnl.std() * np.sqrt(252)
    maxdd = (np.maximum.accumulate(pnl.cumsum()) - pnl.cumsum()).max()
    return {
        'pnl': pnl,
        'sharpe': sharpe,
        'maxdd': maxdd,
        'weights': weights_log,
    }

把这段骨架挂上自己的数据后,可以做的最小实验是把 method'mv''minvar' 之间切换,再分别替换成风险平价、HRP、BL,统一比较 sharpe 与 maxdd,得到一份你自己环境下的「方法基线表」。这张表比任何论文里的实证更值得你信任。

九点十、PnL 归因的最小实现

下面这段代码把组合每日 PnL 拆成 alpha、行业、风格、残差四块,便于上线后做日级监控。

import numpy as np

def pnl_attribution(w, returns_today, factor_loadings, factor_returns):
    """
    w: 权重 (N,)
    returns_today: 当日各标的收益 (N,)
    factor_loadings: 因子暴露矩阵 (N, K)
    factor_returns: 当日因子收益 (K,)
    """
    factor_pnl = (factor_loadings.T @ w) * factor_returns
    total_pnl = w @ returns_today
    residual = total_pnl - factor_pnl.sum()
    return {
        'total': total_pnl,
        'factor': dict(zip(['mkt', 'size', 'value', 'momentum', 'vol'], factor_pnl)),
        'residual': residual,
    }

每天把 attribution 结果打到看板,连续 5 天残差异常(超过 ±2σ)就触发人工复盘。这是组合层面最便宜也最有效的一道监控。


十、参考文献

论文与书

  1. Markowitz H. Portfolio Selection. Journal of Finance, 1952, 7(1): 77-91.
  2. Sharpe W F. Capital Asset Prices: A Theory of Market Equilibrium under Conditions of Risk. Journal of Finance, 1964, 19(3): 425-442.
  3. Black F, Litterman R. Global Portfolio Optimization. Financial Analysts Journal, 1992, 48(5): 28-43.
  4. Michaud R O. The Markowitz Optimization Enigma: Is ‘Optimized’ Optimal? Financial Analysts Journal, 1989, 45(1): 31-42.
  5. Ledoit O, Wolf M. A Well-Conditioned Estimator for Large-Dimensional Covariance Matrices. Journal of Multivariate Analysis, 2004, 88(2): 365-411.
  6. Ledoit O, Wolf M. Honey, I Shrunk the Sample Covariance Matrix. Journal of Portfolio Management, 2004, 30(4): 110-119.
  7. Maillard S, Roncalli T, Teïletche J. The Properties of Equally Weighted Risk Contribution Portfolios. Journal of Portfolio Management, 2010, 36(4): 60-70.
  8. Spinu F. An Algorithm for Computing Risk Parity Weights. SSRN, 2013.
  9. López de Prado M. Building Diversified Portfolios that Outperform Out of Sample. Journal of Portfolio Management, 2016, 42(4): 59-69.
  10. Idzorek T. A Step-By-Step Guide to the Black-Litterman Model. Ibbotson Associates, 2005.
  11. He G, Litterman R. The Intuition Behind Black-Litterman Model Portfolios. Goldman Sachs Investment Management Research, 1999.
  12. Kelly J L. A New Interpretation of Information Rate. Bell System Technical Journal, 1956, 35(4): 917-926.
  13. MacLean L C, Thorp E O, Ziemba W T (eds.). The Kelly Capital Growth Investment Criterion. World Scientific, 2010.
  14. Almgren R, Chriss N. Optimal Execution of Portfolio Transactions. Journal of Risk, 2001, 3(2): 5-39.
  15. Boyd S, Vandenberghe L. Convex Optimization. Cambridge University Press, 2004.
  16. Roncalli T. Introduction to Risk Parity and Budgeting. Chapman & Hall / CRC, 2013.
  17. Meucci A. Risk and Asset Allocation. Springer, 2005.
  18. Grinold R C, Kahn R N. Active Portfolio Management. 2nd ed. McGraw-Hill, 2000.
  19. Bouchaud J P, Potters M. Theory of Financial Risk and Derivative Pricing. 2nd ed. Cambridge University Press, 2003.
  20. de Prado M L. Advances in Financial Machine Learning. Wiley, 2018.

软件与文档

  1. cvxpy 项目文档. https://www.cvxpy.org/
  2. CLARABEL.rs 求解器. https://oxfordcontrol.github.io/ClarabelDocs/
  3. OSQP 求解器. https://osqp.org/
  4. scikit-learn covariance 模块. LedoitWolf, OAS. https://scikit-learn.org/
  5. PyPortfolioOpt 文档. https://pyportfolioopt.readthedocs.io/
  6. Riskfolio-Lib 文档. https://riskfolio-lib.readthedocs.io/

导航:上一篇 【量化交易】加密资产策略:永续、资金费率、跨所套利 | 下一篇 【量化交易】风险模型:Barra、因子风险归因、压力测试

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-05-01 · quant

【量化交易】量化交易全景:从信号到订单的工程链路

量化交易不是策略写得好就能赚钱,更难的是把数据、特征、因子、信号、组合、执行、风控、复盘这八段链路在工程上连成一条不漏数据、不串时间、不丢订单的流水线。本文是【量化交易】系列的总目录与读图,给出八段链路的输入输出、失败模式、不变量清单,并用研究流程图把从一个想法到一笔实盘订单之间所有该过的卡点串起来。

2026-05-01 · quant

【量化交易】市场结构:交易所、做市商、暗池、ECN

系统梳理全球市场结构(Market Structure)的工程图景:从证券交易所、衍生品交易所、加密交易所,到做市商、暗池、ECN/ATS,再到 Maker-Taker 收费、PFOF、Reg NMS 与 MiFID II 的监管影响;给出量化策略选择交易场所的判断框架与基于 ccxt 的多交易所行情聚合代码。

2026-05-01 · quant

【量化交易】市场微结构:订单簿、价差、流动性、冲击

系统讲解市场微结构的核心概念与可计算工具:限价订单簿的数据模型、报价/有效/已实现价差、Roll 模型、四维流动性度量、Kyle's lambda、订单流不平衡(OFI)、Almgren-Chriss 框架下的临时与永久冲击、PIN 与 VPIN、Hawkes 过程,并给出基于 polars 的 L2 增量处理与系数估计代码。

2026-05-01 · quant

【量化交易】订单类型与执行语义:限价、市价、IOC、FOK、冰山

把 Limit、Market、IOC、FOK、Iceberg、Stop、MOO/MOC 这些常被混为一谈的订单类型还原为价格、数量、时效、可见性、触发五个独立维度,并对照 A 股、港股、美股、CME、Binance 五个市场的实际语义差异,给出量化系统中的订单工厂、状态机与风控前置校验的工程实现。


By .