槐序的序章
5690 字6 min read

我全栈开发的第一个产品:Balanced Portfolio 多资产风险平价全天候策略回测系统

关于笔者第一次全栈开发金融产品,将风险平价策略回测系统、股指期货数据看板、场外衍生品定价模块做成开箱即用、可交互、可视化的Web端产品。

为什么要做这个产品

去年在券商自营工作时,我时常怀疑全球大类资产配置的合理性:所谓「配置」,难道只是给买入持有和不定期调仓披上一层合理化外衣?做过一轮研究后,我得到三条结论:

  • 在不考虑极端股债双杀的前提下,配置盘里必须有一定比例的资产,在大部分时间与其余持仓呈零相关或负相关。
  • 买入多个波动高度相关的资产,只是形式主义:既没有分散系统性风险,也没有降低集中度,安慰剂效应居多。
  • 再平衡调仓不一定带来 Alpha;它本质是风险管理。若不调仓,组合会被动承担比预期更多的风险。

那时我认为,传统四象限风险平价早已过时,更该用《因子投资》里的方法,把宏观、行业、市场预期自上而下映射成因子再回测——详见《浅谈风险平价模型》。今年想法有所修正:很多机构和散户并不执着于资产之间的严格独立性(即对协方差矩阵做极致优化、规避多重共线性)。若目标函数只是「持有一篮子核心资产,并通过组合管理平滑单位净值曲线」,而不再纠结流动性危机、股债双杀等尾部情景,那么这个需求其实有一套相对确定的解法。

身边同事和朋友真正需要的,往往是:能控制最大回撤、追求缓慢而稳健的财富增值、不必盯盘、调仓频率较低的投资组合。市面上几乎没有对症的产品;即便有,也多停留在簿记、风控指标、基金绩效计算等基础功能——毕竟多数工具服务于卖基金场景的财富管理产品,用不上复杂的组合优化。

于是我下定决心:把原本只在 Python 环境里跑的风险平价组合管理与回测程序,做成可交互的 Web 产品。顺带把 Alpha 策略和场外衍生品对冲管理里常看的股指期货年化升贴水,以及场外期权结构化产品定价,一并放进系统,方便投资者和研究者查阅使用。

产品介绍

Balanced Portfolio 是一套基于桥水基金风险平价思路的投资组合管理与回测系统,同时提供中金所股指期货描述性统计,以及场外自动敲入敲出(autocallables)结构化产品定价。

主要功能:

  • 风险平价组合管理与回测:按 GDP 增长 / 通胀二分法划分四象限,在四种经济情景下配置该情景占优的品种。优化算法支持最大化夏普或 Sortino;方法包括象限内最大优化指标 + 象限间等风险贡献、全资产风险平价、全体最大化优化指标、按优化指标分配风险预算共四种。输出含优化权重、净值、调仓记录、风险指标、相关性与绩效归因。
  • 中金所股指期货数据看板:跟踪 IF、IH、IC、IM 及对应指数,按同一交易日口径展示收盘快照、期限结构、历史年化升贴水与统计分位。
  • 场外结构化产品定价:覆盖雪球、凤凰、气囊与障碍类产品,支持 Monte Carlo、BSM、积分法定价;输出公允价值、PV、PoL、Delta / Gamma / Vega / Theta / Rho,以及存续路径状态。
  • 机构级可视化:把常用金融工程方法做成开箱即用、可交互的分析与回测界面。

风险平价管理界面

风险平价 dashboard 页
风险平价 dashboard 页
编辑四象限投资品种
编辑四象限投资品种
选择默认优化方法与优化指标
选择默认优化方法与优化指标
配置组合信息、回测窗口、基准、集中度约束、再平衡带、交易成本
配置组合信息、回测窗口、基准、集中度约束、再平衡带、交易成本

股指期货界面

股指期货看板自动更新至最新日收盘行情,计算历史年化升贴水率、分位值等描述性统计
股指期货看板自动更新至最新日收盘行情,计算历史年化升贴水率、分位值等描述性统计

场外衍生品定价界面

场外衍生品定价、簿记、估值,A 股交易日历,股指历史波动率
场外衍生品定价、簿记、估值,A 股交易日历,股指历史波动率

风险平价方法论

基于桥水基金创始人达利欧(Ray Dalio)的风险平价框架,把全球大类资产按宏观周期放入四象限,以历史波动率与协方差为风险度量,目标是构建长期夏普比率大于 1 的全天候指数组合。

下文按顺序说明:风险平价定义、四象限假设、四种优化方法与求解器、回测与再平衡、交易成本、绩效归因、相关性展示,以及我对模型有效性的看法。

风险平价(Risk Parity)

传统组合常按资金等额或主观比例配置,结果往往是少数高波动资产贡献了组合绝大部分风险——典型的「看似分散、实则集中」。风险平价反其道而行:让每个资产(或每个象限)对组合总波动率的边际贡献尽可能相等,从而在风险维度上真正分散。

形式上,资产 ii 的边际风险贡献为

MRCi=(Σw)iσp\text{MRC}_i = \frac{(\Sigma w)_i}{\sigma_p}

其中 Σ\Sigma 为协方差阵,ww 为权重,σp\sigma_p 为组合波动率。等风险贡献(ERC)即对所有 i,ji,j 有 MRCi=MRCj\text{MRC}_i = \text{MRC}_j。

系统用过去 N 个交易日(默认 156)的日收益方差 / 协方差作为风险输入,约覆盖大半年市场状态:既足以估计二阶矩,又尽量避免把过时的相关性、或对近期噪音的过拟合,一并带进决策。

需要强调:风险平价并不以最大化历史收益为目标,而是追求风险调整后收益的稳健性。因此最终优化目标统一落在最大化夏普或 Sortino。象限之间再平衡风险预算,是为了让组合穿越宏观周期,而不是押注单一环境。

经济情景四象限

经济环境沿「增长」与「通胀」两个维度循环,两两组合得到四种宏观象限。不同资产在不同象限表现占优:增长上行利于权益与商品,通胀上行利于商品,通胀下行利于债券与成长股;经济下行时的占优配置缺少统一理论,故因人而异。

基于国内市场的精简版四象限
基于国内市场的精简版四象限

在四个象限中各放入一类「在该象限占优」的资产,再用风险平价分配风险预算,即可得到对单一环境不那么敏感的全天候组合。

若你认为债券和黄金应当长期持有,可以在每个象限都加入它们——这通常不会破坏框架。反过来,若象限内样本过少或多重共线性严重,协方差可能退化(破坏正定性),优化器便难以收敛到「权重之和等于 1」的可行解。

同一资产可以出现在多个象限(例如商品同时用于过热与滞胀对冲)。系统允许标的跨象限重复;在象限层面做风险平价时,这种重叠暴露会自然体现。

将哪些占优资产正确地放入四个象限,是该策略最核心的假设与参数。

四种优化方法

每个组合可任选以下四种方法之一;后台异步任务会同时算出四种结果,便于对照。

比率指标可在夏普比率与 Sortino 比率之间切换(默认夏普)。约束统一为长仓 w≥0w \ge 0、∑w=1\sum w = 1,并可设单资产集中度 max_weight(默认 33%;品种较多时可调至公募常见的 20%)。不建议超过 33%。

方法 说明
象限内最大化优化指标,象限间风险平价(默认) 先在每个象限内最大化所选指标(夏普 / Sortino)得到内部权重,再令各象限波动率贡献相等(ERC),两层权重相乘。 兼顾象限内进取与象限间平衡。
所有投资品之间风险平价 经典风险平价:忽略象限,让每个基础资产对组合波动的边际贡献完全相等。易重仓先天低波动资产(如债券、黄金),低配高弹性资产;适合品种数量较少的情形。
全体最大化优化指标 马科维茨均值–方差框架下,直接最大化组合历史优化指标;对样本高度敏感,适合自动跟踪趋势。
按优化指标²分配风险 各资产风险预算 ∝\propto 单资产指标²,给予历史表现更优者更高风险预算——动量与风险平价之间的折中。

为什么要在波动率平价之外引入夏普 / Sortino?因为波动不完全是坏事:拿固收与权益的绝对波动直接比权,往往没有意义——承担风险可能换来更高期望收益。最大化夏普(收益 / 波动)或 Sortino(收益 / 下行波动),是在风险预算与收益质量之间找平衡。

默认的象限内最大指标 + 象限间风险平价优化方法,据我所知在公开工具里并不常见。桥水强调的是:不猜测未来经济落在哪个象限,而是问“不同情景下谁受益、谁被杀估值“。把占优资产放进对应象限后,情景内追求指标最大化,情景间假设四种宏观状态概率相近并做风险平价———请不要尝试当一名经济学家去预测未来。

为避免协方差退化,建议至少输入 5 种以上走势相对独立的品种;否则在权重之和为 1 等约束下,可能找不到最优解。品种越少,回溯窗口(建议超过 1 个月)和回测区间(建议至少 2 年)越应拉长。

求解器实现

  • ERC / 风险预算:Spinu 循环坐标下降(CCD)闭式迭代。每次固定其余权重,对单一资产有闭式更新,收敛快、精度高于通用规划器,且无需求逆。
  • 最大比率:SLSQP 约束规划;支持热启动(以上一交易日权重作 x0x_0)与预算入参(回测逐日传入预计算的 cov / mean),使逐日调用只需一次热启动求解。
  • 比率可选夏普(对称波动)或 Sortino(仅下行波动),影响最大比率目标与单资产风险预算的计算口径。

回测系统

回测采用单位净值法,逐交易日推进,并严格隔离未来信息:

  • 决策与结算时序隔离:每个交易日 tt,先用滚动 156 日窗口(仅使用 ≤t\le t 的数据)重算最优目标权重;当日 NAV 用当日收益 rtr_t 更新;新权重自 t+1t+1 起生效——「今日决策、明日执行」,决策与收益结算不共享未来信息。
  • 成分逐步纳入:历史行情信息不足的品种权重暂为 0,不拖后组合起点;待其历史行情累积到 min_window(默认 60 日)后自动纳入。
  • 再平衡触发:任一品种的实际(漂移)权重与当日最优目标偏离超过 rebalance_band(默认 5 个百分点,绝对值)时,整体再平衡回当日最优,确保调整后无任一品种偏离超过 5%。默认满仓;若有剩余资金按目标补足,分红通过复权序列再投资。
  • 性能:用前缀和滚动维护每日窗口的均值 / 协方差(S1S_1、S2S_2 加减),将逐日成本从 O(window⋅n2)O(window \cdot n^2) 降为 O(n2)O(n^2);优化器以上一交易日权重热启动,避免重复冷启动。

即使回测结束日之后的未来价格发生变化,也不会改写该日之前的 NAV 与历史权重;日常只需对新增交易日做增量回测。

交易成本

回测可按组合设定三项费率,仅在触发再平衡时扣费,按比例从当日单位净值中扣除:

费率 默认 缴纳方
手续费 0.015%(万 1.5) 双边(买 + 卖)
滑点 0.015%(万 1.5) 双边(买 + 卖)
印花税 0.05% 单边(仅卖出)

设再平衡日权重变化 Δw=目标−漂移\Delta w = \text{目标} - \text{漂移},记 turnover=∑∣Δw∣turnover = \sum |\Delta w|(买 + 卖双边绝对值之和)、sell_turnover=∑max⁡(0,−Δw)sell\_turnover = \sum \max(0, -\Delta w)(净减仓),则:

cost=turnover×(手续费+滑点)+sell_turnover×印花税\text{cost} = turnover \times (\text{手续费} + \text{滑点}) + sell\_turnover \times \text{印花税}

即:建仓 / 买入缴手续费 + 滑点;卖出缴手续费 + 滑点 + 印花税。这是比例换手模型,用于研究级摩擦近似,不含印花税分档、最低佣金等非线性规则。

基于 Brinson 思路的绩效归因

Dashboard「绩效归因」将区间总收益拆为四个可加桶。实现受 Brinson 业绩归因启发,但不是经典 Brinson–Fachler 的「相对基准行业配置 / 选股 / 交互」三分法;此处「截面选择」指按组合自身平均权重静态持有时的结构贡献,请勿与研报中的选股 Alpha 划等号。

桶 日度含义 如何理解
系统性 Beta β⋅rb\beta \cdot r_b 组合对所选基准的线性暴露;β=Cov(rp,rb)/Var(rb)\beta = \text{Cov}(r_p, r_b)/\text{Var}(r_b),全区间估计一个常数 β\beta。
截面选择 ∑wˉiri,t−β⋅rb\sum \bar{w}_i r_{i,t} - \beta \cdot r_b 若每天都按平均配置持有,相对「纯 Beta」多赚或少赚多少——体现选了哪些资产、结构如何。
时序调仓 ∑(wi,t−wˉi)ri,t\sum (w_{i,t} - \bar{w}_i) r_{i,t} 实际权重偏离平均权重的动态部分——再平衡、漂移、偏离带触发的调仓价值。风险平价主要赚截面与 Beta 的钱;时序调仓只要不大亏,通常就算合格。
残差 — 实际净值总收益 − 上述三桶(Carino 链接后)。主要吸收手续费 / 滑点 / 印花税,以及无法用上述因素解释的口径差。

每个交易日(税前)恒等成立:Beta+截面选择+时序调仓=组合日收益\text{Beta} + \text{截面选择} + \text{时序调仓} = \text{组合日收益}。跨多日用 Carino 对数链接将日度效应累加为几何总收益,使各桶可加且合计贴近区间总收益。若设置了交易成本,残差会明显反映费用;未设成本时残差应接近 0。

Dashboard 图表包括:收益拆解瀑布(四桶逐步累加到组合总收益);各资产收益贡献拆为静态(选品)与调仓(择时)两列;逐次调仓贡献展示每次再平衡持有区间的几何收益及对总收益的链接贡献。

相关性与协方差矩阵

Dashboard 展示近回溯窗口(默认 156 日)内各成分日收益率的相关系数与年化协方差。相关系数 ∈[−1,1]\in [-1, 1],衡量收益同向或反向程度;分散化时追求低相关或负相关(相关不等于因果)。协方差绝对值越大,共同波动越强;风险平价优化器直接使用协方差阵。成分较多时可勾选子集查看,默认展示跨类别(指数、ETF、债券、商品等)的多样化样本。

模型有效性:我怎么看这套假设

若以历史 N 日波动率作为风险,波动低时买更多、波动高时卖出,行为上会接近某种「反波动趋势」:下跌时波动往往比上涨时扩张更快。因此,风险平价更适合具备买入并持有逻辑、呈现慢牛或周期性特征的资产;一句话——尽量避开历史上大部分时间都在阴跌的品种。

N 怎么取?我更推荐中长期用 156 个交易日滚动估计,调到 89 日通常也大差不差。窗口不宜过小(例如 15 日),否则模型容易过拟合近期噪音,而不是刻画样本区间内的统计特征与资产间相关性差异。也可以用机器学习在不同区间搜索「最优 N」,但很容易过拟合,实盘未必更好。

股指期货数据看板

中金所(CFFEX)四大股指期货——IF(沪深 300)、IH(上证 50)、IC(中证 500)、IM(中证 1000)——的收盘监控与历史升贴水分析。

该功能受 Alpha / 多空策略 / 期权对冲管理启发:交易员需要盯基差与贴水,决定交易哪份合约,以降低对冲成本。因此除基本行情外,系统还会计算年化升贴水率、综合升贴水率、分位值等描述性统计。

升贴水率反映期货对现货指数的偏离:

升贴水率=期货价格−现货指数现货指数×100%\text{升贴水率} = \frac{\text{期货价格} - \text{现货指数}}{\text{现货指数}} \times 100\%
  • 正值 = 升水:期货高于现货,常见解读是市场对未来偏乐观。
  • 负值 = 贴水:期货低于现货(如 IC、IM 长期深贴水);持有期货多头可能获得展期收益。

基差(Basis)= 现货指数 − 期货价格,与升贴水率互为相反数。年化升贴水率按剩余天数折年:

年化升贴水率=升贴水率×365距离交割天数\text{年化升贴水率} = \text{升贴水率} \times \frac{365}{\text{距离交割天数}}

为规避当月合约临近交割的价格异常,平台采用加权合成口径:

合成年化升贴水率=0.6×次月年化+0.4×当季年化\text{合成年化升贴水率} = 0.6 \times \text{次月年化} + 0.4 \times \text{当季年化}

即只看次月与当季,完全排除当月权重——在保留期限结构信息的同时,削弱到期日前后基差剧烈波动带来的噪声。

数据更新机制

  • bp_ingest 调度器每 BP_CFFEX_SYNC_HOURS 小时(默认 2h)通过 akshare get_futures_daily 增量拉取 CFFEX 日行情;有新交易日则落库并重算升贴水,无增量则跳过。
  • 前端读取最新交易日收盘快照,标注「已收盘」与数据日期时间;页面挂载时拉取一次,无轮询。
  • 后端以 Redis 30min TTL 缓存收盘快照,多客户端并发时只打一次库。
  • 前端 ECharts 支持时间轴拖拽缩放,以及叠加指数价格的双轴对比。

场外结构化产品定价

场外期权自动敲入敲出(autocallables)结构化产品定价与簿记模块,覆盖雪球、凤凰、气囊、障碍四类常见结构。

观测与日历

  • 敲入(KI):按 A 股实际交易日路径逐日观测(离散),未来交易日需人工维护,明年的需要等到年底交易所公布节假日安排。
  • 敲出(KO):仅在月度敲出观察日收盘判定,观察日之间上穿敲出线不构成敲出事件,且锁定期内不安排敲出观察。
  • 非交易日观察日按下一交易日递延。路径步长按实际交易日(默认252个交易日)折现。

票息与计息

雪球采用双票息约定:

  • 敲出票息(coupon_out):在敲出观察日触发敲出时,按一年365天年化应计支付。
  • 红利 / 期末票息(coupon_div):存续至到期且未敲出、未敲入时支付。

凤凰为条件派息结构;气囊 / 障碍按参与率与障碍类型结算。

估值状态与终止

估值日根据已实现路径判定状态:alive存续 / knocked_in敲入 / knocked_out敲出 / expired到期。

  • 已敲出:合约终止,现值记为 0,当前盈亏锁定为已实现敲出票息;示意图截断至敲出观察日。
  • 已敲入未敲出:按敲入后条款继续估值。

技术栈与实现故事

系统分四层:

akshare → bp_ingest(落库 / 清洗)→ PostgreSQL 18 + TimescaleDB → bp_api(FastAPI 量化引擎)→ web(Next.js)
组件 技术 说明
数据源 akshare A 股 / 港股 / 商品 / 海外指数行情
数据库 PostgreSQL 18 + TimescaleDB ≥ 2.23 日行情 hypertable、压缩、增量更新
后端 FastAPI + psycopg3 + SQLAlchemy 量化引擎 + auth + repositories
计算 numpy + scipy ERC 用 Spinu CCD;最大比率用 SLSQP
任务队列 Celery + Redis 异步回测 / 全量拉取 / 定时巡检;无 Redis 时回退 BackgroundTasks
鉴权 PyJWT + bcrypt + pyotp JWT + TOTP 两步验证 + httpOnly Cookie
前端 Next.js 16 App Router + React 19 Tailwind v4 + shadcn/Radix + ECharts

我本来就有投资组合管理与风险计量的经验,写博客时也熟悉 Next.js 与 React;今年跳槽后又补上了数据开发、数仓与模型交互。真正吃力的是后端接口层:我把从前做组合优化的 Python 脚本交给 Claude Code + GLM / DeepSeek 协助生成 ORM 与 API,后端仍是 FastAPI。产品原型用 Figma 出图,前后就只花了两周打磨出可用版本。

行情方面,A 股、指数、ETF、全球指数与期货数据主要走 akshare。东方财富反爬较严,优先用新浪与腾讯源;加 Cookie 也未必管用,且有限速。因此产品不会默认拉取全市场品种落库:需管理员在资产管理菜单添加品种,或直接改 bp_index_config 表。仓库 DDL schema 里已预置一批主流指数与 ETF,方便起步。

写在最后

Balanced Portfolio 不是「保证赚钱」的黑箱,而是一套把风险平价假设、优化约束、再平衡与摩擦成本写清楚、跑得出来的工具。我把它开源并写成这篇长文,是希望同样在琢磨配置与回撤的人,能复现、能质疑、能改参数。让更多人了解怎样才算是分散风险,为什么买多个品种不等于分散风险,协方差矩阵什么时候失效。

我选择将它开源并写下这篇长文,是希望所有同样在死磕投资组合优化和资产配置管理的朋友,能够无缝复现、敢于质疑、自由调整参数。

最后,欢迎大家试用产品(测试账号与密码均为:test1)。目前暂不开放注册,主要是因为服务器资源有限,回测任务极度消耗 CPU 和内存。不过产品完全开源,我也在文档中提供了详细的本地部署方案。

Balanced Portfolio 这个产品,是我对毕业后在券商自营工作两年多生涯的一个交代,是阶段性的终点。

CC BY-NC-SA 4.0

在 GitHub 上查看

Read More

相关推荐