某沪深300指增公募的高级量化研究员,把 4.4.1 的均值方差闭式解 w* = (1/gamma) Sigma^{-1} (mu - lambda * 1) 直接套到她管理的 30 只 CSI 300 成分股核心仓上。闭式解给出的结果:招商银行 600036 做空 -300%、宁德时代 300750 多头 +250%、组合 78% 的仓位扎堆在前三只动量名上。她的产品合同写得很清楚——仅做多、单只股票不超过 5% 主动权重、相对中信一级行业中性、主动份额(active share)不低于 30%,并且要满足 CSRC《公开募集证券投资基金运作管理办法》的双十规则(单只股票不超过 10% NAV、单一发行人不超过 10% NAV)以及流动性新规对 SSE / SZSE 上市公司停牌、ST、退市标的的 15% 上限。合规岗看一眼直接驳回。从闭式解到合规真能放行的组合,这堂课讲的正是这条路:把闭式解换成约束二次规划(constrained QP),用 cvxpy + OSQP 求解,并通过影子价格读出究竟是哪条约束在吃掉 alpha。
术语对齐表
本课新增的核心术语,与 data/glossaries/quant-glossary.yaml 标准译名一致:组合优化(portfolio optimization)、均值方差优化(mean-variance optimization,这里特指带约束的版本)、协方差矩阵(covariance matrix,,默认调用 4.4.1 L4 的 Ledoit-Wolf 收缩估计)、因子暴露(factor loading,行业暴露矩阵 B_sector 与风格暴露矩阵 B_style)、跟踪误差(tracking error,以 TE 预算约束进入 QP)、Barra 模型(Barra model,提供 B_style 作为风格中性的输入)。后续讨论统一采用上述标准译名。
八条机构约束的标准顺序
下面是 Inline-code listing,按固定顺序列出八条约束:
constraint_1_budget: 1.T @ w == 1 (or == 0 for market-neutral)
constraint_2_long_only: w >= 0 (or leverage cap ||w||_1 <= L for long-short)
constraint_3_sector_neutral: B_sector.T @ (w - w_B) == 0
constraint_4_style_neutral: B_style.T @ (w - w_B) == 0 on non-target style factors only
constraint_5_position_limit: cp.abs(w - w_B) <= b_max (typically 0.02-0.05 for active mutual funds)
constraint_6_concentration: sum of top-10 weights <= C_max (typically 0.40 under ICA 1940)
constraint_7_active_share: 0.5 * cp.sum(cp.abs(w - w_B)) >= AS_min (typically 0.30 for active managers, per Cremers-Petajisto 2009)
constraint_8_liquidity: w * V_0 <= L_factor * ADV (typically L_factor = 0.05-0.20 of 30-day ADV)
规则:(1)-(2) are about feasibility; (3)-(4) are about risk-control; (5)-(7) are about diversification + active-management; (8) is about implementability。
约束 (1) 是预算约束(budget)。满仓多头基金权重和为 1;市场中性多空账户权重和为 0(多头由空头自融资);允许留现金的账户写成 1.T @ w <= 1,差额由现金吸收。约束 (2) 是仅做多(long-only)w >= 0——零售公募基金、险资 / 社保、主权财富基金,以及任何按合同不能融券做空的账户,都受这条约束。多空对冲 / 私募基金把它换成杠杆 / 1-范数约束 ||w||_1 <= L:L = 1.6 对应 130/30 加强主动账户;L = 3.0 对应 200/100;L = 6+ 对应典型 stat-arb 账户。约束 (3) 是行业中性(sector-neutral),通过 N×K 的中信一级行业暴露矩阵 B_sector(中信一级 30 个行业)把对基准的主动行业暴露 B_sector.T @ (w - w_B) 压到零,消除被动行业暴露遮蔽 alpha 信号的问题。约束 (4) 是风格中性(style-neutral),针对 Barra 模型 的非目标风格因子做中性化——纯动量账户会把规模、价值、质量、低波这些非目标风格压到零,只暴露动量。
约束 (5) 是头寸限制(position limit)|w_i - w_B_i| <= b_max——分散型公募基金通常 ±2%-5% 主动权重;管理规模特大的基金常用 ±0.5%-1%;集中度高的选股型私募基金可以拉到 ±10% 甚至更大。约束 (6) 是集中度限制(concentration limit)前十大持仓累计不超过 40%——CSRC 双十规则的延伸,落实到 ICA 1940 风格的「分散化基金」分类逻辑上,A 股公募还要叠加单一发行人不超过 10% NAV 的硬上限。约束 (7) 是主动份额(active share),来自 Cremers-Petajisto(2009)《How active is your fund manager?》(RFS):0.5 * sum_i |w_i - w_B_i| >= AS_min,典型取值——指数增强 30%、高确信度主动 60%-80%、完全无约束 100%。约束 (8) 是流动性约束(liquidity)w_i * V_0 <= L_factor * ADV_i:单只股票美元(或人民币)头寸不超过其 30 日日均成交额(ADV)的 5%-20%,使得换仓单日能消化,不被迫付额外的市场冲击成本。
一旦不等式约束生效,闭式解就失效
4.4.1 L2 的无约束 MV 闭式最优 w* = (1/gamma) Sigma^{-1} (mu - lambda * 1) 是「二次目标 + 单条等式约束 1.T @ w = 1」的解析解,经由拉格朗日乘子法即可求出。一旦 (2)–(8) 中任何一条不等式被激活,最优点就不再落在无约束目标的内部驻点,而是落在可行域的边界上。KKT(Karush-Kuhn-Tucker)条件接替简单的拉格朗日方程:互补松弛(complementary slackness)决定哪些不等式被激活,最优点的搜索变成了对「激活集」(active set)的组合搜索。没有闭式解可写——数值优化是必经之路。
KKT 条件的标准顺序
下面是 Inline-code listing,按固定顺序列出五项 KKT 条件:
kkt_1_stationarity: mu - gamma * Sigma @ w + A_eq.T @ nu + A_ineq.T @ mu_lagrange == 0 (gradient of the Lagrangian)
kkt_2_primal_feasibility: A_eq @ w == b_eq AND A_ineq @ w <= b_ineq
kkt_3_dual_feasibility: mu_lagrange >= 0 (inequality multipliers non-negative; nu unrestricted for equalities)
kkt_4_complementary_slackness: mu_lagrange[i] * (A_ineq[i] @ w - b_ineq[i]) == 0 for every i (binding => positive multiplier; non-binding => zero multiplier)
kkt_5_shadow_price: mu_lagrange[i] = marginal increase in objective value if RHS b_ineq[i] is loosened by one unit
规则:reading the shadow prices identifies the most-expensive constraint — the one whose loosening would generate the most alpha。
平稳性条件 写作
即每只资产的边际期望收益 ,要等于边际风险代价 加上所有被激活约束在该资产方向的边际代价之和;后者按各自的影子价格(shadow price)加权。原对偶可行性把迭代点钉在可行多面体内;对偶可行性强制不等式乘子非负——放松一条被激活约束只可能让目标更好、不会更差;互补松弛把未激活不等式的乘子钉为零。影子价格 是目标值对约束 右端常数松弛一个单位的边际响应,也就是说一个基点的约束松弛能换来多少目标值(以效用单位计)的增量。PM 真正用得上的诊断是:把所有影子价格按绝对值排序,排在最前面的那条约束,就是「如果机构客户允许放松,能换最多 alpha」的那条。
求解器栈:cvxpy + OSQP / SCS / Mosek
cvxpy(Diamond-Boyd 2016, JMLR)是 disciplined convex programming 的 Python 前端:你用高级语法把 QP 写出来,cvxpy 验证 DCP 规则、做标准化、再分派给后端求解器。OSQP(Stellato 等 2020, MPC)是开源 ADMM 求解器,也是 cvxpy 求解 QP 的默认后端,典型 500 只名仓的约束 MV QP 在 0.1-0.5 秒内即可解出,可扩展到 ~10000 个变量。SCS(O'Donoghue 等 2016)是分裂式锥优化求解器,也是 cvxpy 解 SOCP 的默认后端——当你加入二次的跟踪误差约束 (w - w_B).T @ Sigma @ (w - w_B) <= TE_max^2 时,它就变成二阶锥约束。Mosek 是机构标准的内点法求解器,商业授权(对学术免费),是大型私募 / 公募生产环境的事实标准。scipy.optimize.minimize 配 SLSQP 是小规模 QP(< 100 变量)的兜底方案,速度更慢、稳定性更弱。
实现:solve_constrained_mv
import cvxpy as cp
import numpy as np
def solve_constrained_mv(
mu: np.ndarray,
Sigma: np.ndarray,
w_B: np.ndarray,
B_sector: np.ndarray,
gamma: float = 2.0,
b_max: float = 0.05,
AS_min: float = 0.30,
solver: str = 'OSQP',
) -> dict:
# mu:N 维预期收益向量;Sigma:N x N 协方差(用 4.4.1 L4 的 Ledoit-Wolf 收缩估计)。
# w_B:N 维基准权重;B_sector:N x K 行业暴露矩阵(中信一级 30 行业)。
# gamma=2.0 典型风险厌恶;b_max=0.05 单名 ±5% 主动权重;
# AS_min=0.30 主动份额下限 30%;solver='OSQP' 默认 QP 后端。
N = mu.shape[0]
w = cp.Variable(N)
objective = cp.Maximize(mu @ w - gamma / 2 * cp.quad_form(w, Sigma))
constraints = [
cp.sum(w) == 1,
w >= 0,
B_sector.T @ (w - w_B) == 0,
cp.abs(w - w_B) <= b_max,
0.5 * cp.sum(cp.abs(w - w_B)) >= AS_min,
]
prob = cp.Problem(objective, constraints)
prob.solve(solver=getattr(cp, solver))
assert prob.status == 'optimal', f'solver failed: {prob.status}'
w_optimal = w.value
utility = float(prob.value)
shadow_prices = [c.dual_value for c in constraints]
herfindahl = float(np.sum(w_optimal ** 2))
max_abs_weight = float(np.max(np.abs(w_optimal)))
active_share = float(0.5 * np.sum(np.abs(w_optimal - w_B)))
return {
'w_optimal': w_optimal,
'utility': utility,
'shadow_prices': shadow_prices,
'herfindahl': herfindahl,
'max_abs_weight': max_abs_weight,
'active_share': active_share,
'status': prob.status,
}
函数签名——参数名 mu、Sigma、w_B、B_sector、gamma、b_max、AS_min、solver,默认值 gamma=2.0、b_max=0.05、AS_min=0.30、solver='OSQP',以及返回 dict 的键名,与美国版按字节对齐,只有注释翻译。主动份额约束 0.5 * cp.sum(cp.abs(w - w_B)) >= AS_min 在原始形式上是非凸(对 L1 范数的下界约束),但 cvxpy 在可行域非空时会把它识别为反凸约束并交给可处理的重写——实务上,行业中性 + 预算约束 + 仅做多的组合,主动份额天然为正,只在基准本身分散度极低时才有可能让该约束失活。生产部署时还会显式补充双侧位限制对 w >= w_B - b_max 和 w <= w_B + b_max,集中度上限 cp.sum_largest(w, 10) <= 0.40(CSRC 双十规则的前十大持仓上限),以及流动性下限 cp.multiply(w, V_0) <= L_factor * ADV,其中 V_0 是基金净资产,ADV 是按人民币计的 30 日日均成交额——每条约束在 A_ineq 中加一行、暴露一个独立的影子价格供事后诊断使用。
读影子价格:哪条约束最贵?
Formula Explorer
x^2prob.solve 返回 optimal 之后,constraints[k].dual_value 给出第 k 条约束的 KKT 乘子。按绝对值排序,排在最前面的就是最贵的那条。在 30 只 CSI 300 成分股、2020-2023 月度回测、动量信号驱动的工作示例中,常见结论:(i) 仅做多约束在截面分散度高、几个标的有强负 alpha 信号时最贵——优化器本想做空,被强制压到 0,年化 alpha 让出 50-100 bp;(ii) 行业中性约束在某个行业(2020-2021 的新能源 / 半导体,2022 的银行 / 煤炭)出现强 alpha 信号、却不允许超配时最贵;(iii) 头寸限制约束在动量信号集中在 2-3 只高确信度标的(典型如「茅指数 / 宁组合」时段)时最贵——±5% 的位限制把仓位逼向低确信度名,年化 alpha 让出 30-60 bp。影子价格诊断是 PM 在向机构客户申请「修改委托合同」之前的第一站。
机构约束目录:按委托类型分
长期主动公募基金(CSRC《公开募集证券投资基金运作管理办法》+《公募证券投资基金流动性风险管理规定》):仅做多、单名 ±3%-5% 主动权重、相对沪深300 / 中证500 / 中证800 行业中性或行业倾斜、主动份额 30%-70%、TE 预算 4%-6%、停牌 / ST / 退市等流动性较差标的上限 15%、双十规则单只 10% / 单一发行人 10% 双硬约束。代表产品:易方达蓝筹精选(张坤)、兴全合润(谢治宇)、广发多策略系列(刘格菘)。
多空对冲 / 私募基金:杠杆约束 130/30(||w||_1 <= 1.6)或 200/100(||w||_1 <= 3.0)、市场中性预算 1.T @ w == 0 或 0.0-0.2、有时叠加中信一级行业中性、不设主动份额、TE 相对零基准或 alpha 叠加;融券保证金按中证金融 / 中信 / 中金等券商的风险等级算,小私募一般只能拿到 1.5:1 的总杠杆。中基协 AMAC 对私募的合格投资者门槛和持仓集中度有自律约束;私募基金管理人登记和基金备案办法是底层合规框架。
养老金 / 险资 / 社保:仅做多、主动份额可放到 40%-100%(相比公募更宽松)、集中度限制更严(常用单名 5% 上限,严于公募的 10%)、多年期持有期、低换手。社保基金理事会、险资保险资产管理 AMC 是国内代表;每一种委托都自带「资产 - 负债久期匹配」要求,影响整体的风险预算。
主权财富基金——挪威 GPFG(Government Pension Fund Global)是「低主动份额、低 TE」类型的典型:主动份额 5%-15%、严格的 GICS / 中信行业 + 国别中性、TE 预算 0.5%-1%、复制战略资产配置基准并在边际上倾斜。委托由挪威财政部下设的 Norges Bank Investment Management(NBIM)运作;中投公司 CIC、SAFE Investment Company 是中国的对应实体。
工作示例:四个递进约束的组合
下面 Fenced Python 块在 china_a_30_universe.csv 给出的 30 只 CSI 300 行业龙头基础上调用 solve_constrained_mv(贵州茅台 600519、平安银行 000001、宁德时代 300750、比亚迪 002594、招商银行 600036、中国平安 601318、美的集团 000333、恒瑞医药 600276、伊利股份 600887、隆基绿能 601012、 ……共 30 名),协方差用 4.4.1 L4 助手返回的 Sigma_LW(Ledoit-Wolf 收缩),信号用截面 12-1 动量打分作 mu_hat,基准权重 w_B 取 510300 沪深300 ETF 的成分股活跃权重并按 30 名子集重新归一。预期单调权衡:效用 (a) > (b) > (c) > (d),Herfindahl 与单名最大绝对权重也单调递减——约束越多,效用越小,但分散度越高、可实施性越强、合规越容易过。2021 年 8 月到 2024 年 2 月「茅指数 / 宁组合」的动量去化阶段是热门案例:很多 CSI 300 增强公募在那段时间因为 ±5%-7% 的位限制不严、前十大持仓累计达 30%-40%,直接被动量回撤吃干。把 (d) 组合的影子价格排序,基本上排在最前面的就是动量最强的名上的位限制约束。
练习
Exercise
在 30 只 CSI 300 成分股(来自 china_a_30_universe.csv)、2020-2023 月度回测的样本内区间,给定 (i) 4.4.1 L4 助手返回的 Ledoit-Wolf 收缩协方差 Sigma_LW,(ii) 每个再平衡日的截面 12-1 动量信号 mu_hat,(iii) 基准权重 w_B(510300 沪深300 ETF 活跃成分股权重按 30 名子集重新归一),(iv) 中信一级行业暴露矩阵 B_sector(30 名映射到约 12-15 个中信一级行业):通过 solve_constrained_mv 求解四个递进约束的 MV 问题。(a) 无约束闭式 w_a = (1/gamma) @ np.linalg.solve(Sigma_LW, mu_hat - lambda_eq * np.ones(N)),其中 lambda_eq 选得使 np.sum(w_a) == 1(没有其它约束,会出现极端正负权重)。(b) 仅做多 + 预算:调用 solve_constrained_mv 时只让约束 (1)-(2) 生效——把 B_sector 设为全零行矩阵以失活约束 (3);把 b_max=1.0 失活 (5);把 AS_min=0.0 失活 (7)。(c) 在 (b) 基础上加中信一级行业中性 B_sector。(d) 在 (c) 基础上加位限制 b_max=0.05 + 主动份额 AS_min=0.30。对每个组合 (a)-(d) 报告 (i) 样本内 MV 效用 mu_hat @ w - gamma/2 * w @ Sigma_LW @ w,(ii) Herfindahl np.sum(w ** 2),(iii) 单名最大绝对权重 np.max(np.abs(w)),(iv) 实际主动份额。验证单调权衡:效用 (a) > (b) > (c) > (d);Herfindahl (a) > (b) > (c) > (d);最大绝对权重 (a) > (b) > (c) > (d)。对 (d) 组合,报告所有约束的影子价格并识别最贵的那条(绝对值最大),用一句话说明它最贵的原因。
提示
B_sector、b_max=1.0、AS_min=0.0 来「失活」对应约束,这样 (b)/(c)/(d) 的 cvxpy 图结构保持一致,只是约束的实际作用域不同。提示
prob.solve 之后影子价格在 constraints[k].dual_value 上读出。按绝对值排序,最大的那条通常是动量最强的标的(如宁德时代 300750)上的位限制。桥接 L2
(d) 组合已经过了合规和委托合同,但它还忽略了一条机构现实:每次再平衡的交易都要花钱。线性佣金、印花税、监管规费、Almgren-Chriss(2000)二次市场冲击,叠加起来,一只年化换手 200% 的产品要平白损失 100-200 bp 年化的成本拖累。L2 把 solve_constrained_mv 扩展为「目标里减去 c_linear @ cp.abs(w - w_prev) + c_q * quad_impact、再加上换手率预算 cp.sum(cp.abs(w - w_prev)) <= TO_max」的成本感知 QP——这正是私募和公募交易台每次再平衡真在跑的版本。