当前位置:首页>python>用 Python 玩转投资组合优化:从零掌握现代投资组合理论

用 Python 玩转投资组合优化:从零掌握现代投资组合理论

  • 2026-09-21 11:30:33
用 Python 玩转投资组合优化:从零掌握现代投资组合理论

2026年重磅升级已全面落地!欢迎加入专注财经数据与量化投研的【数据科学实战】知识星球!您将获取持续更新的《财经数据宝典》与《量化投研宝典》,双典协同提供系统化指引;星球内含300篇以上独有高质量文章,深度覆盖策略开发、因子分析、风险管理等核心领域,内容基本每日更新;同步推出的「量化因子专题教程」系列(含完整可运行代码与实战案例),系统详解因子构建、回测与优化全流程,并实现日更迭代。我们持续扩充独家内容资源,全方位赋能您的投研效率与专业成长。无论您是量化新手还是资深研究者,这里都是助您少走弯路、事半功倍的理想伙伴,携手共探数据驱动的投资未来!

引言

你是否想过,为什么不能把所有的钱都押在一只股票上?为什么基金经理总在强调"不要把鸡蛋放在一个篮子里"?

这背后其实有一套严谨的数学理论——现代投资组合理论(Modern Portfolio Theory,简称 MPT)。诺贝尔经济学奖得主 Harry Markowitz 用一个简单的洞见改变了整个投资界:你不应该孤立地看待每一项资产,而应该关注它们组合在一起的表现。

本文将带你用 Python 从零实现投资组合优化,涵盖以下核心内容:

  • • 投资组合的风险与收益如何用数学表达
  • • 如何用蒙特卡洛模拟构建有效前沿(Efficient Frontier)
  • • 如何用 scipy 优化器找到最优投资组合
  • • 如何用 CAPM 模型计算 Beta 和 Alpha

无论你是量化金融的初学者,还是想用 Python 提升投资分析能力的开发者,这篇文章都会对你有所帮助。


一、分散投资:金融界唯一的"免费午餐"

核心思想很简单:如果资产 A 涨的时候资产 B 跌(负相关),同时持有两者就能在不降低收益的前提下降低风险。这就是分散投资(Diversification)的魔力。

从此刻起,我们不再问"苹果是不是一只好股票?",而是问"苹果能让我的投资组合变得更好吗?"


二、投资组合的数学基础

2.1 投资组合收益

投资组合的收益就是各资产收益的加权平均:

其中 w 是权重向量(比如 [0.5, 0.5]),μ 是各资产的期望收益向量。

2.2 投资组合方差(关键难点)

风险并不是各资产波动率的简单加权平均,它取决于资产之间的协方差:

其中 Σ 是协方差矩阵(N × N),w 是权重向量(N × 1)。

为什么用矩阵运算? 如果你有 50 只股票,手动计算方差需要处理 50² = 2500 个交叉项。而矩阵运算 w @ Cov @ w 一行代码就能搞定。


三、用蒙特卡洛模拟构建有效前沿

有效前沿是指在给定风险水平下,能获得最高期望收益的所有最优投资组合的集合。我们通过模拟 5000 个随机组合来近似它。

3.1 数据准备

import pandas as pd
import
 numpy as np
import
 yfinance as yf
import
 matplotlib.pyplot as plt

# 1. 获取股票数据

tickers = ['AAPL', 'MSFT', 'GOOG', 'AMZN']
data = yf.download(tickers, start='2020-01-01', end='2023-12-31')['Close']
returns = data.pct_change().dropna()

# 2. 计算年化统计量

# 期望收益 = 日均收益 × 252(交易日)

mean_returns = returns.mean() * 252

# 协方差矩阵 = 日协方差 × 252

cov_matrix = returns.cov() * 252

print
("--- 年化协方差矩阵 ---")
print
(cov_matrix)

3.2 模拟随机投资组合

num_portfolios = 5000
results = np.zeros((3, num_portfolios))  # 三行:收益、波动率、夏普比率

np.random.seed(42)

for
 i in range(num_portfolios):
    # 1. 生成随机权重

    weights = np.random.random(4)
    weights /= np.sum(weights)  # 归一化,使权重之和为 1

    # 2. 计算投资组合收益

    p_return = np.sum(mean_returns * weights)

    # 3. 计算投资组合波动率(标准差)

    # 公式:sqrt(w^T * Cov * w)

    p_std = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))

    # 4. 存储结果

    results[0, i] = p_return
    results[1, i] = p_std
    # 夏普比率(假设无风险利率为 0)

    results[2, i] = results[0, i] / results[1, i]

# 转为 DataFrame 方便绘图

results_df = pd.DataFrame(results.T, columns=['Return', 'Volatility', 'Sharpe'])

3.3 可视化有效前沿

plt.figure(figsize=(10, 6))

# 散点图,颜色代表夏普比率

plt.scatter(results_df['Volatility'], results_df['Return'],
            c=results_df['Sharpe'], cmap='viridis', marker='o', s=10, alpha=0.7)
plt.colorbar(label='Sharpe Ratio')
plt.xlabel('波动率(风险)')
plt.ylabel('期望收益')
plt.title('有效前沿(蒙特卡洛模拟)')

# 标记模拟中夏普比率最高的组合

max_sharpe_idx = results_df['Sharpe'].idxmax()
max_sharpe_port = results_df.iloc[max_sharpe_idx]
plt.scatter(max_sharpe_port['Volatility'], max_sharpe_port['Return'],
            c='red', s=100, marker='*', label='最高夏普比率(模拟)')

plt.legend()
plt.show()

如何解读这张图?

  • • 左上方边界就是有效前沿。在这条线左边,不存在更优的组合。
  • • 内部的点都是低效的——同样的风险下,你可以找到收益更高的组合。

四、精确优化:用 scipy 找到最优组合

蒙特卡洛模拟只是近似值。要找到数学上精确的最优权重,我们需要用优化器。

4.1 定义目标函数

import scipy.optimize as sco

def
 get_portfolio_stats(weights, mean_returns, cov_matrix, rf_rate=0):
    """
    返回 [投资组合收益, 波动率, 夏普比率]
    """

    weights = np.array(weights)
    port_return = np.sum(mean_returns * weights)
    port_volatility = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))
    # 夏普比率 =(收益 - 无风险利率)/ 波动率

    sharpe = (port_return - rf_rate) / port_volatility
    return
 [port_return, port_volatility, sharpe]

# 目标函数 1:最大化夏普比率 → 最小化负夏普比率

def
 neg_sharpe(weights, mean_returns, cov_matrix, rf_rate=0):
    return
 -get_portfolio_stats(weights, mean_returns, cov_matrix, rf_rate)[2]

# 目标函数 2:最小化波动率

def
 minimize_volatility(weights, mean_returns, cov_matrix):
    return
 get_portfolio_stats(weights, mean_returns, cov_matrix)[1]

为什么要取负值? 因为 scipy 只有 minimize 函数。要最大化 f(x),就最小化 -f(x)。

4.2 设置约束条件

num_assets = len(tickers)

# 约束:权重之和 = 1(100% 投资)

constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})

# 边界:每个权重在 0 到 1 之间(不允许卖空)

bounds = tuple((0, 1) for _ in range(num_assets))

# 初始猜测:等权分配

init_guess = num_assets * [1. / num_assets]

4.3 求解最优组合

A. 最大夏普比率组合(切线组合)

# 最小化负夏普比率
opt_sharpe = sco.minimize(neg_sharpe,
                          init_guess,
                          args=(mean_returns, cov_matrix),
                          method='SLSQP',
                          bounds=bounds,
                          constraints=constraints)

# 提取结果

max_sharpe_weights = opt_sharpe.x
max_sharpe_stats = get_portfolio_stats(max_sharpe_weights, mean_returns, cov_matrix)

print
("--- 最大夏普比率组合 ---")
print
(f"收益率:{max_sharpe_stats[0]*100:.2f}%")
print
(f"波动率:{max_sharpe_stats[1]*100:.2f}%")
print
(f"夏普比率:{max_sharpe_stats[2]:.2f}")
print
("\n最优权重:")
for
 ticker, weight in zip(tickers, max_sharpe_weights):
    print
(f"{ticker}:{weight*100:.2f}%")

运行结果示例:

--- 最大夏普比率组合 ---
收益率:28.91%
波动率:31.30%
夏普比率:0.92

最优权重:
AAPL:59.24%
MSFT:0.00%
GOOG:1.29%
AMZN:39.47%

注意优化器可能会将某些资产的权重设为 0%。这意味着 MPT 认为在当前条件下,该资产对组合没有正向贡献。

B. 最小波动率组合

# 最小化波动率
opt_vol = sco.minimize(minimize_volatility,
                       init_guess,
                       args=(mean_returns, cov_matrix),
                       method='SLSQP',
                       bounds=bounds,
                       constraints=constraints)

min_vol_weights = opt_vol.x
min_vol_stats = get_portfolio_stats(min_vol_weights, mean_returns, cov_matrix)

print
("\n--- 最小波动率组合 ---")
print
(f"收益率:{min_vol_stats[0]*100:.2f}%")
print
(f"波动率:{min_vol_stats[1]*100:.2f}%")
print
(f"夏普比率:{min_vol_stats[2]:.2f}")

五、CAPM 模型:理解 Beta 和 Alpha

现代投资组合理论告诉我们,风险分为两类:

  • • 个体风险(Idiosyncratic Risk):公司特有的风险,可以通过分散投资消除
  • • 系统风险(Systematic Risk):市场整体的风险,无法消除

CAPM 公式为:

其中 Beta(β) 衡量资产相对于市场的敏感度,Alpha(α) 衡量超出市场预期的超额收益。

5.1 计算 NVDA 的 Beta

from scipy import stats

# 1. 获取数据

tickers = ['NVDA', 'SPY']
data = yf.download(tickers, start='2020-01-01', end='2023-12-31')['Close']
returns = data.pct_change().dropna()

# 2. 准备回归数据

X = returns['SPY'].values   # 市场收益(自变量)
Y = returns['NVDA'].values  # 股票收益(因变量)

# 3. 执行线性回归

slope, intercept, r_value, p_value, std_err = stats.linregress(X, Y)

beta = slope       # Beta = 回归斜率
alpha = intercept   # Alpha = 回归截距
r_squared = r_value ** 2

print
(f"--- CAPM 回归结果(NVDA vs SPY)---")
print
(f"Beta(系统风险):{beta:.4f}")
print
(f"Alpha(日超额收益):{alpha:.5f}")
print
(f"R²(拟合优度):{r_squared:.4f}")

运行结果示例:

Beta(系统风险):1.7182
Alpha(日超额收益):0.00175
R²(拟合优度):0.5140

解读: Beta 约为 1.72,说明 NVDA 是高 Beta 股票——市场涨 1%,NVDA 平均涨约 1.72%;反之市场跌时,它也会跌得更多。

5.2 滚动 Beta:观察风险的动态变化

Beta 并非一成不变。我们可以用滚动窗口来观察它随时间的变化:

# 计算 60 天滚动 Beta
rolling_cov = returns['NVDA'].rolling(window=60).cov(returns['SPY'])
rolling_var = returns['SPY'].rolling(window=60).var()
rolling_beta = rolling_cov / rolling_var

plt.figure(figsize=(10, 5))
rolling_beta.plot(color='purple', label='60 天滚动 Beta')
plt.axhline(beta, color='black', linestyle='--', label='平均 Beta')
plt.title("NVDA 的滚动 Beta 变化")
plt.legend()
plt.show()

六、实战案例:构建简易智能投顾引擎

在实际场景中,用户不会说"给我切线组合",而是说"我偏保守"或"我能承受高风险"。我们可以用不同的目标波动率来匹配不同的风险偏好。

# 资产:股票(SPY)、债券(TLT)、黄金(GLD)
tickers = ['SPY', 'TLT', 'GLD']
data = yf.download(tickers, start='2020-01-01', end='2023-12-31', progress=False)['Close']
returns = data.pct_change().dropna()

mean_returns = returns.mean() * 252
cov_matrix = returns.cov() * 252

def
 get_port_stats(weights):
    """计算投资组合的收益和波动率"""

    weights = np.array(weights)
    ret = np.sum(mean_returns * weights)
    vol = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))
    return
 ret, vol

def
 robo_advisor(target_volatility):
    """根据目标波动率优化投资组合"""

    num_assets = len(tickers)

    # 目标:最大化收益(最小化负收益)

    def
 neg_return(weights):
        return
 -get_port_stats(weights)[0]

    # 约束条件

    constraints = (
        {'type': 'eq', 'fun': lambda x: np.sum(x) - 1},              # 权重之和 = 1
        {'type': 'eq', 'fun': lambda x: get_port_stats(x)[1] - target_volatility}  # 波动率 = 目标值
    )

    bounds = tuple((0, 1) for _ in range(num_assets))
    init_guess = num_assets * [1. / num_assets]

    result = sco.minimize(neg_return, init_guess, method='SLSQP',
                          bounds=bounds, constraints=constraints)
    return
 result.x

# 运行三种风险偏好

profiles = {
    "保守型(波动率 5%)"
: 0.05,
    "均衡型(波动率 10%)"
: 0.10,
    "激进型(波动率 15%)"
: 0.15
}

print
("--- 智能投顾推荐 ---")
for
 profile, target_vol in profiles.items():
    try
:
        weights = robo_advisor(target_vol)
        ret, vol = get_port_stats(weights)
        print
(f"\n{profile}:")
        print
(f"  期望收益:{ret*100:.2f}%")
        print
(f"  配置:SPY {weights[0]*100:.0f}% | TLT {weights[1]*100:.0f}% | GLD {weights[2]*100:.0f}%")
    except
 Exception as e:
        print
(f"无法求解 {profile},目标可能不可行。")

总结

本文带你完整走了一遍现代投资组合理论的核心流程:

  1. 1. 分散投资的数学原理:通过协方差矩阵,我们证明了将不完全相关的资产组合在一起可以降低总体风险。
  2. 2. 有效前沿:用蒙特卡洛模拟生成 5000 个随机组合,可视化了风险-收益的边界。
  3. 3. 精确优化:利用 scipy.optimize 精确求解了最大夏普比率组合和最小波动率组合。
  4. 4. CAPM 模型:将收益分解为 Alpha(技能/运气)和 Beta(市场敞口),理解了市场只为系统风险付费。
  5. 5. 智能投顾:构建了一个根据用户风险偏好自动优化配置的引擎。

掌握这些知识后,你就具备了用 Python 进行量化投资分析的基础能力。下一步可以探索回测引擎、交易成本建模等更深入的主题。


参考文章

加入专注于财经数据与量化投研的知识星球【数据科学实战】,获取本文完整研究解析、代码实现细节。

财经数据与量化投研知识社区

2026年全面升级已落地!【数据科学实战】知识星球核心权益如下:

  1. 1. 双典系统赋能:获赠《财经数据宝典》与《量化投研宝典》完整文档,凝练多年实战经验,构建系统化知识框架;
  2. 2. 量化因子日更教程(2026重磅新增):每日更新「量化因子专题教程」,配套完整可运行代码与实战案例,深度拆解因子构建、回测与优化全流程;
  3. 3. 量化文章专题教程库:300+篇星球独有高质量教程式文章,系统覆盖策略开发、因子研究、风险管理等核心领域,内容基本每日更新,并配套精选学习资料与实战参考;
  4. 4. PyBroker实战课程:赠送《PyBroker-入门及实战》视频课程,手把手教学,快速掌握量化策略开发技能;
  5. 5. 财经数据支持:定期更新国内外财经数据,为策略研发提供精准、可靠的数据基础;
  6. 6. 顶尖学者与行业专家分享:年度邀请学术界博士与业界资深专家开展前沿论文精讲与实战案例分享,不少于4场,直击研究前沿与产业实践;
    专家直连答疑:与核心开发者及领域专家实时互动,高效解决投研实战难题;
  7. 7. 专业社群与专属福利:加入高质量交流社群,获取课程折扣及更多独家资源。

星球已沉淀丰富内容生态——涵盖量化文章专题教程库、因子日更系列、高频数据集、PyBroker实战课程、专家深度分享与实时答疑服务。无论您是初探量化的学习者,还是深耕领域的从业者,这里都是助您少走弯路、高效成长的理想平台。诚邀加入,共探数据驱动的投资未来!

好文推荐

1. 用 Python 打造股票预测系统:Transformer 模型教程(一)

2. 用 Python 打造股票预测系统:Transformer 模型教程(二)

3. 用 Python 打造股票预测系统:Transformer 模型教程(三)

4. 用 Python 打造股票预测系统:Transformer 模型教程(完结)

5. 揭秘隐马尔可夫模型:因子投资的制胜武器

6. YOLO 也能预测股市涨跌?计算机视觉在股票市场预测中的应用

7. 金融 AI 助手:FinGPT 让你轻松掌握市场分析

8. 量化交易秘籍:为什么专业交易员都在用对数收益率?

9. Python 量化投资利器:Ridge、Lasso 和 Elastic Net 回归详解

10. 掌握金融波动率模型:完整 Python 实现指南

好书推荐

最新文章

随机文章