2025-09-16 09:59:38 +08:00
|
|
|
|
{
|
|
|
|
|
|
"cells": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"cell_type": "code",
|
|
|
|
|
|
"id": "initial_id",
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"collapsed": true,
|
|
|
|
|
|
"ExecuteTime": {
|
2025-09-24 23:14:14 +08:00
|
|
|
|
"end_time": "2025-09-22T08:43:09.591953Z",
|
|
|
|
|
|
"start_time": "2025-09-22T08:43:09.395755Z"
|
2025-09-16 09:59:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"source": [
|
|
|
|
|
|
"import pandas as pd\n",
|
|
|
|
|
|
"import numpy as np\n",
|
|
|
|
|
|
"import matplotlib.pyplot as plt\n",
|
|
|
|
|
|
"import seaborn as sns\n",
|
|
|
|
|
|
"import talib as ta # Make sure TA-Lib is installed: pip install TA-Lib\n",
|
|
|
|
|
|
"import statsmodels.api as sm\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"import warnings\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 忽略所有警告\n",
|
|
|
|
|
|
"warnings.filterwarnings(\"ignore\")\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# --- 0. Configure your file path ---\n",
|
|
|
|
|
|
"# Please replace 'your_futures_data.csv' with the actual path to your CSV file\n",
|
2025-09-24 23:14:14 +08:00
|
|
|
|
"file_path = '/mnt/d/PyProject/NewQuant/data/data/KQ_m@CZCE_SA/KQ_m@CZCE_SA_min15.csv'\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"sns.set(style='whitegrid')\n",
|
|
|
|
|
|
"plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签\n",
|
|
|
|
|
|
"plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号"
|
2025-09-16 09:59:38 +08:00
|
|
|
|
],
|
|
|
|
|
|
"outputs": [],
|
|
|
|
|
|
"execution_count": 1
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"ExecuteTime": {
|
2025-09-24 23:14:14 +08:00
|
|
|
|
"end_time": "2025-09-22T08:43:09.779342Z",
|
|
|
|
|
|
"start_time": "2025-09-22T08:43:09.701965Z"
|
2025-09-16 09:59:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"cell_type": "code",
|
|
|
|
|
|
"source": [
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# --- 1. Data Loading and Preprocessing ---\n",
|
|
|
|
|
|
"def load_and_preprocess_data(file_path):\n",
|
|
|
|
|
|
" \"\"\"\n",
|
|
|
|
|
|
" Loads historical futures data and performs basic preprocessing.\n",
|
|
|
|
|
|
" Assumes data contains 'datetime', 'open', 'high', 'low', 'close', 'volume' columns.\n",
|
|
|
|
|
|
" \"\"\"\n",
|
|
|
|
|
|
" try:\n",
|
|
|
|
|
|
" df = pd.read_csv(file_path, parse_dates=['datetime'], index_col='datetime')\n",
|
|
|
|
|
|
" # Ensure data is sorted by time\n",
|
|
|
|
|
|
" df = df.sort_index()\n",
|
|
|
|
|
|
" # Check and handle missing values\n",
|
|
|
|
|
|
" initial_rows = len(df)\n",
|
|
|
|
|
|
" df.dropna(inplace=True)\n",
|
|
|
|
|
|
" if len(df) < initial_rows:\n",
|
|
|
|
|
|
" print(f\"Warning: Missing values found in data, deleted {initial_rows - len(df)} rows.\")\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" # Check if necessary columns exist\n",
|
|
|
|
|
|
" required_columns = ['open', 'high', 'low', 'close', 'volume']\n",
|
|
|
|
|
|
" if not all(col in df.columns for col in required_columns):\n",
|
|
|
|
|
|
" raise ValueError(f\"CSV file is missing required columns. Please ensure it contains: {required_columns}\")\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" print(f\"Successfully loaded {len(df)} rows of data.\")\n",
|
|
|
|
|
|
" print(\"First 5 rows of data:\")\n",
|
|
|
|
|
|
" print(df.head())\n",
|
|
|
|
|
|
" return df\n",
|
|
|
|
|
|
" except FileNotFoundError:\n",
|
|
|
|
|
|
" print(f\"Error: File '{file_path}' not found. Please check the path.\")\n",
|
|
|
|
|
|
" return None\n",
|
|
|
|
|
|
" except Exception as e:\n",
|
|
|
|
|
|
" print(f\"Error during data loading or preprocessing: {e}\")\n",
|
|
|
|
|
|
" return None\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"\n",
|
2025-09-24 23:14:14 +08:00
|
|
|
|
"df_raw = load_and_preprocess_data(file_path)\n",
|
|
|
|
|
|
"# df_raw = df_raw[df_raw.index <= '2024-01-01']"
|
2025-09-16 09:59:38 +08:00
|
|
|
|
],
|
|
|
|
|
|
"id": "548c68daa68af8c1",
|
|
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
2025-09-24 23:14:14 +08:00
|
|
|
|
"Successfully loaded 25662 rows of data.\n",
|
2025-09-16 09:59:38 +08:00
|
|
|
|
"First 5 rows of data:\n",
|
2025-09-24 23:14:14 +08:00
|
|
|
|
" open high low close volume open_oi \\\n",
|
|
|
|
|
|
"datetime \n",
|
|
|
|
|
|
"2020-12-31 14:45:00 1607.0 1611.0 1603.0 1611.0 19480.0 148833.0 \n",
|
|
|
|
|
|
"2021-01-04 09:00:00 1610.0 1636.0 1601.0 1620.0 55486.0 146448.0 \n",
|
|
|
|
|
|
"2021-01-04 09:15:00 1620.0 1620.0 1601.0 1604.0 30314.0 153373.0 \n",
|
|
|
|
|
|
"2021-01-04 09:30:00 1604.0 1606.0 1590.0 1595.0 30803.0 157091.0 \n",
|
|
|
|
|
|
"2021-01-04 09:45:00 1595.0 1601.0 1594.0 1600.0 10031.0 158730.0 \n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" close_oi underlying_symbol \n",
|
|
|
|
|
|
"datetime \n",
|
|
|
|
|
|
"2020-12-31 14:45:00 146448.0 CZCE.SA105 \n",
|
|
|
|
|
|
"2021-01-04 09:00:00 153373.0 CZCE.SA105 \n",
|
|
|
|
|
|
"2021-01-04 09:15:00 157091.0 CZCE.SA105 \n",
|
|
|
|
|
|
"2021-01-04 09:30:00 158730.0 CZCE.SA105 \n",
|
|
|
|
|
|
"2021-01-04 09:45:00 160031.0 CZCE.SA105 \n"
|
2025-09-16 09:59:38 +08:00
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
"execution_count": 2
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"ExecuteTime": {
|
2025-09-24 23:14:14 +08:00
|
|
|
|
"end_time": "2025-09-22T08:43:10.130550Z",
|
|
|
|
|
|
"start_time": "2025-09-22T08:43:09.791034Z"
|
2025-09-16 09:59:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"cell_type": "code",
|
|
|
|
|
|
"source": [
|
2025-09-24 23:14:14 +08:00
|
|
|
|
"import pandas as pd\n",
|
|
|
|
|
|
"import numpy as np\n",
|
|
|
|
|
|
"import talib\n",
|
|
|
|
|
|
"from sklearn.preprocessing import StandardScaler\n",
|
|
|
|
|
|
"from sklearn.decomposition import PCA\n",
|
|
|
|
|
|
"from sklearn.linear_model import LinearRegression\n",
|
|
|
|
|
|
"from sklearn.metrics import mean_squared_error\n",
|
|
|
|
|
|
"import matplotlib.pyplot as plt\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# ================= 1. 构造未来 5 根收益率 label =================\n",
|
|
|
|
|
|
"df = df_raw.copy()\n",
|
|
|
|
|
|
"df['ret5'] = df['close'].shift(-5) / df['close'] - 1\n",
|
|
|
|
|
|
"df = df.dropna(subset=['ret5'])\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# ================= 2. 构造 RSI 2‒24 特征 =================\n",
|
|
|
|
|
|
"for i in range(2, 25):\n",
|
|
|
|
|
|
" df[f'rsi{i}'] = talib.RSI(df['close'], timeperiod=i)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"df = df.dropna() # 去掉 RSI 产生的 NaN\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# ================= 3. 划分训练 / 验证 =================\n",
|
|
|
|
|
|
"train = df[df.index < '2024-01-01']\n",
|
|
|
|
|
|
"valid = df[df.index >= '2024-01-01']\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"X_train = train[[c for c in df.columns if c.startswith('rsi')]]\n",
|
|
|
|
|
|
"y_train = train['ret5']\n",
|
|
|
|
|
|
"X_valid = valid[[c for c in df.columns if c.startswith('rsi')]]\n",
|
|
|
|
|
|
"y_valid = valid['ret5']\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# ================= 4. 标准化 → PCA → 线性回归 =================\n",
|
|
|
|
|
|
"scaler = StandardScaler()\n",
|
|
|
|
|
|
"pca = PCA(n_components=0.95) # 保留 95% 方差\n",
|
|
|
|
|
|
"lr = LinearRegression()\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 管道:fit 只在训练集\n",
|
|
|
|
|
|
"X_train_std = scaler.fit_transform(X_train)\n",
|
|
|
|
|
|
"X_train_pca = pca.fit_transform(X_train_std)\n",
|
|
|
|
|
|
"lr.fit(X_train_pca, y_train)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 验证集变换\n",
|
|
|
|
|
|
"X_valid_std = scaler.transform(X_valid)\n",
|
|
|
|
|
|
"X_valid_pca = pca.transform(X_valid_std)\n",
|
|
|
|
|
|
"pred = lr.predict(X_valid_pca)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# ================= 5. 评估:极端值效果 =================\n",
|
|
|
|
|
|
"valid = valid.copy()\n",
|
|
|
|
|
|
"valid['pred'] = pred\n",
|
|
|
|
|
|
"sigma = pred.std()\n",
|
|
|
|
|
|
"valid['z'] = valid['pred'] / sigma\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# ================= 期货双向版本(固定 1 手) =================\n",
|
|
|
|
|
|
"# 参数\n",
|
|
|
|
|
|
"COST = 0.0000 # 2 bp 双边手续费\n",
|
|
|
|
|
|
"SLIP = 1 # 1 tick = 5 元(螺纹)\n",
|
|
|
|
|
|
"PT_VAL = 10 # 1 点价值 10 元\n",
|
|
|
|
|
|
"ATR_KP = talib.ATR(valid['high'], valid['low'], valid['close'], 20)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 信号\n",
|
|
|
|
|
|
"valid['sig'] = 0\n",
|
|
|
|
|
|
"long_mask = valid['z'] > -2.0\n",
|
|
|
|
|
|
"short_mask = valid['z'] < 2.0\n",
|
|
|
|
|
|
"valid.loc[long_mask, 'sig'] = 1\n",
|
|
|
|
|
|
"valid.loc[short_mask, 'sig'] = -1\n",
|
|
|
|
|
|
"valid['pos'] = valid['sig'].replace(0, method='ffill').fillna(0)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 逐根收益:价格变化 + 滑点 & 手续费\n",
|
|
|
|
|
|
"valid['chg'] = valid['close'].diff()\n",
|
|
|
|
|
|
"valid['turn'] = np.abs(valid['pos'].diff()) # 换手\n",
|
|
|
|
|
|
"valid['ret_hand'] = (\n",
|
|
|
|
|
|
" valid['pos'] * valid['chg']\n",
|
|
|
|
|
|
") * PT_VAL # 折合 1 手价值\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 夏普(按日汇总,15 min × 96 ≈ 1 日)\n",
|
|
|
|
|
|
"daily_ret = valid['ret_hand'].resample('D').sum()\n",
|
|
|
|
|
|
"sharpe = daily_ret.mean() / daily_ret.std() * np.sqrt(252)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 统计\n",
|
|
|
|
|
|
"def stats(mask, name):\n",
|
|
|
|
|
|
" if mask.sum() == 0:\n",
|
|
|
|
|
|
" return\n",
|
|
|
|
|
|
" trades = valid.loc[mask, 'ret_hand']\n",
|
|
|
|
|
|
" print(f'{name} 信号数: {mask.sum()}')\n",
|
|
|
|
|
|
" print(f'命中率 (>0): {(trades > 0).mean():.2%}')\n",
|
|
|
|
|
|
" print(f'平均单手收益: {trades.mean():.2f} 元')\n",
|
|
|
|
|
|
" print(f'盈亏比: {trades[trades>0].mean()/-trades[trades<0].mean():.2f}')\n",
|
|
|
|
|
|
" print()\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"print('=== 扣成本+滑点后的单手表现 ===')\n",
|
|
|
|
|
|
"stats(long_mask, '极端多头(1手)')\n",
|
|
|
|
|
|
"stats(short_mask, '极端空头(1手)')\n",
|
|
|
|
|
|
"print(f'整体日夏普: {sharpe:.2f}')\n",
|
|
|
|
|
|
"print(f'最大回撤: {valid[\"ret_hand\"].cumsum().cummax().sub(valid[\"ret_hand\"].cumsum()).max():.0f} 元')\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 累计收益图\n",
|
|
|
|
|
|
"valid['cum'] = valid['ret_hand'].cumsum()\n",
|
|
|
|
|
|
"valid['cum'].plot(figsize=(12, 3), title='RSI-PCA 极端信号 单手累计盈亏(期货双向)')\n",
|
|
|
|
|
|
"plt.ylabel('元')\n",
|
|
|
|
|
|
"plt.show()\n",
|
|
|
|
|
|
"# ================= 6. 可视化:预测 vs 真实 =================\n",
|
|
|
|
|
|
"plt.figure(figsize=(12, 4))\n",
|
|
|
|
|
|
"plt.scatter(valid['pred'], valid['ret5'], alpha=0.3, s=10)\n",
|
|
|
|
|
|
"plt.axvline(-2 * sigma, color='g', linestyle='--', label='-2σ')\n",
|
|
|
|
|
|
"plt.axvline(2 * sigma, color='r', linestyle='--', label='+2σ')\n",
|
|
|
|
|
|
"plt.xlabel('预测 ret5')\n",
|
|
|
|
|
|
"plt.ylabel('真实 ret5')\n",
|
|
|
|
|
|
"plt.title('PCA-LR 预测 vs 真实收益(验证集)')\n",
|
|
|
|
|
|
"plt.legend()\n",
|
|
|
|
|
|
"plt.show()\n"
|
|
|
|
|
|
],
|
|
|
|
|
|
"id": "3c70d0726c8e024",
|
|
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
|
|
|
|
|
"=== 扣成本+滑点后的单手表现 ===\n",
|
|
|
|
|
|
"极端多头(1手) 信号数: 9095\n",
|
|
|
|
|
|
"命中率 (>0): 49.11%\n",
|
|
|
|
|
|
"平均单手收益: 8.68 元\n",
|
|
|
|
|
|
"盈亏比: 1.23\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"极端空头(1手) 信号数: 7838\n",
|
|
|
|
|
|
"命中率 (>0): 47.86%\n",
|
|
|
|
|
|
"平均单手收益: 5.49 元\n",
|
|
|
|
|
|
"盈亏比: 1.15\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"整体日夏普: 7.62\n",
|
|
|
|
|
|
"最大回撤: 970 元\n"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"text/plain": [
|
|
|
|
|
|
"<Figure size 1200x300 with 1 Axes>"
|
|
|
|
|
|
],
|
|
|
|
|
|
"image/png": "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
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"output_type": "display_data"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"text/plain": [
|
|
|
|
|
|
"<Figure size 1200x400 with 1 Axes>"
|
|
|
|
|
|
],
|
|
|
|
|
|
"image/png": "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rVvH888/zyWXXJKp+OkO+t988022bNnC//t//4+HHnqI73znO/z0pz/ttyfEEUccwfe///1MS/3DDz+c9ffPf/7zHHnkkVm9GXqKxWL8x3/8BytWrOCJJ57IvP7KK69w5ZVX5jRkwmazcdttt/HZz36WX/ziF8OqRBBCCDEyEvgLIYSYMNXV1Rx11FEAnHDCCZx22mn87ne/42tf+xrPPvssNTU1vPTSS5SVlXHSSSexefNmfv7zn2cF/uvXr8dut9PU1MTevXtZuHDhmJZRUZRMGVevXo2iKNxyyy00NDRQWVmZ0zry8vLYsWMHr7/+Oi+88EImqL/zzjv5/e9/j6qqqKqaCeQBbr311sx2qqqKw+Hg/PPPB8i0ll9//fWsXLkys4zdbs8Kcqeqd999t98hCL271//2t7/N2j5I9w6wWCzk5+fz0ksvYZomZ555JpAer37xxRdz6623smbNGiBdadKzd8FA+ygYDPLEE0/wrW99K7Ofa2pq+PznP8+LL76I3W6ntLQUgC9+8Yt88pOfzPQAsFgspFKpAVvzdV0nmUxSUFCQ2w7q8vjjj3PyySdzyimn9PnbgQMHeOCBB7jssstoa2ujpKQESJ8rN998M//2b/9GSUkJ119/PRdddBHXXnstt99+O36/v8+6PvGJTwyrXD396Ec/4uDBgxQWFhIKhTKvx+NxgKzXNE3D7Xbjdrv7rMdut3PnnXfymc98hs997nOZ3i5CCCHGhwT+QgghJoXD4aC6upr9+/cD6aRpq1atoqysLPOeo446qk9L7z//+U8++tGP8vLLL7N+/foxD/x7664EaGpqyinw7x4T/fOf/5y77rorq0Va0zQ++MEPctttt7F//36cTidlZWWcffbZmfeYpsnFF1/Msccey/XXX5/VatvW1kZbW1vm954B7lTWPbb+pptuyrRS99Te3s6tt97ab4D+pS99iYsvvhhIt97ff//9nH766dx0001YrVYCgUCmJwWkh2vk4le/+hW6rrN7924cDgd+v5/du3fj9Xr7nFOFhYWZ3hmGYXDTTTfR0tLCQw89hMPhoL6+nssuu4xLLrmET3ziE5ntHa4nn3xywPIbhpEpQyAQYNasWaRSKW644QZisRinn346e/fuBeDmm2/muuuu45xzzuHSSy/lkksuoaamhoaGhkxlxcqVK4cdbP/jH//gN7/5DatXr2bTpk2ZyhZIV3YAWa+lUil++MMfct555/W7voULF3L44Yfz5z//mY997GPDKosQQojhkUFVQgghJk1bW1umVXXXrl0sWLAg6+9r167lJz/5SSaoiEajvPvuu5xwwgkcd9xxWd39DcNA07R+/3UvPxLdmfL7C1j788Mf/pDDDz+cFStWsG7dOs4991yWLVvGxRdfjN1ux2KxYLPZ+NGPfsT999+f6Q3Q/b/FYuHrX/86Tz75JNdcc03Wum02G16vd8Tb0u3AgQMsWbIka4iAaZqcdNJJPP7445nXXn31Vc4//3xWrFjBmjVrcuqa35/uyo9IJEIoFOrzrzvDe38VGU6nM9O744orruCpp56ipqaGH/zgB5mEh+Xl5cMqT2NjI7/85S/5zne+w5/+9CfeeustIN3iX1VVNeByoVCIq666iqeeeoqCgoJMdvxYLMbChQv54Q9/yJo1a/j5z38+7Kz
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"output_type": "display_data"
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
"execution_count": 3
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"ExecuteTime": {
|
|
|
|
|
|
"end_time": "2025-09-22T08:43:10.408026Z",
|
|
|
|
|
|
"start_time": "2025-09-22T08:43:10.163442Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"cell_type": "code",
|
|
|
|
|
|
"source": [
|
|
|
|
|
|
"import pandas as pd\n",
|
2025-09-16 09:59:38 +08:00
|
|
|
|
"import numpy as np\n",
|
2025-09-24 23:14:14 +08:00
|
|
|
|
"import matplotlib.pyplot as plt\n",
|
|
|
|
|
|
"import seaborn as sns\n",
|
|
|
|
|
|
"from sklearn.decomposition import PCA\n",
|
|
|
|
|
|
"from sklearn.linear_model import LinearRegression\n",
|
|
|
|
|
|
"from sklearn.preprocessing import StandardScaler\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# --- 1. 数据准备与特征工程 ---\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"def calculate_rsi_features(df, periods=range(2, 25)):\n",
|
|
|
|
|
|
" \"\"\"计算一系列RSI作为特征\"\"\"\n",
|
|
|
|
|
|
" df_features = pd.DataFrame(index=df.index)\n",
|
|
|
|
|
|
" for period in periods:\n",
|
|
|
|
|
|
" delta = df['close'].diff()\n",
|
|
|
|
|
|
" gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()\n",
|
|
|
|
|
|
" loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()\n",
|
|
|
|
|
|
" rs = gain / loss\n",
|
|
|
|
|
|
" df_features[f'rsi_{period}'] = 100 - (100 / (1 + rs))\n",
|
|
|
|
|
|
" return df_features\n",
|
|
|
|
|
|
" # df_features = pd.DataFrame(index=df.index)\n",
|
|
|
|
|
|
" # for period in periods:\n",
|
|
|
|
|
|
" # df_features[f'rsi_{period}'] = talib.RSI(df['close'], timeperiod=period)\n",
|
|
|
|
|
|
" # return df_features\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"def calculate_future_returns_label(df, n_periods=5):\n",
|
|
|
|
|
|
" \"\"\"计算未来N期收益率作为标签\"\"\"\n",
|
|
|
|
|
|
" return df['close'].shift(-n_periods) / df['close'] - 1\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"print(\"\\n步骤1: 构建特征 (RSI 2-24) 和标签 (未来5周期收益率)...\")\n",
|
|
|
|
|
|
"X_features = calculate_rsi_features(df_raw)\n",
|
|
|
|
|
|
"y_label = calculate_future_returns_label(df_raw, n_periods=5)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 合并并清理数据\n",
|
|
|
|
|
|
"full_data = pd.concat([X_features, y_label], axis=1)\n",
|
|
|
|
|
|
"full_data.columns = [*X_features.columns, 'future_return']\n",
|
|
|
|
|
|
"full_data.dropna(inplace=True)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"print(f\"数据构建完成,清理后剩余 {len(full_data)} 条记录。\")\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# --- 2. 训练集 / 测试集 分割 ---\n",
|
|
|
|
|
|
"print(\"\\n步骤2: 按时间分割训练集 (< 2024年) 和测试集 (>= 2024年)...\")\n",
|
|
|
|
|
|
"train_df = full_data[full_data.index.year < 2024]\n",
|
|
|
|
|
|
"test_df = full_data[full_data.index.year >= 2024]\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"if len(train_df) == 0 or len(test_df) == 0:\n",
|
|
|
|
|
|
" print(\"错误: 数据不足以进行训练和测试分割。请确保您的数据包含2024年前后的数据。\")\n",
|
|
|
|
|
|
" exit()\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"X_train, y_train = train_df.iloc[:, :-1], train_df.iloc[:, -1]\n",
|
|
|
|
|
|
"X_test, y_test = test_df.iloc[:, :-1], test_df.iloc[:, -1]\n",
|
|
|
|
|
|
"print(f\"训练集大小: {len(X_train)} | 测试集大小: {len(X_test)}\")\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# --- 3. 模型训练 (在训练集上) ---\n",
|
|
|
|
|
|
"print(\"\\n步骤3: 在训练集上训练PCA线性回归模型...\")\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 数据中心化\n",
|
|
|
|
|
|
"# train_means = X_train.mean()\n",
|
|
|
|
|
|
"# X_train_centered = X_train - train_means\n",
|
|
|
|
|
|
"scaler = StandardScaler()\n",
|
|
|
|
|
|
"X_train_centered = scaler.fit_transform(X_train)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# PCA降维\n",
|
|
|
|
|
|
"N_COMPONENTS = 5 # 这是一个超参数,可以调整\n",
|
|
|
|
|
|
"pca = PCA(n_components=N_COMPONENTS, random_state=42)\n",
|
|
|
|
|
|
"X_train_pca = pca.fit_transform(X_train_centered)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 线性回归\n",
|
|
|
|
|
|
"model = LinearRegression()\n",
|
|
|
|
|
|
"model.fit(X_train_pca, y_train)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 对训练集本身进行预测,以找到阈值\n",
|
|
|
|
|
|
"train_predictions = model.predict(X_train_pca)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 找到极端值阈值\n",
|
|
|
|
|
|
"long_threshold = np.quantile(train_predictions, 0.99)\n",
|
|
|
|
|
|
"short_threshold = np.quantile(train_predictions, 0.01)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"print(f\"模型训练完成。PCA保留 {N_COMPONENTS} 个主成分。\")\n",
|
|
|
|
|
|
"print(f\"根据训练集预测,极端值阈值确定为:\")\n",
|
|
|
|
|
|
"print(f\" - 做多阈值 (99%分位): {long_threshold:.6f}\")\n",
|
|
|
|
|
|
"print(f\" - 做空阈值 (1%分位): {short_threshold:.6f}\")\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# --- 4. 样本外预测与分析 ---\n",
|
|
|
|
|
|
"print(\"\\n步骤4: 在测试集上进行样本外预测与分析...\")\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 使用训练集的均值来中心化测试集 (防止未来数据泄露)\n",
|
|
|
|
|
|
"# X_test_centered = X_test - train_means\n",
|
|
|
|
|
|
"X_test_centered = scaler.transform(X_test)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 使用训练集训练好的PCA模型来转换测试集\n",
|
|
|
|
|
|
"X_test_pca = pca.transform(X_test_centered)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 使用训练集训练好的线性模型进行预测\n",
|
|
|
|
|
|
"test_predictions = model.predict(X_test_pca)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 将预测结果合并到测试数据中\n",
|
|
|
|
|
|
"results = test_df.copy()\n",
|
|
|
|
|
|
"results['prediction'] = test_predictions\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 根据阈值生成交易信号 (-1: 做空, 1: 做多, 0: 无操作)\n",
|
|
|
|
|
|
"results['signal'] = 0\n",
|
|
|
|
|
|
"results.loc[results['prediction'] > long_threshold, 'signal'] = 1\n",
|
|
|
|
|
|
"results.loc[results['prediction'] < short_threshold, 'signal'] = -1\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# --- 5. 结果分析与可视化 ---\n",
|
|
|
|
|
|
"print(\"\\n步骤5: 分析策略在测试集上的表现...\")\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 核心分析: 检查极端信号的预测能力\n",
|
|
|
|
|
|
"signal_groups = results.groupby('signal')['future_return']\n",
|
|
|
|
|
|
"signal_performance = signal_groups.agg(['mean', 'std', 'count'])\n",
|
|
|
|
|
|
"signal_performance['sharpe_ratio'] = (signal_performance['mean'] / signal_performance['std']) * np.sqrt(252 * 96) # 年化夏普率 (粗略估计)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"print(\"\\n--- 核心分析:不同信号下的未来平均收益 ---\")\n",
|
|
|
|
|
|
"print(signal_performance)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 可视化核心分析\n",
|
|
|
|
|
|
"fig, ax = plt.subplots(figsize=(10, 6))\n",
|
|
|
|
|
|
"signal_performance['mean'].plot(kind='bar', ax=ax, color=['red', 'gray', 'green'])\n",
|
|
|
|
|
|
"ax.set_title('Out-of-Sample: Average Future Return per Signal', fontsize=16)\n",
|
|
|
|
|
|
"ax.set_ylabel('Average Return over next 5 periods (1.25 hours)')\n",
|
|
|
|
|
|
"ax.set_xlabel('Signal Generated by Model')\n",
|
|
|
|
|
|
"ax.set_xticklabels(['Short (-1)', 'No Signal (0)', 'Long (1)'], rotation=0)\n",
|
|
|
|
|
|
"plt.tight_layout()\n",
|
|
|
|
|
|
"plt.show()\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 可视化预测值与真实值的关系 (散点图)\n",
|
|
|
|
|
|
"plt.figure(figsize=(12, 8))\n",
|
|
|
|
|
|
"# 只绘制一部分点以避免过于密集\n",
|
|
|
|
|
|
"sample_results = results.sample(min(len(results), 5000))\n",
|
|
|
|
|
|
"sns.scatterplot(x='prediction', y='future_return', data=sample_results, hue='signal', palette={-1:'red', 0:'gray', 1:'green'}, alpha=0.6)\n",
|
|
|
|
|
|
"plt.axvline(long_threshold, color='green', linestyle='--', label='Long Threshold')\n",
|
|
|
|
|
|
"plt.axvline(short_threshold, color='red', linestyle='--', label='Short Threshold')\n",
|
|
|
|
|
|
"plt.title('Out-of-Sample: Predictions vs. Actual Future Returns', fontsize=16)\n",
|
|
|
|
|
|
"plt.xlabel('Model Prediction (Signal Strength)')\n",
|
|
|
|
|
|
"plt.ylabel('Actual Future Return')\n",
|
|
|
|
|
|
"plt.legend()\n",
|
|
|
|
|
|
"plt.show()"
|
|
|
|
|
|
],
|
|
|
|
|
|
"id": "2f4c19c01fa250fe",
|
|
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"步骤1: 构建特征 (RSI 2-24) 和标签 (未来5周期收益率)...\n",
|
|
|
|
|
|
"数据构建完成,清理后剩余 25448 条记录。\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"步骤2: 按时间分割训练集 (< 2024年) 和测试集 (>= 2024年)...\n",
|
|
|
|
|
|
"训练集大小: 16423 | 测试集大小: 9025\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"步骤3: 在训练集上训练PCA线性回归模型...\n",
|
|
|
|
|
|
"模型训练完成。PCA保留 5 个主成分。\n",
|
|
|
|
|
|
"根据训练集预测,极端值阈值确定为:\n",
|
|
|
|
|
|
" - 做多阈值 (99%分位): 0.001107\n",
|
|
|
|
|
|
" - 做空阈值 (1%分位): -0.000767\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"步骤4: 在测试集上进行样本外预测与分析...\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"步骤5: 分析策略在测试集上的表现...\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"--- 核心分析:不同信号下的未来平均收益 ---\n",
|
|
|
|
|
|
" mean std count sharpe_ratio\n",
|
|
|
|
|
|
"signal \n",
|
|
|
|
|
|
"-1 -0.001367 0.007883 102 -26.976471\n",
|
|
|
|
|
|
" 0 -0.000165 0.010078 8852 -2.549084\n",
|
|
|
|
|
|
" 1 0.001000 0.007690 71 20.216590\n"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"text/plain": [
|
|
|
|
|
|
"<Figure size 1000x600 with 1 Axes>"
|
|
|
|
|
|
],
|
|
|
|
|
|
"image/png": "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PGjVe/Akx48flyS5ublZtP/1118aNGiQeaGsJk2aaM6cOUpMTEzxutJ6vHrcolSp7f/w+xwZGakpU6aoWbNmKS65ePj9PXz4sDp37ixvb2/VqVNHI0eOVExMjFXvTfK469ev15AhQ1SzZk15eXmpc+fOj5zxDw0NVUBAgGrVqqXKlSurRYsWWrFixRNf87FjxzRgwADVrVtXixcvtqpeSebvvVGjRmnp0qXm46qTk5NcXFyUPXv2x77Wxy1yFh8fr5kzZ+q1115TtWrV9O6772rnzp0KDAxUtWrVzItQWvP1WLVqldq1a6fq1avrlVdeUefOnbV3716r3wfgecE12AAy7O7du5Kk3Llzp7o9Z86ciomJUWRkZLrGdXFxUe/evSU9WKF8yZIl5gWBHt5HejAL/OOPPz5xzOQF2CRp7NixWrhwoQoWLKg2bdooPDxcQUFB2rFjh7755htVr15dklSjRg1zHQcPHtTBgwfVsmVL83VxD5/ePWXKlCfWkCdPHr3//vuyt7fXpEmT1LNnT4WEhOitt95StWrV1LdvX9WpUydFvx07dqhPnz7KmTOnXn/9dTk7O2vfvn367LPPFBMTo/fff99i/379+qlx48YKDw/X4sWL9fPPP6t27dpKTEzUjh07tG7dOr355puSpPPnz6t///6qVauWatasqb1792rOnDk6fvy4vvnmG6sXWgoMDNTq1atVqVIldezYUX///bc2bdqk/fv364cfflC5cuVS9ClatKh69+5t8b5m1M6dOyU9OKPCw8NDTk5O2rFjh4YOHWrep06dOsqfP782bNiggQMHWvRfv369JFlcd3/lyhW9/fbb+vvvv80LFx09elTffPONQkJCtGDBglTft9DQUPn7+ysqKkrly5dXsWLFLLYnJCSod+/eOnDggF5++WX5+vrq1q1b2rhxo44cOaKff/7Z/H0vSRs2bNCAAQPk7u6u9u3ba+/evRo8eLA8PT3Vtm1b5cyZ07zvihUrNGLECBUsWFAtWrSQyWTS5s2b1bdv3yx3fXP58uXNP3cnT57Utm3b5Ovraw6DRi2cFhkZqY4dO+r48ePy8PBQ6dKlU+wzbdo0ffnllypZsqTefPNNxcTEKCgoSF27dtXcuXP16quvGlJLRESE1q5dq59++kk5c+ZUixYtzNuOHz+url27KiEhQa+99pry5cunvXv3atKkSTp37px5bYr0HK8y4urVq+ratauuXr2q8uXLq3jx4qnut3fvXn3zzTeqWbOm3nzzTW3ZskXfffed7O3tNWLECKuff9SoUbK3t9frr7+uu3fvasuWLerSpYvmzp2r2rVrm/fbsWOHAgIClDNnTr322mvKkSOHtm/fruHDh+vatWvq169fquOvWrVKI0aMUO7cuVWiRAm9+OKLVtfao0cP/f7779q5c6dGjRqlL7/8Ul27dlWnTp0sfj6t8fnnn+u7775T3bp1Vb9+fa1fv149evRQkyZN9M4776Q4xqb16zFlyhTNnj1bJUuWVLt27RQXF6fNmzfL399f3333XarXjAP4/0wAkEE//fSTyd3d3fTuu++mur1+/fomd3d3U3BwsMlkMpkGDx5scnd3N128eDHFvr6+viZfX98U7RcvXjS5u7ubBg8enOpz7Nu3z+Tu7v7EP8n9t2/fbnJ3dze1aNH
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"output_type": "display_data"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"text/plain": [
|
|
|
|
|
|
"<Figure size 1200x800 with 1 Axes>"
|
|
|
|
|
|
],
|
|
|
|
|
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA/wAAALBCAYAAADlMqo8AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Xd4W+X5N/Cv9rIk7x3v2Emc2DFZjIYMNhTCKKQDAr8yCoEAhZbVMl5KoVB2CKVAy4aWsBpGSEgJKS0FspezHNuJt+MhWXue9w9XJ1YseUWyPL6f6/IV+SzdUo7kc5/nee5HIgiCACIiIiIiIiIaU6SxDoCIiIiIiIiIIo8JPxEREREREdEYxISfiIiIiIiIaAxiwk9EREREREQ0BjHhJyIiIiIiIhqDmPATERERERERjUFM+ImIiIiIiIjGICb8RERERERERGMQE34iIiIiIiKiMYgJPxHBarXi0UcfxcKFCzF16lScdtppeOqpp+B0OmMdWkTt3r0bV199NWbNmoWpU6fivPPOw9dffz3k433//fe48sorMX36dEyfPh2XXXYZ1q1bF8GIo+O7775DSUkJli9fHutQwgrE2PNn6tSpOOuss/D000/H/Nzs7z384IMPUFJSgg8++GCYIwtWUlKCK664IqYx0FG33norSkpK8NFHH8U6lKi44oorUFJSctzHqa+v7/X5D/VDwUJ9b06bNg3nnHMOnnrqKVit1liHSEQxII91AEQUWzabDVdccQUqKytRWlqKM888E9988w1eeOEFbNmyBa+88grk8tH/VdHS0oL/+7//AwAsWrQIcrkce/bsQW1tLebOnTvo461evRq33XYbDAYDFi1aBLfbjc8++ww33XQTXnjhBcyfPz/Cr2B8yszMxAUXXAAAOHLkCL799lv86U9/wnfffYfXXnsNSqUyxhESDYzX68V//vMfAMC//vUvXHjhhbENaBTQ6/X42c9+FuswRp3A96YgCGhubsZ//vMfvPDCC1i3bh3effdd6HS6WIdIRMNo9F/FE9Fxeemll1BZWYl58+bhT3/6E2QyGdxuN5YsWYLvv/8e7733Hn784x8P+fj19fU47bTTcNFFF+EPf/hDBCMfnH/9618wm8149NFHgy60/X7/oI/l8Xjw4IMPQqlU4v3330d2djYA4MILL8SSJUvw6KOPMuGPkOzsbPzy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
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"output_type": "display_data"
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
"execution_count": 4
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"ExecuteTime": {
|
|
|
|
|
|
"end_time": "2025-09-22T08:43:10.725801Z",
|
|
|
|
|
|
"start_time": "2025-09-22T08:43:10.454896Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"cell_type": "code",
|
|
|
|
|
|
"source": [
|
|
|
|
|
|
"# ==============================================================================\n",
|
|
|
|
|
|
"# 强烈建议:在您的Jupyter Notebook的第一个代码单元格中运行此魔法命令\n",
|
|
|
|
|
|
"# %matplotlib inline\n",
|
|
|
|
|
|
"# ==============================================================================\n",
|
|
|
|
|
|
"\n",
|
2025-09-16 09:59:38 +08:00
|
|
|
|
"import pandas as pd\n",
|
2025-09-24 23:14:14 +08:00
|
|
|
|
"import numpy as np\n",
|
2025-09-16 09:59:38 +08:00
|
|
|
|
"import talib\n",
|
2025-09-24 23:14:14 +08:00
|
|
|
|
"from sklearn.preprocessing import StandardScaler\n",
|
|
|
|
|
|
"from sklearn.decomposition import PCA\n",
|
|
|
|
|
|
"from sklearn.linear_model import LinearRegression\n",
|
|
|
|
|
|
"import matplotlib.pyplot as plt\n",
|
|
|
|
|
|
"import seaborn as sns\n",
|
2025-09-16 09:59:38 +08:00
|
|
|
|
"\n",
|
2025-09-24 23:14:14 +08:00
|
|
|
|
"# --- 1. 特征与标签工程 ---\n",
|
2025-09-16 09:59:38 +08:00
|
|
|
|
"\n",
|
2025-09-24 23:14:14 +08:00
|
|
|
|
"def build_features_and_label(df_raw, rsi_method='talib', rsi_periods=range(2, 25), future_n=5):\n",
|
2025-09-16 09:59:38 +08:00
|
|
|
|
" \"\"\"\n",
|
2025-09-24 23:14:14 +08:00
|
|
|
|
" 在给定的DataFrame上构建特征和标签。\n",
|
2025-09-16 09:59:38 +08:00
|
|
|
|
" \"\"\"\n",
|
2025-09-24 23:14:14 +08:00
|
|
|
|
" df = df_raw.copy()\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" feature_names = [f'rsi_{i}' for i in rsi_periods]\n",
|
2025-09-16 09:59:38 +08:00
|
|
|
|
"\n",
|
2025-09-24 23:14:14 +08:00
|
|
|
|
" if rsi_method == 'talib':\n",
|
|
|
|
|
|
" # print(\"正在使用 'talib' (EMA) 方法计算RSI特征...\")\n",
|
|
|
|
|
|
" for i in rsi_periods:\n",
|
|
|
|
|
|
" df[f'rsi_{i}'] = talib.RSI(df['close'], timeperiod=i)\n",
|
|
|
|
|
|
" elif rsi_method == 'manual':\n",
|
|
|
|
|
|
" # print(\"正在使用 'manual' (SMA) 方法计算RSI特征...\")\n",
|
|
|
|
|
|
" delta = df['close'].diff()\n",
|
|
|
|
|
|
" gain = delta.where(delta > 0, 0)\n",
|
|
|
|
|
|
" loss = -delta.where(delta < 0, 0)\n",
|
|
|
|
|
|
" for i in rsi_periods:\n",
|
|
|
|
|
|
" avg_gain = gain.rolling(window=i).mean()\n",
|
|
|
|
|
|
" avg_loss = loss.rolling(window=i).mean()\n",
|
|
|
|
|
|
" rs = avg_gain / avg_loss\n",
|
|
|
|
|
|
" df[f'rsi_{i}'] = 100 - (100 / (1 + rs))\n",
|
2025-09-16 09:59:38 +08:00
|
|
|
|
" else:\n",
|
2025-09-24 23:14:14 +08:00
|
|
|
|
" raise ValueError(f\"未知的 rsi_method: '{rsi_method}'. 请选择 'talib' 或 'manual'.\")\n",
|
2025-09-16 09:59:38 +08:00
|
|
|
|
"\n",
|
2025-09-24 23:14:14 +08:00
|
|
|
|
" df['future_return'] = df['close'].shift(-future_n) / df['close'] - 1\n",
|
|
|
|
|
|
" df['forward_return_1p'] = df['close'].shift(-1) / df['close'] - 1\n",
|
2025-09-16 09:59:38 +08:00
|
|
|
|
"\n",
|
2025-09-24 23:14:14 +08:00
|
|
|
|
" df.dropna(inplace=True)\n",
|
2025-09-16 09:59:38 +08:00
|
|
|
|
"\n",
|
2025-09-24 23:14:14 +08:00
|
|
|
|
" return df, feature_names\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# --- 2. 模型训练与策略诊断 --- (不再需要分割函数)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"def train_model_and_diagnose(train_df, features, label, n_components=0.95):\n",
|
|
|
|
|
|
" \"\"\"\n",
|
|
|
|
|
|
" 在纯净的训练集上训练模型,并返回所有需要的组件。\n",
|
|
|
|
|
|
" \"\"\"\n",
|
|
|
|
|
|
" X_train = train_df[features]\n",
|
|
|
|
|
|
" y_train = train_df[label]\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" if len(X_train) < 100:\n",
|
|
|
|
|
|
" raise ValueError(f\"训练样本不足100条,仅剩 {len(X_train)} 条!\")\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" scaler = StandardScaler()\n",
|
|
|
|
|
|
" pca = PCA(n_components=n_components, random_state=42)\n",
|
|
|
|
|
|
" model = LinearRegression()\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" X_train_scaled = scaler.fit_transform(X_train)\n",
|
|
|
|
|
|
" X_train_pca = pca.fit_transform(X_train_scaled)\n",
|
|
|
|
|
|
" model.fit(X_train_pca, y_train)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" train_predictions = model.predict(X_train_pca)\n",
|
|
|
|
|
|
" correlation = np.corrcoef(train_predictions, y_train)[0, 1]\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" print(\"\\n--- 策略性质诊断 (基于训练集) ---\")\n",
|
|
|
|
|
|
" print(f\"训练样本数: {len(X_train)}\")\n",
|
|
|
|
|
|
" print(f\"模型预测与未来收益的相关系数: {correlation:.4f}\")\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" if correlation > 0.01:\n",
|
|
|
|
|
|
" strategy_type = 'momentum'\n",
|
|
|
|
|
|
" print(\"诊断结果: 呈正相关 -> 这是一个'趋势跟随'策略。\")\n",
|
|
|
|
|
|
" elif correlation < -0.01:\n",
|
|
|
|
|
|
" strategy_type = 'reversion'\n",
|
|
|
|
|
|
" print(\"诊断结果: 呈负相关 -> 这是一个'均值回归'策略。\")\n",
|
|
|
|
|
|
" else:\n",
|
|
|
|
|
|
" strategy_type = 'none'\n",
|
|
|
|
|
|
" print(\"诊断结果: 相关性不显著,策略可能无效。\")\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" thresholds = {\n",
|
|
|
|
|
|
" 'quantile': {'high': np.quantile(train_predictions, 0.99), 'low': np.quantile(train_predictions, 0.01)},\n",
|
|
|
|
|
|
" 'sigma': {'high': train_predictions.mean() + 2 * train_predictions.std(), 'low': train_predictions.mean() - 2 * train_predictions.std()}\n",
|
|
|
|
|
|
" }\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" print(\"\\n模型训练完成。交易阈值已确定:\")\n",
|
|
|
|
|
|
" print(f\" - 分位数阈值 (99%/1%): {thresholds['quantile']['high']:.6f} / {thresholds['quantile']['low']:.6f}\")\n",
|
|
|
|
|
|
" print(f\" - Sigma阈值 (±2σ): {thresholds['sigma']['high']:.6f} / {thresholds['sigma']['low']:.6f}\")\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" return scaler, pca, model, strategy_type, thresholds\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# --- 3. 样本外回测计算 ---\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"def run_backtest_for_threshold(test_df, features, pipeline, strategy_info, threshold_type):\n",
|
|
|
|
|
|
" \"\"\"为指定的阈值类型运行回测\"\"\"\n",
|
|
|
|
|
|
" scaler, pca, model = pipeline\n",
|
|
|
|
|
|
" strategy_type, thresholds = strategy_info\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" if strategy_type == 'none': return None\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" high_thresh = thresholds[threshold_type]['high']\n",
|
|
|
|
|
|
" low_thresh = thresholds[threshold_type]['low']\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" X_test = test_df[features]\n",
|
|
|
|
|
|
" X_test_scaled = scaler.transform(X_test)\n",
|
|
|
|
|
|
" X_test_pca = pca.transform(X_test_scaled)\n",
|
|
|
|
|
|
" test_predictions = model.predict(X_test_pca)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" results = test_df.copy()\n",
|
|
|
|
|
|
" results['prediction'] = test_predictions\n",
|
|
|
|
|
|
" results['signal'] = 0\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" if strategy_type == 'momentum':\n",
|
|
|
|
|
|
" results.loc[results['prediction'] > high_thresh, 'signal'] = 1\n",
|
|
|
|
|
|
" results.loc[results['prediction'] < low_thresh, 'signal'] = -1\n",
|
|
|
|
|
|
" elif strategy_type == 'reversion':\n",
|
|
|
|
|
|
" results.loc[results['prediction'] > high_thresh, 'signal'] = -1\n",
|
|
|
|
|
|
" results.loc[results['prediction'] < low_thresh, 'signal'] = 1\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" results['position'] = results['signal'].shift(1).fillna(0)\n",
|
|
|
|
|
|
" results['strategy_return'] = results['position'] * results['forward_return_1p']\n",
|
|
|
|
|
|
" results[f'pnl_{threshold_type}'] = results['strategy_return'].cumsum()\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" return results\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# --- 4. 结果可视化与交易明细打印 ---\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"def print_trade_details(results_df, top_n=10):\n",
|
|
|
|
|
|
" \"\"\"提取并打印交易明细\"\"\"\n",
|
|
|
|
|
|
" trades = results_df[results_df['position'] != results_df['position'].shift(1)].copy()\n",
|
|
|
|
|
|
" trades = trades[trades['position'] != 0]\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" if len(trades) == 0:\n",
|
|
|
|
|
|
" print(\"在测试期间没有发生任何交易。\")\n",
|
|
|
|
|
|
" return\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" print(f\"\\n--- 前 {top_n} 笔交易明细 ---\")\n",
|
|
|
|
|
|
" trade_list = []\n",
|
|
|
|
|
|
" for i in range(len(trades)):\n",
|
|
|
|
|
|
" entry_trade = trades.iloc[i]\n",
|
|
|
|
|
|
" entry_time = entry_trade.name\n",
|
|
|
|
|
|
" entry_price = entry_trade['open']\n",
|
|
|
|
|
|
" direction = 'Long' if entry_trade['position'] > 0 else 'Short'\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" exit_time_index = results_df.index.get_loc(entry_time) + 5\n",
|
|
|
|
|
|
" if exit_time_index < len(results_df):\n",
|
|
|
|
|
|
" exit_time = results_df.index[exit_time_index]\n",
|
|
|
|
|
|
" exit_price = results_df.iloc[exit_time_index]['open']\n",
|
|
|
|
|
|
" pnl = (exit_price - entry_price) * entry_trade['position']\n",
|
|
|
|
|
|
" else:\n",
|
|
|
|
|
|
" exit_time, exit_price, pnl = 'N/A', np.nan, np.nan\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" trade_list.append({\n",
|
|
|
|
|
|
" 'EntryTime': entry_time, 'Direction': direction, 'EntryPrice': entry_price,\n",
|
|
|
|
|
|
" 'ExitTime': exit_time, 'ExitPrice': exit_price, 'PNL_Points': pnl\n",
|
|
|
|
|
|
" })\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" trade_details_df = pd.DataFrame(trade_list)\n",
|
|
|
|
|
|
" print(trade_details_df.head(top_n).to_string())\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"def plot_results(results_df, threshold_type, label):\n",
|
|
|
|
|
|
" if results_df is None: return\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" print(f\"\\n--- 核心分析 ({threshold_type} 阈值) ---\")\n",
|
|
|
|
|
|
" signal_groups = results_df.groupby('signal')[label]\n",
|
|
|
|
|
|
" signal_performance = signal_groups.agg(['mean', 'std', 'count'])\n",
|
|
|
|
|
|
" print(signal_performance)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" fig, ax = plt.subplots(figsize=(10, 6))\n",
|
|
|
|
|
|
" signal_performance['mean'].plot(kind='bar', ax=ax, color=['red', 'gray', 'green'])\n",
|
|
|
|
|
|
" ax.set_title(f'Out-of-Sample: Average Future Return per Signal ({threshold_type} Threshold)', fontsize=16)\n",
|
|
|
|
|
|
" plt.show()\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# ==============================================================================\n",
|
|
|
|
|
|
"# --- 主流程 ---\n",
|
|
|
|
|
|
"# ==============================================================================\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# !!! 1. 配置参数 !!!\n",
|
|
|
|
|
|
"RSI_METHOD = 'talib' # 可选: 'talib' 或 'manual'\n",
|
|
|
|
|
|
"SPLIT_DATE = '2024-01-01'\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"try:\n",
|
|
|
|
|
|
" df_raw = pd.read_csv(file_path, index_col=0, parse_dates=True).sort_index()\n",
|
|
|
|
|
|
"except FileNotFoundError:\n",
|
|
|
|
|
|
" print(f\"错误: 文件 '{file_path}' 未找到。\")\n",
|
|
|
|
|
|
" exit()\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# --- MODIFICATION START: 先分割,再计算 ---\n",
|
|
|
|
|
|
"# 2. 首先按时间分割原始数据\n",
|
|
|
|
|
|
"train_raw = df_raw[df_raw.index < SPLIT_DATE]\n",
|
|
|
|
|
|
"test_raw = df_raw[df_raw.index >= SPLIT_DATE]\n",
|
|
|
|
|
|
"print(f\"原始数据分割完成: 训练集 {len(train_raw)} 条 | 测试集 {len(test_raw)} 条\")\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 3. 分别在“纯净”的数据集上构建特征和标签\n",
|
|
|
|
|
|
"print(\"\\n--- 正在处理训练集 ---\")\n",
|
|
|
|
|
|
"train_set, train_features = build_features_and_label(train_raw, rsi_method=RSI_METHOD)\n",
|
|
|
|
|
|
"print(\"\\n--- 正在处理测试集 ---\")\n",
|
|
|
|
|
|
"test_set, test_features = build_features_and_label(test_raw, rsi_method=RSI_METHOD)\n",
|
|
|
|
|
|
"# --- MODIFICATION END ---\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 4. 在纯净的训练集上训练模型并诊断\n",
|
|
|
|
|
|
"scaler, pca, model, strategy_type, thresholds = train_model_and_diagnose(\n",
|
|
|
|
|
|
" train_set, train_features, 'future_return'\n",
|
|
|
|
|
|
")\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 5. 在纯净的测试集上运行两种回测\n",
|
|
|
|
|
|
"pipeline = (scaler, pca, model)\n",
|
|
|
|
|
|
"strategy_info = (strategy_type, thresholds)\n",
|
|
|
|
|
|
"results_quantile = run_backtest_for_threshold(test_set, test_features, pipeline, strategy_info, 'quantile')\n",
|
|
|
|
|
|
"results_sigma = run_backtest_for_threshold(test_set, test_features, pipeline, strategy_info, 'sigma')\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 6. 可视化与打印交易明细\n",
|
|
|
|
|
|
"print(\"\\n\" + \"=\"*50)\n",
|
|
|
|
|
|
"print(\" 评估结果: 1%/99% 分位数阈值\")\n",
|
|
|
|
|
|
"print(\"=\"*50)\n",
|
|
|
|
|
|
"plot_results(results_quantile, 'Quantile', 'future_return')\n",
|
|
|
|
|
|
"print_trade_details(results_quantile)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"print(\"\\n\" + \"=\"*50)\n",
|
|
|
|
|
|
"print(\" 评估结果: ±2 Sigma 阈值\")\n",
|
|
|
|
|
|
"print(\"=\"*50)\n",
|
|
|
|
|
|
"plot_results(results_sigma, 'Sigma', 'future_return')\n",
|
|
|
|
|
|
"print_trade_details(results_sigma)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 7. PNL曲线对比图\n",
|
|
|
|
|
|
"print(\"\\n\" + \"=\"*50)\n",
|
|
|
|
|
|
"print(\" PNL 曲线对比\")\n",
|
|
|
|
|
|
"print(\"=\"*50)\n",
|
|
|
|
|
|
"if results_quantile is not None and results_sigma is not None:\n",
|
|
|
|
|
|
" plt.figure(figsize=(15, 7))\n",
|
|
|
|
|
|
" results_quantile['pnl_quantile'].plot(label='PNL Curve (Quantile Threshold)')\n",
|
|
|
|
|
|
" results_sigma['pnl_sigma'].plot(label='PNL Curve (Sigma Threshold)', linestyle='--')\n",
|
|
|
|
|
|
" plt.title('PNL Curve Comparison (Out-of-Sample)', fontsize=16)\n",
|
|
|
|
|
|
" plt.xlabel('Date')\n",
|
|
|
|
|
|
" plt.ylabel('Cumulative Return')\n",
|
|
|
|
|
|
" plt.legend()\n",
|
|
|
|
|
|
" plt.grid(True)\n",
|
|
|
|
|
|
" plt.show()"
|
2025-09-16 09:59:38 +08:00
|
|
|
|
],
|
2025-09-24 23:14:14 +08:00
|
|
|
|
"id": "75a6f3218fbb45b",
|
2025-09-16 09:59:38 +08:00
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
2025-09-24 23:14:14 +08:00
|
|
|
|
"原始数据分割完成: 训练集 16562 条 | 测试集 9100 条\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"--- 正在处理训练集 ---\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"--- 正在处理测试集 ---\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"--- 策略性质诊断 (基于训练集) ---\n",
|
|
|
|
|
|
"训练样本数: 16533\n",
|
|
|
|
|
|
"模型预测与未来收益的相关系数: 0.0113\n",
|
|
|
|
|
|
"诊断结果: 呈正相关 -> 这是一个'趋势跟随'策略。\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"模型训练完成。交易阈值已确定:\n",
|
|
|
|
|
|
" - 分位数阈值 (99%/1%): 0.000495 / -0.000209\n",
|
|
|
|
|
|
" - Sigma阈值 (±2σ): 0.000449 / -0.000130\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"==================================================\n",
|
|
|
|
|
|
" 评估结果: 1%/99% 分位数阈值\n",
|
|
|
|
|
|
"==================================================\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"--- 核心分析 (Quantile 阈值) ---\n",
|
|
|
|
|
|
" mean std count\n",
|
|
|
|
|
|
"signal \n",
|
|
|
|
|
|
"-1 -0.000026 0.006193 24\n",
|
|
|
|
|
|
" 0 -0.000230 0.010009 8954\n",
|
|
|
|
|
|
" 1 0.003313 0.011496 93\n"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"text/plain": [
|
|
|
|
|
|
"<Figure size 1000x600 with 1 Axes>"
|
|
|
|
|
|
],
|
|
|
|
|
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA1YAAAIrCAYAAADslwjAAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAAYwhJREFUeJzt3XlcVGXj//83a+CCUmZqgguKihu4gIqCYllabpitLpWlpuinbu+7tOzO21xuTc20bPG2sMwWl9Tcc0nNshAICXHLpRQ1yY1lEoTz+8PfzNcREIajjsbr+XjwKM91rjPXOTNzzbzPuc41LoZhGAIAAAAAlJqrsxsAAAAAALc6ghUAAAAAmESwAgAAAACTCFYAAAAAYBLBCgAAAABMIlgBAAAAgEkEKwAAAAAwiWAFAAAAACYRrAAAAADAJIIVAADATW7Pnj366KOPnN0M4JawbNky/fDDDzf8cQlWAAAAN7G0tDQNGjRImzZt0sWLF53dHOCmt337dg0bNky7d+++oY9LsDIpMzNTU6ZMUVRUlJo0aaLOnTvrzTff1F9//eXspl1TKSkpGjRokFq3bq0mTZrogQce0LZt20q9vZ9++kkDBw5UcHCwgoOD9fDDD2vDhg3XsMXXx48//qgGDRpo9uzZzm5KiRiGofbt26tBgwY6fvy4s5vzt2F9HVztLyoqytnNvOksXbq0wHFq3ry5evXqpblz5yonJ8fZTcQ1dPr0ab322mtq3769mjRpoqioKE2dOvWqn4+zZ89WgwYN9OOPP97AlprTv39/NWjQ4LptPzc3VzExMapWrZreffddubu728rS09P16quvqn379mratKm6du2qDz/8UHl5edetPc4QFRVV4j519OjRatCggY4ePXqdW2UvKiqq2M8Fa5usfeHSpUtvaBvNuN6vc0k6evSoGjRooNGjRztc98rXyKRJk9S6dWsNHz5cGRkZ17KZV+Ve/CooSlZWlvr376/du3ercePG6tKli77//nu99957SkhI0EcffWTXAd6qTp48qaeeekqS1LNnT7m7uys1NVWHDx9Whw4dHN7emjVr9I9//EM+Pj7q2bOncnJytHr1asXExOi9995Tx44dr/EelF2//PKLTp06JUnasmWLHn30USe36O+lRo0a6tGjR6FllSpVusGtuXU0aNBAnTp1Ul5eno4fP65vv/1W06ZN09atWzV//ny5unLO71Z3+vRpPfzwwzp69KiioqJUo0YNfffdd5o3b56OHTumt956y9lNvGW8//77Onz4sFatWqUKFSrYlp88eVKPPfaYjh07ptDQUAUFBWnDhg2aMmWK9uzZo6lTpzqx1WVPv379dO7cOUnS3r17tXnzZltfZ+Xj4+Os5pU5Hh4emjFjhh588EFNmTJFEyZMuCGPe+t/63eiuXPnavfu3YqMjNS7774rNzc35eTkaMCAAfrpp5+0ePFiU19kjx49qs6dO6t3797673//ew1b7pitW7fq3LlzmjJlinr16mVbnp+f7/C2cnNzNX78eHl6emrJkiWqWbOmJKlXr14aMGCApkyZQrC6hrZs2WL3/wSra6tmzZp64YUXrsu2ly5dqjFjxmjy5MmKjo6+Lo/hLEFBQXbH7Y8//lB0dLR++uknbdiwQV26dCn1tmfPnq23335bH3/8scLCwq5Fc1EK7777rn7//Xf985//1LPPPitJslgseuihh7R27VolJSWpefPmBeo98cQT6tatm2rUqHGjm3xT+vPPP/W///1P//rXv1S9enW7smnTpunYsWN65JFHNH78eElSTEyM+vTpo+XLl6tXr15q166dM5pdLOuVhU2bNpVo/djY2OvYmmvj6aeftv3/0qVLtXnz5gJ9HW6sChUqaNy4cXruuef05JNPql69etf9MTktWEqGYeirr76SJD3//PNyc3OTJHl6emrIkCGSLt0493dw8uRJSSrQqZfmrPK+fft0+vRp1atXzxaqJCksLEy1atXSwYMH9eeff5prMGy2bt2qqlWrqlOnTtqxYwdDrXBTqlq1qvr27StJSkhIcHJrcC3s2LFDkuxOlHl7e+vBBx+UdGk4eGFuv/12BQQEyNvb+7q38VawZMkSlStXTo888ojd8qysLK1du1aurq56/vnnbcsrVqyoAQMGSNItNcysOP7+/vL393d2M3AL6tixoxo2bKiFCxfekMcjWJXSsWPHdOLECZUvX16NGjWyK2vRooUkadeuXcrNzXVG864pwzCu2bY8PT0lSb///rsyMzPtyr766ivFxcXJ19f3mj1eWXb69GklJyerRYsWatGihbKzsxUXF+fsZgGFqlKliiT97e5PLausff2VN44PGjRIcXFxti//uLrNmzerW7duBW4r+OWXX5STk6OAgADdfvvtdmXW7yCcpAAu6d69e4mvjppFsCqlw4cPS7p0FcfFxcWurFKlSipXrpzy8vJ07NgxSVe/mfLyG+6sN+41aNBAnTt3lnQpcBR282NprVixQn369FGzZs0UFham559/XgcPHrRbx3oDcYMGDfT2229LkgYMGGBb1r9//1I9dt26dVW7dm2dO3dOTz75pH7++WdbWfny5eXj41PgStjOnTs1aNAgtWvXTiEhIerZs6c+//xzu8BnvakyPj5ePXr0UPPmzTVhwgQdPHhQDz/8sIKDgzV48GDbl7YGDRrYJszo3r27mjRposjISE2fPt30VR3DMPTFF18oOjpazZs3V8uWLTV06FDt3bu3yDrWyRBKe1wLs23bNuXn59uClWQ/NND67wYNGmj69OlFtunKccnp6ekaP368OnbsqCZNmqhTp0564403Cv1CfPnNridPntTEiRN177332p1htcrKytLs2bN1//33q3nz5oqMjNTzzz+vI0eOFLp/a9euVa9evRQSEqIHHnhAy5Yt09tvv60OHToUOI47duzQoEGD1KpVKzVr1kx9+/a9oZOlFHXj9ZU36l4+ucOYMWMkSWPGjClyQoyS9itWl08MkJOTo7lz56pXr16KjIwstN3r1q3T448/rpCQEAUHB2vAgAHauXNnqY5BcVJSUiRJfn5+dst/++03vfjii7YJEO677z598MEHdjfnO9pfXe0m7MLWv/w4Z2Rk6M0331S3bt0KDK29/PgmJCSof//+CgkJUfv27TVu3DhlZ2eX6thYt7tq1SqNGTNGoaGhat68ufr371/kl+fdu3crJiZGYWFhatKkibp3765FixYVu8+7du3SCy+8oA4dOuiTTz4pVXul/zfUa/z48fr0009t/aqnp6d8fHx02223XXVfrzZ5RW5urt5++23de++9atGihZ544glt3bpVo0ePVosWLWyTC5Xm+Vi6dKn69Omjli1bqk2bNurfv79TpmyWLn2W7N69W61bty5QZv0OUtiQyWrVqkmSjh8/bjvujr7mS9ofXz4Jw4EDBzRkyBC1atVKbdq00ahRo3T69GnbupdP7nDs2DEdO3bM7rvN1a6wOTJ5RUk48jl2vRV33KSCE2etXr1aAwYMUFhYmPbs2VNgm468/3/++WcNHjxY7du3V/PmzdWtWzd98MEHV519cunSpXrwwQfVvHlzdenSRR9//HGBdS5cuKC3335bXbp0UZMmTdSxY0f997//1fnz5x05PAVkZmZq4sSJat++vZo1a6ZHH31Uu3btumqd1q1b6/jx4zdkRBT3WJWS9YVx+Y2klytXrpyys7MdnonEx8dHQ4cOlXTpxbNgwYIib378/ffftXjx4mK3aZ1YQ7o0S8r8+fNVtWpV9e7dW6dPn9a6deu0ZcsW/e9//1PLli0lSa1atbK1Y+fOndq5c6d69Ohh68QvH8b35ptvFtuGSpUq6emnn5abm5umT5+uwYMHKzk5WY888ohatGih4cOHq3379gXqbdmyRcOGDVO5cuV0//33y9vbWzt27NBrr72m7OxsuzHNkjRixAh16dJFp0+f1ieffKKvv/5abdu2VV5enrZs2aKVK1fqoYcekiQdOnRII0eOVFhYmEJDQ/XDDz/ogw8+UEpKiv73v/+V+gb60aNHa9myZWrcuLEeffR
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"output_type": "display_data"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"--- 前 10 笔交易明细 ---\n",
|
|
|
|
|
|
" EntryTime Direction EntryPrice ExitTime ExitPrice PNL_Points\n",
|
|
|
|
|
|
"0 2024-01-22 09:15:00 Long 1994.0 2024-01-22 10:45:00 2077.0 83.0\n",
|
|
|
|
|
|
"1 2024-01-22 10:30:00 Long 2019.0 2024-01-22 13:45:00 2071.0 52.0\n",
|
|
|
|
|
|
"2 2024-02-19 09:45:00 Short 1833.0 2024-02-19 11:15:00 1842.0 -9.0\n",
|
|
|
|
|
|
"3 2024-03-05 09:00:00 Long 1883.0 2024-03-05 10:30:00 1890.0 7.0\n",
|
|
|
|
|
|
"4 2024-03-05 09:45:00 Long 1916.0 2024-03-05 11:15:00 1884.0 -32.0\n",
|
|
|
|
|
|
"5 2024-04-17 21:30:00 Long 2010.0 2024-04-17 22:45:00 2026.0 16.0\n",
|
|
|
|
|
|
"6 2024-05-17 09:15:00 Long 2255.0 2024-05-17 10:45:00 2252.0 -3.0\n",
|
|
|
|
|
|
"7 2024-05-17 14:30:00 Long 2281.0 2024-05-17 21:45:00 2311.0 30.0\n",
|
|
|
|
|
|
"8 2024-06-28 21:30:00 Long 2125.0 2024-06-28 22:45:00 2125.0 0.0\n",
|
|
|
|
|
|
"9 2024-07-29 10:00:00 Short 1783.0 2024-07-29 13:30:00 1778.0 5.0\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"==================================================\n",
|
|
|
|
|
|
" 评估结果: ±2 Sigma 阈值\n",
|
|
|
|
|
|
"==================================================\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"--- 核心分析 (Sigma 阈值) ---\n",
|
|
|
|
|
|
" mean std count\n",
|
|
|
|
|
|
"signal \n",
|
|
|
|
|
|
"-1 -0.000699 0.007173 238\n",
|
|
|
|
|
|
" 0 -0.000230 0.010019 8646\n",
|
|
|
|
|
|
" 1 0.002156 0.012699 187\n"
|
2025-09-16 09:59:38 +08:00
|
|
|
|
]
|
2025-09-24 23:14:14 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"text/plain": [
|
|
|
|
|
|
"<Figure size 1000x600 with 1 Axes>"
|
|
|
|
|
|
],
|
|
|
|
|
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA10AAAIrCAYAAAAUd/M3AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAAYHtJREFUeJzt3XlcVGXj//83sgioKKam5r6AGwJupAgmmqWllmW2uFTmjn41K63sbrO6yyXTtMUsKrLSXDCXNAuxLAuB0BvQNJdyIxEXllEQzu8PfzMfJ1aR42i8no8Hj/Kcc11znTMz18z7nHNd42QYhiEAAAAAgCkqOboBAAAAAPBvRugCAAAAABMRugAAAADARIQuAAAAADARoQsAAAAATEToAgAAAAATEboAAAAAwESELgAAAAAwEaELAAAAAExE6AIAAIB2796tjz76yNHNAIp19uxZzZo1S7m5uY5uymUhdAEAAFRwR48e1ciRI/X999/rwoULjm4OUKQjR47o888/1/Tp0x3dlMtC6LoOZGZm6vXXX1dYWJjatWunXr166c0339S5c+cc3bRylZSUpJEjR6pz585q166d7rjjDv3www9lru/XX3/ViBEjFBAQoICAAN13333avHlzObbYHL/88ot8fX21YMECRzelVAzDUPfu3eXr66tjx445ujn/GtbXQXF/YWFhjm7mNWflypUFjpO/v7/uuusuLV68WDk5OY5uIspRenq6nn/+eXXv3l3t2rVTWFiY3njjjWI/HxcsWCBfX1/98ssvV7GlV2bYsGHy9fU1rf7c3FyFh4erbt26euedd+Ti4mJbd/jwYT3xxBMKCgpSu3btdPvtt+u9995Tfn5+kfVNnz5dvr6+Onz4sGltvpoK61f++XdpAAgLC7uu+uer9b2jrK8L6/FfuXKlJKl169ZauHChNm3apIiICBNaag6XkjeBI2VlZWnYsGFKTk5W27Zt1adPH/3000969913FR8fr48++siuc7xepaam6pFHHpEkDRw4UC4uLkpJSdHBgwcVEhJy2fVt2LBBjz/+uLy8vDRw4EDl5ORo/fr1Cg8P17vvvqtbbrmlnPeg4vrf//6nEydOSJJiYmJ0//33O7hF/y7169fXgAEDCl1XvXr1q9ya64evr6969uypvLw8HTt2TFu2bNHs2bO1detWffzxx6pUiXOO17v09HTdd999Onz4sMLCwlS/fn39+OOPWrJkiY4cOaK33nrL0U28brz33ns6ePCg1q1bp6pVq9qWHzhwQPfff78yMzN1++23y8vLS99//73mzp2rM2fO6KmnnnJgq6+eVq1aaezYsbZ/f/bZZ8rIyNDQoUNtx6tt27aOal6F1LVrVz3xxBOaM2eOwsLC1KhRI0c3qWQGrmlvvvmm4ePjY4waNcq4cOGCYRiGcf78eWPIkCGGj4+P8fnnn19R/X/99Zfh4+NjTJs2rTyaW2bLli0zfHx8jFWrVtktz8vLu+y6cnJyjJtvvtlo37698ddff9mWb9++3fDx8TFuv/32K22uqaztnD9/vqObUioLFiwwfHx8DB8fH2Ps2LGObs6/hvV1MHToUNMeY8WKFYaPj4+xYsUK0x7jarPu0z/7tNTUVCM4ONjw8fExNm7ceEWPMX/+fMPHx8fYvn37FdWDKzNz5kzDx8fHeP/9923LsrOzjX79+hk+Pj7Gb7/9Vmi5kydPGvv27TOys7OvVlOv2NChQw0fHx9T6k5LSzP8/f2NyMjIAuvGjRtn+Pj4GOvWrbMtO3nypBEUFGS0bdvWOHr0aKF1pqamGvv27TNycnJMabOj9ezZ0/Dx8bH7jvHP9T179rzKrSq7q/W9Y9q0acUet6IU9VmVl5dnDB482JgyZUp5NtM0nOq7hhmGoVWrVkmSJk+eLGdnZ0mSm5ubxowZI0lavXq1o5pXrlJTUyVJ9erVs1telrPRv//+u9LT09WiRQs1aNDAtjwoKEiNGzfW/v37dfLkyStrMGy2bt2qOnXqqGfPntq+fTu3b+GaVKdOHQ0ePFiSFB8f7+DWoDxs375dkuzuXPDw8NCdd94p6eIt5oWpWbOmmjdvLg8PD9PbeD1YsWKFPD09NWTIkALrCjvGNWvWVO/evZWbm6uEhIRC66xTp46aN28uV1dXU9oMSBe/I44ZM0YbN2603XFzLSN0XcOOHDmi48ePq0qVKmrdurXdug4dOkiSdu7ced3N3lIYwzDKrS43NzdJ0l9//aXMzEy7datWrVJsbKy8vb3L7fEqsvT0dO3atUsdOnRQhw4dlJ2drdjYWEc3CyhUrVq1JOlfNx62orL29cnJyXbLR44cqdjYWA0fPtwRzbruREdHq1+/foUOVSjqGD/33HOKjY1V7969r0obgaKEhoaqatWq2rJli6ObUiJC1zXs4MGDki5e/XFycrJbV716dXl6eiovL09HjhyRVPwAxUsHdR4+fNg28LNXr16SLoaRSweEXung1zVr1uiee+5R+/btFRQUpMmTJ2v//v1221gHM/v6+urtt9+WJA0fPty2bNiwYWV67GbNmqlJkyY6c+aMHn74Yf3222+2dVWqVJGXl1eBK2g7duzQyJEj1a1bNwUGBmrgwIH64osv7MKgdSBzXFycBgwYIH9/f82cOVP79+/Xfffdp4CAAI0ePdr2hc7X19c2eUf//v3Vrl079ejRQ3PmzLniq0GGYejLL7/UoEGD5O/vr44dO2rs2LHas2dPkWWsA2XLelwL88MPPyg/P98WuqSL47ouFRMTI19fX82ZM6fINs2cOdNueVpaml566SXdcsstateunXr27KlZs2YV+mX50gHmqampeuWVV3Trrbdq8uTJBbbNysrSggULdPvtt8vf3189evTQ5MmTdejQoUL375tvvtFdd92lwMBA3XHHHVq9erXefvtthYSEFDiO27dv18iRI9WpUye1b99egwcPvqoTtxQ1cNv6frcO8r50QPjTTz8tSXr66aeLnJyjtP2K1aWTFOTk5Gjx4sW666671KNHj0LbvXHjRj344IMKDAxUQECAhg8frh07dpTpGJQkKSlJktSwYUO75X/++aeeeuop22QMt912m95//33l5eUV2K/S9lfFTXxQ2PaXHueMjAy9+eab6tevX4Exkpce3/j4eA0bNkyBgYHq3r27XnjhBWVnZ5fp2FjrXbdunZ5++ml16dJF/v7+GjZsWJFXBpOTkxUeHm6bYKF///5avnx5ifu8c+dOTZkyRSEhIfr000/L1F5JttfeSy+9pM8++8zWr7q5ucnLy0uVK1cudl+Lm0gjNzdXb7/9tm699VZ16NBBDz30kLZu3arp06erQ4cOtgkHyvJ8rFy5Uvfcc486duyom2++WcOGDdPPP/9c5uNwJQzDUHJysjp37lzoeusxnjx5statW2ebPKNy5cry8vKyhbJ/Ks2ECdnZ2XrllVd0yy23qHPnzho9erS2bdumCRMmyN/fXytXrrQd36+//lqjRo2Sv7+/hg8frtTUVD3++OMKCAjQgAED9Mcff9jqzc3NVUREhPr37297LkaPHl0gOF4NhmFoyZIluvXWW+Xv76877rhD69evL7Dd5XyO5ebm6v3339edd94pPz8/BQUFaerUqbbvgpc6d+6cFi1apH79+ikgIEDBwcEaO3asUlJSimyz9dgGBQXZnpe//vqrwHaJiYkaPXq0OnbsqICAAA0dOvSKJj+z2rZtm+6//361b99ewcHBmjNnTrGzabq6uiogIMDWv1/Lrv8ZGP7Fzp49K0l2g1ov5enpqezsbGVkZFxWvV5eXrYBoZmZmYqMjLQNOr90G+ni1aKvvvqqxDqtk3xI0quvvqqPP/5YderU0d1336309HRt3LhRMTEx+uCDD9SxY0dJUqdOnWzt2LFjh3bs2KEBAwaofv36kmR3a+Cbb75ZYhuqV6+uRx99VM7OzpozZ45Gjx6tXbt2aciQIerQoYMmTJig7t27FygXExOj8ePHy9PTU7fffrs8PDy0fft2Pf/888rOztajjz5qt/3EiRPVp08fpaen69NPP9XXX3+trl27Ki8vTzE
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"output_type": "display_data"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"--- 前 10 笔交易明细 ---\n",
|
|
|
|
|
|
" EntryTime Direction EntryPrice ExitTime ExitPrice PNL_Points\n",
|
|
|
|
|
|
"0 2024-01-04 22:15:00 Short 1960.0 2024-01-05 09:30:00 1946.0 14.0\n",
|
|
|
|
|
|
"1 2024-01-10 09:30:00 Short 1865.0 2024-01-10 11:00:00 1873.0 -8.0\n",
|
|
|
|
|
|
"2 2024-01-22 09:15:00 Long 1994.0 2024-01-22 10:45:00 2077.0 83.0\n",
|
|
|
|
|
|
"3 2024-01-23 09:30:00 Long 2143.0 2024-01-23 11:00:00 2123.0 -20.0\n",
|
|
|
|
|
|
"4 2024-01-30 21:00:00 Short 1936.0 2024-01-30 22:15:00 1946.0 -10.0\n",
|
|
|
|
|
|
"5 2024-02-19 09:15:00 Short 1867.0 2024-02-19 10:45:00 1845.0 22.0\n",
|
|
|
|
|
|
"6 2024-02-20 11:00:00 Short 1814.0 2024-02-20 14:15:00 1846.0 -32.0\n",
|
|
|
|
|
|
"7 2024-02-26 09:15:00 Short 1793.0 2024-02-26 10:45:00 1792.0 1.0\n",
|
|
|
|
|
|
"8 2024-02-26 21:00:00 Short 1767.0 2024-02-26 22:15:00 1772.0 -5.0\n",
|
|
|
|
|
|
"9 2024-02-26 22:00:00 Short 1770.0 2024-02-27 09:15:00 1777.0 -7.0\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"==================================================\n",
|
|
|
|
|
|
" PNL 曲线对比\n",
|
|
|
|
|
|
"==================================================\n"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"text/plain": [
|
|
|
|
|
|
"<Figure size 1500x700 with 1 Axes>"
|
|
|
|
|
|
],
|
|
|
|
|
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAABOUAAAJPCAYAAAAzCGA1AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Xd4U2X7B/BvdpPuBaUUKHvvqWyQvUEElKmAC1EcL44fvsoQUUFfAcWBICIiW0S2LNlDluwNpXTRvTLP74/Q08buNslJ2u/nurhITp5znjt5kvTkPs+QCYIggIiIiIiIiIiIiJxGLnUARERERERERERE5Q2TckRERERERERERE7GpBwREREREREREZGTMSlHRERERERERETkZEzKERERERERERERORmTckRERERERERERE7GpBwREREREREREZGTMSlHRERERERERETkZEzKERERERERERERORmTckRERERUrly+fBnLli2TOgwqAwRBwP/+9z9ERUVJHQoREbkhJuWIiIiIqNyIjIzEc889hz179sBkMkkdDrm5tLQ07Nq1C88++yxSUlKkDoeIiNwMk3JERETF1K1bN9StW1f8V69ePbRr1w5Tp07F1atXbcpmlVm7dq3N9oiICPGxLGPGjEHdunVx7Ngxh8SdkpKCOXPmoFOnTmjcuDEGDBiANWvWOKQuV3fz5k1MnToVbdq0QbNmzTBmzBicOHFC6rAks2HDBtStWxcbNmyQOhSHMhqNmDJlCkJCQvD1119DqVSKj8XFxWHGjBno0KEDGjdujD59+mDp0qUwm80SRmx/R44cwciRI9G8eXM0adIEQ4cOxcWLF0t8vF27duGpp55CkyZN0LJlS4wbNw4nT560Y8SOYa/3vJeXF5YuXYrMzExMnz7dTtEREVF5waQcERFRCY0ePRovvPACxowZg9q1a2PHjh0YMWIEzp07l6vsypUrJYgwW3JyMkaOHIkVK1agZs2aGDFiBARBwIwZM/DZZ59JGpuzXbhwAcOGDcOePXvQsWNHDBw4EDdv3sT48eOxd+9eqcMjB/rmm29w+/ZtLFq0CF5eXuL26OhoPPXUU1izZg2qV6+Op59+GgaDAZ988kmZSrRcvnwZkyZNQlRUFIYPH44RI0ZApVIhMjKyRMdbtmwZpkyZgtjYWAwfPhydOnXCiRMnMH78eFy6dMnO0buuihUr4ssvv8S+ffuwefNmqcMhIiI3oiy8CBEREeVlwoQJCAsLE+///vvvePPNN/Hhhx9i/fr1NmUvX76MY8eOoW3bts4OEwAwd+5cXL9+HZMmTcKbb74JAMjMzMTTTz+N77//Hv3790e9evUkic2ZjEYjXn/9dWRkZOCbb75B586dAQCTJk3CgAEDMGPGDOzZswdqtVriSJ2rR48eaNq0KSpUqCB1KA7z8OFDfP/993jrrbdQqVIlm8c+++wz3L9/HyNGjMDMmTMBAFOmTMGwYcPw+++/Y9CgQejYsWOJ6z527BjGjh2LKVOm4JVXXinV8yiNbdu2wWg04tNPP0Xr1q3F7RaLpdjHiouLw4IFCxAcHIzffvsNPj4+AKw90N555x3Mnz8f33//vd1id3WNGjXCuHHjsGDBAvTp0wcqlUrqkIiIyA2wpxwREZGdDBgwACEhIfjnn38QHR2d6/EVK1ZIEJX1x/OmTZvg5+dnkxDw8PDAqFGjIAgCtm7dKklszrZ7927cvn0bnTp1EhNyAFClShU88cQTiI2NxfHjxyWMUBre3t6oWbMmvL29pQ7FYdavXw+dTocRI0bYbE9LS8P27dshl8vx2muvidu9vb0xduxYAMCmTZucGKnjxMTEAECupKRcXvyfBH///TcMBgNatGghJuQAYMiQIVCpVDh16hQEQShdwG5m8uTJiIuLw59//il1KERE5CaYlCMiIrKjrB+7/x4OVqtWLezduxcRERFOj2nfvn2wWCxo164dNBqNzWPVq1cHAFy7ds3pcUkha3hqzoRclho1agAArl+/7tSYyDn27t2Lvn372swjBwD//PMPDAYDatasiYCAAJvHWrRoAcCagCoL7Jkky+pNevXqVZt592QyGY4ePYr9+/dDJpPZrT534O/vjw4dOjApR0RERcakHBERkR0ZDAYA1l5oOY0bNw5msxk///yz02O6fPkyAKBmzZq5Hqtbty4++eQTsUdQ1gIUb7/9dq6y+U2MXrduXYwZMwYAcO7cOUybNg0dO3bETz/9JJa5ceMG6tati9dffz3XcbPqfPHFF222p6amYsGCBejZsycaNWqEDh064P3330dCQkIxX4FsBb0Wffr0wSeffILHH3/cZvvmzZsxbNgwNGnSBG3btsVrr72Gmzdv5or/7bffxuLFi9G2bVt06tQJhw8fxpo1a9ChQwe0bdtWbPus1/HHH3/E3Llz0b59ezRu3BhPPvlkvnPaXb58GVOnTkXHjh3RrFkz9O3bF9988434fsspa8EQwDpX2pw5c9CjRw+bXmD/VpRJ7zMzM/HVV1+hb9++aNasGdq3b48XXngh37nDDhw4gNGjR6NZs2Zo2bIlJk+enGu+xZyvXUpKCv773/+iQ4cOaN68OZ555hm7zUsmCAIuXrxoM2Qzy+3btwEAoaGhuR4LCQkBADx48EB8rXO+vv+W87Nw7NgxcTGXrM/XokWLbBaJKQ2LxYIVK1ZgwIABaNy4Mdq3b4/33nsvVy/dt99+W6xv48aNAIDu3buL2/L6rBdF8+bN4efnh1u3bmHy5Mm4ceOG+JiXl5dN77ksf/75J5555hm0bdsWrVu3xvDhw7F9+3abMt26dUO3bt2we/du9OzZEy1atMD333+Pv//+G/3790fz5s0xffp0CIIgvn+mTZuGX3/9Fb169UKjRo3Qo0cPfP/99yUalpuT0WjEt99+i/79+6Nx48Zo27Yt3njjDdy/fz/ffVq3bo0LFy6Uql4iIio/OKccERGRnSQnJ+P69evw8PAQe11lGTRoEBYsWIB169Y5fU6pqKgoAEBgYGCux7y9vTFo0CC71LNhwwbMmDEDXl5eqFatmk19NWvWRMOGDbF3715kZGRAq9WKj2UNnR08eLC4LSUlBU8//TSuXbuGjh07onv37rh69Sp+/fVXnDp1CuvWrbM5RlFlvRZBQUG5HqtevbrYczDLRx99hB9//BEVKlTAkCFDEB8fjx07dmD//v34/vvv0bJlS7Hsjh07ULlyZXTr1g0bNmzAtGnTIJPJ0LdvX2zcuBFz587Fk08+KZb/6quvYDQa0bt3b5hMJuzatQsvvvgiPv74Y5vX4uLFi3jmmWdgsVjQu3dv+Pv74+zZs1iwYAFiYmIwY8aMPJ/rxYsX8dxzzyE1NRX16tVD5cqVi/165fTmm29i165daNq0KUaNGoWUlBRs374dY8eOxaZNm2yOv3z5csydOxe+vr7o168f9Ho9du7cicOHD2P+/Pno1auXzbHT09MxevRoJCcno0+fPrh27RqOHDmCSZMmYceOHfD09CxV7AkJCcjMzETFihVzPZacnAwANgs/ZMmqVxAEpKam5upJV5DQ0FC88MILAKw9Zzdv3oxWrVqhVatWucpevHgRO3bsKPSY7dq1w2OPPQaLxYJXX30VO3fuRLVq1TB8+HDcuXMH69atw759+7By5UrxvdytWzfxee/duxdXrlzB6NGjxefbsGFD8XVYunRpoTGEhYVh+PDh8PX1xSeffILXXnsNBw8eRL9+/dCxY0dMmTIFTZs2zbXfmjVrMGPGDFSoUAH9+vWDTCbD/v378eqrr+LLL7+0eU/ExcXhv//9L3r16oWNGzfi888/h5eXF7p37w6j0YhNmzZh8ODBqFKlCgDg8OHD2LZtG7p374527drhr7/+wqeffoqbN2/io48+KvQ55cVoNGLSpEk4cuQIWrdujY4dOyIiIgLbtm3DsWPHsHHjRgQHB+far2LFinjw4EGJ6iQiovKHSTkiIqJSSktLw9WrVzF//nzo9XqMHz8+1zBRjUaDkSNH4uuvv8amTZvQqVMnp8WXnp4uxuAod+7cwQcffID//Oc/GDVqVJ4
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"output_type": "display_data"
|
2025-09-16 09:59:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-09-24 23:14:14 +08:00
|
|
|
|
"execution_count": 5
|
2025-09-16 09:59:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"kernelspec": {
|
|
|
|
|
|
"display_name": "Python 3",
|
|
|
|
|
|
"language": "python",
|
|
|
|
|
|
"name": "python3"
|
|
|
|
|
|
},
|
|
|
|
|
|
"language_info": {
|
|
|
|
|
|
"codemirror_mode": {
|
|
|
|
|
|
"name": "ipython",
|
|
|
|
|
|
"version": 2
|
|
|
|
|
|
},
|
|
|
|
|
|
"file_extension": ".py",
|
|
|
|
|
|
"mimetype": "text/x-python",
|
|
|
|
|
|
"name": "python",
|
|
|
|
|
|
"nbconvert_exporter": "python",
|
|
|
|
|
|
"pygments_lexer": "ipython2",
|
|
|
|
|
|
"version": "2.7.6"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"nbformat": 4,
|
|
|
|
|
|
"nbformat_minor": 5
|
|
|
|
|
|
}
|