# 求解资产配置比例以期待在达到目标期望收益率时将风险最小化
# 投资者的资产配置问题最终可以转化为一个二次规划问题来表达
# 构建一个MeanVariance类,该类可以根据输入的收益率序列,求解上述的二次规划问题,计算出最优资产比例,并绘制最小方差前缘曲线。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.linalg import solve
from scipy.optimize import minimize
from scipy import linalg


class MeanVariance:
    # 定义构造器,传入收益率数据
    def __init__(self, returns):
        self.returns = returns

    # 定义最小化方差的函数,即求解二次规划(不允许做空)
    def minVar(self, goalRet):
        covs = np.array(self.returns.cov())
        means = np.array(self.returns.mean())
        n = len(means)

        # 使用scipy.optimize.minimize来求解带约束的二次规划问题
        # 目标函数:最小化方差 w^T * Cov * w
        def objective(w):
            return np.dot(w.T, np.dot(covs, w))

        # 约束条件
        constraints = [
            # 期望收益率约束:w^T * means = goalRet
            {'type': 'eq', 'fun': lambda w: np.dot(w, means) - goalRet},
            # 权重和为1约束:sum(w) = 1
            {'type': 'eq', 'fun': lambda w: np.sum(w) - 1}
        ]

        # 边界条件:不允许做空,权重在0到1之间
        bounds = [(0, 1) for _ in range(n)]

        # 初始猜测(等权重)
        x0 = np.ones(n) / n

        # 求解优化问题
        result = minimize(objective, x0, method='SLSQP', bounds=bounds, constraints=constraints)

        if result.success:
            return np.array([list(self.returns.columns), result.x])
        else:
            raise ValueError("优化失败: " + result.message)


# 读取数据(注意使用制表符分隔)
data = pd.read_csv('industry2.csv', sep='\t', index_col=0)
print("原始数据形状:", data.shape)
print("\n前5行数据:")
print(data.head())

# 计算各资产的统计信息
print("\n各资产的平均收益率:")
mean_returns = data.mean()
print(mean_returns)

print("\n各资产的标准差:")
print(data.std())

# 创建MeanVariance实例
mv = MeanVariance(data)

# 设置目标收益率(取平均收益率的平均值)
target_return = mean_returns.mean()
print(f"\n目标收益率: {target_return:.6f}")

# 求解最优权重
result = mv.minVar(target_return)
print("\n二次规划输出结果:")
print(result)

# 解析和格式化结果
print("\n=== 最优资产配置权重 ===")
asset_names = result[0]
weights = result[1].astype(float)

for i, (asset, weight) in enumerate(zip(asset_names, weights)):
    print(f"{i + 1}. {asset}: {weight:.6f} ({weight * 100:.4f}%)")

# 验证权重和为1
print(f"\n权重总和: {weights.sum():.10f}")

# 计算实际收益率和风险
actual_return = np.dot(weights, mean_returns)
cov_matrix = data.cov()
portfolio_variance = np.dot(weights.T, np.dot(cov_matrix, weights))
portfolio_std = np.sqrt(portfolio_variance)

print(f"\n实际获得的收益率: {actual_return:.6f}")
print(f"投资组合标准差(风险): {portfolio_std:.6f}")

# 计算夏普比率(假设无风险收益率为0)
sharpe_ratio = actual_return / portfolio_std if portfolio_std > 0 else 0
print(f"夏普比率: {sharpe_ratio:.6f}")

# 显示各资产的详细统计信息
print("\n=== 各资产详细统计 ===")
stats_df = pd.DataFrame({
    '平均收益率': mean_returns,
    '标准差': data.std(),
    '最终权重': weights
})
print(stats_df)

# 创建时间段的标识(使用数据的时间范围)
start_date = data.index.min()
end_date = data.index.max()
time_period = f"{start_date}-{end_date}"

# 创建权重数据框,格式为:时间段作为行,资产作为列
weights_dict = {}
for asset, weight in zip(asset_names, weights):
    weights_dict[asset] = weight

# 创建DataFrame,时间段作为索引,资产权重作为值
weights_df = pd.DataFrame([weights_dict], index=[time_period])

print("\n=== 权重数据框 ===")
print(weights_df)

# 保存为CSV文件
output_filename = 'portfolio_weights_by_period.csv'
weights_df.to_csv(output_filename, encoding='utf-8-sig')
print(f"\n权重数据已保存到: {output_filename}")

输出结果:

portfolio_weights_by_period.csv数据结果如下:

使用的industry2.csv数据文件与Black-Litterman模型数据文件一致。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐