有效前沿图

有效前沿图2

均值方差模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
代码如下:
import pandas as pd
import numpy as np
import scipy.optimize as sco
import matplotlib.pyplot as plt

# ====================== 1. 内嵌模拟A股数据 ======================
np.random.seed(42) # 固定随机种子,结果可复现
# 标的信息(A股)
tickers = ['600519.SH', '300750.SZ', '600036.SH', '002594.SZ', '000651.SZ']
ticker_names = ['贵州茅台', '宁德时代', '招商银行', '比亚迪', '格力电器']
name_map = dict(zip(tickers, ticker_names))

# 生成5年日频收盘价(252*5=1260个交易日)
n_days = 252 * 5
base_prices = [1000, 200, 30, 150, 30] # 初始股价(贴合A股真实价格)
prices_data = []
for i in range(len(tickers)):
# 模拟日收益率(均值0.03%,波动率1.5%,贴合A股特征)
daily_ret = np.random.normal(0.0003, 0.015, n_days)
# 计算累计收益率 -> 收盘价
cum_ret = np.cumprod(1 + daily_ret)
price_series = base_prices[i] * cum_ret
prices_data.append(price_series)

# 构建价格DataFrame
prices = pd.DataFrame(
np.array(prices_data).T,
columns=tickers,
index=pd.date_range(start='2019-01-01', periods=n_days, freq='B') # B=工作日
)

# 计算日对数收益率(剔除首行缺失值)
daily_returns = np.log(prices / prices.shift(1)).dropna()

# ====================== 2. 均值方差模型核心逻辑 ======================
# 年化预期收益率(日均值 * 252)
mu = daily_returns.mean() * 252
# 年化协方差矩阵(日协方差 * 252)
cov = daily_returns.cov() * 252
# 无风险利率(国内1年期国债收益率,取2%)
risk_free_rate = 0.02

# 定义组合收益/风险/夏普比率计算函数
def portfolio_performance(weights, mu, cov):
port_return = np.sum(mu * weights) # 组合收益
port_vol = np.sqrt(np.dot(weights.T, np.dot(cov, weights))) # 组合波动率
sharpe_ratio = (port_return - risk_free_rate) / port_vol # 夏普比率
return port_return, port_vol, sharpe_ratio

# 优化目标1:最小化组合波动率(用于有效前沿/最小方差组合)
def minimize_volatility(weights, mu, cov):
return portfolio_performance(weights, mu, cov)[1]

# 优化目标2:最大化夏普比率(等价于最小化负夏普比率)
def maximize_sharpe(weights, mu, cov):
return -portfolio_performance(weights, mu, cov)[2]

# 约束条件:权重和为1;边界:权重∈[0,1](不允许做空)
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
bounds = tuple((0, 1) for _ in range(len(tickers)))
initial_guess = np.array([1/len(tickers)] * len(tickers)) # 等权初始值

# 2.1 最小方差组合(风险最低)
min_vol_result = sco.minimize(
minimize_volatility, initial_guess,
args=(mu, cov), method='SLSQP',
bounds=bounds, constraints=constraints
)
min_vol_weights = min_vol_result['x']
min_vol_return, min_vol_vol, _ = portfolio_performance(min_vol_weights, mu, cov)

# 2.2 最大夏普比率组合(风险调整收益最高)
max_sharpe_result = sco.minimize(
maximize_sharpe, initial_guess,
args=(mu, cov), method='SLSQP',
bounds=bounds, constraints=constraints
)
max_sharpe_weights = max_sharpe_result['x']
max_sharpe_return, max_sharpe_vol, _ = portfolio_performance(max_sharpe_weights, mu, cov)

# 2.3 生成有效前沿(遍历不同目标收益,求解最小风险)
target_returns = np.linspace(min_vol_return, mu.max(), 50)
target_vols = []
for target_ret in target_returns:
# 新增约束:组合收益=目标收益
constraints_target = (
{'type': 'eq', 'fun': lambda x: np.sum(x) - 1},
{'type': 'eq', 'fun': lambda x: portfolio_performance(x, mu, cov)[0] - target_ret}
)
# 求解该收益下的最小波动率
result = sco.minimize(
minimize_volatility, initial_guess,
args=(mu, cov), method='SLSQP',
bounds=bounds, constraints=constraints_target
)
target_vols.append(result['fun'])
target_vols = np.array(target_vols)

# ====================== 3. 可视化有效前沿 ======================
plt.rcParams['font.sans-serif'] = ['SimHei'] # 解决中文显示问题
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题

plt.figure(figsize=(10, 6))
# 绘制有效前沿曲线
plt.plot(target_vols, target_returns, 'b-', linewidth=2, label='有效前沿')
# 标注最小方差组合(红色)
plt.scatter(min_vol_vol, min_vol_return, c='red', s=120, label='最小方差组合')
# 标注最大夏普比率组合(绿色)
plt.scatter(max_sharpe_vol, max_sharpe_return, c='green', s=120, label='最大夏普比率组合')

# 标注单个资产(橙色)
for i, ticker in enumerate(tickers):
asset_vol = np.sqrt(cov.iloc[i, i]) # 单资产波动率
asset_ret = mu[i] # 单资产收益率
plt.scatter(asset_vol, asset_ret, c='orange', s=90, label=ticker_names[i] if i==0 else "")
plt.text(asset_vol + 0.005, asset_ret, ticker_names[i], fontsize=9)

# 图表样式设置
plt.xlabel('年化波动率(风险)', fontsize=11)
plt.ylabel('年化收益率', fontsize=11)
plt.title('均值方差模型 - A股有效前沿(模拟数据)', fontsize=12)
plt.legend(loc='upper left', fontsize=9)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

# ====================== 4. 输出结果 ======================
print("="*40)
print("均值方差模型计算结果(A股模拟数据)")
print("="*40)

print("\n【最小方差组合】(风险最低)")
print("-"*30)
for ticker, weight in zip(tickers, min_vol_weights):
print(f"{name_map[ticker]} ({ticker}): {weight:.2%}")
print(f"组合年化收益:{min_vol_return:.2%}")
print(f"组合年化波动率:{min_vol_vol:.2%}")

print("\n【最大夏普比率组合】(风险调整收益最高)")
print("-"*30)
for ticker, weight in zip(tickers, max_sharpe_weights):
print(f"{name_map[ticker]} ({ticker}): {weight:.2%}")
print(f"组合年化收益:{max_sharpe_return:.2%}")
print(f"组合年化波动率:{max_sharpe_vol:.2%}")
print(f"组合夏普比率:{(max_sharpe_return - risk_free_rate)/max_sharpe_vol:.2f}")