传统观念分散持仓越多风险越低,编程逐步增加持仓个股数量,测算组合波动率拐点,找到最优分散上限。
一、实际应用场景描述
在智能证券投资课程中,投资组合分散化(Diversification)与最优持仓数量是核心教学内容。
本程序适用于:
- 高校量化投资、投资组合管理课程实验
- 投资者教育(Investor Education)
- 风险分散的边际效应教学
- 个人投资者仓位优化训练
核心目标:
- 模拟逐步增加持仓个股数量(如 1→2→5→10→20→50)
- 测算每个组合规模下的波动率变化
- 找到波动率拐点(分散化收益递减点)
- 用数据回答:"是不是分散得越多,风险越低?"
✅ 不做未来预测
✅ 不构成投资建议
✅ 仅作为历史数据教学回测示例
二、痛点引入(真实可感知)
痛点 表现
"分散 = 万能" 以为买 50 只就不亏
忽略边际递减 从 1→10 只降波幅大,10→50 几乎不变
管理成本飙升 仓位太散,复盘和调仓困难
过度分散 = 变相指数 收益率被稀释
工具门槛高 专业组合优化平台复杂
👉 需要一个轻量、本地、可解释、可复现的组合分散化分析工具
三、核心逻辑讲解(工程视角)
1️⃣ 数据模型设计
PortfolioSimulation
├── base_symbol 锚定个股(用于生成组合)
├── return_matrix 各股收益率矩阵
└── volatility_curve 波动率随持仓数变化曲线
2️⃣ 组合构建逻辑(教学用)
步骤 操作
① 从个股池中随机等权选取 N 只
② 计算组合收益率 = 各股收益率等权平均
③ 计算组合波动率 = 收益率序列标准差
④ 重复多次取平均(消除随机性)
⑤ N 从 1 到 Max 逐步增加
3️⃣ 核心公式
组合收益率:
R_portfolio = (R₁ + R₂ + ... + Rₙ) / N
组合波动率:
σ = StdDev(R_portfolio) × √252 (年化)
边际降幅:
边际降幅 = σ(N) − σ(N+1)
当边际降幅 < 阈值(如 0.5%),认为已达分散上限。
4️⃣ 拐点判定算法
遍历 N = 1 → Max:
计算 σ(N)
计算边际降幅
若连续 3 个 N 边际降幅 < 阈值:
标记为拐点
输出"最优分散数量"
5️⃣ 设计原则
- 不假设正态分布,直接基于历史数据
- 等权假设,贴近散户真实操作
- 多次抽样,减少随机性干扰
四、Python 模块化代码(可直接运行)
📁 项目结构
diversification_optimizer/
│
├── main.py
├── models.py
├── simulator.py
├── analyzer.py
├── reporter.py
├── storage.py
├── README.md
└── DISCLAIMER.md
✅ models.py(数据建模)
"""
models.py
组合分散化分析数据模型
"""
class StockReturnSeries:
"""单只个股收益率序列"""
def __init__(self, symbol, returns):
"""
symbol: 股票代码
returns: 日收益率列表(%)
"""
self.symbol = symbol
self.returns = returns
class SimulationConfig:
"""模拟配置"""
def __init__(
self,
max_holdings=50,
simulations_per_n=20,
annualize=True
):
self.max_holdings = max_holdings
self.simulations_per_n = simulations_per_n
self.annualize = annualize
✅ simulator.py(核心模拟引擎)
"""
simulator.py
逐步增加持仓数量,模拟组合波动率变化
"""
import random
import numpy as np
from models import StockReturnSeries, SimulationConfig
def simulate_diversification(stock_pool, config):
"""
核心模拟引擎:
对每个持仓数量 N,随机抽样 N 只股票构成等权组合,
计算组合波动率,多次取平均。
"""
n_stocks = len(stock_pool)
max_n = min(config.max_holdings, n_stocks)
volatility_curve = []
for n in range(1, max_n + 1):
vol_list = []
for _ in range(config.simulations_per_n):
# 随机抽样 N 只
selected = random.sample(stock_pool, n)
# 等权组合收益率
portfolio_returns = [
sum(s.returns[i] for s in selected) / n
for i in range(len(selected[0].returns))
]
# 波动率
vol = np.std(portfolio_returns)
if config.annualize:
vol *= np.sqrt(252) # 年化
vol_list.append(vol * 100) # 转百分比
avg_vol = np.mean(vol_list)
std_vol = np.std(vol_list)
volatility_curve.append({
"n": n,
"avg_volatility": round(avg_vol, 2),
"std_volatility": round(std_vol, 2),
"min_vol": round(min(vol_list), 2),
"max_vol": round(max(vol_list), 2)
})
return volatility_curve
✅ analyzer.py(拐点检测)
"""
analyzer.py
波动率拐点检测——找到最优分散上限
"""
def find_elbow_point(volatility_curve, threshold=0.5):
"""
检测波动率边际降幅的拐点
逻辑:
当连续 3 个 N 的边际降幅都 < threshold 时,
认为已达到分散化上限。
"""
if len(volatility_curve) < 4:
return None, []
# 计算边际降幅
margins = []
for i in range(1, len(volatility_curve)):
prev_vol = volatility_curve[i - 1]["avg_volatility"]
curr_vol = volatility_curve[i]["avg_volatility"]
margin = prev_vol - curr_vol
margins.append({
"n": volatility_curve[i]["n"],
"margin": round(margin, 2),
"cumulative_vol": round(curr_vol, 2)
})
# 寻找拐点
elbow_n = None
for i in range(2, len(margins)):
if (
margins[i]["margin"] < threshold and
margins[i - 1]["margin"] < threshold and
margins[i - 2]["margin"] < threshold
):
elbow_n = margins[i]["n"]
break
return elbow_n, margins
def summarize(volatility_curve, elbow_n):
"""汇总统计"""
vol_list = [v["avg_volatility"] for v in volatility_curve]
return {
"min_vol": round(min(vol_list), 2),
"max_vol": round(max(vol_list), 2),
"total_reduction": round(vol_list[0] - vol_list[-1], 2),
"reduction_pct": round((vol_list[0] - vol_list[-1]) / vol_list[0] * 100, 2),
"elbow_n": elbow_n,
"elbow_vol": next(
(v["avg_volatility"] for v in volatility_curve if v["n"] == elbow_n),
None
)
}
✅ reporter.py(分析报告输出)
"""
reporter.py
分散化优化分析报告
"""
def report(volatility_curve, elbow_n, margins, summary):
print("\n" + "=" * 65)
print("【分散持仓数量 vs 组合波动率分析报告】")
print("=" * 65)
# 逐 N 展示
print(f"\n📊 波动率变化曲线:")
print("-" * 65)
print(f"{'N':>4} | {'波动率(%)':>10} | {'边际降幅(%)':>12} | {'累计降幅(%)':>12}")
print("-" * 65)
for i, v in enumerate(volatility_curve):
if i == 0:
print(f"{v['n']:>4} | {v['avg_volatility']:>10} | {'—':>12} | {'—':>12}")
else:
cumul = v["avg_volatility"] - volatility_curve[0]["avg_volatility"]
# 找到对应的边际降幅
m = next((m["margin"] for m in margins if m["n"] == v["n"]), 0)
print(f"{v['n']:>4} | {v['avg_volatility']:>10} | {m:>12} | {cumul:>12}")
# 拐点分析
print(f"\n{'=' * 65}")
print(f"\n💡 拐点检测结果:")
if elbow_n:
print(f" ✅ 最优分散上限:{elbow_n} 只")
print(f" 对应波动率:{summary['elbow_vol']}%")
print(f" 从 1→{elbow_n} 只,波动率降低 {abs(summary['elbow_vol'] - volatility_curve[0]['avg_volatility']):.2f}%")
print(f"\n → 超过 {elbow_n} 只后,继续分散的边际收益极小")
print(f" → 建议持仓数量控制在 {elbow_n} 只以内")
else:
print(f" ⚠️ 未检测到明显拐点(阈值 0.5%)")
print(f" → 可能需增加模拟次数或扩大样本")
# 汇总
print(f"\n📋 汇总统计:")
print(f" 波动率降幅:{summary['total_reduction']}% ({summary['reduction_pct']}%)")
print(f" 最大波动率(N=1):{summary['max_vol']}%")
print(f" 最小波动率(N={volatility_curve[-1]['n']}):{summary['min_vol']}%")
# 教学结论
print(f"\n{'=' * 65}")
print(f"\n💡 教学启示:")
first_vol = volatility_curve[0]["avg_volatility"]
last_vol = volatility_curve[-1]["avg_volatility"]
if summary["reduction_pct"] > 50:
print(f" ✅ 分散化效果显著:波动率降低 {summary['reduction_pct']}%")
elif summary["reduction_pct"] > 20:
print(f" ⚠️ 分散化有一定效果:波动率降低 {summary['reduction_pct']}%")
else:
print(f" ⚠️ 分散化效果有限:波动率仅降低 {summary['reduction_pct']}%")
print(f" → 可能个股相关性过高,分散化无法有效降风险")
if elbow_n and elbow_n <= 5:
print(f"\n 📌 关键发现:仅需 {elbow_n} 只即可捕获大部分分散化收益")
print(f" → 「分散越多越好」存在明显天花板")
elif elbow_n:
print(f"\n 📌 关键发现:需要 {elbow_n} 只才趋于稳定")
print(f" → 该股票池相关性较低,需要更多标的分散")
print(f"\n 核心结论:")
print(f" 分散持仓能降风险,但存在明确边际递减规律。")
print(f" 「越多越好」不成立——找到拐点才是关键。")
print("=" * 65)
✅ storage.py(本地存储)
"""
storage.py
JSON 本地存储
"""
import json
FILE_PATH = "diversification_analysis.json"
def save_result(data):
with open(FILE_PATH, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
✅ main.py(交互入口)
"""
main.py
分散持仓数量与组合波动率分析工具
"""
import random
import numpy as np
from models import StockReturnSeries, SimulationConfig
from simulator import simulate_diversification
from analyzer import find_elbow_point, summarize
from reporter import report
from storage import save_result
def main():
print("=== 分散持仓数量 vs 组合波动率分析工具(教学版)===")
print("量化「分散越多风险越低」是否存在天花板\n")
# 个股数量
n_stocks = int(input("个股池数量(默认 30):") or "30")
max_holdings = int(input("最大测试持仓数(默认 20):") or "20")
sims = int(input("每个 N 模拟次数(默认 20):") or "20")
# 生成模拟数据(教学中可替换为真实数据)
print(f"\n📌 生成 {n_stocks} 只个股的模拟收益率序列...")
stock_pool = []
for i in range(1, n_stocks + 1):
symbol = f"STK{str(i).zfill(3)}"
# 模拟:均值 0.05%,标准差 2% 的日收益
returns = np.random.normal(0.05, 2.0, 252).tolist()
stock_pool.append(StockReturnSeries(symbol, returns))
config = SimulationConfig(
max_holdings=max_holdings,
simulations_per_n=sims
)
# 执行模拟
print(f"\n⏳ 模拟中...")
volatility_curve = simulate_diversification(stock_pool, config)
# 拐点检测
elbow_n, margins = find_elbow_point(volatility_curve)
# 汇总
summary = summarize(volatility_curve, elbow_n)
# 输出报告
report(volatility_curve, elbow_n, margins, summary)
# 保存结果
result_data = {
"config": {
"stock_pool_size": n_stocks,
"max_holdings": max_holdings,
"simulations_per_n": sims
},
"volatility_curve": volatility_curve,
"margins": margins,
"summary": summary
}
save_result(result_data)
print("\n✅ 分析结果已保存")
if __name__ == "__main__":
main()
五、README 与使用说明
# 分散持仓数量 vs 组合波动率分析工具(教学版)
## 项目说明
逐步增加持仓个股数量,测算组合波动率拐点,找到最优分散上限。
## 使用方式
bash
pip install numpy
python main.py
## 输入示例
个股池数量:30
最大测试持仓数:20
每个 N 模拟次数:20
## 核心概念
| 概念 | 含义 |
|---|---|
| 波动率 | 收益率的标准差(年化) |
| 边际降幅 | 增加 1 只股票带来的波动率减少量 |
| 拐点 | 边际降幅趋近于 0 的位置 |
| 最优分散上限 | 拐点对应的持仓数量 |
## 适用范围
- 量化投资课程
- 投资组合管理教学
- 风险分散边际效应演示
## 注意事项
- 仅基于历史 / 模拟数据
- 不构成任何投资建议
- 使用前请阅读 DISCLAIMER.md
六、DISCLAIMER.md(免责声明与风险提示)
# 免责声明与风险提示
## 免责声明
本程序仅供**教学与科研用途**,用于演示组合分散化与波动率关系。
作者不提供任何证券交易建议,不推荐任何股票,不承诺任何收益。
## 风险提示
1. 历史波动率 ≠ 未来波动率,市场结构可能突变
2. 等权假设简化了现实,实际仓位管理更复杂
3. 个股相关性在高波动期趋于 1,分散化效果骤降
4. 拐点检测阈值(0.5%)为教学假设,非"唯一正确答案"
5. 模拟数据基于正态分布假设,真实收益率分布可能有偏度和峰度
6. 分散化降低的是非系统性风险,系统性风险无法消除
使用本工具产生的任何后果,作者概不负责。
七、核心知识点卡片(教学向)
分类 内容
Python 类、随机数、NumPy 统计、列表推导
量化金融 组合波动率、分散化、边际递减效应
投资组合 非系统性风险 vs 系统性风险
数据分析 拐点检测、边际分析、蒙特卡洛模拟
工程思想 模块化、可配置、可复现
风险教育 分散有上限,不是越多越好
可扩展性 可接入真实收益率 API、支持不等权组合
八、总结(工程师视角)
这是一个完全中立、去营销化、可教学的原型系统:
✅ 不鼓吹分散投资
✅ 不妖魔化集中持仓
✅ 不伪装成组合优化神器
它真正展示的是:
如何用 Python 把"分散越多风险越低"这个直觉,变成可量化、可检验、可反思的数据实验
核心教学价值:
传统观念 数据可能揭示的真相
"买 50 只就稳了" 10 只之后的边际降幅可能 < 0.5%
"分散 = 无风险" 系统性风险(如 2008 年)无法分散消除
"指数基金最分散" 指数本身就是最优分散的近似解
"多买几只就够了" 个股相关性高时,分散效果大打折扣
典型模拟结果参考(教学示意):
持仓数 N 组合波动率(年化) 边际降幅
1 32.5% —
2 24.1% 8.4%
3 19.8% 4.3%
5 16.2% 1.8%
10 14.1% 0.4% ← 拐点
20 13.5% 0.1%
50 13.2% 0.0%
关键发现:从 1→10 只,波动率降低 57%;从 10→50 只,仅再降 6%。
拐点在 N=10 附近,之后继续分散意义极小。
本文代码仅供学习与技术交流,不构成任何投资建议,股市有风险,入市需谨慎!
利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!
