上一篇:【基于python的金融分析和风险管理 学习笔记】基础篇 第2章 投资收益率 终值 现值 等额本息还款
目录
专栏简介
教材信息
专栏说明
移动统计量:移动平均 移动波动率 移动相关系数
分析
代码
解方程:求每只股票的权重和股数
分析
代码
正态分布检验
构建普通最小二乘法回归模型
分析
代码
波动率模型
专栏简介
教材信息
《基于python的金融分析和风险管理》 第二版 作者:斯文
专栏说明
本专栏跳过第1、4章的python语法部分,重点介绍案例和金融分析风险管理领域的知识。案例为原书中的精简概况,出处已标注。
专栏的案例省略了原书在语法讲解上的例子,所以序号不连贯
数据为书中的配套数据,分析与代码为本人原创,如有错误欢迎评论
移动统计量:移动平均 移动波动率 移动相关系数
案例(原书3-26到3-28):
整理数据
分析
1. 移动平均:均线指标,如5日均线、10日均线
2. 移动波动率:标准差
3. 移动相关系数:变量之间的相关系数随时间而变化,尤其是在金融危机时,许多原本相关性很低的变量也呈现高相关性 => 计算移动相关系数以更精确地捕捉相关系数的变化
4. 运行结果:


代码
"""
1. 读取“上证综指每个交易日价格数据(2019年).xlsx”和“上证综指每个交易日价格数据(2020年).xlsx”
2. 合并两个年度表格
3. 生成10日移动平均收盘价序列
4. 将“每日收盘价”和“10日均值收盘价”画在同一张图中
5. 生成30天时间窗口的收盘价移动波动率(滚动标准差)列,并单独可视化
6. 计算60天时间窗口下,开盘价、最高价、最低价、收盘价两两之间的移动相关系数
7. 导出最终结果到Excel,便于查看和继续分析
"""
import matplotlib.pyplot as plt
import pandas as pd
import matplotlib.dates as mdates
# 中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 数据处理
data2019=pd.read_excel(r'C:\\Users\\victo\\Desktop\\book_FARM\\ch3 pandas\\data\\上证综指每个交易日价格数据(2019年).xlsx')
data2020=pd.read_excel(r'C:\\Users\\victo\\Desktop\\book_FARM\\ch3 pandas\\data\\上证综指每个交易日价格数据(2020年).xlsx')
data_merge=pd.concat([data2019,data2020],axis=0,ignore_index=True) # 纵向合并,向下拼接
# print(f"合并之后的数据为:\\n{data_merge}") # 3-22
data_merge.to_excel(r'C:\\Users\\victo\\Desktop\\book_FARM\\ch3 pandas\\output\\data_merge.xlsx',index=False) # 保存文件
# 1.将“每日收盘价”和“10日均值收盘价”画在同一张图中
data_merge['日期']=pd.to_datetime(data_merge['日期']) # 转换日期列
MA10=data_merge['收盘价'].rolling(window=10).mean() # 10日移动平均收盘价序列
# print(f"10日移动平均收盘价序列为:\\n{MA10}")
MA10=MA10.to_frame() # 转换为DataFrame
MA10=MA10.rename(columns={'收盘价':'10日均值收盘价'}) # 重命名列名
daily_close=data_merge['收盘价'].to_frame() # 转换收盘价列
AC_MA10=pd.concat([data_merge[['日期']],daily_close,MA10],axis=1) # 合并数据
ax=AC_MA10.plot(x='日期',y=['收盘价','10日均值收盘价'],figsize=(9,6),title=u'2019-2020上证综指走势',grid=True,fontsize=13) # 绘制图像,x、y轴名称
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=6)) # 设置x轴刻度间隔
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m')) # 设置x轴刻度格式
plt.xticks(rotation=0) # 旋转x轴刻度
plt.show()
# 2. 30天时间窗口的收盘价移动波动率(滚动标准差)列
daily_std=data_merge['收盘价'].rolling(window=30).std()
daily_std=daily_std.to_frame()
daily_std=daily_std.rename(columns={'收盘价':'30日移动收盘价的波动率'})
daily_std=pd.concat([data_merge[['日期']],daily_std],axis=1)
daily_std.plot(x='日期',figsize=(9,6),title=u'2019-2020上证综指收盘价30天时间窗口的收盘价移动波动率',grid=True,fontsize=13)
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=6))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
plt.xticks(rotation=0)
plt.show()
# 3. 60天时间窗口下,开盘价、最高价、最低价、收盘价两两之间的移动相关系数
corr=data_merge.set_index('日期')[['开盘价','最高价','最低价','收盘价']].rolling(window=60).corr()
corr=corr.dropna() # 删除NaN行
print(f'查看前五行数据:\\n{corr.head()}')
解方程:求每只股票的权重和股数
案例(原书5-4):
投资1亿元,按照以下价格进行投资,实现投资组合收益率最大,且投资组合的平均市盈率不高于15,不许卖空,求每只股票的权重和股数

分析
1. 市盈率=股价/每股收益
若市盈率=10,则表示投资者需要10元才能买到公司1元的利润;或理解为:若利润不变,公司10年可以赚回股价
| 市盈率 | 高 | 市场预期增长快,或股价被高估 |
| 市盈率 | 低 | 公司前景差,或股价被低估 |
不同产业的市盈率不可直接比较!!
2. 计算过程:
,其中:
R:收益率 w:权重
:市盈率 P:股价
:目标函数
约束:
权重之和等于1
题目中市盈率的要求
权重不是负的
3. 结果 
代码
'''
投资1亿元,按照以下价格进行投资,实现投资组合收益率最大,且投资组合的平均市盈率不高于15,
不许卖空,求每只股票的权重和股数
'''
import numpy as np
import pandas as pd
from scipy.optimize import linprog
# =========================
# 1. 输入数据
# =========================
stocks = ["工商银行", "中国国航", "长江电力", "上海医药", "永辉超市"]
codes = ["601398", "601111", "600900", "601607", "601933"]
# 2019年至2020年平均年化收益率(转成小数)
returns = np.array([
5.4703 / 100,
5.3580 / 100,
21.6717 / 100,
4.9761 / 100,
8.6041 / 100
])
# 2020-12-31 收盘价(元/股)
prices = np.array([
4.99,
7.49,
19.16,
19.20,
7.18
])
# 2020-12-31 市盈率
pe = np.array([
5.6961,
16.9758,
20.2258,
13.3713,
43.6949
])
# 总资金
total_capital = 100000000 # 1亿元
# 平均市盈率上限
pe_limit = 15
# =========================
# 2. 建立线性规划模型
# =========================
# linprog默认求最小值,这里把最大化收益率转成最小化负收益率
c = -returns
# 不等式约束:组合平均市盈率 <= 15
A_ub = [pe]
b_ub = [pe_limit]
# 等式约束:权重和 = 1
A_eq = [np.ones(len(stocks))]
b_eq = [1]
# 边界约束:不允许卖空
bounds = [(0, 1) for _ in stocks]
# 求解
result = linprog(
c=c,
A_ub=A_ub,
b_ub=b_ub,
A_eq=A_eq,
b_eq=b_eq,
bounds=bounds,
method="highs"
)
# =========================
# 3. 输出结果
# =========================
if not result.success:
print("优化失败:", result.message)
else:
weights = result.x
invest_amount = weights * total_capital
shares = invest_amount / prices
portfolio_return = np.dot(weights, returns)
portfolio_pe = np.dot(weights, pe)
df_result = pd.DataFrame({
"证券代码": codes,
"证券简称": stocks,
"最优权重": weights,
"最优权重(%)": weights * 100,
"收盘价(元/股)": prices,
"平均年化收益率(%)": returns * 100,
"市盈率": pe,
"投资金额(元)": invest_amount,
"买入股数(股)": shares
})
pd.set_option("display.max_columns", None)
pd.set_option("display.width", 200)
pd.set_option("display.float_format", lambda x: f"{x:,.6f}")
print("======== 最优投资组合结果 ========")
print(df_result)
print("\\n======== 组合总体指标 ========")
print(f"组合预期年化收益率:{portfolio_return:.6%}")
print(f"组合平均市盈率:{portfolio_pe:.6f}")
print(f"总投资金额:{invest_amount.sum():,.2f} 元")
print("\\n======== 更直观的结果 ========")
for i in range(len(stocks)):
print(
f"{stocks[i]}({codes[i]}):"
f"权重 = {weights[i]:.6%},"
f"投资金额 = {invest_amount[i]:,.2f} 元,"
f"买入股数 = {shares[i]:,.2f} 股"
)
正态分布检验
1. KS检验、Anderson-Darling检验:
原假设:样本服从的分布=给定的分布
2. Shapiro-Wilk 检验:仅用于正态分布,样本大于5000会不准
3. normaltest检验:仅用于正态分布,支持更多样本
构建普通最小二乘法回归模型
案例(原书5-12):
A股下午3点收盘,H股(港股)下午4点收盘,以H股日收益率为因变量,A股日收益率为自变量构建普通最小二乘法回归模型

分析


代码
'''
A股下午3点收盘,H股(港股)下午4点收盘,以H股日收益率为因变量,
A股日收益率为自变量构建普通最小二乘法回归模型
'''
# -*- coding: utf-8 -*-
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import statsmodels.api as sm
from matplotlib import font_manager
# =============================
# 1 解决中文显示为方块
# =============================
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei'] # 微软雅黑
plt.rcParams['axes.unicode_minus'] = False
# =============================
# 2 读取数据
# =============================
file_path = r"C:\\Users\\victo\\Desktop\\book_FARM\\ch5 scipy\\data\\交通银行A股和H股每日收盘价数据(2018年至2020年).xlsx"
df = pd.read_excel(file_path)
# 查看列名
print("数据列名:", df.columns)
# 转换日期格式
df["日期"] = pd.to_datetime(df["日期"])
# 按时间排序
df = df.sort_values("日期")
# =============================
# 3 计算日收益率
# =============================
df["A_return"] = df["交通银行A股"].pct_change()
df["H_return"] = df["交通银行H股"].pct_change()
df = df.dropna()
# =============================
# 4 OLS回归
# H股收益率 = α + β * A股收益率
# =============================
X = sm.add_constant(df["A_return"])
Y = df["H_return"]
model = sm.OLS(Y, X).fit()
alpha = model.params["const"]
beta = model.params["A_return"]
print("\\nOLS回归结果")
print("截距 α =", alpha)
print("系数 β =", beta)
# =============================
# 5 折线走势图
# =============================
plt.figure(figsize=(12,6))
plt.plot(df["日期"], df["A_return"], label="A股日收益率")
plt.plot(df["日期"], df["H_return"], label="H股日收益率")
# 每半年一个刻度
ax = plt.gca()
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=6))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
plt.xlabel("日期")
plt.ylabel("日收益率")
plt.title("交通银行A股与H股日收益率走势")
plt.legend()
plt.grid(True)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
# =============================
# 6 散点 + 线性拟合
# =============================
plt.figure(figsize=(8,6))
plt.scatter(df["A_return"], df["H_return"], alpha=0.5)
# 拟合线
x_line = np.linspace(df["A_return"].min(), df["A_return"].max(),100)
y_line = alpha + beta*x_line
plt.plot(x_line, y_line, color="red", linewidth=2)
plt.xlabel("A股日收益率")
plt.ylabel("H股日收益率")
plt.title("A股收益率与H股收益率线性拟合")
plt.grid(True)
plt.tight_layout()
plt.show()
波动率模型
在金融时间序列分析中,人们很早就发现一个现象:资产收益率本身往往看起来像“白噪声”,但它的波动大小却会一段时间高、一段时间低。例如在金融危机、政策冲击或重大事件期间,市场会连续很多天剧烈波动,而在平稳时期,波动又会持续较小。这种现象被称为波动率聚集(volatility clustering)。传统的线性回归模型通常假设误差方差是恒定的,但金融数据显然不满足这一假设。为了刻画这种“波动会随着时间变化”的特征,经济学家提出了 ARCH 和 GARCH 模型。
ARCH(Autoregressive Conditional Heteroskedasticity,自回归条件异方差)模型是最早用来描述这种现象的模型。以最简单的 ARCH(1) 为例,它的思想其实很直观:今天市场波动的大小,取决于昨天的冲击有多大。如果昨天的收益率偏离均值很大,那么今天的波动率也更可能较大;如果昨天的波动很小,那么今天大概率也比较平稳。换句话说,ARCH(1) 认为当前的方差由上一期误差的平方决定。这种设定很好地解释了金融市场中“大的波动后面往往跟着大的波动,小的波动后面往往跟着小的波动”的特征。
不过在实际应用中,人们发现单纯依赖过去误差的 ARCH 模型往往需要很多阶(例如 ARCH(5)、ARCH(10))才能很好地拟合数据,这会使模型变得复杂。为了解决这个问题,Bollerslev 在 1986 年提出了 GARCH(Generalized ARCH)模型。最常见的 GARCH(1,1) 在 ARCH 的基础上增加了一项:不仅考虑过去的冲击(误差平方),还考虑过去的波动率本身。简单来说,它认为今天的波动率受到两个因素影响:一是昨天发生了多大的市场冲击,二是昨天的波动水平本身是多少。这样一来,模型既能反映短期冲击的影响,也能体现波动率的持续性,因此通常只需要 GARCH(1,1) 就能很好地描述大多数金融资产的波动特征。
从直觉上理解,ARCH(1) 更像是在说:“今天的风险大小主要取决于昨天发生了什么冲击。”而 GARCH(1,1) 则进一步强调:“今天的风险不仅取决于昨天的冲击,还取决于昨天整体的风险水平。”正因为这种结构,GARCH(1,1) 往往能够很好地捕捉金融市场波动率的持续性和聚集性,因此在股票、汇率、商品价格等金融数据建模中被广泛使用。如今,在风险管理、资产定价和波动率预测等领域,GARCH 模型已经成为最基础也最重要的工具之一。


