43 KiB
43 KiB
In [1]:
import numpy as np
import pandas as pd
import statsmodels.formula.api as smf
pd.set_option('display.max_columns', None)In [2]:
data = pd.read_csv('datasets/000001.csv')
data['Day'] = pd.to_datetime(data['Day'], format='%Y/%m/%d')
data = data.set_index('Day').sort_index()
dataOut [2]:
| Preclose | Open | Highest | Lowest | Close | |
|---|---|---|---|---|---|
| Day | |||||
| 1990-12-19 | 96.050 | 99.980 | 95.790 | 99.980 | |
| 1990-12-20 | 99.98 | 104.300 | 104.390 | 99.980 | 104.390 |
| 1990-12-21 | 104.39 | 109.070 | 109.130 | 103.730 | 109.130 |
| 1990-12-24 | 109.13 | 113.570 | 114.550 | 109.130 | 114.550 |
| 1990-12-25 | 114.55 | 120.090 | 120.250 | 114.550 | 120.250 |
| ... | ... | ... | ... | ... | ... |
| 2026-09-24 | 3936.52 | 3925.323 | 3930.496 | 3888.374 | 3888.374 |
| 2026-09-28 | 3888.374 | 3878.409 | 3878.409 | 3806.671 | 3823.621 |
| 2026-09-29 | 3823.621 | 3816.154 | 3843.838 | 3810.812 | 3830.451 |
| 2026-09-30 | 3830.451 | 3839.253 | 3851.217 | 3833.086 | 3842.195 |
| 2026-10-08 | 3842.195 | 3838.977 | 3864.118 | 3795.374 | 3811.904 |
8737 rows × 5 columns
In [3]:
data_new = data['1995-01':'2025-08'].copy()
data_new['Close'] = pd.to_numeric(data_new['Close'])
data_new['Preclose'] = pd.to_numeric(data_new['Preclose'])
data_new['Raw_return'] = data_new['Close'] / data_new['Preclose'] - 1
data_new['Log_return'] = np.log(data_new['Close']) - np.log(data_new['Preclose'])
data_new[['Close', 'Preclose', 'Raw_return', 'Log_return']].head()Out [3]:
| Close | Preclose | Raw_return | Log_return | |
|---|---|---|---|---|
| Day | ||||
| 1995-01-03 | 639.88 | 647.87 | -0.012333 | -0.012409 |
| 1995-01-04 | 653.81 | 639.88 | 0.021770 | 0.021536 |
| 1995-01-05 | 646.89 | 653.81 | -0.010584 | -0.010641 |
| 1995-01-06 | 640.76 | 646.89 | -0.009476 | -0.009521 |
| 1995-01-09 | 626.00 | 640.76 | -0.023035 | -0.023305 |
In [4]:
log_sum = data_new['Log_return'].loc['2010'].sum()
last_close = data_new.loc['2010', 'Close'].iloc[-1]
prev_close = data_new.loc[:'2009-12-31', 'Close'].iloc[-1]
log_direct = np.log(last_close / prev_close)
print(f'方法A(对数收益率求和): {log_sum:.12f}')
print(f'方法B(首尾价格直接计算): {log_direct:.12f}')
print(f'差异: {abs(log_sum - log_direct):.2e}')方法A(对数收益率求和): -0.154470115020 方法B(首尾价格直接计算): -0.154470115020 差异: 3.61e-16
In [5]:
pct = data_new['Close'].pct_change()
pct.head()Out [5]:
Day 1995-01-03 NaN 1995-01-04 0.021770 1995-01-05 -0.010584 1995-01-06 -0.009476 1995-01-09 -0.023035 Name: Close, dtype: float64
In [6]:
print(f"含首个 NaN 时:均值={pct.mean():.6f}, 标准差={pct.std():.6f}, 非缺失样本={pct.notna().sum()}")
masked = pct.fillna(0)
print(f"fillna(0) 后:均值={masked.mean():.6f}, 标准差={masked.std():.6f}, 非缺失样本={masked.notna().sum()}")含首个 NaN 时:均值=0.000374, 标准差=0.016332, 非缺失样本=7444 fillna(0) 后:均值=0.000374, 标准差=0.016330, 非缺失样本=7445
In [7]:
d20 = data_new.loc['2020'].copy()
d20['Direction'] = np.where(d20['Raw_return'] > 0, '涨', '跌')
d20['Direction'].value_counts()Out [7]:
Direction 涨 131 跌 112 Name: count, dtype: int64
In [8]:
ratio = (d20['Direction'] == '涨').mean()
print(f'上涨天数占比: {ratio:.2%}')上涨天数占比: 53.91%
In [9]:
weekly = pd.DataFrame({'Close': d20['Close'].resample('W-FRI').last()})
weekly['Weekly_raw'] = weekly['Close'].pct_change()
weekly['Weekly_log'] = d20['Log_return'].resample('W-FRI').apply(lambda s: s.sum() if len(s) else np.nan)
weekly.head()Out [9]:
| Close | Weekly_raw | Weekly_log | |
|---|---|---|---|
| Day | |||
| 2020-01-03 | 3083.79 | NaN | 0.010978 |
| 2020-01-10 | 3092.29 | 0.002756 | 0.002753 |
| 2020-01-17 | 3075.50 | -0.005430 | -0.005444 |
| 2020-01-24 | 2976.53 | -0.032180 | -0.032709 |
| 2020-01-31 | NaN | NaN | NaN |
In [10]:
bins_with_data = d20['Close'].resample('W-FRI').count()
print(f'W-FRI 自然周数: {len(weekly)}')
print(f'其中有交易日的周数: {(bins_with_data > 0).sum()}')
print(f'可计算原始周收益率的周数: {weekly["Weekly_raw"].notna().sum()}')
print(f'周度原始收益率标准差: {weekly["Weekly_raw"].std():.4%}')
print(f'日度原始收益率标准差: {d20["Raw_return"].std():.4%}')W-FRI 自然周数: 53 其中有交易日的周数: 52 可计算原始周收益率的周数: 50 周度原始收益率标准差: 2.6844% 日度原始收益率标准差: 1.3085%
In [11]:
conditions = [
d20['Raw_return'] > 0.02,
d20['Raw_return'] > 0,
d20['Raw_return'] == 0,
d20['Raw_return'] >= -0.02,
]
choices = ['大涨', '小涨', '平盘', '小跌']
d20['Magnitude'] = np.select(conditions, choices, default='大跌')
counts = d20['Magnitude'].value_counts().reindex(['大涨', '小涨', '平盘', '小跌', '大跌'], fill_value=0)
print(counts)
print(f'合计: {counts.sum()} 天')Magnitude 大涨 14 小涨 117 平盘 0 小跌 104 大跌 8 Name: count, dtype: int64 合计: 243 天
In [12]:
cumA = np.exp(d20['Log_return'].cumsum()) - 1
cumB = (1 + d20['Raw_return']).cumprod() - 1
print(f'np.allclose: {np.allclose(cumA, cumB)}')
print(f'最大绝对差异: {(cumA - cumB).abs().max():.2e}')
print(f'年末累积收益率 A/B: {cumA.iloc[-1]:.6f} / {cumB.iloc[-1]:.6f}')np.allclose: True 最大绝对差异: 1.78e-15 年末累积收益率 A/B: 0.138667 / 0.138667
In [13]:
comparison = pd.DataFrame({'cumA': cumA, 'cumB': cumB})
comparison['diff'] = comparison['cumA'] - comparison['cumB']
comparison.iloc[[0, -1]]Out [13]:
| cumA | cumB | diff | |
|---|---|---|---|
| Day | |||
| 2020-01-02 | 0.011501 | 0.011501 | 6.661338e-16 |
| 2020-12-31 | 0.138667 | 0.138667 | -2.220446e-16 |
In [14]:
rolling_20 = np.exp(d20['Log_return'].rolling(window=20).sum()) - 1
print(f'有效值个数: {rolling_20.notna().sum()}(前 19 个交易日因窗口不足为 NaN)')
print(f'最高: {rolling_20.idxmax():%Y-%m-%d},收益率 {rolling_20.max():.4%}')
print(f'最低: {rolling_20.idxmin():%Y-%m-%d},收益率 {rolling_20.min():.4%}')有效值个数: 224(前 19 个交易日因窗口不足为 NaN) 最高: 2020-07-13,收益率 17.8846% 最低: 2020-03-23,收益率 -12.2412%
In [15]:
df1 = pd.DataFrame({'v': [1, 2, 3]}, index=['A', 'B', 'C'])
df2 = pd.DataFrame({'v': [10, 20, 30]}, index=['B', 'C', 'D'])
df1 + df2Out [15]:
| v | |
|---|---|
| A | NaN |
| B | 12.0 |
| C | 23.0 |
| D | NaN |
In [16]:
df1.add(df2, fill_value=0)Out [16]:
| v | |
|---|---|
| A | 1.0 |
| B | 12.0 |
| C | 23.0 |
| D | 30.0 |
In [17]:
d20['l1'] = d20['Raw_return'].shift(1)
d20['l_1'] = d20['Raw_return'].shift(-1)
d20[['Raw_return', 'l1', 'l_1']].head()Out [17]:
| Raw_return | l1 | l_1 | |
|---|---|---|---|
| Day | |||
| 2020-01-02 | 0.011501 | NaN | -0.000457 |
| 2020-01-03 | -0.000457 | 0.011501 | -0.000123 |
| 2020-01-06 | -0.000123 | -0.000457 | 0.006937 |
| 2020-01-07 | 0.006937 | -0.000123 | -0.012210 |
| 2020-01-08 | -0.012210 | 0.006937 | 0.009127 |
In [18]:
model_lag = smf.ols('Raw_return ~ l1', data=d20).fit()
model_look = smf.ols('Raw_return ~ l_1', data=d20).fit()
print(f'使用滞后收益 l1 : R2 = {model_lag.rsquared:.6f},系数 = {model_lag.params["l1"]:.6f}')
print(f'使用未来收益 l_1 : R2 = {model_look.rsquared:.6f},系数 = {model_look.params["l_1"]:.6f}')使用滞后收益 l1 : R2 = 0.003048,系数 = 0.055317 使用未来收益 l_1 : R2 = 0.003048,系数 = 0.055108
In [19]:
perfect = smf.ols('Raw_return ~ l_1', data=d20.assign(Raw_return=d20['Raw_return'].shift(-1))).fit()
print(f'把预测目标也设成未来收益时 R2 = {perfect.rsquared:.6f}')把预测目标也设成未来收益时 R2 = 1.000000
In [20]:
buy_close = d20['Close'].iloc[0]
hold_close = d20['Close'].iloc[-1]
ret = hold_close / buy_close - 1
print(f'买入日: {d20.index[0]:%Y-%m-%d},收盘价 {buy_close:.2f}')
print(f'卖出日: {d20.index[-1]:%Y-%m-%d},收盘价 {hold_close:.2f}')
print(f'全年累积收益率: {ret:.4%}')
print(f'10 万元变为: {100000 * (1 + ret):,.2f} 元')
print(f'(若把买入日当天自身的涨幅也算入,则为 {np.exp(d20["Log_return"].sum()) - 1:.4%})')买入日: 2020-01-02,收盘价 3085.20 卖出日: 2020-12-31,收盘价 3473.07 全年累积收益率: 12.5720% 10 万元变为: 112,571.96 元 (若把买入日当天自身的涨幅也算入,则为 13.8667%)