1411 lines
43 KiB
Plaintext
1411 lines
43 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "e7b2232c",
|
||
"metadata": {},
|
||
"source": [
|
||
"# Python程序设计与数据处理 Class 2 课后练习\n",
|
||
"\n",
|
||
"数据:`datasets/000001.csv`(上证综指日度行情,1990-12-19 起)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "05451ce3",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 数据准备"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 1,
|
||
"id": "56372adc",
|
||
"metadata": {
|
||
"execution": {
|
||
"iopub.execute_input": "2026-10-23T03:57:59.742788Z",
|
||
"iopub.status.busy": "2026-10-23T03:57:59.741311Z",
|
||
"iopub.status.idle": "2026-10-23T03:58:04.697913Z",
|
||
"shell.execute_reply": "2026-10-23T03:58:04.693135Z"
|
||
}
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"import numpy as np\n",
|
||
"import pandas as pd\n",
|
||
"import statsmodels.formula.api as smf\n",
|
||
"\n",
|
||
"pd.set_option('display.max_columns', None)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 2,
|
||
"id": "f3be458a",
|
||
"metadata": {
|
||
"execution": {
|
||
"iopub.execute_input": "2026-10-23T03:58:04.707882Z",
|
||
"iopub.status.busy": "2026-10-23T03:58:04.706421Z",
|
||
"iopub.status.idle": "2026-10-23T03:58:04.901869Z",
|
||
"shell.execute_reply": "2026-10-23T03:58:04.897076Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/html": [
|
||
"<div>\n",
|
||
"<style scoped>\n",
|
||
" .dataframe tbody tr th:only-of-type {\n",
|
||
" vertical-align: middle;\n",
|
||
" }\n",
|
||
"\n",
|
||
" .dataframe tbody tr th {\n",
|
||
" vertical-align: top;\n",
|
||
" }\n",
|
||
"\n",
|
||
" .dataframe thead th {\n",
|
||
" text-align: right;\n",
|
||
" }\n",
|
||
"</style>\n",
|
||
"<table border=\"1\" class=\"dataframe\">\n",
|
||
" <thead>\n",
|
||
" <tr style=\"text-align: right;\">\n",
|
||
" <th></th>\n",
|
||
" <th>Preclose</th>\n",
|
||
" <th>Open</th>\n",
|
||
" <th>Highest</th>\n",
|
||
" <th>Lowest</th>\n",
|
||
" <th>Close</th>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>Day</th>\n",
|
||
" <th></th>\n",
|
||
" <th></th>\n",
|
||
" <th></th>\n",
|
||
" <th></th>\n",
|
||
" <th></th>\n",
|
||
" </tr>\n",
|
||
" </thead>\n",
|
||
" <tbody>\n",
|
||
" <tr>\n",
|
||
" <th>1990-12-19</th>\n",
|
||
" <td></td>\n",
|
||
" <td>96.050</td>\n",
|
||
" <td>99.980</td>\n",
|
||
" <td>95.790</td>\n",
|
||
" <td>99.980</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>1990-12-20</th>\n",
|
||
" <td>99.98</td>\n",
|
||
" <td>104.300</td>\n",
|
||
" <td>104.390</td>\n",
|
||
" <td>99.980</td>\n",
|
||
" <td>104.390</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>1990-12-21</th>\n",
|
||
" <td>104.39</td>\n",
|
||
" <td>109.070</td>\n",
|
||
" <td>109.130</td>\n",
|
||
" <td>103.730</td>\n",
|
||
" <td>109.130</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>1990-12-24</th>\n",
|
||
" <td>109.13</td>\n",
|
||
" <td>113.570</td>\n",
|
||
" <td>114.550</td>\n",
|
||
" <td>109.130</td>\n",
|
||
" <td>114.550</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>1990-12-25</th>\n",
|
||
" <td>114.55</td>\n",
|
||
" <td>120.090</td>\n",
|
||
" <td>120.250</td>\n",
|
||
" <td>114.550</td>\n",
|
||
" <td>120.250</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>...</th>\n",
|
||
" <td>...</td>\n",
|
||
" <td>...</td>\n",
|
||
" <td>...</td>\n",
|
||
" <td>...</td>\n",
|
||
" <td>...</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>2026-09-24</th>\n",
|
||
" <td>3936.52</td>\n",
|
||
" <td>3925.323</td>\n",
|
||
" <td>3930.496</td>\n",
|
||
" <td>3888.374</td>\n",
|
||
" <td>3888.374</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>2026-09-28</th>\n",
|
||
" <td>3888.374</td>\n",
|
||
" <td>3878.409</td>\n",
|
||
" <td>3878.409</td>\n",
|
||
" <td>3806.671</td>\n",
|
||
" <td>3823.621</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>2026-09-29</th>\n",
|
||
" <td>3823.621</td>\n",
|
||
" <td>3816.154</td>\n",
|
||
" <td>3843.838</td>\n",
|
||
" <td>3810.812</td>\n",
|
||
" <td>3830.451</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>2026-09-30</th>\n",
|
||
" <td>3830.451</td>\n",
|
||
" <td>3839.253</td>\n",
|
||
" <td>3851.217</td>\n",
|
||
" <td>3833.086</td>\n",
|
||
" <td>3842.195</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>2026-10-08</th>\n",
|
||
" <td>3842.195</td>\n",
|
||
" <td>3838.977</td>\n",
|
||
" <td>3864.118</td>\n",
|
||
" <td>3795.374</td>\n",
|
||
" <td>3811.904</td>\n",
|
||
" </tr>\n",
|
||
" </tbody>\n",
|
||
"</table>\n",
|
||
"<p>8737 rows × 5 columns</p>\n",
|
||
"</div>"
|
||
],
|
||
"text/plain": [
|
||
" Preclose Open Highest Lowest Close\n",
|
||
"Day \n",
|
||
"1990-12-19 96.050 99.980 95.790 99.980\n",
|
||
"1990-12-20 99.98 104.300 104.390 99.980 104.390\n",
|
||
"1990-12-21 104.39 109.070 109.130 103.730 109.130\n",
|
||
"1990-12-24 109.13 113.570 114.550 109.130 114.550\n",
|
||
"1990-12-25 114.55 120.090 120.250 114.550 120.250\n",
|
||
"... ... ... ... ... ...\n",
|
||
"2026-09-24 3936.52 3925.323 3930.496 3888.374 3888.374\n",
|
||
"2026-09-28 3888.374 3878.409 3878.409 3806.671 3823.621\n",
|
||
"2026-09-29 3823.621 3816.154 3843.838 3810.812 3830.451\n",
|
||
"2026-09-30 3830.451 3839.253 3851.217 3833.086 3842.195\n",
|
||
"2026-10-08 3842.195 3838.977 3864.118 3795.374 3811.904\n",
|
||
"\n",
|
||
"[8737 rows x 5 columns]"
|
||
]
|
||
},
|
||
"execution_count": 2,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"data = pd.read_csv('datasets/000001.csv')\n",
|
||
"data['Day'] = pd.to_datetime(data['Day'], format='%Y/%m/%d')\n",
|
||
"data = data.set_index('Day').sort_index()\n",
|
||
"data"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 3,
|
||
"id": "fb63507e",
|
||
"metadata": {
|
||
"execution": {
|
||
"iopub.execute_input": "2026-10-23T03:58:04.909406Z",
|
||
"iopub.status.busy": "2026-10-23T03:58:04.908682Z",
|
||
"iopub.status.idle": "2026-10-23T03:58:04.979451Z",
|
||
"shell.execute_reply": "2026-10-23T03:58:04.975722Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/html": [
|
||
"<div>\n",
|
||
"<style scoped>\n",
|
||
" .dataframe tbody tr th:only-of-type {\n",
|
||
" vertical-align: middle;\n",
|
||
" }\n",
|
||
"\n",
|
||
" .dataframe tbody tr th {\n",
|
||
" vertical-align: top;\n",
|
||
" }\n",
|
||
"\n",
|
||
" .dataframe thead th {\n",
|
||
" text-align: right;\n",
|
||
" }\n",
|
||
"</style>\n",
|
||
"<table border=\"1\" class=\"dataframe\">\n",
|
||
" <thead>\n",
|
||
" <tr style=\"text-align: right;\">\n",
|
||
" <th></th>\n",
|
||
" <th>Close</th>\n",
|
||
" <th>Preclose</th>\n",
|
||
" <th>Raw_return</th>\n",
|
||
" <th>Log_return</th>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>Day</th>\n",
|
||
" <th></th>\n",
|
||
" <th></th>\n",
|
||
" <th></th>\n",
|
||
" <th></th>\n",
|
||
" </tr>\n",
|
||
" </thead>\n",
|
||
" <tbody>\n",
|
||
" <tr>\n",
|
||
" <th>1995-01-03</th>\n",
|
||
" <td>639.88</td>\n",
|
||
" <td>647.87</td>\n",
|
||
" <td>-0.012333</td>\n",
|
||
" <td>-0.012409</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>1995-01-04</th>\n",
|
||
" <td>653.81</td>\n",
|
||
" <td>639.88</td>\n",
|
||
" <td>0.021770</td>\n",
|
||
" <td>0.021536</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>1995-01-05</th>\n",
|
||
" <td>646.89</td>\n",
|
||
" <td>653.81</td>\n",
|
||
" <td>-0.010584</td>\n",
|
||
" <td>-0.010641</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>1995-01-06</th>\n",
|
||
" <td>640.76</td>\n",
|
||
" <td>646.89</td>\n",
|
||
" <td>-0.009476</td>\n",
|
||
" <td>-0.009521</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>1995-01-09</th>\n",
|
||
" <td>626.00</td>\n",
|
||
" <td>640.76</td>\n",
|
||
" <td>-0.023035</td>\n",
|
||
" <td>-0.023305</td>\n",
|
||
" </tr>\n",
|
||
" </tbody>\n",
|
||
"</table>\n",
|
||
"</div>"
|
||
],
|
||
"text/plain": [
|
||
" Close Preclose Raw_return Log_return\n",
|
||
"Day \n",
|
||
"1995-01-03 639.88 647.87 -0.012333 -0.012409\n",
|
||
"1995-01-04 653.81 639.88 0.021770 0.021536\n",
|
||
"1995-01-05 646.89 653.81 -0.010584 -0.010641\n",
|
||
"1995-01-06 640.76 646.89 -0.009476 -0.009521\n",
|
||
"1995-01-09 626.00 640.76 -0.023035 -0.023305"
|
||
]
|
||
},
|
||
"execution_count": 3,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"data_new = data['1995-01':'2025-08'].copy()\n",
|
||
"data_new['Close'] = pd.to_numeric(data_new['Close'])\n",
|
||
"data_new['Preclose'] = pd.to_numeric(data_new['Preclose'])\n",
|
||
"data_new['Raw_return'] = data_new['Close'] / data_new['Preclose'] - 1\n",
|
||
"data_new['Log_return'] = np.log(data_new['Close']) - np.log(data_new['Preclose'])\n",
|
||
"data_new[['Close', 'Preclose', 'Raw_return', 'Log_return']].head()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "2ff4bc1b",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 题目 1(对数收益率可加性)\n",
|
||
"\n",
|
||
"取 2010-01-01 到 2010-12-31 的数据,分别用两种方法计算 2010 年全年的对数收益率,并验证它们相等:\n",
|
||
"- 方法 A:`data_new['Log_return'].loc['2010'].sum()`\n",
|
||
"- 方法 B:`np.log(年末收盘价 / 年初前一日收盘价)`"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 4,
|
||
"id": "1d765934",
|
||
"metadata": {
|
||
"execution": {
|
||
"iopub.execute_input": "2026-10-23T03:58:04.987812Z",
|
||
"iopub.status.busy": "2026-10-23T03:58:04.986705Z",
|
||
"iopub.status.idle": "2026-10-23T03:58:05.023757Z",
|
||
"shell.execute_reply": "2026-10-23T03:58:05.019016Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"方法A(对数收益率求和): -0.154470115020\n",
|
||
"方法B(首尾价格直接计算): -0.154470115020\n",
|
||
"差异: 3.61e-16\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"log_sum = data_new['Log_return'].loc['2010'].sum()\n",
|
||
"\n",
|
||
"last_close = data_new.loc['2010', 'Close'].iloc[-1]\n",
|
||
"prev_close = data_new.loc[:'2009-12-31', 'Close'].iloc[-1]\n",
|
||
"log_direct = np.log(last_close / prev_close)\n",
|
||
"\n",
|
||
"print(f'方法A(对数收益率求和): {log_sum:.12f}')\n",
|
||
"print(f'方法B(首尾价格直接计算): {log_direct:.12f}')\n",
|
||
"print(f'差异: {abs(log_sum - log_direct):.2e}')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "00ea185a",
|
||
"metadata": {},
|
||
"source": [
|
||
"两者在浮点精度范围内相等(差异约 $10^{-16}$),验证了对数收益率的可加性:多期对数收益率等于各期对数收益率之和。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "c0ec51c3",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 题目 2(首日收益率为什么是 NaN)\n",
|
||
"\n",
|
||
"计算 `data_new['Close'].pct_change()`,观察第一个值为什么是 NaN。\n",
|
||
"- 解释:为什么首日的收益率**应该**是 NaN?\n",
|
||
"- 如果强行用 `fillna(0)` 填充,会带来什么问题?"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 5,
|
||
"id": "8d7dc7e1",
|
||
"metadata": {
|
||
"execution": {
|
||
"iopub.execute_input": "2026-10-23T03:58:05.032192Z",
|
||
"iopub.status.busy": "2026-10-23T03:58:05.031239Z",
|
||
"iopub.status.idle": "2026-10-23T03:58:05.056557Z",
|
||
"shell.execute_reply": "2026-10-23T03:58:05.052643Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"Day\n",
|
||
"1995-01-03 NaN\n",
|
||
"1995-01-04 0.021770\n",
|
||
"1995-01-05 -0.010584\n",
|
||
"1995-01-06 -0.009476\n",
|
||
"1995-01-09 -0.023035\n",
|
||
"Name: Close, dtype: float64"
|
||
]
|
||
},
|
||
"execution_count": 5,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"pct = data_new['Close'].pct_change()\n",
|
||
"pct.head()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "3accca36",
|
||
"metadata": {},
|
||
"source": [
|
||
"首日(1995-01-03)没有“前一日”的价格可供比较,`pct_change()` 只能用缺失值表示“无法计算”,所以是 NaN。用 `fillna(0)` 会把“不知道”伪装成“当天收益为 0”,凭空多出一个假样本;本数据只缺 1 个观测,影响有限,但若数据中间存在停牌、缺日等缺口,这种填充会把缺口全部填成“0 收益”,既掩盖数据质量问题,又让均值、标准差等统计量系统性失真。下面看影响:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 6,
|
||
"id": "5916d078",
|
||
"metadata": {
|
||
"execution": {
|
||
"iopub.execute_input": "2026-10-23T03:58:05.067100Z",
|
||
"iopub.status.busy": "2026-10-23T03:58:05.066092Z",
|
||
"iopub.status.idle": "2026-10-23T03:58:05.088378Z",
|
||
"shell.execute_reply": "2026-10-23T03:58:05.084935Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"含首个 NaN 时:均值=0.000374, 标准差=0.016332, 非缺失样本=7444\n",
|
||
"fillna(0) 后:均值=0.000374, 标准差=0.016330, 非缺失样本=7445\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"print(f\"含首个 NaN 时:均值={pct.mean():.6f}, 标准差={pct.std():.6f}, 非缺失样本={pct.notna().sum()}\")\n",
|
||
"masked = pct.fillna(0)\n",
|
||
"print(f\"fillna(0) 后:均值={masked.mean():.6f}, 标准差={masked.std():.6f}, 非缺失样本={masked.notna().sum()}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "668e3389",
|
||
"metadata": {},
|
||
"source": [
|
||
"累积收益不受影响(首行乘以 `1+0=1`),但统计量已经受到污染(本数据只缺 1 个观测,变化很小;缺口越多影响越大)——把“没有数据”当成“0 收益”本质上是在伪造观测值。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "c11d15e9",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 题目 3(np.where 打标签)\n",
|
||
"\n",
|
||
"用 `np.where` 给 2020 年的日收益率打标签:收益率为正标为\"涨\",否则标为\"跌\"。\n",
|
||
"- 统计 2020 年上涨和下跌的天数各是多少?\n",
|
||
"- 上涨天数占比是否等于 50%?为什么?"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 7,
|
||
"id": "5a03bbb7",
|
||
"metadata": {
|
||
"execution": {
|
||
"iopub.execute_input": "2026-10-23T03:58:05.097467Z",
|
||
"iopub.status.busy": "2026-10-23T03:58:05.096419Z",
|
||
"iopub.status.idle": "2026-10-23T03:58:05.132354Z",
|
||
"shell.execute_reply": "2026-10-23T03:58:05.128521Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"Direction\n",
|
||
"涨 131\n",
|
||
"跌 112\n",
|
||
"Name: count, dtype: int64"
|
||
]
|
||
},
|
||
"execution_count": 7,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"d20 = data_new.loc['2020'].copy()\n",
|
||
"d20['Direction'] = np.where(d20['Raw_return'] > 0, '涨', '跌')\n",
|
||
"\n",
|
||
"d20['Direction'].value_counts()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 8,
|
||
"id": "6363ad22",
|
||
"metadata": {
|
||
"execution": {
|
||
"iopub.execute_input": "2026-10-23T03:58:05.141302Z",
|
||
"iopub.status.busy": "2026-10-23T03:58:05.140594Z",
|
||
"iopub.status.idle": "2026-10-23T03:58:05.157594Z",
|
||
"shell.execute_reply": "2026-10-23T03:58:05.155048Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"上涨天数占比: 53.91%\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"ratio = (d20['Direction'] == '涨').mean()\n",
|
||
"print(f'上涨天数占比: {ratio:.2%}')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "52c7997f",
|
||
"metadata": {},
|
||
"source": [
|
||
"2020 年共 243 个交易日:上涨 131 天、下跌 112 天,上涨占比约 53.9%,不等于 50%。原因有二:一是股票收益率的分布并非对称,均值受少数大幅上涨日驱动(正偏态);二是 2020 年全年指数整体上涨,涨跌天数自然偏向上涨。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "fe809893",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 题目 4(周度收益率)\n",
|
||
"\n",
|
||
"用 `resample('W-FRI')` 计算 2020 年上证指数的周度收益率(原始收益率和对数收益率都要)。\n",
|
||
"- 一年大约有多少个交易周?\n",
|
||
"- 周度收益率的波动比日度收益率大还是小?为什么?"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 9,
|
||
"id": "e6751332",
|
||
"metadata": {
|
||
"execution": {
|
||
"iopub.execute_input": "2026-10-23T03:58:05.166866Z",
|
||
"iopub.status.busy": "2026-10-23T03:58:05.166166Z",
|
||
"iopub.status.idle": "2026-10-23T03:58:05.253635Z",
|
||
"shell.execute_reply": "2026-10-23T03:58:05.251412Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/html": [
|
||
"<div>\n",
|
||
"<style scoped>\n",
|
||
" .dataframe tbody tr th:only-of-type {\n",
|
||
" vertical-align: middle;\n",
|
||
" }\n",
|
||
"\n",
|
||
" .dataframe tbody tr th {\n",
|
||
" vertical-align: top;\n",
|
||
" }\n",
|
||
"\n",
|
||
" .dataframe thead th {\n",
|
||
" text-align: right;\n",
|
||
" }\n",
|
||
"</style>\n",
|
||
"<table border=\"1\" class=\"dataframe\">\n",
|
||
" <thead>\n",
|
||
" <tr style=\"text-align: right;\">\n",
|
||
" <th></th>\n",
|
||
" <th>Close</th>\n",
|
||
" <th>Weekly_raw</th>\n",
|
||
" <th>Weekly_log</th>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>Day</th>\n",
|
||
" <th></th>\n",
|
||
" <th></th>\n",
|
||
" <th></th>\n",
|
||
" </tr>\n",
|
||
" </thead>\n",
|
||
" <tbody>\n",
|
||
" <tr>\n",
|
||
" <th>2020-01-03</th>\n",
|
||
" <td>3083.79</td>\n",
|
||
" <td>NaN</td>\n",
|
||
" <td>0.010978</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>2020-01-10</th>\n",
|
||
" <td>3092.29</td>\n",
|
||
" <td>0.002756</td>\n",
|
||
" <td>0.002753</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>2020-01-17</th>\n",
|
||
" <td>3075.50</td>\n",
|
||
" <td>-0.005430</td>\n",
|
||
" <td>-0.005444</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>2020-01-24</th>\n",
|
||
" <td>2976.53</td>\n",
|
||
" <td>-0.032180</td>\n",
|
||
" <td>-0.032709</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>2020-01-31</th>\n",
|
||
" <td>NaN</td>\n",
|
||
" <td>NaN</td>\n",
|
||
" <td>NaN</td>\n",
|
||
" </tr>\n",
|
||
" </tbody>\n",
|
||
"</table>\n",
|
||
"</div>"
|
||
],
|
||
"text/plain": [
|
||
" Close Weekly_raw Weekly_log\n",
|
||
"Day \n",
|
||
"2020-01-03 3083.79 NaN 0.010978\n",
|
||
"2020-01-10 3092.29 0.002756 0.002753\n",
|
||
"2020-01-17 3075.50 -0.005430 -0.005444\n",
|
||
"2020-01-24 2976.53 -0.032180 -0.032709\n",
|
||
"2020-01-31 NaN NaN NaN"
|
||
]
|
||
},
|
||
"execution_count": 9,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"weekly = pd.DataFrame({'Close': d20['Close'].resample('W-FRI').last()})\n",
|
||
"weekly['Weekly_raw'] = weekly['Close'].pct_change()\n",
|
||
"weekly['Weekly_log'] = d20['Log_return'].resample('W-FRI').apply(lambda s: s.sum() if len(s) else np.nan)\n",
|
||
"weekly.head()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 10,
|
||
"id": "9f6f7099",
|
||
"metadata": {
|
||
"execution": {
|
||
"iopub.execute_input": "2026-10-23T03:58:05.266022Z",
|
||
"iopub.status.busy": "2026-10-23T03:58:05.265285Z",
|
||
"iopub.status.idle": "2026-10-23T03:58:05.291463Z",
|
||
"shell.execute_reply": "2026-10-23T03:58:05.289374Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"W-FRI 自然周数: 53\n",
|
||
"其中有交易日的周数: 52\n",
|
||
"可计算原始周收益率的周数: 50\n",
|
||
"周度原始收益率标准差: 2.6844%\n",
|
||
"日度原始收益率标准差: 1.3085%\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"bins_with_data = d20['Close'].resample('W-FRI').count()\n",
|
||
"print(f'W-FRI 自然周数: {len(weekly)}')\n",
|
||
"print(f'其中有交易日的周数: {(bins_with_data > 0).sum()}')\n",
|
||
"print(f'可计算原始周收益率的周数: {weekly[\"Weekly_raw\"].notna().sum()}')\n",
|
||
"print(f'周度原始收益率标准差: {weekly[\"Weekly_raw\"].std():.4%}')\n",
|
||
"print(f'日度原始收益率标准差: {d20[\"Raw_return\"].std():.4%}')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "8f73874c",
|
||
"metadata": {},
|
||
"source": [
|
||
"按 `W-FRI` 划分,2020 年落在 53 个自然周(周箱子)中,其中 52 周有交易日——2020-01-31 那一周因春节假期延长没有任何交易日。可计算的原始周收益率有 50 个:首周(2020-01-03)缺少上一周收盘价,春节空白周及其后一周也因没有可比基准而为 NaN。周度收益率的**绝对波动**明显大于日度(约 2.68% vs 1.31%),因为统计区间从 1 天拉长到约 5 天,价格变化自然更大——这也符合“波动随时间跨度按 $\\sqrt{T}$ 放大”的规律。另外首周的对数周收益率(1.10%)仍可由当周两个交易日的日对数收益率累加得到,只是它在“周收益率”口径下没有上一周收盘价作基准。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "9fa4a5e4",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 题目 5(np.select 多分类)\n",
|
||
"\n",
|
||
"用 `np.select` 把 2020 年每一天的收益率分为 5 类:\n",
|
||
"- 大涨:`> 2%`\n",
|
||
"- 小涨:`0 ~ 2%`\n",
|
||
"- 平盘:`= 0`(如果存在)\n",
|
||
"- 小跌:`-2% ~ 0`\n",
|
||
"- 大跌:`< -2%`\n",
|
||
"\n",
|
||
"统计每一类的天数。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 11,
|
||
"id": "227127f7",
|
||
"metadata": {
|
||
"execution": {
|
||
"iopub.execute_input": "2026-10-23T03:58:05.302819Z",
|
||
"iopub.status.busy": "2026-10-23T03:58:05.302103Z",
|
||
"iopub.status.idle": "2026-10-23T03:58:05.340511Z",
|
||
"shell.execute_reply": "2026-10-23T03:58:05.337744Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Magnitude\n",
|
||
"大涨 14\n",
|
||
"小涨 117\n",
|
||
"平盘 0\n",
|
||
"小跌 104\n",
|
||
"大跌 8\n",
|
||
"Name: count, dtype: int64\n",
|
||
"合计: 243 天\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"conditions = [\n",
|
||
" d20['Raw_return'] > 0.02,\n",
|
||
" d20['Raw_return'] > 0,\n",
|
||
" d20['Raw_return'] == 0,\n",
|
||
" d20['Raw_return'] >= -0.02,\n",
|
||
"]\n",
|
||
"choices = ['大涨', '小涨', '平盘', '小跌']\n",
|
||
"\n",
|
||
"d20['Magnitude'] = np.select(conditions, choices, default='大跌')\n",
|
||
"counts = d20['Magnitude'].value_counts().reindex(['大涨', '小涨', '平盘', '小跌', '大跌'], fill_value=0)\n",
|
||
"print(counts)\n",
|
||
"print(f'合计: {counts.sum()} 天')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "6e58f830",
|
||
"metadata": {},
|
||
"source": [
|
||
"2020 年没有收益率恰好为 0 的交易日(平盘 0 天)。大涨 14 天、大跌 8 天,涨跌幅超过 2% 的极端日共 22 天,分布并不均匀:2–3 月疫情冲击期间连续出现大跌和大涨(2 月 3 天、3 月 6 天),7 月快速上涨阶段又集中出现 7 天,其余月份各 1 天左右。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "9699858b",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 题目 6(累积收益率的两种方法)\n",
|
||
"\n",
|
||
"用两种方法计算 2020 年上证指数的累积收益率曲线:\n",
|
||
"- 方法 A:`np.exp(Log_return.cumsum()) - 1`\n",
|
||
"- 方法 B:`(1 + Raw_return).cumprod() - 1`\n",
|
||
"\n",
|
||
"验证两种方法结果是否相同(用 `np.allclose`)。如果不同,差在哪里?"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 12,
|
||
"id": "3a540ce8",
|
||
"metadata": {
|
||
"execution": {
|
||
"iopub.execute_input": "2026-10-23T03:58:05.349976Z",
|
||
"iopub.status.busy": "2026-10-23T03:58:05.349104Z",
|
||
"iopub.status.idle": "2026-10-23T03:58:05.373884Z",
|
||
"shell.execute_reply": "2026-10-23T03:58:05.370551Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"np.allclose: True\n",
|
||
"最大绝对差异: 1.78e-15\n",
|
||
"年末累积收益率 A/B: 0.138667 / 0.138667\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"cumA = np.exp(d20['Log_return'].cumsum()) - 1\n",
|
||
"cumB = (1 + d20['Raw_return']).cumprod() - 1\n",
|
||
"\n",
|
||
"print(f'np.allclose: {np.allclose(cumA, cumB)}')\n",
|
||
"print(f'最大绝对差异: {(cumA - cumB).abs().max():.2e}')\n",
|
||
"print(f'年末累积收益率 A/B: {cumA.iloc[-1]:.6f} / {cumB.iloc[-1]:.6f}')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 13,
|
||
"id": "868e6959",
|
||
"metadata": {
|
||
"execution": {
|
||
"iopub.execute_input": "2026-10-23T03:58:05.381986Z",
|
||
"iopub.status.busy": "2026-10-23T03:58:05.381300Z",
|
||
"iopub.status.idle": "2026-10-23T03:58:05.423899Z",
|
||
"shell.execute_reply": "2026-10-23T03:58:05.419513Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/html": [
|
||
"<div>\n",
|
||
"<style scoped>\n",
|
||
" .dataframe tbody tr th:only-of-type {\n",
|
||
" vertical-align: middle;\n",
|
||
" }\n",
|
||
"\n",
|
||
" .dataframe tbody tr th {\n",
|
||
" vertical-align: top;\n",
|
||
" }\n",
|
||
"\n",
|
||
" .dataframe thead th {\n",
|
||
" text-align: right;\n",
|
||
" }\n",
|
||
"</style>\n",
|
||
"<table border=\"1\" class=\"dataframe\">\n",
|
||
" <thead>\n",
|
||
" <tr style=\"text-align: right;\">\n",
|
||
" <th></th>\n",
|
||
" <th>cumA</th>\n",
|
||
" <th>cumB</th>\n",
|
||
" <th>diff</th>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>Day</th>\n",
|
||
" <th></th>\n",
|
||
" <th></th>\n",
|
||
" <th></th>\n",
|
||
" </tr>\n",
|
||
" </thead>\n",
|
||
" <tbody>\n",
|
||
" <tr>\n",
|
||
" <th>2020-01-02</th>\n",
|
||
" <td>0.011501</td>\n",
|
||
" <td>0.011501</td>\n",
|
||
" <td>6.661338e-16</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>2020-12-31</th>\n",
|
||
" <td>0.138667</td>\n",
|
||
" <td>0.138667</td>\n",
|
||
" <td>-2.220446e-16</td>\n",
|
||
" </tr>\n",
|
||
" </tbody>\n",
|
||
"</table>\n",
|
||
"</div>"
|
||
],
|
||
"text/plain": [
|
||
" cumA cumB diff\n",
|
||
"Day \n",
|
||
"2020-01-02 0.011501 0.011501 6.661338e-16\n",
|
||
"2020-12-31 0.138667 0.138667 -2.220446e-16"
|
||
]
|
||
},
|
||
"execution_count": 13,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"comparison = pd.DataFrame({'cumA': cumA, 'cumB': cumB})\n",
|
||
"comparison['diff'] = comparison['cumA'] - comparison['cumB']\n",
|
||
"comparison.iloc[[0, -1]]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "69273b13",
|
||
"metadata": {},
|
||
"source": [
|
||
"两种方法结果相同(`allclose=True`),仅在最后一位浮点数上有约 $10^{-15}$ 的舍入差异,来自 `exp/log` 与乘法的运算次序不同。数学上 $e^{\\sum r_t}=\\prod(1+R_t)$,二者等价。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "d61c267a",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 题目 7(滚动收益率的最大值)\n",
|
||
"\n",
|
||
"计算 2020 年滚动 20 日收益率(基于对数收益率累加),找出收益率最高和最低的日期分别是哪一天。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 14,
|
||
"id": "500c9812",
|
||
"metadata": {
|
||
"execution": {
|
||
"iopub.execute_input": "2026-10-23T03:58:05.432218Z",
|
||
"iopub.status.busy": "2026-10-23T03:58:05.431249Z",
|
||
"iopub.status.idle": "2026-10-23T03:58:05.455327Z",
|
||
"shell.execute_reply": "2026-10-23T03:58:05.451674Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"有效值个数: 224(前 19 个交易日因窗口不足为 NaN)\n",
|
||
"最高: 2020-07-13,收益率 17.8846%\n",
|
||
"最低: 2020-03-23,收益率 -12.2412%\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"rolling_20 = np.exp(d20['Log_return'].rolling(window=20).sum()) - 1\n",
|
||
"\n",
|
||
"print(f'有效值个数: {rolling_20.notna().sum()}(前 19 个交易日因窗口不足为 NaN)')\n",
|
||
"print(f'最高: {rolling_20.idxmax():%Y-%m-%d},收益率 {rolling_20.max():.4%}')\n",
|
||
"print(f'最低: {rolling_20.idxmin():%Y-%m-%d},收益率 {rolling_20.min():.4%}')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "67a2306d",
|
||
"metadata": {},
|
||
"source": [
|
||
"2020 年滚动 20 日收益率最高出现在 2020-07-13(约 +17.9%,对应 7 月初的快速上涨),最低出现在 2020-03-23(约 -12.2%,对应疫情引发的全球市场暴跌)。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "156a927e",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 题目 8(索引对齐)\n",
|
||
"\n",
|
||
"构造下面两个 DataFrame 并相加,观察结果:\n",
|
||
"```python\n",
|
||
"df1 = pd.DataFrame({'v': [1, 2, 3]}, index=['A', 'B', 'C'])\n",
|
||
"df2 = pd.DataFrame({'v': [10, 20, 30]}, index=['B', 'C', 'D'])\n",
|
||
"```\n",
|
||
"- 结果中哪些索引是 NaN?\n",
|
||
"\n",
|
||
"- 用什么方法可以让所有 4 个索引都有值?(提示:`.add(fill_value=0)`)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 15,
|
||
"id": "06e2b8d3",
|
||
"metadata": {
|
||
"execution": {
|
||
"iopub.execute_input": "2026-10-23T03:58:05.468320Z",
|
||
"iopub.status.busy": "2026-10-23T03:58:05.467568Z",
|
||
"iopub.status.idle": "2026-10-23T03:58:05.503717Z",
|
||
"shell.execute_reply": "2026-10-23T03:58:05.500498Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/html": [
|
||
"<div>\n",
|
||
"<style scoped>\n",
|
||
" .dataframe tbody tr th:only-of-type {\n",
|
||
" vertical-align: middle;\n",
|
||
" }\n",
|
||
"\n",
|
||
" .dataframe tbody tr th {\n",
|
||
" vertical-align: top;\n",
|
||
" }\n",
|
||
"\n",
|
||
" .dataframe thead th {\n",
|
||
" text-align: right;\n",
|
||
" }\n",
|
||
"</style>\n",
|
||
"<table border=\"1\" class=\"dataframe\">\n",
|
||
" <thead>\n",
|
||
" <tr style=\"text-align: right;\">\n",
|
||
" <th></th>\n",
|
||
" <th>v</th>\n",
|
||
" </tr>\n",
|
||
" </thead>\n",
|
||
" <tbody>\n",
|
||
" <tr>\n",
|
||
" <th>A</th>\n",
|
||
" <td>NaN</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>B</th>\n",
|
||
" <td>12.0</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>C</th>\n",
|
||
" <td>23.0</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>D</th>\n",
|
||
" <td>NaN</td>\n",
|
||
" </tr>\n",
|
||
" </tbody>\n",
|
||
"</table>\n",
|
||
"</div>"
|
||
],
|
||
"text/plain": [
|
||
" v\n",
|
||
"A NaN\n",
|
||
"B 12.0\n",
|
||
"C 23.0\n",
|
||
"D NaN"
|
||
]
|
||
},
|
||
"execution_count": 15,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"df1 = pd.DataFrame({'v': [1, 2, 3]}, index=['A', 'B', 'C'])\n",
|
||
"df2 = pd.DataFrame({'v': [10, 20, 30]}, index=['B', 'C', 'D'])\n",
|
||
"\n",
|
||
"df1 + df2"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 16,
|
||
"id": "456415c6",
|
||
"metadata": {
|
||
"execution": {
|
||
"iopub.execute_input": "2026-10-23T03:58:05.511622Z",
|
||
"iopub.status.busy": "2026-10-23T03:58:05.510525Z",
|
||
"iopub.status.idle": "2026-10-23T03:58:05.546737Z",
|
||
"shell.execute_reply": "2026-10-23T03:58:05.541440Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/html": [
|
||
"<div>\n",
|
||
"<style scoped>\n",
|
||
" .dataframe tbody tr th:only-of-type {\n",
|
||
" vertical-align: middle;\n",
|
||
" }\n",
|
||
"\n",
|
||
" .dataframe tbody tr th {\n",
|
||
" vertical-align: top;\n",
|
||
" }\n",
|
||
"\n",
|
||
" .dataframe thead th {\n",
|
||
" text-align: right;\n",
|
||
" }\n",
|
||
"</style>\n",
|
||
"<table border=\"1\" class=\"dataframe\">\n",
|
||
" <thead>\n",
|
||
" <tr style=\"text-align: right;\">\n",
|
||
" <th></th>\n",
|
||
" <th>v</th>\n",
|
||
" </tr>\n",
|
||
" </thead>\n",
|
||
" <tbody>\n",
|
||
" <tr>\n",
|
||
" <th>A</th>\n",
|
||
" <td>1.0</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>B</th>\n",
|
||
" <td>12.0</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>C</th>\n",
|
||
" <td>23.0</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>D</th>\n",
|
||
" <td>30.0</td>\n",
|
||
" </tr>\n",
|
||
" </tbody>\n",
|
||
"</table>\n",
|
||
"</div>"
|
||
],
|
||
"text/plain": [
|
||
" v\n",
|
||
"A 1.0\n",
|
||
"B 12.0\n",
|
||
"C 23.0\n",
|
||
"D 30.0"
|
||
]
|
||
},
|
||
"execution_count": 16,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"df1.add(df2, fill_value=0)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "76559a0e",
|
||
"metadata": {},
|
||
"source": [
|
||
"直接相加按索引对齐取交集:A、D 只存在于其中一个表中,结果为 NaN;B、C 正常相加。用 `add(fill_value=0)` 把缺失一侧视为 0,A 得 1、D 得 30,四个索引就都有值了。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "15ac6898",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 题目 9(前视偏差)\n",
|
||
"\n",
|
||
"用 shift(1) 和 shift(-1) 分别构造两个滞后收益率,然后各跑一次 OLS:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 17,
|
||
"id": "43c99a1a",
|
||
"metadata": {
|
||
"execution": {
|
||
"iopub.execute_input": "2026-10-23T03:58:05.554937Z",
|
||
"iopub.status.busy": "2026-10-23T03:58:05.553992Z",
|
||
"iopub.status.idle": "2026-10-23T03:58:05.606821Z",
|
||
"shell.execute_reply": "2026-10-23T03:58:05.601801Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/html": [
|
||
"<div>\n",
|
||
"<style scoped>\n",
|
||
" .dataframe tbody tr th:only-of-type {\n",
|
||
" vertical-align: middle;\n",
|
||
" }\n",
|
||
"\n",
|
||
" .dataframe tbody tr th {\n",
|
||
" vertical-align: top;\n",
|
||
" }\n",
|
||
"\n",
|
||
" .dataframe thead th {\n",
|
||
" text-align: right;\n",
|
||
" }\n",
|
||
"</style>\n",
|
||
"<table border=\"1\" class=\"dataframe\">\n",
|
||
" <thead>\n",
|
||
" <tr style=\"text-align: right;\">\n",
|
||
" <th></th>\n",
|
||
" <th>Raw_return</th>\n",
|
||
" <th>l1</th>\n",
|
||
" <th>l_1</th>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>Day</th>\n",
|
||
" <th></th>\n",
|
||
" <th></th>\n",
|
||
" <th></th>\n",
|
||
" </tr>\n",
|
||
" </thead>\n",
|
||
" <tbody>\n",
|
||
" <tr>\n",
|
||
" <th>2020-01-02</th>\n",
|
||
" <td>0.011501</td>\n",
|
||
" <td>NaN</td>\n",
|
||
" <td>-0.000457</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>2020-01-03</th>\n",
|
||
" <td>-0.000457</td>\n",
|
||
" <td>0.011501</td>\n",
|
||
" <td>-0.000123</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>2020-01-06</th>\n",
|
||
" <td>-0.000123</td>\n",
|
||
" <td>-0.000457</td>\n",
|
||
" <td>0.006937</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>2020-01-07</th>\n",
|
||
" <td>0.006937</td>\n",
|
||
" <td>-0.000123</td>\n",
|
||
" <td>-0.012210</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>2020-01-08</th>\n",
|
||
" <td>-0.012210</td>\n",
|
||
" <td>0.006937</td>\n",
|
||
" <td>0.009127</td>\n",
|
||
" </tr>\n",
|
||
" </tbody>\n",
|
||
"</table>\n",
|
||
"</div>"
|
||
],
|
||
"text/plain": [
|
||
" Raw_return l1 l_1\n",
|
||
"Day \n",
|
||
"2020-01-02 0.011501 NaN -0.000457\n",
|
||
"2020-01-03 -0.000457 0.011501 -0.000123\n",
|
||
"2020-01-06 -0.000123 -0.000457 0.006937\n",
|
||
"2020-01-07 0.006937 -0.000123 -0.012210\n",
|
||
"2020-01-08 -0.012210 0.006937 0.009127"
|
||
]
|
||
},
|
||
"execution_count": 17,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"d20['l1'] = d20['Raw_return'].shift(1)\n",
|
||
"d20['l_1'] = d20['Raw_return'].shift(-1)\n",
|
||
"\n",
|
||
"d20[['Raw_return', 'l1', 'l_1']].head()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "ad063ee3",
|
||
"metadata": {},
|
||
"source": [
|
||
"分别用 `smf.ols('Raw_return ~ l1', ...)` 和 `smf.ols('Raw_return ~ l_1', ...)` 拟合 2020 年数据。哪个 R² 更高?为什么前视偏差会让你\"看起来\"能预测收益率?这是真实的预测能力吗?"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 18,
|
||
"id": "de8d2594",
|
||
"metadata": {
|
||
"execution": {
|
||
"iopub.execute_input": "2026-10-23T03:58:05.614863Z",
|
||
"iopub.status.busy": "2026-10-23T03:58:05.614171Z",
|
||
"iopub.status.idle": "2026-10-23T03:58:05.698237Z",
|
||
"shell.execute_reply": "2026-10-23T03:58:05.695073Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"使用滞后收益 l1 : R2 = 0.003048,系数 = 0.055317\n",
|
||
"使用未来收益 l_1 : R2 = 0.003048,系数 = 0.055108\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"model_lag = smf.ols('Raw_return ~ l1', data=d20).fit()\n",
|
||
"model_look = smf.ols('Raw_return ~ l_1', data=d20).fit()\n",
|
||
"\n",
|
||
"print(f'使用滞后收益 l1 : R2 = {model_lag.rsquared:.6f},系数 = {model_lag.params[\"l1\"]:.6f}')\n",
|
||
"print(f'使用未来收益 l_1 : R2 = {model_look.rsquared:.6f},系数 = {model_look.params[\"l_1\"]:.6f}')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "e2ad0f1b",
|
||
"metadata": {},
|
||
"source": [
|
||
"在本数据上两者几乎相等(R² 都约 0.003,差别只来自样本端点)。原因:对同一条收益率序列,`corr(r_t, r_{t-1})` 与 `corr(r_t, r_{t+1})` 是同一个一阶自相关系数,回归的 R² 自然相同。所以单看 R²,「用未来解释现在」并不会自动变高。\n",
|
||
"\n",
|
||
"前视偏差真正危险的地方在于:把 t+1 的信息当作 t 时刻可用的信号去构建策略。例如用 `l_1` 作为交易信号,等价于在收盘前就知道明后天的涨跌,实盘根本无法执行;又比如若把预测目标本身误当作特征,R² 会直接等于 1。下面的例子更直观——用明天的收益去\"预测\"明天的收益:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 19,
|
||
"id": "9f15d5c9",
|
||
"metadata": {
|
||
"execution": {
|
||
"iopub.execute_input": "2026-10-23T03:58:05.709509Z",
|
||
"iopub.status.busy": "2026-10-23T03:58:05.708802Z",
|
||
"iopub.status.idle": "2026-10-23T03:58:05.757320Z",
|
||
"shell.execute_reply": "2026-10-23T03:58:05.754121Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"把预测目标也设成未来收益时 R2 = 1.000000\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"perfect = smf.ols('Raw_return ~ l_1', data=d20.assign(Raw_return=d20['Raw_return'].shift(-1))).fit()\n",
|
||
"print(f'把预测目标也设成未来收益时 R2 = {perfect.rsquared:.6f}')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "966dea6a",
|
||
"metadata": {},
|
||
"source": [
|
||
"这种「完美预测」来自偷看答案,不是模型能力。判断特征是否可用,关键看它**在做出决策的那一刻是否已知**,而不是拟合优度有多高。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "30d44e31",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 题目 10\n",
|
||
"\n",
|
||
"假设你在 2020 年第一个交易日买入上证指数,一直持有到 2020 年最后一个交易日。\n",
|
||
"- 计算这笔投资的全年累积收益率。\n",
|
||
"- 如果当时投入 10 万元,年底变成多少钱?"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 20,
|
||
"id": "a24163b9",
|
||
"metadata": {
|
||
"execution": {
|
||
"iopub.execute_input": "2026-10-23T03:58:05.767233Z",
|
||
"iopub.status.busy": "2026-10-23T03:58:05.765803Z",
|
||
"iopub.status.idle": "2026-10-23T03:58:05.784909Z",
|
||
"shell.execute_reply": "2026-10-23T03:58:05.781760Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"买入日: 2020-01-02,收盘价 3085.20\n",
|
||
"卖出日: 2020-12-31,收盘价 3473.07\n",
|
||
"全年累积收益率: 12.5720%\n",
|
||
"10 万元变为: 112,571.96 元\n",
|
||
"(若把买入日当天自身的涨幅也算入,则为 13.8667%)\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"buy_close = d20['Close'].iloc[0]\n",
|
||
"hold_close = d20['Close'].iloc[-1]\n",
|
||
"\n",
|
||
"ret = hold_close / buy_close - 1\n",
|
||
"print(f'买入日: {d20.index[0]:%Y-%m-%d},收盘价 {buy_close:.2f}')\n",
|
||
"print(f'卖出日: {d20.index[-1]:%Y-%m-%d},收盘价 {hold_close:.2f}')\n",
|
||
"print(f'全年累积收益率: {ret:.4%}')\n",
|
||
"print(f'10 万元变为: {100000 * (1 + ret):,.2f} 元')\n",
|
||
"print(f'(若把买入日当天自身的涨幅也算入,则为 {np.exp(d20[\"Log_return\"].sum()) - 1:.4%})')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "8c2e08c9",
|
||
"metadata": {},
|
||
"source": [
|
||
"以第一个交易日收盘价买入、最后一个交易日收盘价卖出,2020 年累积收益率约 **12.57%**,10 万元变成约 **112,571.96 元**。若把买入日当天的涨跌也算进持有期,收益率为 13.87%,两者的比值正好对应 2020-01-02 当天的 1.15% 涨幅(1.138667 / 1.125720 − 1 ≈ 1.15%)。"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"kernelspec": {
|
||
"display_name": "Python 3",
|
||
"language": "python",
|
||
"name": "python3"
|
||
},
|
||
"language_info": {
|
||
"codemirror_mode": {
|
||
"name": "ipython",
|
||
"version": 3
|
||
},
|
||
"file_extension": ".py",
|
||
"mimetype": "text/x-python",
|
||
"name": "python",
|
||
"nbconvert_exporter": "python",
|
||
"pygments_lexer": "ipython3",
|
||
"version": "3.11.16"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 5
|
||
}
|