Balanced Portfolio 是基于桥水基金风险平价理论的投资组合管理与回测系统,功能包括中金所股指期货描述性统计和场外期权自动敲入敲出产品(autocallables)结构化产品定价功能,供广大投资者参考和交流学习。
项目官网/Demo:https://xushilu.com
Demo的测试账户和密码都是test1
介绍与说明(长文):我全栈开发的第一个产品:Balanced Portfolio 多资产风险平价全天候策略回测系统
- 风险平价组合:按GDP增长/宏观通胀二分法划分四象限,将四种不同经济场景里分配对应经济场景占优的投资品种,优化算法包括
最大化夏普/Sortino,象限内最大优化指标、象限间等风险贡献,全资产风险平价、按优化指标分配风险预算共四种方法,输出优化后的投资品权重、组合单位净值、历史调仓、风险指标、相关性和绩效归因。 - 中金所股指期货数据看板:跟踪 IF、IH、IC、IM 及对应指数,展示同一交易日口径的收盘快照、历史年化升贴水及分位值统计。
- 加密货币相关性看板:比特币对数日收益率与黄金(COMEX/沪金)、标普500、纳斯达克100 的滚动相关系数(Pearson/Spearman/Kendall/Hoeffding),以及 BTC 与远期美元指数 (DXY) 的走势。
- 场外期权自动敲入敲出(autocallables)结构化产品定价:覆盖雪球、凤凰、气囊和障碍产品,支持Monte Carlo、BSM求解、积分法定价,模型求解输出公允价值、PV值、PoL、Delta/Gamma/Vega/Theta/Rho 与存续路径状态。
- 机构级别的数据可视化:打造一个开箱即用、可交互的、可视化的投资组合分析与回测工具,以及丰富的数据可视化图表。
系统分四层:
akshare → bp_ingest(落库 / 清洗)→ PostgreSQL 18 + TimescaleDB → bp_api(FastAPI 量化引擎)→ web(Next.js)
| 组件 | 技术 | 说明 |
|---|---|---|
| 数据源 | akshare, 东方财富, Yahoo Finance | A 股 / 港股 / 商品 / 海外指数 / 加密货币 / 美元指数 |
| 数据库 | PostgreSQL 18 + TimescaleDB ≥ 2.23 | 存储日行情 hypertable、压缩、增量更新、投资组合簿记 |
| 后端 | FastAPI + psycopg3 | 模型结果预计算和接口请求 |
| 计算 | numpy + scipy | 风险平价与风险预算模型用循环坐标下降法(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 |
行情通过 AKShare 和Yahoo Finance获取日行情并写入 PostgreSQL/TimescaleDB,底层来源包括交易所及东财、新浪、Yahoo Finance等公开接口,具体来源由资产配置决定。
原始行情不会直接进入回测:系统先按 A 股交易日历进行对齐,线性填补内部缺口,剔除A股休市情况下的海外周末行情。
回测只会使用每个交易日当日及之前的滚动窗口数据,未来收益不会改变历史净值。
组合回测、OTC 定价和全量行情任务采用Redis + Celery异步执行。本地开发可设置 BP_TASK_MODE=inline,由 FastAPI BackgroundTasks 执行,不需要启动 Redis 和 worker。
Balanced Portfolio 由行情接入、数据库、量化与业务 API、Web 前端四层组成:
AKShare
│
▼
bp_ingest ──► PostgreSQL 18 + TimescaleDB
│
▼
Redis + Celery worker
│
▼
bp_api / quant
│
▼
Next.js web
生产环境在 API 与耗时计算之间增加 Redis、Celery worker 和定时任务;开发环境可用 FastAPI 后台任务代替。
bp_ingest 工具通过适配器调用 AKShare,增量写入指数、ETF、商品、加密货币和 CFFEX 股指期货行情。bp_index_quote_daily 表保存原始 OHLCV,是组合回测行情的单一事实源。
常规更新流程:
- 按资产配置和回看天数(.env里配置)拉取增量行情。
- 对近期数据做覆盖写入,以接收上游修订。
- 刷新清洗行情和资产数据状态。
- 找出数据已推进的组合并提交重算任务。
组合回测只读取 bp_quote_clean表,不直接读取原始行情。清洗流程以 A 股交易日历为基准:
- 外币资产先折算:以新浪
fx_sina的当日人民币汇率为唯一口径,把非人民币资产的 OHLC 折算成人民币计价(fx_rate列记录当日汇率,便于审计)。加密/美元指数/COMEX 黄金不折算——它们的原生计价就是/crypto看板的展示口径。 - 把各资产价格重建到统一交易日索引。
- 对有前后有效价格锚点的内部缺口根据上一个值进行填充。
- 保留资产上市前的前导空白,由回测的有效起始日处理。
- 若资产在回测区间尾部停止更新且无法插值填充,构建面板时直接报错。
- 基于清洗后的价格重新计算收益率,保证协方差、净值、回撤和归因使用同一口径。
这套处理便于跨市场资产对齐,但插值价格不等于可成交价格。涉及实盘执行时,应采用与目标市场一致的交易日历、汇率、复权和停牌处理规则。
本地改完清洗口径后请先发布生产再跑本地
bp_ingest clean:生产上运行的旧版本会在下一个增量周期把清洗表覆写回它自己的口径。
平台支持四种组合优化方法:
quadrant_inner_sharpe_outer_rp:象限内最大夏普或 Sortino,象限间等风险贡献。all_risk_parity:全部资产等风险贡献。all_max_sharpe:全部资产最大夏普或 Sortino。sharpe_sq_risk_budget:按单资产比率平方分配风险预算。
等风险贡献和风险预算使用循环坐标下降求解,最大比率问题使用连续最小二乘法(SLSQP)。无风险利率、回看窗口和再平衡偏离带保存在组合上,环境变量只提供新建组合或缺省场景的默认值。
回测按交易日推进。每一天重新取不晚于当天的滚动窗口,计算当日目标权重;任一资产的实际权重偏离目标超过组合设定阈值时,整个组合再平衡。新资产在积累到最小回看窗口后才加入,不会拖延其他资产的回测起点。
CFFEX 模块覆盖 IF、IH、IC、IM 及沪深 300、上证 50、中证 500、中证 1000:
- 现货指数收盘价复用资产管理日 K 表(
bp_quote_clean/bp_index_quote_daily),不另建实时源。 - 日 K 源盘中会把最新价写入当日 close;入库与看板均在上海时区 15:10 后才认「今日」正式收盘(
BP_CLOSE_CONFIRM_HHMM可调)。盘中写入的今日行会被丢弃;若已误写入,收盘后强制重拉覆盖,且看板在updated_at未过确认时刻前会回退到上一完整交易日。 - ingest 定期拉取合约日行情并计算升贴水,现货修正后会重算近窗 premium。
- 快照只选择期货品种和挂钩指数在同一交易日齐全、且收盘已确认的数据,避免混日与盘中脏价。
- API 提供收盘快照、历史走势和统计分位,Redis 可缓存查询结果(盘前/盘后分桶)。
bp_api/quant/otc 是基于 NumPy/SciPy 的定价实现,覆盖雪球、凤凰、气囊和障碍结构:
- 全品种支持批量 GBM 蒙特卡洛。
- 障碍和气囊结构支持相应解析方法。
- Greeks 使用共同随机数的 bump-and-reprice。
- 交易日、观察日调整和 ACT/365、ACT/360、BUS/252 日计数由日历模块处理。
- 定价结果、存续状态和路径图可由异步任务计算并持久化。
模块的产品分层、术语和参数命名参考 Apache-2.0 许可的 pricelib,运行时定价代码不依赖 pricelib。
创建、修改、复制或重算组合时,API 先写入 bp_task,再派发计算:
请求 ─► 数据库任务记录 ─► Celery/Redis ─► worker ─► 结果表
│
└─ 失败或 inline 模式 ─► FastAPI BackgroundTasks
生产环境建议保持 Redis、bp-worker 和 bp-beat 运行。bp-beat 定期检查可推进到最新行情日的组合;bp-ingest 也有轻量巡检作为补充。本地开发可设置 BP_TASK_MODE=inline 后,不需要单独运行队列。
FastAPI 提供组合、行情、CFFEX、OTC、任务、管理和鉴权接口。JWT 用于身份校验,管理员必须绑定 TOTP;Redis 可用于登录限流和结果缓存。
Next.js 使用 App Router。浏览器登录时,web/app/api/session Route Handler 负责写入 httpOnly bp_session Cookie。因此生产反向代理必须区分两类路径:
/api/session* ─► Next.js :3000
/api/* ─► FastAPI :8000
/* ─► Next.js :3000
若把所有 /api/ 都转给 FastAPI,登录会返回 404。具体配置见 部署说明。
- 原始行情与清洗行情。
- 资产、数据源、交易日历及数据状态。
- 用户、权限、TOTP 和会话相关状态。
- 组合定义、象限资产和组合级参数。
- 净值、调仓、指标、相关性、末日持仓和绩效归因。
- CFFEX 合约行情与升贴水序列。
- OTC 交易、定价结果和历史。
- 异步任务状态、进度和错误信息。
数据库定义以 ddl/schema.sql 为全新安装基线。
- Python 3.11–3.14
- Node.js 20+
- PostgreSQL 18
- TimescaleDB 2.23+(建议使用当前稳定版)
psql命令行工具
-
克隆仓库并创建配置:
git clone https://github.com/hxlog/balanced-portfolio.git cd balanced-portfolio cp .env.example .env编辑
.env,至少填写数据库连接、JWT 密钥和首次管理员凭据。开发环境可使用 inline 任务模式:PGPASSWORD=<database-password> BP_JWT_SECRET=<random-long-secret> <!-- JWT推荐生成高强度密钥 --> BP_ADMIN_EMAIL=admin@example.com <!-- 管理员账户的又想和密码 --> BP_ADMIN_INITIAL_PASSWORD=<initial-admin-password> <!-- inline是开发模式,生产环境可使用redis(填写celery) --> BP_TASK_MODE=inline
可用
python -c "import secrets; print(secrets.token_urlsafe(48))"生成 JWT 密钥,管理员创建成功后可从运行环境移除BP_ADMIN_INITIAL_PASSWORD,或直接在数据库里的bp_admin_user修改。 -
安装 Python 依赖:
python -m venv .venv # Linux/macOS source .venv/bin/activate # Windows PowerShell # .venv\Scripts\Activate.ps1 pip install -r requirements.txt
-
初始化数据库。全新环境只执行合并后的 schema,不要再逐个执行旧编号脚本:
createdb -h localhost -U postgres balanced_portfolio psql -h localhost -U postgres -d balanced_portfolio -f ddl/schema.sql
-
拉取并清洗行情:
python -m bp_ingest run
-
启动后端:
uvicorn bp_api.main:app --host 127.0.0.1 --port 8000 --reload
-
在另一个终端启动前端:
cd web npm install npm run dev
打开 http://localhost:3000。后端健康检查地址为 http://localhost:8000/api/health,OpenAPI 页面为 http://localhost:8000/docs。若 API 不在本机 8000 端口,启动前端前设置 BP_API_BASE。
# 行情调度测试
python -m bp_ingest ping
# 行情调度启动
python -m bp_ingest run
# 行情调度只提取沪深300和恒生指数,不自动清洗数据
python -m bp_ingest run --symbols 000300 HSI --no-clean
# 部分数据走yahoo finance需要代理,国内云服务器无法访问,需要在开发环境连代理补数据
python -m bp_ingest run --symbols BTC-USD
# 清洗行情数据,将交易日标准化成A股交易日,再计算涨跌幅
python -m bp_ingest clean
# 行情调度任务,建议配合pm2使用
python -m bp_ingest schedule
# 中金所股指期货行情跑增量
python -m bp_ingest cffex-backfill
# 中金所股指期货行情跑全量(会删除历史期货行情数据!!!)
python -m bp_ingest cffex-backfill --full
# 中金所股指期货数据重新计算基差和贴水
python -m bp_ingest cffex-backfill --recompute-premium
# 后端服务启动,建议配合pm2
uvicorn bp_api.main:app --host 127.0.0.1 --port 8000 --reload
# redis celery查询
celery -A bp_api.workers.celery_app worker -c 2
# 前端next.js的开发与build命令
cd web
npm run dev
npm run build# CI测试
python -m pytest bp_api/tests -q
cd web && npm run build后端测试覆盖优化权重、ERC、绩效指标、CFFEX 数据口径、OTC 定价和无未来函数约束。提交前请同时确认前端生产构建通过。
本文说明如何手工部署 Balanced Portfolio。项目当前不提供 Docker 编排;生产模板采用 Linux、PM2 和 Nginx。
建议使用一台应用服务器和一套 PostgreSQL/TimescaleDB 数据库。最低软件要求:
- Python 3.11–3.14
- Node.js 20+
- PostgreSQL 18
- TimescaleDB 2.23+
- Redis
- PM2、Nginx、
psql、Git
数据库应只允许应用服务器或内网访问,不要向公网开放 5432。应用服务器只需公开 80 和 443。
sudo mkdir -p /opt/balanced-portfolio
sudo chown "$USER:$USER" /opt/balanced-portfolio
git clone https://github.com/hxlog/balanced-portfolio.git /opt/balanced-portfolio
cd /opt/balanced-portfolio创建 Python 虚拟环境并安装依赖:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cd web
npm ci --legacy-peer-deps
cd ..cp .env.example .env
chmod 600 .env至少检查以下配置:
PGHOST=<database-host>
PGPORT=5432
PGDATABASE=<database-name>
PGUSER=<database-user>
PGPASSWORD=<strong-database-password>
BP_CORS_ORIGINS=https://<your-domain>
BP_JWT_SECRET=<random-long-secret>
BP_ADMIN_EMAIL=<admin@example.com>
BP_ADMIN_INITIAL_PASSWORD=<initial-admin-password>
BP_API_BASE=http://127.0.0.1:8000
BP_SITE_URL=https://<your-domain>
# /crypto 看板: 后端重算后 ping Next.js 失效 SSR 缓存(可选; 不设则靠 cacheLife TTL 兜底)
BP_WEB_BASE=https://<your-domain>
BP_INTERNAL_REVALIDATE_TOKEN=<random-32+-chars>
BP_CRYPTO_CLOSE_CONFIRM_HHMM=1600
REDIS_URL=redis://127.0.0.1:6379/0
CELERY_BROKER_URL=redis://127.0.0.1:6379/0
CELERY_RESULT_BACKEND=redis://127.0.0.1:6379/0
BP_TASK_MODE=celery
# 站点埋点(可选; 三项都只在 NODE_ENV=production 且 website-id 非空时生效)
BP_UMAMI_WEBSITE_ID=<umami-website-id>
BP_UMAMI_SRC=https://<umami-host>/script.js
# 会话回放/热力图: 独立的第二个 bundle, 不填则从 BP_UMAMI_SRC 推导为同级 recorder.js
# 需 umami >= v3.1.0 且后台该站点开启 Replays; 不需要录屏时留空可省掉 ~190KB 脚本
BP_UMAMI_RECORDER_SRC=https://<umami-host>/recorder.js生产环境必须显式设置随机的 BP_JWT_SECRET,不要依赖数据库密码派生。BP_ADMIN_INITIAL_PASSWORD 只在首次创建管理员时使用,创建成功并修改密码后可从运行环境移除。
全新数据库只执行合并后的 schema:
set -a
source .env
set +a
# 数据库尚未创建时,先使用具备 CREATEDB 权限的账号执行:
createdb -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" "$PGDATABASE"
psql \
-h "$PGHOST" \
-p "$PGPORT" \
-U "$PGUSER" \
-d "$PGDATABASE" \
-f ddl/schema.sql数据库已经存在时跳过 createdb;应用账号没有建库权限时,由数据库管理员预先创建数据库和扩展。
验证连接并完成首轮行情:
source .venv/bin/activate
python -m bp_ingest ping
python -m bp_ingest run首轮拉取耗时取决于资产数量、起始日期和上游限流。遇到单一资产失败时,先核对资产代码和 AKShare 接口,不要直接跳过清洗错误。
项目提供 PM2 配置,包含:
bp-api:FastAPI,监听127.0.0.1:8000bp-web:Next.js,监听127.0.0.1:3000bp-ingest:行情定时更新bp-worker:Celery workerbp-beat:Celery 定时巡检
cd /opt/balanced-portfolio
export APP_ROOT=/opt/balanced-portfolio
set -a
source .env
set +a
cd web
npm run build
cd ..
pm2 start deploy/ecosystem.config.cjs
pm2 save
pm2 startuppm2 startup 会输出一条需要 sudo 执行的命令,按终端提示完成即可。
复制模板并替换域名:
sudo cp deploy/nginx.conf /etc/nginx/sites-available/balanced-portfolio
sudo editor /etc/nginx/sites-available/balanced-portfolio
sudo ln -s /etc/nginx/sites-available/balanced-portfolio \
/etc/nginx/sites-enabled/balanced-portfolio
sudo nginx -t
sudo systemctl reload nginx模板中最重要的是 Session 路由顺序:
location /api/session {
proxy_pass http://bp_web;
}
location /api/ {
proxy_pass http://bp_api;
}/api/session 必须写在通用 /api/ 之前,并转给 Next.js。它负责写入 httpOnly Cookie;FastAPI 没有这个路由。
确认 HTTP 可访问后,用 Certbot 或现有证书系统启用 HTTPS:
sudo certbot --nginx -d <your-domain>
sudo certbot renew --dry-run服务器端发版脚本会拉取代码、更新 Python 和前端依赖、构建前端、重载 PM2、执行健康检查并在配置有效时重载 Nginx:
cd /opt/balanced-portfolio
bash deploy/deploy.sh发版前应在开发或 CI 环境执行:
python -m pytest bp_api/tests -q
cd web && npm run build如果版本包含新的编号数据库迁移,先阅读迁移内容并安排备份和维护窗口,再对已有数据库单独执行。deploy.sh 不会自动执行数据库迁移。
pm2 status
curl -fsS http://127.0.0.1:8000/api/health
curl -s -o /dev/null -w "%{http_code}\n" \
http://127.0.0.1:3000/api/session
sudo nginx -t
redis-cli ping未登录时 Session 地址应返回鉴权相关状态,不能是 404。同时检查:
bp-api、bp-web、bp-ingest、bp-worker、bp-beat均为online。- 首页和 API 可通过 HTTPS 访问。
- 新建组合能从
queued/running进入done。 - 行情最新日期和组合
data_as_of_date符合预期。 - 日志中没有持续的数据库连接、上游限流或任务重试错误。
本项目按 Apache License 2.0 发布。第三方组件仍适用各自许可证。
- 加密货币相关性看板(
/crypto):BTC 与黄金(COMEX/沪金)、标普500、纳斯达克100 的对数日收益率滚动相关性 + BTC vs 美元指数 (DXY) 远期走势。4 种方法(Pearson/Spearman/Kendall/Hoeffding)×4 窗口(3/6/9/12 月)。预计算落库(bp_crypto_corr_daily64 series-per-row JSONB +bp_crypto_meta),NYSE 16:00 ET 双时区「数据截至」带时分(ET + 北京时间,DST 自洽),bp_ingest.run钩子 + 每日定时任务重算,API 只读、请求路径永不计算,Next.js→Redis→DB 三层缓存。 - yfinance 数据源:crypto/forex/commodity 日线(BTC-USD、美元指数 DXY、COMEX 黄金期货),与 AKShare 行情共用
bp_ingest增量/清洗/调度链路。
- 加密看板数据源切换:DXY(美元指数)与 COMEX 黄金从 yfinance 切到东方财富(
dxy_em直连 push2his secid=100.UDI;gold_comex_em= aksharefutures_foreign_hist("GC")),生产服务器不再受 Yahoo 429 限流影响;BTC-USD 保留 yfinance(由 atomicity hold-back 兜底)。迁移ddl/33_crypto_source_em.sql,bp_ingest/sources.py新增 2 个 EM 适配器。 - 加密看板原子性 + hold-back:
effective_td恢复 CFFEX 式「6 资产同日齐全 + NYSE 16:00 ET 收盘确认」门槛(修复此前放宽导致用 ffill 假价冒充最新收盘的 bug);任一资产缺口则看板 hold 在前一完整日 + 琥珀「数据待齐」徽章(is_synced),不再冒进。预计算输出截断至effective_td+ 删残留行;symbols_by_date只统计真实非 NaN 收盘日,杜绝 interp 假日被误判齐全。 - 1h crypto 增量调度:新增
crypto_job(每 1h 拉 6 资产 → 钩子重算),镜像 CFFEXcffex_job;保留每日 05:30 北京兜底。仅effective_td/is_synced推进才 bump version + ping SSR revalidate(no-op 不刷 cacheTag)。 - yfinance 陈旧可见化:
bp_ingest/ingest.py::_sync_one加 STALE 探测(源未推进或滞后超BP_STALE_TOL_DAYS=3 → 打 WARNING + 计入汇总),让 prod IP 被 Yahoo 429 时不再静默吞掉。 - 首页加密看板 section:
/新增全宽扁平 crypto 看板卡(6 资产快照 + 4 个 3M Pearson 相关值 + BTC 滚动相关性 SVG sparkline + 数据截至/数据待齐徽章 + CTA),PPR 动态洞 +cacheTag "crypto"与/crypto同步刷新;扁平/现代风(无 shadow、细边框、monospace 价格)。
AKShare 聚合公开数据接口,数据源可能调整、延迟、限流、修订或停止服务。项目不保证行情、交易日历、复权、基差、波动率、定价结果和回测结果完整、准确或持续可用,使用者应自行核验数据授权、质量和适用性。
本项目仅用于软件开发、教学和研究,不构成投资建议、交易信号、估值意见、要约或任何收益承诺。回测和模型价格不代表未来表现或可成交价格,实盘决策及损失由使用者自行承担。


