2553 lines
285 KiB
Plaintext
2553 lines
285 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 1,
|
||
"id": "79a7758178bafdd3",
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-04-03T12:46:06.987506Z",
|
||
"start_time": "2025-04-03T12:46:06.259551Z"
|
||
},
|
||
"jupyter": {
|
||
"source_hidden": true
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"e:\\PyProject\\NewStock\\main\\train\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"%load_ext autoreload\n",
|
||
"%autoreload 2\n",
|
||
"\n",
|
||
"import gc\n",
|
||
"import os\n",
|
||
"import sys\n",
|
||
"sys.path.append('../../')\n",
|
||
"print(os.getcwd())\n",
|
||
"import pandas as pd\n",
|
||
"from main.factor.factor import get_rolling_factor, get_simple_factor\n",
|
||
"from main.utils.factor import read_industry_data\n",
|
||
"from main.utils.factor_processor import calculate_score\n",
|
||
"from main.utils.utils import read_and_merge_h5_data, merge_with_industry_data\n",
|
||
"\n",
|
||
"import warnings\n",
|
||
"\n",
|
||
"warnings.filterwarnings(\"ignore\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 2,
|
||
"id": "a79cafb06a7e0e43",
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-04-03T12:47:00.212859Z",
|
||
"start_time": "2025-04-03T12:46:06.998047Z"
|
||
},
|
||
"scrolled": true
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"daily data\n"
|
||
]
|
||
},
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"daily basic\n",
|
||
"inner merge on ['ts_code', 'trade_date']\n",
|
||
"stk limit\n",
|
||
"left merge on ['ts_code', 'trade_date']\n",
|
||
"money flow\n",
|
||
"left merge on ['ts_code', 'trade_date']\n",
|
||
"cyq perf\n",
|
||
"left merge on ['ts_code', 'trade_date']\n",
|
||
"<class 'pandas.core.frame.DataFrame'>\n",
|
||
"RangeIndex: 8665405 entries, 0 to 8665404\n",
|
||
"Data columns (total 32 columns):\n",
|
||
" # Column Dtype \n",
|
||
"--- ------ ----- \n",
|
||
" 0 ts_code object \n",
|
||
" 1 trade_date datetime64[ns]\n",
|
||
" 2 open float64 \n",
|
||
" 3 close float64 \n",
|
||
" 4 high float64 \n",
|
||
" 5 low float64 \n",
|
||
" 6 vol float64 \n",
|
||
" 7 pct_chg float64 \n",
|
||
" 8 turnover_rate float64 \n",
|
||
" 9 pe_ttm float64 \n",
|
||
" 10 circ_mv float64 \n",
|
||
" 11 total_mv float64 \n",
|
||
" 12 volume_ratio float64 \n",
|
||
" 13 is_st bool \n",
|
||
" 14 up_limit float64 \n",
|
||
" 15 down_limit float64 \n",
|
||
" 16 buy_sm_vol float64 \n",
|
||
" 17 sell_sm_vol float64 \n",
|
||
" 18 buy_lg_vol float64 \n",
|
||
" 19 sell_lg_vol float64 \n",
|
||
" 20 buy_elg_vol float64 \n",
|
||
" 21 sell_elg_vol float64 \n",
|
||
" 22 net_mf_vol float64 \n",
|
||
" 23 his_low float64 \n",
|
||
" 24 his_high float64 \n",
|
||
" 25 cost_5pct float64 \n",
|
||
" 26 cost_15pct float64 \n",
|
||
" 27 cost_50pct float64 \n",
|
||
" 28 cost_85pct float64 \n",
|
||
" 29 cost_95pct float64 \n",
|
||
" 30 weight_avg float64 \n",
|
||
" 31 winner_rate float64 \n",
|
||
"dtypes: bool(1), datetime64[ns](1), float64(29), object(1)\n",
|
||
"memory usage: 2.0+ GB\n",
|
||
"None\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"from main.utils.utils import read_and_merge_h5_data\n",
|
||
"\n",
|
||
"print('daily data')\n",
|
||
"df = read_and_merge_h5_data('../../data/daily_data.h5', key='daily_data',\n",
|
||
" columns=['ts_code', 'trade_date', 'open', 'close', 'high', 'low', 'vol', 'pct_chg'],\n",
|
||
" df=None)\n",
|
||
"\n",
|
||
"print('daily basic')\n",
|
||
"df = read_and_merge_h5_data('../../data/daily_basic.h5', key='daily_basic',\n",
|
||
" columns=['ts_code', 'trade_date', 'turnover_rate', 'pe_ttm', 'circ_mv', 'total_mv', 'volume_ratio',\n",
|
||
" 'is_st'], df=df, join='inner')\n",
|
||
"\n",
|
||
"print('stk limit')\n",
|
||
"df = read_and_merge_h5_data('../../data/stk_limit.h5', key='stk_limit',\n",
|
||
" columns=['ts_code', 'trade_date', 'pre_close', 'up_limit', 'down_limit'],\n",
|
||
" df=df)\n",
|
||
"print('money flow')\n",
|
||
"df = read_and_merge_h5_data('../../data/money_flow.h5', key='money_flow',\n",
|
||
" columns=['ts_code', 'trade_date', 'buy_sm_vol', 'sell_sm_vol', 'buy_lg_vol', 'sell_lg_vol',\n",
|
||
" 'buy_elg_vol', 'sell_elg_vol', 'net_mf_vol'],\n",
|
||
" df=df)\n",
|
||
"print('cyq perf')\n",
|
||
"df = read_and_merge_h5_data('../../data/cyq_perf.h5', key='cyq_perf',\n",
|
||
" columns=['ts_code', 'trade_date', 'his_low', 'his_high', 'cost_5pct', 'cost_15pct',\n",
|
||
" 'cost_50pct',\n",
|
||
" 'cost_85pct', 'cost_95pct', 'weight_avg', 'winner_rate'],\n",
|
||
" df=df)\n",
|
||
"print(df.info())"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 3,
|
||
"id": "cac01788dac10678",
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-04-03T12:47:10.527104Z",
|
||
"start_time": "2025-04-03T12:47:00.488715Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"industry\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"print('industry')\n",
|
||
"industry_df = read_and_merge_h5_data('../../data/industry_data.h5', key='industry_data',\n",
|
||
" columns=['ts_code', 'l2_code', 'in_date'],\n",
|
||
" df=None, on=['ts_code'], join='left')\n",
|
||
"\n",
|
||
"\n",
|
||
"def merge_with_industry_data(df, industry_df):\n",
|
||
" # 确保日期字段是 datetime 类型\n",
|
||
" df['trade_date'] = pd.to_datetime(df['trade_date'])\n",
|
||
" industry_df['in_date'] = pd.to_datetime(industry_df['in_date'])\n",
|
||
"\n",
|
||
" # 对 industry_df 按 ts_code 和 in_date 排序\n",
|
||
" industry_df_sorted = industry_df.sort_values(['in_date', 'ts_code'])\n",
|
||
"\n",
|
||
" # 对原始 df 按 ts_code 和 trade_date 排序\n",
|
||
" df_sorted = df.sort_values(['trade_date', 'ts_code'])\n",
|
||
"\n",
|
||
" # 使用 merge_asof 进行向后合并\n",
|
||
" merged = pd.merge_asof(\n",
|
||
" df_sorted,\n",
|
||
" industry_df_sorted,\n",
|
||
" by='ts_code', # 按 ts_code 分组\n",
|
||
" left_on='trade_date',\n",
|
||
" right_on='in_date',\n",
|
||
" direction='backward'\n",
|
||
" )\n",
|
||
"\n",
|
||
" # 获取每个 ts_code 的最早 in_date 记录\n",
|
||
" min_in_date_per_ts = (industry_df_sorted\n",
|
||
" .groupby('ts_code')\n",
|
||
" .first()\n",
|
||
" .reset_index()[['ts_code', 'l2_code']])\n",
|
||
"\n",
|
||
" # 填充未匹配到的记录(trade_date 早于所有 in_date 的情况)\n",
|
||
" merged['l2_code'] = merged['l2_code'].fillna(\n",
|
||
" merged['ts_code'].map(min_in_date_per_ts.set_index('ts_code')['l2_code'])\n",
|
||
" )\n",
|
||
"\n",
|
||
" # 保留需要的列并重置索引\n",
|
||
" result = merged.reset_index(drop=True)\n",
|
||
" return result\n",
|
||
"\n",
|
||
"\n",
|
||
"# 使用示例\n",
|
||
"df = merge_with_industry_data(df, industry_df)\n",
|
||
"# print(mdf[mdf['ts_code'] == '600751.SH'][['ts_code', 'trade_date', 'l2_code']])"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 4,
|
||
"id": "c4e9e1d31da6dba6",
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-04-03T12:47:10.719252Z",
|
||
"start_time": "2025-04-03T12:47:10.541247Z"
|
||
},
|
||
"jupyter": {
|
||
"source_hidden": true
|
||
}
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"from main.factor.factor import *\n",
|
||
"\n",
|
||
"def calculate_indicators(df):\n",
|
||
" \"\"\"\n",
|
||
" 计算四个指标:当日涨跌幅、5日移动平均、RSI、MACD。\n",
|
||
" \"\"\"\n",
|
||
" df = df.sort_values('trade_date')\n",
|
||
" df['daily_return'] = (df['close'] - df['pre_close']) / df['pre_close'] * 100\n",
|
||
" # df['5_day_ma'] = df['close'].rolling(window=5).mean()\n",
|
||
" delta = df['close'].diff()\n",
|
||
" gain = delta.where(delta > 0, 0)\n",
|
||
" loss = -delta.where(delta < 0, 0)\n",
|
||
" avg_gain = gain.rolling(window=14).mean()\n",
|
||
" avg_loss = loss.rolling(window=14).mean()\n",
|
||
" rs = avg_gain / avg_loss\n",
|
||
" df['RSI'] = 100 - (100 / (1 + rs))\n",
|
||
"\n",
|
||
" # 计算MACD\n",
|
||
" ema12 = df['close'].ewm(span=12, adjust=False).mean()\n",
|
||
" ema26 = df['close'].ewm(span=26, adjust=False).mean()\n",
|
||
" df['MACD'] = ema12 - ema26\n",
|
||
" df['Signal_line'] = df['MACD'].ewm(span=9, adjust=False).mean()\n",
|
||
" df['MACD_hist'] = df['MACD'] - df['Signal_line']\n",
|
||
"\n",
|
||
" # 4. 情绪因子1:市场上涨比例(Up Ratio)\n",
|
||
" df['up_ratio'] = df['daily_return'].apply(lambda x: 1 if x > 0 else 0)\n",
|
||
" df['up_ratio_20d'] = df['up_ratio'].rolling(window=20).mean() # 过去20天上涨比例\n",
|
||
"\n",
|
||
" # 5. 情绪因子2:成交量变化率(Volume Change Rate)\n",
|
||
" df['volume_mean'] = df['vol'].rolling(window=20).mean() # 过去20天的平均成交量\n",
|
||
" df['volume_change_rate'] = (df['vol'] - df['volume_mean']) / df['volume_mean'] * 100 # 成交量变化率\n",
|
||
"\n",
|
||
" # 6. 情绪因子3:波动率(Volatility)\n",
|
||
" df['volatility'] = df['daily_return'].rolling(window=20).std() # 过去20天的日收益率标准差\n",
|
||
"\n",
|
||
" # 7. 情绪因子4:成交额变化率(Amount Change Rate)\n",
|
||
" df['amount_mean'] = df['amount'].rolling(window=20).mean() # 过去20天的平均成交额\n",
|
||
" df['amount_change_rate'] = (df['amount'] - df['amount_mean']) / df['amount_mean'] * 100 # 成交额变化率\n",
|
||
"\n",
|
||
" # df = sentiment_panic_greed_index(df)\n",
|
||
" # df = sentiment_market_breadth_proxy(df)\n",
|
||
" # df = sentiment_reversal_indicator(df)\n",
|
||
"\n",
|
||
" return df\n",
|
||
"\n",
|
||
"\n",
|
||
"def generate_index_indicators(h5_filename):\n",
|
||
" df = pd.read_hdf(h5_filename, key='index_data')\n",
|
||
" df['trade_date'] = pd.to_datetime(df['trade_date'], format='%Y%m%d')\n",
|
||
" df = df.sort_values('trade_date')\n",
|
||
"\n",
|
||
" # 计算每个ts_code的相关指标\n",
|
||
" df_indicators = []\n",
|
||
" for ts_code in df['ts_code'].unique():\n",
|
||
" df_index = df[df['ts_code'] == ts_code].copy()\n",
|
||
" df_index = calculate_indicators(df_index)\n",
|
||
" df_indicators.append(df_index)\n",
|
||
"\n",
|
||
" # 合并所有指数的结果\n",
|
||
" df_all_indicators = pd.concat(df_indicators, ignore_index=True)\n",
|
||
"\n",
|
||
" # 保留trade_date列,并将同一天的数据按ts_code合并成一行\n",
|
||
" df_final = df_all_indicators.pivot_table(\n",
|
||
" index='trade_date',\n",
|
||
" columns='ts_code',\n",
|
||
" values=['daily_return', \n",
|
||
" 'RSI', 'MACD', 'Signal_line', 'MACD_hist', \n",
|
||
" # 'sentiment_panic_greed_index',\n",
|
||
" 'up_ratio_20d', 'volume_change_rate', 'volatility',\n",
|
||
" 'amount_change_rate', 'amount_mean'],\n",
|
||
" aggfunc='last'\n",
|
||
" )\n",
|
||
"\n",
|
||
" df_final.columns = [f\"{col[1]}_{col[0]}\" for col in df_final.columns]\n",
|
||
" df_final = df_final.reset_index()\n",
|
||
"\n",
|
||
" return df_final\n",
|
||
"\n",
|
||
"\n",
|
||
"# 使用函数\n",
|
||
"h5_filename = '../../data/index_data.h5'\n",
|
||
"index_data = generate_index_indicators(h5_filename)\n",
|
||
"index_data = index_data.dropna()\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 5,
|
||
"id": "a735bc02ceb4d872",
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-04-03T12:47:10.821169Z",
|
||
"start_time": "2025-04-03T12:47:10.751831Z"
|
||
}
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"import talib\n",
|
||
"import numpy as np"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 6,
|
||
"id": "53f86ddc0677a6d7",
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-04-03T12:47:15.944254Z",
|
||
"start_time": "2025-04-03T12:47:10.826179Z"
|
||
},
|
||
"jupyter": {
|
||
"source_hidden": true
|
||
},
|
||
"scrolled": true
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"from main.utils.factor import get_act_factor\n",
|
||
"\n",
|
||
"\n",
|
||
"def read_industry_data(h5_filename):\n",
|
||
" # 读取 H5 文件中所有的行业数据\n",
|
||
" industry_data = pd.read_hdf(h5_filename, key='sw_daily', columns=[\n",
|
||
" 'ts_code', 'trade_date', 'open', 'close', 'high', 'low', 'pe', 'pb', 'vol'\n",
|
||
" ]) # 假设 H5 文件的键是 'industry_data'\n",
|
||
" industry_data = industry_data.sort_values(by=['ts_code', 'trade_date'])\n",
|
||
" industry_data = industry_data.reindex()\n",
|
||
" industry_data['trade_date'] = pd.to_datetime(industry_data['trade_date'], format='%Y%m%d')\n",
|
||
"\n",
|
||
" grouped = industry_data.groupby('ts_code', group_keys=False)\n",
|
||
" industry_data['obv'] = grouped.apply(\n",
|
||
" lambda x: pd.Series(talib.OBV(x['close'].values, x['vol'].values), index=x.index)\n",
|
||
" )\n",
|
||
" industry_data['return_5'] = grouped['close'].apply(lambda x: x / x.shift(5) - 1)\n",
|
||
" industry_data['return_20'] = grouped['close'].apply(lambda x: x / x.shift(20) - 1)\n",
|
||
"\n",
|
||
" industry_data = get_act_factor(industry_data, cat=False)\n",
|
||
" industry_data = industry_data.sort_values(by=['trade_date', 'ts_code'])\n",
|
||
"\n",
|
||
" # # 计算每天每个 ts_code 的因子和当天所有 ts_code 的中位数的偏差\n",
|
||
" # factor_columns = ['obv', 'return_5', 'return_20', 'act_factor1', 'act_factor2', 'act_factor3', 'act_factor4'] # 因子列\n",
|
||
" # \n",
|
||
" # for factor in factor_columns:\n",
|
||
" # if factor in industry_data.columns:\n",
|
||
" # # 计算每天每个 ts_code 的因子值与当天所有 ts_code 的中位数的偏差\n",
|
||
" # industry_data[f'{factor}_deviation'] = industry_data.groupby('trade_date')[factor].transform(\n",
|
||
" # lambda x: x - x.mean())\n",
|
||
"\n",
|
||
" industry_data['return_5_percentile'] = industry_data.groupby('trade_date')['return_5'].transform(\n",
|
||
" lambda x: x.rank(pct=True))\n",
|
||
" industry_data['return_20_percentile'] = industry_data.groupby('trade_date')['return_20'].transform(\n",
|
||
" lambda x: x.rank(pct=True))\n",
|
||
"\n",
|
||
" # cs_rank_intraday_range(industry_data)\n",
|
||
" # cs_rank_close_pos_in_range(industry_data)\n",
|
||
"\n",
|
||
" industry_data = industry_data.drop(columns=['open', 'close', 'high', 'low', 'pe', 'pb', 'vol'])\n",
|
||
"\n",
|
||
" industry_data = industry_data.rename(\n",
|
||
" columns={col: f'industry_{col}' for col in industry_data.columns if col not in ['ts_code', 'trade_date']})\n",
|
||
"\n",
|
||
" industry_data = industry_data.rename(columns={'ts_code': 'cat_l2_code'})\n",
|
||
" return industry_data\n",
|
||
"\n",
|
||
"\n",
|
||
"industry_df = read_industry_data('../../data/sw_daily.h5')\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 7,
|
||
"id": "dbe2fd8021b9417f",
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-04-03T12:47:15.969344Z",
|
||
"start_time": "2025-04-03T12:47:15.963327Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"['ts_code', 'open', 'close', 'high', 'low', 'circ_mv', 'total_mv', 'is_st', 'up_limit', 'down_limit', 'buy_sm_vol', 'sell_sm_vol', 'buy_lg_vol', 'sell_lg_vol', 'buy_elg_vol', 'sell_elg_vol', 'net_mf_vol', 'his_low', 'his_high', 'cost_5pct', 'cost_15pct', 'cost_50pct', 'cost_85pct', 'cost_95pct', 'weight_avg', 'in_date']\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"origin_columns = df.columns.tolist()\n",
|
||
"origin_columns = [col for col in origin_columns if\n",
|
||
" col not in ['turnover_rate', 'pe_ttm', 'volume_ratio', 'vol', 'pct_chg', 'l2_code', 'winner_rate']]\n",
|
||
"origin_columns = [col for col in origin_columns if col not in index_data.columns]\n",
|
||
"origin_columns = [col for col in origin_columns if 'cyq' not in col]\n",
|
||
"print(origin_columns)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 8,
|
||
"id": "85c3e3d0235ffffa",
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-04-03T12:47:16.089879Z",
|
||
"start_time": "2025-04-03T12:47:15.990101Z"
|
||
}
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"fina_indicator_df = read_and_merge_h5_data('../../data/fina_indicator.h5', key='fina_indicator',\n",
|
||
" columns=['ts_code', 'ann_date', 'undist_profit_ps', 'ocfps', 'bps', 'roa', 'roe'],\n",
|
||
" df=None)\n",
|
||
"cashflow_df = read_and_merge_h5_data('../../data/cashflow.h5', key='cashflow',\n",
|
||
" columns=['ts_code', 'ann_date', 'n_cashflow_act'],\n",
|
||
" df=None)\n",
|
||
"balancesheet_df = read_and_merge_h5_data('../../data/balancesheet.h5', key='balancesheet',\n",
|
||
" columns=['ts_code', 'ann_date', 'money_cap', 'total_liab'],\n",
|
||
" df=None)\n",
|
||
"top_list_df = read_and_merge_h5_data('../../data/top_list.h5', key='top_list',\n",
|
||
" columns=['ts_code', 'trade_date', 'reason'],\n",
|
||
" df=None)\n",
|
||
"\n",
|
||
"top_list_df = top_list_df.sort_values(by='trade_date', ascending=False).drop_duplicates(subset=['ts_code', 'trade_date'], keep='first').sort_values(by='trade_date')\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 9,
|
||
"id": "92d84ce15a562ec6",
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-04-03T13:08:01.612695Z",
|
||
"start_time": "2025-04-03T12:47:16.121802Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"使用 'ann_date' 作为财务数据生效日期。\n",
|
||
"警告: 从 financial_data_subset 中移除了 366 行,因为其 'ts_code' 或 'ann_date' 列存在空值。\n",
|
||
"使用 'ann_date' 作为财务数据生效日期。\n",
|
||
"警告: 从 financial_data_subset 中移除了 366 行,因为其 'ts_code' 或 'ann_date' 列存在空值。\n",
|
||
"使用 'ann_date' 作为财务数据生效日期。\n",
|
||
"警告: 从 financial_data_subset 中移除了 366 行,因为其 'ts_code' 或 'ann_date' 列存在空值。\n",
|
||
"使用 'ann_date' 作为财务数据生效日期。\n",
|
||
"警告: 从 financial_data_subset 中移除了 366 行,因为其 'ts_code' 或 'ann_date' 列存在空值。\n",
|
||
"开始计算因子: AR, BR (原地修改)...\n",
|
||
"因子 AR, BR 计算成功。\n",
|
||
"因子 AR, BR 计算流程结束。\n",
|
||
"使用 'ann_date' 作为财务数据生效日期。\n",
|
||
"使用 'ann_date' 作为财务数据生效日期。\n",
|
||
"使用 'ann_date' 作为财务数据生效日期。\n",
|
||
"使用 'ann_date' 作为财务数据生效日期。\n",
|
||
"警告: 从 financial_data_subset 中移除了 366 行,因为其 'ts_code' 或 'ann_date' 列存在空值。\n",
|
||
"计算 BBI...\n",
|
||
"--- 计算日级别偏离度 (使用 pct_chg) ---\n",
|
||
"--- 计算日级别动量基准 (使用 pct_chg) ---\n",
|
||
"日级别动量基准计算完成 (使用 pct_chg)。\n",
|
||
"日级别偏离度计算完成 (使用 pct_chg)。\n",
|
||
"--- 计算日级别行业偏离度 (使用 pct_chg 和行业基准) ---\n",
|
||
"--- 计算日级别行业动量基准 (使用 pct_chg 和 cat_l2_code) ---\n",
|
||
"错误: 计算日级别行业动量基准需要以下列: ['pct_chg', 'cat_l2_code', 'trade_date', 'ts_code']。\n",
|
||
"错误: 计算日级别行业偏离度需要以下列: ['pct_chg', 'daily_industry_positive_benchmark', 'daily_industry_negative_benchmark']。请先运行 daily_industry_momentum_benchmark(df)。\n",
|
||
"Index(['ts_code', 'trade_date', 'open', 'close', 'high', 'low', 'vol',\n",
|
||
" 'pct_chg', 'turnover_rate', 'pe_ttm', 'circ_mv', 'total_mv',\n",
|
||
" 'volume_ratio', 'is_st', 'up_limit', 'down_limit', 'buy_sm_vol',\n",
|
||
" 'sell_sm_vol', 'buy_lg_vol', 'sell_lg_vol', 'buy_elg_vol',\n",
|
||
" 'sell_elg_vol', 'net_mf_vol', 'his_low', 'his_high', 'cost_5pct',\n",
|
||
" 'cost_15pct', 'cost_50pct', 'cost_85pct', 'cost_95pct', 'weight_avg',\n",
|
||
" 'winner_rate', 'l2_code', 'undist_profit_ps', 'ocfps', 'roa', 'roe',\n",
|
||
" 'AR', 'BR', 'AR_BR', 'log_circ_mv', 'cashflow_to_ev_factor',\n",
|
||
" 'book_to_price_ratio', 'turnover_rate_mean_5', 'variance_20',\n",
|
||
" 'bbi_ratio_factor', 'daily_deviation', 'lg_elg_net_buy_vol',\n",
|
||
" 'flow_lg_elg_intensity', 'sm_net_buy_vol', 'flow_divergence_diff',\n",
|
||
" 'flow_divergence_ratio', 'total_buy_vol', 'lg_elg_buy_prop',\n",
|
||
" 'flow_struct_buy_change', 'lg_elg_net_buy_vol_change',\n",
|
||
" 'flow_lg_elg_accel', 'chip_concentration_range', 'chip_skewness',\n",
|
||
" 'floating_chip_proxy', 'cost_support_15pct_change',\n",
|
||
" 'cat_winner_price_zone', 'flow_chip_consistency',\n",
|
||
" 'profit_taking_vs_absorb', '_is_positive', '_is_negative',\n",
|
||
" 'cat_is_positive', '_pos_returns', '_neg_returns', '_pos_returns_sq',\n",
|
||
" '_neg_returns_sq', 'upside_vol', 'downside_vol', 'vol_ratio',\n",
|
||
" 'return_skew', 'return_kurtosis', 'volume_change_rate',\n",
|
||
" 'cat_volume_breakout', 'turnover_deviation', 'cat_turnover_spike',\n",
|
||
" 'avg_volume_ratio', 'cat_volume_ratio_breakout', 'vol_spike',\n",
|
||
" 'vol_std_5', 'atr_14', 'atr_6', 'obv'],\n",
|
||
" dtype='object')\n",
|
||
"Calculating lg_flow_mom_corr_20_60...\n",
|
||
"Finished lg_flow_mom_corr_20_60.\n",
|
||
"Calculating lg_flow_accel...\n",
|
||
"Finished lg_flow_accel.\n",
|
||
"Calculating profit_pressure...\n",
|
||
"Finished profit_pressure.\n",
|
||
"Calculating underwater_resistance...\n",
|
||
"Finished underwater_resistance.\n",
|
||
"Calculating cost_conc_std_20...\n",
|
||
"Finished cost_conc_std_20.\n",
|
||
"Calculating profit_decay_20...\n",
|
||
"Finished profit_decay_20.\n",
|
||
"Calculating vol_amp_loss_20...\n",
|
||
"Finished vol_amp_loss_20.\n",
|
||
"Calculating vol_drop_profit_cnt_5...\n",
|
||
"Finished vol_drop_profit_cnt_5.\n",
|
||
"Calculating lg_flow_vol_interact_20...\n",
|
||
"Finished lg_flow_vol_interact_20.\n",
|
||
"Calculating cost_break_confirm_cnt_5...\n",
|
||
"Finished cost_break_confirm_cnt_5.\n",
|
||
"Calculating atr_norm_channel_pos_14...\n",
|
||
"Finished atr_norm_channel_pos_14.\n",
|
||
"Calculating turnover_diff_skew_20...\n",
|
||
"Finished turnover_diff_skew_20.\n",
|
||
"Calculating lg_sm_flow_diverge_20...\n",
|
||
"Finished lg_sm_flow_diverge_20.\n",
|
||
"Calculating pullback_strong_20_20...\n",
|
||
"Finished pullback_strong_20_20.\n",
|
||
"Calculating vol_wgt_hist_pos_20...\n",
|
||
"Finished vol_wgt_hist_pos_20.\n",
|
||
"Calculating vol_adj_roc_20...\n",
|
||
"Finished vol_adj_roc_20.\n",
|
||
"Calculating cs_rank_net_lg_flow_val...\n",
|
||
"Finished cs_rank_net_lg_flow_val.\n",
|
||
"Calculating cs_rank_flow_divergence...\n",
|
||
"Finished cs_rank_flow_divergence.\n",
|
||
"Calculating cs_rank_ind_adj_lg_flow...\n",
|
||
"Finished cs_rank_ind_adj_lg_flow.\n",
|
||
"Calculating cs_rank_elg_buy_ratio...\n",
|
||
"Finished cs_rank_elg_buy_ratio.\n",
|
||
"Calculating cs_rank_rel_profit_margin...\n",
|
||
"Finished cs_rank_rel_profit_margin.\n",
|
||
"Calculating cs_rank_cost_breadth...\n",
|
||
"Finished cs_rank_cost_breadth.\n",
|
||
"Calculating cs_rank_dist_to_upper_cost...\n",
|
||
"Finished cs_rank_dist_to_upper_cost.\n",
|
||
"Calculating cs_rank_winner_rate...\n",
|
||
"Finished cs_rank_winner_rate.\n",
|
||
"Calculating cs_rank_intraday_range...\n",
|
||
"Finished cs_rank_intraday_range.\n",
|
||
"Calculating cs_rank_close_pos_in_range...\n",
|
||
"Finished cs_rank_close_pos_in_range.\n",
|
||
"Calculating cs_rank_opening_gap...\n",
|
||
"Error calculating cs_rank_opening_gap: Missing 'pre_close' column. Assigning NaN.\n",
|
||
"Calculating cs_rank_pos_in_hist_range...\n",
|
||
"Finished cs_rank_pos_in_hist_range.\n",
|
||
"Calculating cs_rank_vol_x_profit_margin...\n",
|
||
"Finished cs_rank_vol_x_profit_margin.\n",
|
||
"Calculating cs_rank_lg_flow_price_concordance...\n",
|
||
"Finished cs_rank_lg_flow_price_concordance.\n",
|
||
"Calculating cs_rank_turnover_per_winner...\n",
|
||
"Finished cs_rank_turnover_per_winner.\n",
|
||
"Calculating cs_rank_ind_cap_neutral_pe (Placeholder - requires statsmodels)...\n",
|
||
"Finished cs_rank_ind_cap_neutral_pe (Placeholder).\n",
|
||
"Calculating cs_rank_volume_ratio...\n",
|
||
"Finished cs_rank_volume_ratio.\n",
|
||
"Calculating cs_rank_elg_buy_sell_sm_ratio...\n",
|
||
"Finished cs_rank_elg_buy_sell_sm_ratio.\n",
|
||
"Calculating cs_rank_cost_dist_vol_ratio...\n",
|
||
"Finished cs_rank_cost_dist_vol_ratio.\n",
|
||
"Calculating cs_rank_size...\n",
|
||
"Finished cs_rank_size.\n",
|
||
"<class 'pandas.core.frame.DataFrame'>\n",
|
||
"RangeIndex: 4539678 entries, 0 to 4539677\n",
|
||
"Columns: 180 entries, ts_code to cs_rank_size\n",
|
||
"dtypes: bool(10), datetime64[ns](1), float64(164), int32(3), object(2)\n",
|
||
"memory usage: 5.7+ GB\n",
|
||
"None\n",
|
||
"['ts_code', 'trade_date', 'open', 'close', 'high', 'low', 'vol', 'pct_chg', 'turnover_rate', 'pe_ttm', 'circ_mv', 'total_mv', 'volume_ratio', 'is_st', 'up_limit', 'down_limit', 'buy_sm_vol', 'sell_sm_vol', 'buy_lg_vol', 'sell_lg_vol', 'buy_elg_vol', 'sell_elg_vol', 'net_mf_vol', 'his_low', 'his_high', 'cost_5pct', 'cost_15pct', 'cost_50pct', 'cost_85pct', 'cost_95pct', 'weight_avg', 'winner_rate', 'cat_l2_code', 'undist_profit_ps', 'ocfps', 'roa', 'roe', 'AR', 'BR', 'AR_BR', 'log_circ_mv', 'cashflow_to_ev_factor', 'book_to_price_ratio', 'turnover_rate_mean_5', 'variance_20', 'bbi_ratio_factor', 'daily_deviation', 'lg_elg_net_buy_vol', 'flow_lg_elg_intensity', 'sm_net_buy_vol', 'flow_divergence_diff', 'flow_divergence_ratio', 'total_buy_vol', 'lg_elg_buy_prop', 'flow_struct_buy_change', 'lg_elg_net_buy_vol_change', 'flow_lg_elg_accel', 'chip_concentration_range', 'chip_skewness', 'floating_chip_proxy', 'cost_support_15pct_change', 'cat_winner_price_zone', 'flow_chip_consistency', 'profit_taking_vs_absorb', 'cat_is_positive', 'upside_vol', 'downside_vol', 'vol_ratio', 'return_skew', 'return_kurtosis', 'volume_change_rate', 'cat_volume_breakout', 'turnover_deviation', 'cat_turnover_spike', 'avg_volume_ratio', 'cat_volume_ratio_breakout', 'vol_spike', 'vol_std_5', 'atr_14', 'atr_6', 'obv', 'maobv_6', 'rsi_3', 'return_5', 'return_20', 'std_return_5', 'std_return_90', 'std_return_90_2', 'act_factor1', 'act_factor2', 'act_factor3', 'act_factor4', 'rank_act_factor1', 'rank_act_factor2', 'rank_act_factor3', 'cov', 'delta_cov', 'alpha_22_improved', 'alpha_003', 'alpha_007', 'alpha_013', 'vol_break', 'weight_roc5', 'price_cost_divergence', 'smallcap_concentration', 'cost_stability', 'high_cost_break_days', 'liquidity_risk', 'turnover_std', 'mv_volatility', 'volume_growth', 'mv_growth', 'momentum_factor', 'resonance_factor', 'log_close', 'cat_vol_spike', 'up', 'down', 'obv_maobv_6', 'std_return_5_over_std_return_90', 'std_return_90_minus_std_return_90_2', 'cat_af2', 'cat_af3', 'cat_af4', 'act_factor5', 'act_factor6', 'active_buy_volume_large', 'active_buy_volume_big', 'active_buy_volume_small', 'buy_lg_vol_minus_sell_lg_vol', 'buy_elg_vol_minus_sell_elg_vol', 'ctrl_strength', 'low_cost_dev', 'asymmetry', 'lock_factor', 'cat_vol_break', 'cost_atr_adj', 'cat_golden_resonance', 'mv_turnover_ratio', 'mv_adjusted_volume', 'mv_weighted_turnover', 'nonlinear_mv_volume', 'mv_volume_ratio', 'mv_momentum', 'lg_flow_mom_corr_20_60', 'lg_flow_accel', 'profit_pressure', 'underwater_resistance', 'cost_conc_std_20', 'profit_decay_20', 'vol_amp_loss_20', 'vol_drop_profit_cnt_5', 'lg_flow_vol_interact_20', 'cost_break_confirm_cnt_5', 'atr_norm_channel_pos_14', 'turnover_diff_skew_20', 'lg_sm_flow_diverge_20', 'pullback_strong_20_20', 'vol_wgt_hist_pos_20', 'vol_adj_roc_20', 'cs_rank_net_lg_flow_val', 'cs_rank_flow_divergence', 'cs_rank_ind_adj_lg_flow', 'cs_rank_elg_buy_ratio', 'cs_rank_rel_profit_margin', 'cs_rank_cost_breadth', 'cs_rank_dist_to_upper_cost', 'cs_rank_winner_rate', 'cs_rank_intraday_range', 'cs_rank_close_pos_in_range', 'cs_rank_opening_gap', 'cs_rank_pos_in_hist_range', 'cs_rank_vol_x_profit_margin', 'cs_rank_lg_flow_price_concordance', 'cs_rank_turnover_per_winner', 'cs_rank_ind_cap_neutral_pe', 'cs_rank_volume_ratio', 'cs_rank_elg_buy_sell_sm_ratio', 'cs_rank_cost_dist_vol_ratio', 'cs_rank_size']\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"\n",
|
||
"import numpy as np\n",
|
||
"from main.factor.factor import *\n",
|
||
"\n",
|
||
"def filter_data(df):\n",
|
||
" # df = df.groupby('trade_date').apply(lambda x: x.nlargest(1000, 'act_factor1'))\n",
|
||
" df = df[~df['is_st']]\n",
|
||
" df = df[~df['ts_code'].str.endswith('BJ')]\n",
|
||
" df = df[~df['ts_code'].str.startswith('30')]\n",
|
||
" df = df[~df['ts_code'].str.startswith('68')]\n",
|
||
" df = df[~df['ts_code'].str.startswith('8')]\n",
|
||
" df = df[df['trade_date'] >= '2019-01-01']\n",
|
||
" if 'in_date' in df.columns:\n",
|
||
" df = df.drop(columns=['in_date'])\n",
|
||
" df = df.reset_index(drop=True)\n",
|
||
" return df\n",
|
||
"\n",
|
||
"gc.collect()\n",
|
||
"\n",
|
||
"df = filter_data(df)\n",
|
||
"df = df.sort_values(by=['ts_code', 'trade_date'])\n",
|
||
"\n",
|
||
"# df = price_minus_deduction_price(df, n=120)\n",
|
||
"# df = price_deduction_price_diff_ratio_to_sma(df, n=120)\n",
|
||
"# df = cat_price_vs_sma_vs_deduction_price(df, n=120)\n",
|
||
"# df = cat_reason(df, top_list_df)\n",
|
||
"# df = cat_is_on_top_list(df, top_list_df)\n",
|
||
"\n",
|
||
"df = add_financial_factor(df, fina_indicator_df, factor_value_col='undist_profit_ps')\n",
|
||
"df = add_financial_factor(df, fina_indicator_df, factor_value_col='ocfps')\n",
|
||
"df = add_financial_factor(df, fina_indicator_df, factor_value_col='roa')\n",
|
||
"df = add_financial_factor(df, fina_indicator_df, factor_value_col='roe')\n",
|
||
"\n",
|
||
"calculate_arbr(df, N=26)\n",
|
||
"df['log_circ_mv'] = np.log(df['circ_mv'])\n",
|
||
"df = calculate_cashflow_to_ev_factor(df, cashflow_df, balancesheet_df)\n",
|
||
"df = caculate_book_to_price_ratio(df, fina_indicator_df)\n",
|
||
"\n",
|
||
"df = turnover_rate_n(df, n=5)\n",
|
||
"df = variance_n(df, n=20)\n",
|
||
"df = bbi_ratio_factor(df)\n",
|
||
"df = daily_deviation(df)\n",
|
||
"df = daily_industry_deviation(df)\n",
|
||
"df, _ = get_rolling_factor(df)\n",
|
||
"df, _ = get_simple_factor(df)\n",
|
||
"\n",
|
||
"df = df.rename(columns={'l1_code': 'cat_l1_code'})\n",
|
||
"df = df.rename(columns={'l2_code': 'cat_l2_code'})\n",
|
||
"\n",
|
||
"lg_flow_mom_corr(df, N=20, M=60)\n",
|
||
"lg_flow_accel(df)\n",
|
||
"profit_pressure(df)\n",
|
||
"underwater_resistance(df)\n",
|
||
"cost_conc_std(df, N=20)\n",
|
||
"profit_decay(df, N=20)\n",
|
||
"vol_amp_loss(df, N=20)\n",
|
||
"vol_drop_profit_cnt(df, N=20, M=5)\n",
|
||
"lg_flow_vol_interact(df, N=20)\n",
|
||
"cost_break_confirm_cnt(df, M=5)\n",
|
||
"atr_norm_channel_pos(df, N=14)\n",
|
||
"turnover_diff_skew(df, N=20)\n",
|
||
"lg_sm_flow_diverge(df, N=20)\n",
|
||
"pullback_strong(df, N=20, M=20)\n",
|
||
"vol_wgt_hist_pos(df, N=20)\n",
|
||
"vol_adj_roc(df, N=20)\n",
|
||
"\n",
|
||
"cs_rank_net_lg_flow_val(df)\n",
|
||
"cs_rank_flow_divergence(df)\n",
|
||
"cs_rank_industry_adj_lg_flow(df) # Needs cat_l2_code\n",
|
||
"cs_rank_elg_buy_ratio(df)\n",
|
||
"cs_rank_rel_profit_margin(df)\n",
|
||
"cs_rank_cost_breadth(df)\n",
|
||
"cs_rank_dist_to_upper_cost(df)\n",
|
||
"cs_rank_winner_rate(df)\n",
|
||
"cs_rank_intraday_range(df)\n",
|
||
"cs_rank_close_pos_in_range(df)\n",
|
||
"cs_rank_opening_gap(df) # Needs pre_close\n",
|
||
"cs_rank_pos_in_hist_range(df) # Needs his_low, his_high\n",
|
||
"cs_rank_vol_x_profit_margin(df)\n",
|
||
"cs_rank_lg_flow_price_concordance(df)\n",
|
||
"cs_rank_turnover_per_winner(df)\n",
|
||
"cs_rank_ind_cap_neutral_pe(df) # Placeholder - needs external libraries\n",
|
||
"cs_rank_volume_ratio(df) # Needs volume_ratio\n",
|
||
"cs_rank_elg_buy_sell_sm_ratio(df)\n",
|
||
"cs_rank_cost_dist_vol_ratio(df) # Needs volume_ratio\n",
|
||
"cs_rank_size(df) # Needs circ_mv\n",
|
||
"\n",
|
||
"\n",
|
||
"# df = df.merge(index_data, on='trade_date', how='left')\n",
|
||
"\n",
|
||
"print(df.info())\n",
|
||
"print(df.columns.tolist())"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 10,
|
||
"id": "b87b938028afa206",
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-04-03T13:08:03.658725Z",
|
||
"start_time": "2025-04-03T13:08:02.469611Z"
|
||
}
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"from scipy.stats import ks_2samp, wasserstein_distance\n",
|
||
"\n",
|
||
"\n",
|
||
"def remove_shifted_features(train_data, test_data, feature_columns, ks_threshold=0.05, wasserstein_threshold=0.1,\n",
|
||
" importance_threshold=0.05):\n",
|
||
" dropped_features = []\n",
|
||
"\n",
|
||
" # **统计数据漂移**\n",
|
||
" numeric_columns = train_data.select_dtypes(include=['float64', 'int64']).columns\n",
|
||
" numeric_columns = [col for col in numeric_columns if col in feature_columns]\n",
|
||
" for feature in numeric_columns:\n",
|
||
" ks_stat, p_value = ks_2samp(train_data[feature], test_data[feature])\n",
|
||
" wasserstein_dist = wasserstein_distance(train_data[feature], test_data[feature])\n",
|
||
"\n",
|
||
" if p_value < ks_threshold or wasserstein_dist > wasserstein_threshold:\n",
|
||
" dropped_features.append(feature)\n",
|
||
"\n",
|
||
" print(f\"检测到 {len(dropped_features)} 个可能漂移的特征: {dropped_features}\")\n",
|
||
"\n",
|
||
" # **应用阈值进行最终筛选**\n",
|
||
" filtered_features = [f for f in feature_columns if f not in dropped_features]\n",
|
||
"\n",
|
||
" return filtered_features, dropped_features\n",
|
||
"\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 11,
|
||
"id": "f4f16d63ad18d1bc",
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-04-03T13:08:03.670700Z",
|
||
"start_time": "2025-04-03T13:08:03.665739Z"
|
||
}
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"import pandas as pd\n",
|
||
"import numpy as np\n",
|
||
"import statsmodels.api as sm # 用于中性化回归\n",
|
||
"from tqdm import tqdm # 可选,用于显示进度条\n",
|
||
"\n",
|
||
"# --- 常量 ---\n",
|
||
"epsilon = 1e-10 # 防止除零\n",
|
||
"\n",
|
||
"# --- 1. 中位数去极值 (MAD) ---\n",
|
||
"\n",
|
||
"def cs_mad_filter(df: pd.DataFrame,\n",
|
||
" features: list,\n",
|
||
" k: float = 3.0,\n",
|
||
" scale_factor: float = 1.4826):\n",
|
||
" \"\"\"\n",
|
||
" 对指定特征列进行截面 MAD 去极值处理 (原地修改)。\n",
|
||
"\n",
|
||
" 方法: 对每日截面数据,计算 median 和 MAD,\n",
|
||
" 将超出 [median - k * scale * MAD, median + k * scale * MAD] 范围的值\n",
|
||
" 替换为边界值 (Winsorization)。\n",
|
||
" scale_factor=1.4826 使得 MAD 约等于正态分布的标准差。\n",
|
||
"\n",
|
||
" Args:\n",
|
||
" df (pd.DataFrame): 输入 DataFrame,需包含 'trade_date' 和 features 列。\n",
|
||
" features (list): 需要处理的特征列名列表。\n",
|
||
" k (float): MAD 的倍数,用于确定边界。默认为 3.0。\n",
|
||
" scale_factor (float): MAD 的缩放因子。默认为 1.4826。\n",
|
||
"\n",
|
||
" WARNING: 此函数会原地修改输入的 DataFrame 'df'。\n",
|
||
" \"\"\"\n",
|
||
" print(f\"开始截面 MAD 去极值处理 (k={k})...\")\n",
|
||
" if not all(col in df.columns for col in features):\n",
|
||
" missing = [col for col in features if col not in df.columns]\n",
|
||
" print(f\"错误: DataFrame 中缺少以下特征列: {missing}。跳过去极值处理。\")\n",
|
||
" return\n",
|
||
"\n",
|
||
" grouped = df.groupby('trade_date')\n",
|
||
"\n",
|
||
" for col in tqdm(features, desc=\"MAD Filtering\"):\n",
|
||
" try:\n",
|
||
" # 计算截面中位数\n",
|
||
" median = grouped[col].transform('median')\n",
|
||
" # 计算截面 MAD (Median Absolute Deviation from Median)\n",
|
||
" mad = (df[col] - median).abs().groupby(df['trade_date']).transform('median')\n",
|
||
"\n",
|
||
" # 计算上下边界\n",
|
||
" lower_bound = median - k * scale_factor * mad\n",
|
||
" upper_bound = median + k * scale_factor * mad\n",
|
||
"\n",
|
||
" # 原地应用 clip\n",
|
||
" df[col] = np.clip(df[col], lower_bound, upper_bound)\n",
|
||
"\n",
|
||
" except KeyError:\n",
|
||
" print(f\"警告: 列 '{col}' 可能不存在或在分组中出错,跳过此列的 MAD 处理。\")\n",
|
||
" except Exception as e:\n",
|
||
" print(f\"警告: 处理列 '{col}' 时发生错误: {e},跳过此列的 MAD 处理。\")\n",
|
||
"\n",
|
||
" print(\"截面 MAD 去极值处理完成。\")\n",
|
||
"\n",
|
||
"\n",
|
||
"# --- 2. 行业市值中性化 ---\n",
|
||
"\n",
|
||
"from tqdm import tqdm\n",
|
||
"\n",
|
||
"def cs_neutralize_market_cap_numpy(df: pd.DataFrame,\n",
|
||
" features: list,\n",
|
||
" market_cap_col: str = 'circ_mv'):\n",
|
||
" \"\"\"\n",
|
||
" 对 DataFrame 中的指定特征进行截面市值中性化 (NumPy 优化)。\n",
|
||
"\n",
|
||
" Args:\n",
|
||
" df (pd.DataFrame): 包含数据的 DataFrame,需要有 'trade_date' 和 market_cap_col 列。\n",
|
||
" features (list): 需要进行市值中性化的特征列名列表。\n",
|
||
" market_cap_col (str): 包含市值数据的列名,默认为 'circ_mv'。\n",
|
||
" \"\"\"\n",
|
||
" print(\"开始截面市值中性化 (NumPy 优化)...\")\n",
|
||
" required_cols = features + ['trade_date', market_cap_col]\n",
|
||
" if not all(col in df.columns for col in required_cols):\n",
|
||
" missing = [col for col in required_cols if col not in df.columns]\n",
|
||
" print(f\"错误: DataFrame 中缺少必需列: {missing}。无法进行中性化。\")\n",
|
||
" return\n",
|
||
"\n",
|
||
" df_copy = df\n",
|
||
" log_cap_col = '_log_market_cap'\n",
|
||
" df_copy[log_cap_col] = np.log1p(df_copy[market_cap_col])\n",
|
||
"\n",
|
||
" # 创建一个 DataFrame 来存储所有日期的残差结果\n",
|
||
" residuals_container = pd.DataFrame(index=df_copy.index, columns=features, dtype=float)\n",
|
||
"\n",
|
||
" for date, group_df in tqdm(df_copy.groupby('trade_date'), desc=\"Neutralizing by Date (NumPy)\"):\n",
|
||
" # 准备 X 矩阵 (自变量):常数项和对数市值\n",
|
||
" X_daily = np.concatenate([np.ones((len(group_df), 1)), group_df[[log_cap_col]].values], axis=1)\n",
|
||
"\n",
|
||
" for feature_col in features:\n",
|
||
" Y_daily = group_df[feature_col].values\n",
|
||
"\n",
|
||
" # 处理 NaN:只对有效数据对进行回归\n",
|
||
" valid_mask_y = ~np.isnan(Y_daily)\n",
|
||
" valid_mask_x = ~np.isnan(X_daily).any(axis=1)\n",
|
||
" valid_mask = valid_mask_y & valid_mask_x\n",
|
||
"\n",
|
||
" current_feature_indices = group_df.index[valid_mask]\n",
|
||
"\n",
|
||
" if np.sum(valid_mask) < X_daily.shape[1] + 1:\n",
|
||
" # 有效数据不足,此特征在此日期保持 NaN\n",
|
||
" continue\n",
|
||
"\n",
|
||
" Y_valid = Y_daily[valid_mask]\n",
|
||
" X_valid = X_daily[valid_mask, :]\n",
|
||
"\n",
|
||
" try:\n",
|
||
" # 使用 np.linalg.lstsq 进行 OLS 计算\n",
|
||
" beta, sum_sq_resid, rank, s = np.linalg.lstsq(X_valid, Y_valid, rcond=None)\n",
|
||
"\n",
|
||
" # 计算预测值 Y_hat = X_valid @ beta\n",
|
||
" Y_hat_valid = X_valid @ beta\n",
|
||
"\n",
|
||
" # 计算残差 residuals = Y_valid - Y_hat_valid\n",
|
||
" residuals_valid = Y_valid - Y_hat_valid\n",
|
||
"\n",
|
||
" # 将计算得到的残差放回 residuals_container\n",
|
||
" residuals_container.loc[current_feature_indices, feature_col] = residuals_valid\n",
|
||
"\n",
|
||
" except np.linalg.LinAlgError:\n",
|
||
" pass\n",
|
||
" except Exception as e:\n",
|
||
" pass\n",
|
||
"\n",
|
||
" # 将所有计算得到的残差更新回原始的 df (原地修改)\n",
|
||
" for feature_col in features:\n",
|
||
" df[feature_col] = residuals_container[feature_col]\n",
|
||
"\n",
|
||
" # 清理临时列\n",
|
||
" df.drop(columns=[log_cap_col], inplace=True, errors='ignore')\n",
|
||
" print(\"截面市值中性化完成 (NumPy 优化)。\")\n",
|
||
"\n",
|
||
"# --- 3. Z-Score 标准化 ---\n",
|
||
"\n",
|
||
"def cs_zscore_standardize(df: pd.DataFrame, features: list, epsilon: float = 1e-10):\n",
|
||
" \"\"\"\n",
|
||
" 对指定特征列进行截面 Z-Score 标准化 (原地修改)。\n",
|
||
" 方法: Z = (value - cross_sectional_mean) / (cross_sectional_std + epsilon)\n",
|
||
"\n",
|
||
" Args:\n",
|
||
" df (pd.DataFrame): 输入 DataFrame,需包含 'trade_date' 和 features 列。\n",
|
||
" features (list): 需要处理的特征列名列表。\n",
|
||
" epsilon (float): 防止除以零的小常数。\n",
|
||
"\n",
|
||
" WARNING: 此函数会原地修改输入的 DataFrame 'df'。\n",
|
||
" \"\"\"\n",
|
||
" print(\"开始截面 Z-Score 标准化...\")\n",
|
||
" if not all(col in df.columns for col in features):\n",
|
||
" missing = [col for col in features if col not in df.columns]\n",
|
||
" print(f\"错误: DataFrame 中缺少以下特征列: {missing}。跳过标准化处理。\")\n",
|
||
" return\n",
|
||
"\n",
|
||
" grouped = df.groupby('trade_date')\n",
|
||
"\n",
|
||
" for col in tqdm(features, desc=\"Standardizing\"):\n",
|
||
" try:\n",
|
||
" # 使用 transform 计算截面均值和标准差\n",
|
||
" mean = grouped[col].transform('mean')\n",
|
||
" std = grouped[col].transform('std')\n",
|
||
"\n",
|
||
" # 计算 Z-Score 并原地赋值\n",
|
||
" df[col] = (df[col] - mean) / (std + epsilon)\n",
|
||
"\n",
|
||
" except KeyError:\n",
|
||
" print(f\"警告: 列 '{col}' 可能不存在或在分组中出错,跳过此列的标准化处理。\")\n",
|
||
" except Exception as e:\n",
|
||
" print(f\"警告: 处理列 '{col}' 时发生错误: {e},跳过此列的标准化处理。\")\n",
|
||
"\n",
|
||
" print(\"截面 Z-Score 标准化完成。\")\n",
|
||
"\n",
|
||
"def fill_nan_with_daily_median(df: pd.DataFrame, feature_columns: list[str]) -> pd.DataFrame:\n",
|
||
" \"\"\"\n",
|
||
" 对指定特征列进行每日截面中位数填充缺失值 (NaN)。\n",
|
||
"\n",
|
||
" 参数:\n",
|
||
" df (pd.DataFrame): 包含多日数据的DataFrame,需要包含 'trade_date' 和 feature_columns 中的列。\n",
|
||
" feature_columns (list[str]): 需要进行缺失值填充的特征列名称列表。\n",
|
||
"\n",
|
||
" 返回:\n",
|
||
" pd.DataFrame: 包含缺失值填充后特征列的DataFrame。在输入DataFrame的副本上操作。\n",
|
||
" \"\"\"\n",
|
||
" processed_df = df.copy() # 在副本上操作,保留原始数据\n",
|
||
"\n",
|
||
" # 确保 trade_date 是 datetime 类型以便正确分组\n",
|
||
" processed_df['trade_date'] = pd.to_datetime(processed_df['trade_date'])\n",
|
||
"\n",
|
||
" def _fill_daily_nan(group):\n",
|
||
" # group 是某一个交易日的 DataFrame\n",
|
||
"\n",
|
||
" # 遍历指定的特征列\n",
|
||
" for feature_col in feature_columns:\n",
|
||
" # 检查列是否存在于当前分组中\n",
|
||
" if feature_col in group.columns:\n",
|
||
" # 计算当日该特征的中位数\n",
|
||
" median_val = group[feature_col].median()\n",
|
||
"\n",
|
||
" # 使用当日中位数填充该特征列的 NaN 值\n",
|
||
" # inplace=True 会直接修改 group DataFrame\n",
|
||
" group[feature_col].fillna(median_val, inplace=True)\n",
|
||
" # else:\n",
|
||
" # print(f\"Warning: Feature column '{feature_col}' not found in daily group for {group['trade_date'].iloc[0]}. Skipping.\")\n",
|
||
"\n",
|
||
" return group\n",
|
||
"\n",
|
||
" # 按交易日期分组,并应用每日填充函数\n",
|
||
" # group_keys=False 避免将分组键添加到结果索引中\n",
|
||
" filled_df = processed_df.groupby('trade_date', group_keys=False).apply(_fill_daily_nan)\n",
|
||
"\n",
|
||
" return filled_df"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 12,
|
||
"id": "40e6b68a91b30c79",
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-04-03T13:08:04.694262Z",
|
||
"start_time": "2025-04-03T13:08:03.694904Z"
|
||
}
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"import pandas as pd\n",
|
||
"\n",
|
||
"\n",
|
||
"def remove_outliers_label_percentile(label: pd.Series, lower_percentile: float = 0.01, upper_percentile: float = 0.99,\n",
|
||
" log=True):\n",
|
||
" if not (0 <= lower_percentile < upper_percentile <= 1):\n",
|
||
" raise ValueError(\"Percentile values must satisfy 0 <= lower_percentile < upper_percentile <= 1.\")\n",
|
||
"\n",
|
||
" # Calculate lower and upper bounds based on percentiles\n",
|
||
" lower_bound = label.quantile(lower_percentile)\n",
|
||
" upper_bound = label.quantile(upper_percentile)\n",
|
||
"\n",
|
||
" # Filter out values outside the bounds\n",
|
||
" filtered_label = label[(label >= lower_bound) & (label <= upper_bound)]\n",
|
||
"\n",
|
||
" # Print the number of removed outliers\n",
|
||
" if log:\n",
|
||
" print(f\"Removed {len(label) - len(filtered_label)} outliers.\")\n",
|
||
" return filtered_label\n",
|
||
"\n",
|
||
"\n",
|
||
"def calculate_risk_adjusted_target(df, days=5):\n",
|
||
" df = df.sort_values(by=['ts_code', 'trade_date'])\n",
|
||
"\n",
|
||
" df['future_close'] = df.groupby('ts_code')['close'].shift(-days)\n",
|
||
" df['future_open'] = df.groupby('ts_code')['open'].shift(-1)\n",
|
||
" df['future_return'] = (df['future_close'] - df['future_open']) / df['future_open']\n",
|
||
"\n",
|
||
" df['future_volatility'] = df.groupby('ts_code')['future_return'].rolling(days, min_periods=1).std().reset_index(\n",
|
||
" level=0, drop=True)\n",
|
||
" sharpe_ratio = df['future_return'] * df['future_volatility']\n",
|
||
" sharpe_ratio.replace([np.inf, -np.inf], np.nan, inplace=True)\n",
|
||
"\n",
|
||
" return sharpe_ratio\n",
|
||
"\n",
|
||
"\n",
|
||
"def calculate_score(df, days=5, lambda_param=1.0):\n",
|
||
" def calculate_max_drawdown(prices):\n",
|
||
" peak = prices.iloc[0] # 初始化峰值\n",
|
||
" max_drawdown = 0 # 初始化最大回撤\n",
|
||
"\n",
|
||
" for price in prices:\n",
|
||
" if price > peak:\n",
|
||
" peak = price # 更新峰值\n",
|
||
" else:\n",
|
||
" drawdown = (peak - price) / peak # 计算当前回撤\n",
|
||
" max_drawdown = max(max_drawdown, drawdown) # 更新最大回撤\n",
|
||
"\n",
|
||
" return max_drawdown\n",
|
||
"\n",
|
||
" def compute_stock_score(stock_df):\n",
|
||
" stock_df = stock_df.sort_values(by=['trade_date'])\n",
|
||
" future_return = stock_df['future_return']\n",
|
||
" # 使用已有的 pct_chg 字段计算波动率\n",
|
||
" volatility = stock_df['pct_chg'].rolling(days).std().shift(-days)\n",
|
||
" max_drawdown = stock_df['close'].rolling(days).apply(calculate_max_drawdown, raw=False).shift(-days)\n",
|
||
" score = future_return - lambda_param * max_drawdown\n",
|
||
" return score\n",
|
||
"\n",
|
||
" # # 确保 DataFrame 按照股票代码和交易日期排序\n",
|
||
" # df = df.sort_values(by=['ts_code', 'trade_date'])\n",
|
||
"\n",
|
||
" # 对每个股票分别计算 score\n",
|
||
" df['score'] = df.groupby('ts_code').apply(compute_stock_score).reset_index(level=0, drop=True)\n",
|
||
"\n",
|
||
" return df['score']\n",
|
||
"\n",
|
||
"\n",
|
||
"def remove_highly_correlated_features(df, feature_columns, threshold=0.9):\n",
|
||
" numeric_features = df[feature_columns].select_dtypes(include=[np.number]).columns.tolist()\n",
|
||
" if not numeric_features:\n",
|
||
" raise ValueError(\"No numeric features found in the provided data.\")\n",
|
||
"\n",
|
||
" corr_matrix = df[numeric_features].corr().abs()\n",
|
||
" upper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(bool))\n",
|
||
" to_drop = [column for column in upper.columns if any(upper[column] > threshold)]\n",
|
||
" remaining_features = [col for col in feature_columns if col not in to_drop\n",
|
||
" or 'act' in col or 'af' in col]\n",
|
||
" return remaining_features\n",
|
||
"\n",
|
||
"\n",
|
||
"def cross_sectional_standardization(df, features):\n",
|
||
" df_sorted = df.sort_values(by='trade_date') # 按时间排序\n",
|
||
" df_standardized = df_sorted.copy()\n",
|
||
"\n",
|
||
" for date in df_sorted['trade_date'].unique():\n",
|
||
" # 获取当前时间点的数据\n",
|
||
" current_data = df_standardized[df_standardized['trade_date'] == date]\n",
|
||
"\n",
|
||
" # 只对指定特征进行标准化\n",
|
||
" scaler = StandardScaler()\n",
|
||
" standardized_values = scaler.fit_transform(current_data[features])\n",
|
||
"\n",
|
||
" # 将标准化结果重新赋值回去\n",
|
||
" df_standardized.loc[df_standardized['trade_date'] == date, features] = standardized_values\n",
|
||
"\n",
|
||
" return df_standardized\n",
|
||
"\n",
|
||
"\n",
|
||
"import numpy as np\n",
|
||
"import pandas as pd\n",
|
||
"\n",
|
||
"\n",
|
||
"def neutralize_manual_revised(df: pd.DataFrame, features: list, industry_col: str, mkt_cap_col: str) -> pd.DataFrame:\n",
|
||
" \"\"\"\n",
|
||
" 手动实现简单回归以提升速度,通过构建 Series 确保索引对齐。\n",
|
||
" 对特征在行业内部进行市值中性化。\n",
|
||
"\n",
|
||
" Args:\n",
|
||
" df: 输入的 DataFrame,包含特征、行业分类和市值列。\n",
|
||
" features: 需要进行中性化的特征列名列表。\n",
|
||
" industry_col: 行业分类列的列名。\n",
|
||
" mkt_cap_col: 市值列的列名。\n",
|
||
"\n",
|
||
" Returns:\n",
|
||
" 中性化后的 DataFrame。\n",
|
||
" \"\"\"\n",
|
||
"\n",
|
||
" df[mkt_cap_col] = pd.to_numeric(df[mkt_cap_col], errors='coerce')\n",
|
||
" df_cleaned = df.dropna(subset=[mkt_cap_col]).copy()\n",
|
||
" df_cleaned = df_cleaned[df_cleaned[mkt_cap_col] > 0].copy()\n",
|
||
"\n",
|
||
" if df_cleaned.empty:\n",
|
||
" print(\"警告: 清理市值异常值后 DataFrame 为空。\")\n",
|
||
" return df # 返回原始或空df,取决于清理前的状态\n",
|
||
"\n",
|
||
" processed_df = df\n",
|
||
"\n",
|
||
" for col in features:\n",
|
||
" if col not in df_cleaned.columns:\n",
|
||
" print(f\"警告: 特征列 '{col}' 不存在于清理后的 DataFrame 中,已跳过。\")\n",
|
||
" # 对于原始 df 中该列不存在的,在结果 df 中也保持原样(可能全是NaN)\n",
|
||
" processed_df[col] = df[col] if col in df.columns else np.nan\n",
|
||
" continue\n",
|
||
"\n",
|
||
" # 跳过对控制变量本身进行中性化\n",
|
||
" if col == mkt_cap_col or col == industry_col:\n",
|
||
" print(f\"警告: 特征列 '{col}' 是控制变量或内部使用的列,跳过中性化。\")\n",
|
||
" # 在结果 df 中也保持原样\n",
|
||
" processed_df[col] = df[col] if col in df.columns else np.nan\n",
|
||
" continue\n",
|
||
"\n",
|
||
" residual_series = pd.Series(index=df_cleaned.index, dtype=float)\n",
|
||
"\n",
|
||
" # 在分组前处理特征列的 NaN,只对有因子值的行进行回归计算\n",
|
||
" df_subset_factor = df_cleaned.dropna(subset=[col]).copy()\n",
|
||
"\n",
|
||
" if not df_subset_factor.empty:\n",
|
||
" for industry, group in df_subset_factor.groupby(industry_col):\n",
|
||
" x = group[mkt_cap_col] # 市值对数\n",
|
||
" y = group[col] # 因子值\n",
|
||
"\n",
|
||
" # 确保有足够的数据点 (>1) 且市值对数有方差 (>0) 进行回归计算\n",
|
||
" # 检查 np.var > 一个很小的正数,避免浮点数误差导致的零方差判断问题\n",
|
||
" if len(group) > 1 and np.var(x) > 1e-9:\n",
|
||
" try:\n",
|
||
" beta = np.cov(y, x)[0, 1] / np.var(x)\n",
|
||
" alpha = np.mean(y) - beta * np.mean(x)\n",
|
||
"\n",
|
||
" # 计算残差\n",
|
||
" resid = y - (alpha + beta * x)\n",
|
||
"\n",
|
||
" # 将计算出的残差存储到 residual_series 中,通过索引自动对齐\n",
|
||
" residual_series.loc[resid.index] = resid\n",
|
||
"\n",
|
||
" except Exception as e:\n",
|
||
" # 捕获可能的计算异常,例如np.cov或np.var因为极端数据报错\n",
|
||
" print(f\"警告: 在行业 {industry} 计算回归时发生错误: {e}。该组残差将设为原始值或 NaN。\")\n",
|
||
" # 此时该组的残差会保持 residual_series 初始化时的 NaN 或后续处理\n",
|
||
" # 也可以选择保留原始值:residual_series.loc[group.index] = group[col]\n",
|
||
"\n",
|
||
" else:\n",
|
||
" residual_series.loc[group.index] = group[col] # 保留原始因子值\n",
|
||
" processed_df.loc[residual_series.index, col] = residual_series\n",
|
||
"\n",
|
||
"\n",
|
||
" else:\n",
|
||
" processed_df[col] = np.nan # 或 df[col] if col in df.columns else np.nan\n",
|
||
"\n",
|
||
" return processed_df\n",
|
||
"\n",
|
||
"\n",
|
||
"import gc\n",
|
||
"\n",
|
||
"gc.collect()\n",
|
||
"\n",
|
||
"\n",
|
||
"def mad_filter(df, features, n=3):\n",
|
||
" for col in features:\n",
|
||
" median = df[col].median()\n",
|
||
" mad = np.median(np.abs(df[col] - median))\n",
|
||
" upper = median + n * mad\n",
|
||
" lower = median - n * mad\n",
|
||
" df[col] = np.clip(df[col], lower, upper) # 截断极值\n",
|
||
" return df\n",
|
||
"\n",
|
||
"\n",
|
||
"def percentile_filter(df, features, lower_percentile=0.01, upper_percentile=0.99):\n",
|
||
" for col in features:\n",
|
||
" # 按日期分组计算上下百分位数\n",
|
||
" lower_bound = df.groupby('trade_date')[col].transform(\n",
|
||
" lambda x: x.quantile(lower_percentile)\n",
|
||
" )\n",
|
||
" upper_bound = df.groupby('trade_date')[col].transform(\n",
|
||
" lambda x: x.quantile(upper_percentile)\n",
|
||
" )\n",
|
||
" # 截断超出范围的值\n",
|
||
" df[col] = np.clip(df[col], lower_bound, upper_bound)\n",
|
||
" return df\n",
|
||
"\n",
|
||
"\n",
|
||
"from scipy.stats import iqr\n",
|
||
"\n",
|
||
"\n",
|
||
"def iqr_filter(df, features):\n",
|
||
" for col in features:\n",
|
||
" df[col] = df.groupby('trade_date')[col].transform(\n",
|
||
" lambda x: (x - x.median()) / iqr(x) if iqr(x) != 0 else x\n",
|
||
" )\n",
|
||
" return df\n",
|
||
"\n",
|
||
"\n",
|
||
"def quantile_filter(df, features, lower_quantile=0.01, upper_quantile=0.99, window=60):\n",
|
||
" df = df.copy()\n",
|
||
" for col in features:\n",
|
||
" # 计算 rolling 统计量,需要按日期进行 groupby\n",
|
||
" rolling_lower = df.groupby('trade_date')[col].transform(lambda x: x.rolling(window=min(len(x), window)).quantile(lower_quantile))\n",
|
||
" rolling_upper = df.groupby('trade_date')[col].transform(lambda x: x.rolling(window=min(len(x), window)).quantile(upper_quantile))\n",
|
||
"\n",
|
||
" # 对数据进行裁剪\n",
|
||
" df[col] = np.clip(df[col], rolling_lower, rolling_upper)\n",
|
||
" \n",
|
||
" return df\n",
|
||
"\n",
|
||
"def select_top_features_by_rankic(df: pd.DataFrame, feature_columns: list, n: int, target_column: str = 'future_return') -> list:\n",
|
||
" \"\"\"\n",
|
||
" 计算给定特征与目标列的 RankIC,并返回 RankIC 绝对值最高的 n 个特征。\n",
|
||
"\n",
|
||
" Args:\n",
|
||
" df: 包含特征列和目标列的 Pandas DataFrame。\n",
|
||
" feature_columns: 包含所有待评估特征列名的列表。\n",
|
||
" n: 希望选取的 RankIC 绝对值最高的特征数量。\n",
|
||
" target_column: 目标列的名称,用于计算 RankIC。默认为 'future_return'。\n",
|
||
"\n",
|
||
" Returns:\n",
|
||
" 包含 RankIC 绝对值最高的 n 个特征列名的列表。\n",
|
||
" \"\"\"\n",
|
||
" numeric_columns = df.select_dtypes(include=['float64', 'int64']).columns\n",
|
||
" numeric_columns = [col for col in numeric_columns if col in feature_columns]\n",
|
||
" if target_column not in df.columns:\n",
|
||
" raise ValueError(f\"目标列 '{target_column}' 不存在于 DataFrame 中。\")\n",
|
||
"\n",
|
||
" rankic_scores = {}\n",
|
||
" for feature in numeric_columns:\n",
|
||
" if feature not in df.columns:\n",
|
||
" print(f\"警告: 特征列 '{feature}' 不存在于 DataFrame 中,已跳过。\")\n",
|
||
" continue\n",
|
||
"\n",
|
||
" # 计算特征与目标列的 RankIC (斯皮尔曼相关系数)\n",
|
||
" # dropna() 是为了处理缺失值,确保相关性计算不失败\n",
|
||
" valid_data = df[[feature, target_column]].dropna()\n",
|
||
" if len(valid_data) > 1: # 确保有足够的数据点进行相关性计算\n",
|
||
" # 计算斯皮尔曼相关性\n",
|
||
" correlation = valid_data[feature].corr(valid_data[target_column], method='spearman')\n",
|
||
" rankic_scores[feature] = abs(correlation) # 使用绝对值来衡量相关性强度\n",
|
||
" else:\n",
|
||
" rankic_scores[feature] = 0 # 数据不足,RankIC设为0或跳过\n",
|
||
"\n",
|
||
" # 将 RankIC 分数转换为 Series 便于排序\n",
|
||
" rankic_series = pd.Series(rankic_scores)\n",
|
||
"\n",
|
||
" # 按 RankIC 绝对值降序排序,选取前 n 个特征\n",
|
||
" # handle case where n might be larger than available features\n",
|
||
" n_actual = min(n, len(rankic_series))\n",
|
||
" top_features = rankic_series.sort_values(ascending=False).head(n_actual).index.tolist()\n",
|
||
" top_features = [col for col in feature_columns if col in top_features or col not in numeric_columns]\n",
|
||
" return top_features\n",
|
||
"\n",
|
||
"def create_deviation_within_dates(df, feature_columns):\n",
|
||
" groupby_col = 'cat_l2_code' # 使用 trade_date 进行分组\n",
|
||
" new_columns = {}\n",
|
||
" ret_feature_columns = feature_columns[:]\n",
|
||
"\n",
|
||
" # 自动选择所有数值型特征\n",
|
||
" num_features = [col for col in feature_columns if 'cat' not in col and 'index' not in col]\n",
|
||
"\n",
|
||
" # num_features = ['vol', 'pct_chg', 'turnover_rate', 'volume_ratio', 'cat_vol_spike', 'obv', 'maobv_6', 'return_5', 'return_10', 'return_20', 'std_return_5', 'std_return_15', 'std_return_90', 'std_return_90_2', 'act_factor1', 'act_factor2', 'act_factor3', 'act_factor4', 'act_factor5', 'act_factor6', 'rank_act_factor1', 'rank_act_factor2', 'rank_act_factor3', 'active_buy_volume_large', 'active_buy_volume_big', 'active_buy_volume_small', 'alpha_022', 'alpha_003', 'alpha_007', 'alpha_013']\n",
|
||
" num_features = [col for col in num_features if 'cat' not in col and 'industry' not in col]\n",
|
||
" num_features = [col for col in num_features if 'limit' not in col]\n",
|
||
" num_features = [col for col in num_features if 'cyq' not in col]\n",
|
||
"\n",
|
||
" # 遍历所有数值型特征\n",
|
||
" for feature in num_features:\n",
|
||
" if feature == 'trade_date': # 不需要对 'trade_date' 计算偏差\n",
|
||
" continue\n",
|
||
"\n",
|
||
" # grouped_mean = df.groupby(['trade_date'])[feature].transform('mean')\n",
|
||
" # deviation_col_name = f'deviation_mean_{feature}'\n",
|
||
" # new_columns[deviation_col_name] = df[feature] - grouped_mean\n",
|
||
" # ret_feature_columns.append(deviation_col_name)\n",
|
||
"\n",
|
||
" grouped_mean = df.groupby(['trade_date', groupby_col])[feature].transform('mean')\n",
|
||
" deviation_col_name = f'deviation_mean_{feature}'\n",
|
||
" new_columns[deviation_col_name] = df[feature] - grouped_mean\n",
|
||
" ret_feature_columns.append(deviation_col_name)\n",
|
||
"\n",
|
||
" # 将新计算的偏差特征与原始 DataFrame 合并\n",
|
||
" df = pd.concat([df, pd.DataFrame(new_columns)], axis=1)\n",
|
||
"\n",
|
||
" # for feature in ['obv', 'return_20', 'act_factor1', 'act_factor2', 'act_factor3', 'act_factor4']:\n",
|
||
" # df[f'deviation_industry_{feature}'] = df[feature] - df[f'industry_{feature}']\n",
|
||
"\n",
|
||
" return df, ret_feature_columns\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 13,
|
||
"id": "47c12bb34062ae7a",
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-04-03T14:57:50.841165Z",
|
||
"start_time": "2025-04-03T14:49:25.889057Z"
|
||
}
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"days = 5\n",
|
||
"validation_days = 120\n",
|
||
"\n",
|
||
"import gc\n",
|
||
"\n",
|
||
"gc.collect()\n",
|
||
"\n",
|
||
"df = df.sort_values(by=['ts_code', 'trade_date'])\n",
|
||
"df['future_return'] = df.groupby('ts_code', group_keys=False)['close'].apply(lambda x: x.shift(-days) / x - 1)\n",
|
||
"# df['future_return'] = (df.groupby('ts_code')['close'].shift(-days) - df.groupby('ts_code')['open'].shift(-1)) / \\\n",
|
||
"# df.groupby('ts_code')['open'].shift(-1)\n",
|
||
"\n",
|
||
"df['cat_up_limit'] = df['pct_chg'] > 5\n",
|
||
"df['label'] = df.groupby('ts_code')['cat_up_limit'].rolling(window=5, min_periods=1).max().groupby('ts_code').shift(-5).fillna(0).astype(int).reset_index(level=0, drop=True)\n",
|
||
"\n",
|
||
"filter_index = df['future_return'].between(df['future_return'].quantile(0.01), df['future_return'].quantile(0.99))\n",
|
||
"\n",
|
||
"# for col in [col for col in df.columns]:\n",
|
||
"# train_data[col] = train_data[col].astype('str')\n",
|
||
"# test_data[col] = test_data[col].astype('str')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 14,
|
||
"id": "29221dde",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"191\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"feature_columns = [col for col in df.head(10).merge(industry_df, on=['cat_l2_code', 'trade_date'], how='left').merge(index_data, on='trade_date', how='left').columns]\n",
|
||
"feature_columns = [col for col in feature_columns if col not in ['trade_date',\n",
|
||
" 'ts_code',\n",
|
||
" 'label']]\n",
|
||
"feature_columns = [col for col in feature_columns if 'future' not in col]\n",
|
||
"feature_columns = [col for col in feature_columns if 'label' not in col]\n",
|
||
"feature_columns = [col for col in feature_columns if 'score' not in col]\n",
|
||
"feature_columns = [col for col in feature_columns if 'gen' not in col]\n",
|
||
"feature_columns = [col for col in feature_columns if 'is_st' not in col]\n",
|
||
"feature_columns = [col for col in feature_columns if 'pe_ttm' not in col]\n",
|
||
"# feature_columns = [col for col in feature_columns if 'volatility' not in col]\n",
|
||
"feature_columns = [col for col in feature_columns if 'circ_mv' not in col]\n",
|
||
"feature_columns = [col for col in feature_columns if 'code' not in col]\n",
|
||
"feature_columns = [col for col in feature_columns if col not in origin_columns]\n",
|
||
"feature_columns = [col for col in feature_columns if not col.startswith('_')]\n",
|
||
"# feature_columns = [col for col in feature_columns if col not in ['ts_code', 'trade_date', 'vol_std_5', 'cov', 'delta_cov', 'alpha_22_improved', 'alpha_007', 'consecutive_up_limit', 'mv_volatility', 'volume_growth', 'mv_growth', 'arbr']]\n",
|
||
"feature_columns = [col for col in feature_columns if col not in ['intraday_lg_flow_corr_20', \n",
|
||
" 'cap_neutral_cost_metric', \n",
|
||
" 'hurst_net_mf_vol_60', \n",
|
||
" 'complex_factor_deap_1', \n",
|
||
" 'lg_buy_consolidation_20',\n",
|
||
" 'cs_rank_ind_cap_neutral_pe',\n",
|
||
" 'cs_rank_opening_gap',\n",
|
||
" 'cs_rank_ind_adj_lg_flow']]\n",
|
||
"feature_columns = [col for col in feature_columns if col not in ['roa', 'roe']]\n",
|
||
"print(len(feature_columns))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 15,
|
||
"id": "03ee5daf",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"# df = fill_nan_with_daily_median(df, feature_columns)\n",
|
||
"for feature_col in [col for col in feature_columns if col in df.columns]:\n",
|
||
" # median_val = df[feature_col].median()\n",
|
||
" df[feature_col].fillna(0, inplace=True)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 16,
|
||
"id": "b76ea08a",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
" ts_code trade_date log_circ_mv\n",
|
||
"0 000001.SZ 2019-01-02 16.574219\n",
|
||
"1 000001.SZ 2019-01-03 16.583965\n",
|
||
"2 000001.SZ 2019-01-04 16.633371\n",
|
||
"['vol', 'pct_chg', 'turnover_rate', 'volume_ratio', 'winner_rate', 'undist_profit_ps', 'ocfps', 'AR', 'BR', 'AR_BR', 'cashflow_to_ev_factor', 'book_to_price_ratio', 'turnover_rate_mean_5', 'variance_20', 'bbi_ratio_factor', 'daily_deviation', 'lg_elg_net_buy_vol', 'flow_lg_elg_intensity', 'sm_net_buy_vol', 'total_buy_vol', 'lg_elg_buy_prop', 'flow_struct_buy_change', 'lg_elg_net_buy_vol_change', 'flow_lg_elg_accel', 'chip_concentration_range', 'chip_skewness', 'floating_chip_proxy', 'cost_support_15pct_change', 'cat_winner_price_zone', 'flow_chip_consistency', 'profit_taking_vs_absorb', 'cat_is_positive', 'upside_vol', 'downside_vol', 'vol_ratio', 'return_skew', 'return_kurtosis', 'volume_change_rate', 'cat_volume_breakout', 'turnover_deviation', 'cat_turnover_spike', 'avg_volume_ratio', 'cat_volume_ratio_breakout', 'vol_spike', 'vol_std_5', 'atr_14', 'atr_6', 'obv', 'maobv_6', 'rsi_3', 'return_5', 'return_20', 'std_return_5', 'std_return_90', 'std_return_90_2', 'act_factor1', 'act_factor2', 'act_factor3', 'act_factor4', 'rank_act_factor1', 'rank_act_factor2', 'rank_act_factor3', 'cov', 'delta_cov', 'alpha_22_improved', 'alpha_003', 'alpha_007', 'alpha_013', 'vol_break', 'weight_roc5', 'smallcap_concentration', 'cost_stability', 'high_cost_break_days', 'liquidity_risk', 'turnover_std', 'mv_volatility', 'volume_growth', 'mv_growth', 'momentum_factor', 'resonance_factor', 'log_close', 'cat_vol_spike', 'up', 'down', 'obv_maobv_6', 'std_return_5_over_std_return_90', 'std_return_90_minus_std_return_90_2', 'cat_af2', 'cat_af3', 'cat_af4', 'act_factor5', 'act_factor6', 'active_buy_volume_large', 'active_buy_volume_big', 'active_buy_volume_small', 'buy_lg_vol_minus_sell_lg_vol', 'buy_elg_vol_minus_sell_elg_vol', 'ctrl_strength', 'low_cost_dev', 'asymmetry', 'lock_factor', 'cat_vol_break', 'cost_atr_adj', 'cat_golden_resonance', 'mv_turnover_ratio', 'mv_adjusted_volume', 'mv_weighted_turnover', 'nonlinear_mv_volume', 'mv_volume_ratio', 'mv_momentum', 'lg_flow_mom_corr_20_60', 'lg_flow_accel', 'profit_pressure', 'underwater_resistance', 'cost_conc_std_20', 'profit_decay_20', 'vol_amp_loss_20', 'vol_drop_profit_cnt_5', 'lg_flow_vol_interact_20', 'cost_break_confirm_cnt_5', 'atr_norm_channel_pos_14', 'turnover_diff_skew_20', 'lg_sm_flow_diverge_20', 'pullback_strong_20_20', 'vol_wgt_hist_pos_20', 'vol_adj_roc_20', 'cs_rank_net_lg_flow_val', 'cs_rank_elg_buy_ratio', 'cs_rank_rel_profit_margin', 'cs_rank_cost_breadth', 'cs_rank_dist_to_upper_cost', 'cs_rank_winner_rate', 'cs_rank_intraday_range', 'cs_rank_close_pos_in_range', 'cs_rank_pos_in_hist_range', 'cs_rank_vol_x_profit_margin', 'cs_rank_lg_flow_price_concordance', 'cs_rank_turnover_per_winner', 'cs_rank_volume_ratio', 'cs_rank_elg_buy_sell_sm_ratio', 'cs_rank_cost_dist_vol_ratio', 'cs_rank_size', 'cat_up_limit', 'industry_obv', 'industry_return_5', 'industry_return_20', 'industry__ema_5', 'industry__ema_13', 'industry__ema_20', 'industry__ema_60', 'industry_act_factor1', 'industry_act_factor2', 'industry_act_factor3', 'industry_act_factor4', 'industry_act_factor5', 'industry_act_factor6', 'industry_rank_act_factor1', 'industry_rank_act_factor2', 'industry_rank_act_factor3', 'industry_return_5_percentile', 'industry_return_20_percentile', '000852.SH_MACD', '000905.SH_MACD', '399006.SZ_MACD', '000852.SH_MACD_hist', '000905.SH_MACD_hist', '399006.SZ_MACD_hist', '000852.SH_RSI', '000905.SH_RSI', '399006.SZ_RSI', '000852.SH_Signal_line', '000905.SH_Signal_line', '399006.SZ_Signal_line', '000852.SH_amount_change_rate', '000905.SH_amount_change_rate', '399006.SZ_amount_change_rate', '000852.SH_amount_mean', '000905.SH_amount_mean', '399006.SZ_amount_mean', '000852.SH_daily_return', '000905.SH_daily_return', '399006.SZ_daily_return', '000852.SH_up_ratio_20d', '000905.SH_up_ratio_20d', '399006.SZ_up_ratio_20d', '000852.SH_volatility', '000905.SH_volatility', '399006.SZ_volatility', '000852.SH_volume_change_rate', '000905.SH_volume_change_rate', '399006.SZ_volume_change_rate']\n",
|
||
"去除极值\n",
|
||
"开始截面 MAD 去极值处理 (k=3.0)...\n"
|
||
]
|
||
},
|
||
{
|
||
"name": "stderr",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"MAD Filtering: 100%|██████████| 131/131 [00:28<00:00, 4.67it/s]\n"
|
||
]
|
||
},
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"截面 MAD 去极值处理完成。\n",
|
||
"开始截面 MAD 去极值处理 (k=3.0)...\n"
|
||
]
|
||
},
|
||
{
|
||
"name": "stderr",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"MAD Filtering: 100%|██████████| 131/131 [00:24<00:00, 5.43it/s]\n"
|
||
]
|
||
},
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"截面 MAD 去极值处理完成。\n",
|
||
"开始截面 MAD 去极值处理 (k=3.0)...\n"
|
||
]
|
||
},
|
||
{
|
||
"name": "stderr",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"MAD Filtering: 0it [00:00, ?it/s]\n"
|
||
]
|
||
},
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"截面 MAD 去极值处理完成。\n",
|
||
"开始截面 MAD 去极值处理 (k=3.0)...\n"
|
||
]
|
||
},
|
||
{
|
||
"name": "stderr",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"MAD Filtering: 0it [00:00, ?it/s]\n"
|
||
]
|
||
},
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"截面 MAD 去极值处理完成。\n",
|
||
"feature_columns: ['vol', 'pct_chg', 'turnover_rate', 'volume_ratio', 'winner_rate', 'undist_profit_ps', 'ocfps', 'AR', 'BR', 'AR_BR', 'cashflow_to_ev_factor', 'book_to_price_ratio', 'turnover_rate_mean_5', 'variance_20', 'bbi_ratio_factor', 'daily_deviation', 'lg_elg_net_buy_vol', 'flow_lg_elg_intensity', 'sm_net_buy_vol', 'total_buy_vol', 'lg_elg_buy_prop', 'flow_struct_buy_change', 'lg_elg_net_buy_vol_change', 'flow_lg_elg_accel', 'chip_concentration_range', 'chip_skewness', 'floating_chip_proxy', 'cost_support_15pct_change', 'cat_winner_price_zone', 'flow_chip_consistency', 'profit_taking_vs_absorb', 'cat_is_positive', 'upside_vol', 'downside_vol', 'vol_ratio', 'return_skew', 'return_kurtosis', 'volume_change_rate', 'cat_volume_breakout', 'turnover_deviation', 'cat_turnover_spike', 'avg_volume_ratio', 'cat_volume_ratio_breakout', 'vol_spike', 'vol_std_5', 'atr_14', 'atr_6', 'obv', 'maobv_6', 'rsi_3', 'return_5', 'return_20', 'std_return_5', 'std_return_90', 'std_return_90_2', 'act_factor1', 'act_factor2', 'act_factor3', 'act_factor4', 'rank_act_factor1', 'rank_act_factor2', 'rank_act_factor3', 'cov', 'delta_cov', 'alpha_22_improved', 'alpha_003', 'alpha_007', 'alpha_013', 'vol_break', 'weight_roc5', 'smallcap_concentration', 'cost_stability', 'high_cost_break_days', 'liquidity_risk', 'turnover_std', 'mv_volatility', 'volume_growth', 'mv_growth', 'momentum_factor', 'resonance_factor', 'log_close', 'cat_vol_spike', 'up', 'down', 'obv_maobv_6', 'std_return_5_over_std_return_90', 'std_return_90_minus_std_return_90_2', 'cat_af2', 'cat_af3', 'cat_af4', 'act_factor5', 'act_factor6', 'active_buy_volume_large', 'active_buy_volume_big', 'active_buy_volume_small', 'buy_lg_vol_minus_sell_lg_vol', 'buy_elg_vol_minus_sell_elg_vol', 'ctrl_strength', 'low_cost_dev', 'asymmetry', 'lock_factor', 'cat_vol_break', 'cost_atr_adj', 'cat_golden_resonance', 'mv_turnover_ratio', 'mv_adjusted_volume', 'mv_weighted_turnover', 'nonlinear_mv_volume', 'mv_volume_ratio', 'mv_momentum', 'lg_flow_mom_corr_20_60', 'lg_flow_accel', 'profit_pressure', 'underwater_resistance', 'cost_conc_std_20', 'profit_decay_20', 'vol_amp_loss_20', 'vol_drop_profit_cnt_5', 'lg_flow_vol_interact_20', 'cost_break_confirm_cnt_5', 'atr_norm_channel_pos_14', 'turnover_diff_skew_20', 'lg_sm_flow_diverge_20', 'pullback_strong_20_20', 'vol_wgt_hist_pos_20', 'vol_adj_roc_20', 'cs_rank_net_lg_flow_val', 'cs_rank_elg_buy_ratio', 'cs_rank_rel_profit_margin', 'cs_rank_cost_breadth', 'cs_rank_dist_to_upper_cost', 'cs_rank_winner_rate', 'cs_rank_intraday_range', 'cs_rank_close_pos_in_range', 'cs_rank_pos_in_hist_range', 'cs_rank_vol_x_profit_margin', 'cs_rank_lg_flow_price_concordance', 'cs_rank_turnover_per_winner', 'cs_rank_volume_ratio', 'cs_rank_elg_buy_sell_sm_ratio', 'cs_rank_cost_dist_vol_ratio', 'cs_rank_size', 'cat_up_limit', 'industry_obv', 'industry_return_5', 'industry_return_20', 'industry__ema_5', 'industry__ema_13', 'industry__ema_20', 'industry__ema_60', 'industry_act_factor1', 'industry_act_factor2', 'industry_act_factor3', 'industry_act_factor4', 'industry_act_factor5', 'industry_act_factor6', 'industry_rank_act_factor1', 'industry_rank_act_factor2', 'industry_rank_act_factor3', 'industry_return_5_percentile', 'industry_return_20_percentile', '000852.SH_MACD', '000905.SH_MACD', '399006.SZ_MACD', '000852.SH_MACD_hist', '000905.SH_MACD_hist', '399006.SZ_MACD_hist', '000852.SH_RSI', '000905.SH_RSI', '399006.SZ_RSI', '000852.SH_Signal_line', '000905.SH_Signal_line', '399006.SZ_Signal_line', '000852.SH_amount_change_rate', '000905.SH_amount_change_rate', '399006.SZ_amount_change_rate', '000852.SH_amount_mean', '000905.SH_amount_mean', '399006.SZ_amount_mean', '000852.SH_daily_return', '000905.SH_daily_return', '399006.SZ_daily_return', '000852.SH_up_ratio_20d', '000905.SH_up_ratio_20d', '399006.SZ_up_ratio_20d', '000852.SH_volatility', '000905.SH_volatility', '399006.SZ_volatility', '000852.SH_volume_change_rate', '000905.SH_volume_change_rate', '399006.SZ_volume_change_rate']\n",
|
||
"df最小日期: 2019-01-02\n",
|
||
"df最大日期: 2025-05-23\n",
|
||
"2057539\n",
|
||
"train_data最小日期: 2020-01-02\n",
|
||
"train_data最大日期: 2022-12-30\n",
|
||
"1766694\n",
|
||
"test_data最小日期: 2023-01-03\n",
|
||
"test_data最大日期: 2025-05-23\n",
|
||
" ts_code trade_date log_circ_mv\n",
|
||
"0 000001.SZ 2019-01-02 16.574219\n",
|
||
"1 000001.SZ 2019-01-03 16.583965\n",
|
||
"2 000001.SZ 2019-01-04 16.633371\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"split_date = '2023-01-01'\n",
|
||
"train_data = df[filter_index & (df['trade_date'] <= split_date) & (df['trade_date'] >= '2020-01-01')]\n",
|
||
"test_data = df[(df['trade_date'] >= split_date)]\n",
|
||
"\n",
|
||
"print(df[['ts_code', 'trade_date', 'log_circ_mv']].head(3))\n",
|
||
"\n",
|
||
"industry_df = industry_df.sort_values(by=['trade_date'])\n",
|
||
"index_data = index_data.sort_values(by=['trade_date'])\n",
|
||
"\n",
|
||
"# train_data = train_data.merge(industry_df, on=['cat_l2_code', 'trade_date'], how='left')\n",
|
||
"# train_data = train_data.merge(index_data, on='trade_date', how='left')\n",
|
||
"# test_data = test_data.merge(industry_df, on=['cat_l2_code', 'trade_date'], how='left')\n",
|
||
"# test_data = test_data.merge(index_data, on='trade_date', how='left')\n",
|
||
"\n",
|
||
"train_data, test_data = train_data.replace([np.inf, -np.inf], np.nan), test_data.replace([np.inf, -np.inf], np.nan)\n",
|
||
"\n",
|
||
"# feature_columns_new = feature_columns[:]\n",
|
||
"# train_data, _ = create_deviation_within_dates(train_data, [col for col in feature_columns if col in train_data.columns])\n",
|
||
"# test_data, _ = create_deviation_within_dates(test_data, [col for col in feature_columns if col in train_data.columns])\n",
|
||
"\n",
|
||
"# feature_columns = [\n",
|
||
"# 'undist_profit_ps', \n",
|
||
"# 'AR_BR',\n",
|
||
"# 'pe_ttm',\n",
|
||
"# 'alpha_22_improved', \n",
|
||
"# 'alpha_003', \n",
|
||
"# 'alpha_007', \n",
|
||
"# 'alpha_013', \n",
|
||
"# 'cat_up_limit', \n",
|
||
"# 'cat_down_limit', \n",
|
||
"# 'up_limit_count_10d', \n",
|
||
"# 'down_limit_count_10d', \n",
|
||
"# 'consecutive_up_limit', \n",
|
||
"# 'vol_break', \n",
|
||
"# 'weight_roc5', \n",
|
||
"# 'price_cost_divergence', \n",
|
||
"# 'smallcap_concentration', \n",
|
||
"# 'cost_stability', \n",
|
||
"# 'high_cost_break_days', \n",
|
||
"# 'liquidity_risk', \n",
|
||
"# 'turnover_std', \n",
|
||
"# 'mv_volatility', \n",
|
||
"# 'volume_growth', \n",
|
||
"# 'mv_growth', \n",
|
||
"# 'lg_flow_mom_corr_20_60', \n",
|
||
"# 'lg_flow_accel', \n",
|
||
"# 'profit_pressure', \n",
|
||
"# 'underwater_resistance', \n",
|
||
"# 'cost_conc_std_20', \n",
|
||
"# 'profit_decay_20', \n",
|
||
"# 'vol_amp_loss_20', \n",
|
||
"# 'vol_drop_profit_cnt_5', \n",
|
||
"# 'lg_flow_vol_interact_20', \n",
|
||
"# 'cost_break_confirm_cnt_5', \n",
|
||
"# 'atr_norm_channel_pos_14', \n",
|
||
"# 'turnover_diff_skew_20', \n",
|
||
"# 'lg_sm_flow_diverge_20', \n",
|
||
"# 'pullback_strong_20_20', \n",
|
||
"# 'vol_wgt_hist_pos_20', \n",
|
||
"# 'vol_adj_roc_20',\n",
|
||
"# 'cashflow_to_ev_factor',\n",
|
||
"# 'ocfps',\n",
|
||
"# 'book_to_price_ratio',\n",
|
||
"# 'turnover_rate_mean_5',\n",
|
||
"# 'variance_20',\n",
|
||
"# 'bbi_ratio_factor'\n",
|
||
"# ]\n",
|
||
"# feature_columns = [col for col in feature_columns if col in train_data.columns]\n",
|
||
"# feature_columns = [col for col in feature_columns if not col.startswith('_')]\n",
|
||
"\n",
|
||
"numeric_columns = df.select_dtypes(include=['float64', 'int64']).columns\n",
|
||
"numeric_columns = [col for col in numeric_columns if col in feature_columns]\n",
|
||
"# feature_columns = select_top_features_by_rankic(df, numeric_columns, n=10)\n",
|
||
"print(feature_columns)\n",
|
||
"\n",
|
||
"# train_data = fill_nan_with_daily_median(train_data, feature_columns)\n",
|
||
"# test_data = fill_nan_with_daily_median(test_data, feature_columns)\n",
|
||
"\n",
|
||
"train_data = train_data.dropna(subset=[col for col in feature_columns if col in train_data.columns])\n",
|
||
"train_data = train_data.dropna(subset=['label'])\n",
|
||
"train_data = train_data.reset_index(drop=True)\n",
|
||
"# print(test_data.tail())\n",
|
||
"test_data = test_data.dropna(subset=[col for col in feature_columns if col in train_data.columns])\n",
|
||
"# test_data = test_data.dropna(subset=['label'])\n",
|
||
"test_data = test_data.reset_index(drop=True)\n",
|
||
"\n",
|
||
"transform_feature_columns = feature_columns\n",
|
||
"transform_feature_columns = [col for col in transform_feature_columns if col in feature_columns and not col.startswith('cat') and col in train_data.columns]\n",
|
||
"# transform_feature_columns.remove('undist_profit_ps')\n",
|
||
"print('去除极值')\n",
|
||
"cs_mad_filter(train_data, transform_feature_columns)\n",
|
||
"# print('中性化')\n",
|
||
"# cs_neutralize_market_cap_numpy(train_data, transform_feature_columns)\n",
|
||
"# print('标准化')\n",
|
||
"# cs_zscore_standardize(train_data, transform_feature_columns)\n",
|
||
"\n",
|
||
"cs_mad_filter(test_data, transform_feature_columns)\n",
|
||
"# cs_neutralize_market_cap_numpy(test_data, transform_feature_columns)\n",
|
||
"# cs_zscore_standardize(test_data, transform_feature_columns)\n",
|
||
"\n",
|
||
"mad_filter_feature_columns = [col for col in feature_columns if col not in transform_feature_columns and not col.startswith('cat') and col in train_data.columns]\n",
|
||
"cs_mad_filter(train_data, mad_filter_feature_columns)\n",
|
||
"cs_mad_filter(test_data, mad_filter_feature_columns)\n",
|
||
"\n",
|
||
"\n",
|
||
"print(f'feature_columns: {feature_columns}')\n",
|
||
"\n",
|
||
"\n",
|
||
"print(f\"df最小日期: {df['trade_date'].min().strftime('%Y-%m-%d')}\")\n",
|
||
"print(f\"df最大日期: {df['trade_date'].max().strftime('%Y-%m-%d')}\")\n",
|
||
"print(len(train_data))\n",
|
||
"print(f\"train_data最小日期: {train_data['trade_date'].min().strftime('%Y-%m-%d')}\")\n",
|
||
"print(f\"train_data最大日期: {train_data['trade_date'].max().strftime('%Y-%m-%d')}\")\n",
|
||
"print(len(test_data))\n",
|
||
"print(f\"test_data最小日期: {test_data['trade_date'].min().strftime('%Y-%m-%d')}\")\n",
|
||
"print(f\"test_data最大日期: {test_data['trade_date'].max().strftime('%Y-%m-%d')}\")\n",
|
||
"\n",
|
||
"cat_columns = [col for col in feature_columns if col.startswith('cat')]\n",
|
||
"for col in cat_columns:\n",
|
||
" train_data[col] = train_data[col].astype('category')\n",
|
||
" test_data[col] = test_data[col].astype('category')\n",
|
||
"\n",
|
||
"print(df[['ts_code', 'trade_date', 'log_circ_mv']].head(3))\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 17,
|
||
"id": "3ff2d1c5",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from sklearn.preprocessing import StandardScaler\n",
|
||
"from sklearn.linear_model import LogisticRegression\n",
|
||
"import matplotlib.pyplot as plt # 保持 matplotlib 导入,尽管LightGBM的绘图功能已移除\n",
|
||
"from sklearn.decomposition import PCA\n",
|
||
"import pandas as pd\n",
|
||
"import numpy as np\n",
|
||
"import datetime # 用于日期计算\n",
|
||
"from catboost import CatBoostClassifier\n",
|
||
"from catboost import Pool\n",
|
||
"import lightgbm as lgb\n",
|
||
"\n",
|
||
"def train_model(train_data_df, feature_columns,\n",
|
||
" print_info=True, # 调整参数名,更通用\n",
|
||
" validation_days=180, use_pca=False, split_date=None,\n",
|
||
" target_column='label', type='light'): # 增加目标列参数\n",
|
||
"\n",
|
||
" print('train data size: ', len(train_data_df))\n",
|
||
" print(train_data_df[['ts_code', 'trade_date', 'log_circ_mv']])\n",
|
||
" # 确保数据按时间排序\n",
|
||
" train_data_df = train_data_df.sort_values(by='trade_date')\n",
|
||
"\n",
|
||
" # 识别数值型特征列\n",
|
||
" numeric_feature_columns = train_data_df[feature_columns].select_dtypes(include=['float64', 'int64']).columns.tolist()\n",
|
||
"\n",
|
||
" # 去除标签为空的样本\n",
|
||
" initial_len = len(train_data_df)\n",
|
||
" train_data_df = train_data_df.dropna(subset=[target_column])\n",
|
||
"\n",
|
||
" if print_info:\n",
|
||
" print(f'原始样本数: {initial_len}, 去除标签为空后样本数: {len(train_data_df)}')\n",
|
||
"\n",
|
||
" # 提取特征和标签,只取数值型特征用于线性回归\n",
|
||
" \n",
|
||
" if split_date is None:\n",
|
||
" all_dates = train_data_df['trade_date'].unique() # 获取所有唯一的 trade_date\n",
|
||
" split_date = all_dates[-validation_days] # 划分点为倒数第 validation_days 天\n",
|
||
" train_data_split = train_data_df[train_data_df['trade_date'] < split_date] # 训练集\n",
|
||
" val_data_split = train_data_df[train_data_df['trade_date'] >= split_date] # 验证集\n",
|
||
" \n",
|
||
" X_train = train_data_split[feature_columns]\n",
|
||
" y_train = train_data_split[target_column]\n",
|
||
" \n",
|
||
" X_val = val_data_split[feature_columns]\n",
|
||
" y_val = val_data_split['label']\n",
|
||
"\n",
|
||
"\n",
|
||
" # # 标准化数值特征 (使用 StandardScaler 对训练集fit并transform, 对验证集只transform)\n",
|
||
" scaler = StandardScaler()\n",
|
||
" # X_train = scaler.fit_transform(X_train)\n",
|
||
"\n",
|
||
" # 训练线性回归模型\n",
|
||
" # model = LogisticRegression(random_state=42)\n",
|
||
" \n",
|
||
" # # 使用处理后的特征和样本权重进行训练\n",
|
||
" # model.fit(X_train, y_train)\n",
|
||
"\n",
|
||
"\n",
|
||
" if type == 'cat':\n",
|
||
" params = {\n",
|
||
" 'loss_function': 'Logloss', # 适用于二分类\n",
|
||
" 'eval_metric': 'Logloss', # 评估指标\n",
|
||
" 'iterations': 1500,\n",
|
||
" 'learning_rate': 0.01,\n",
|
||
" 'depth': 10, # 控制模型复杂度\n",
|
||
" 'l2_leaf_reg': 50, # L2 正则化\n",
|
||
" 'verbose': 5000,\n",
|
||
" 'early_stopping_rounds': 300,\n",
|
||
" # 'od_type': 'Iter', # Overfitting detector type\n",
|
||
" # 'od_wait': 300, # Number of iterations to wait after the bes\n",
|
||
" 'one_hot_max_size': 50,\n",
|
||
" 'class_weights': [0.6, 1.2],\n",
|
||
" 'task_type': 'GPU',\n",
|
||
" 'has_time': True,\n",
|
||
" 'random_seed': 7\n",
|
||
" }\n",
|
||
" cat_features = [i for i, col in enumerate(feature_columns) if col.startswith('cat')]\n",
|
||
" train_pool = Pool(data=X_train, label=y_train, cat_features=cat_features)\n",
|
||
" val_pool = Pool(data=X_val, label=y_val, cat_features=cat_features)\n",
|
||
"\n",
|
||
"\n",
|
||
" model = CatBoostClassifier(**params)\n",
|
||
" model.fit(train_pool,\n",
|
||
" eval_set=val_pool, \n",
|
||
" plot=True, \n",
|
||
" use_best_model=True\n",
|
||
" )\n",
|
||
" elif type == 'light':\n",
|
||
" params = {\n",
|
||
" 'objective': 'binary',\n",
|
||
" 'metric': 'average_precision',\n",
|
||
" 'learning_rate': 0.01,\n",
|
||
" 'is_unbalance': True,\n",
|
||
" 'num_leaves': 2048,\n",
|
||
" 'min_data_in_leaf': 1024,\n",
|
||
" 'max_depth': 32,\n",
|
||
" 'max_bin': 1024,\n",
|
||
" 'feature_fraction': 0.5,\n",
|
||
" 'bagging_fraction': 0.5,\n",
|
||
" 'bagging_freq': 1,\n",
|
||
" 'lambda_l1': 50,\n",
|
||
" 'lambda_l2': 50,\n",
|
||
" 'verbosity': -1,\n",
|
||
" 'num_threads' : 8\n",
|
||
" }\n",
|
||
" categorical_feature = [col for col in feature_columns if 'cat' in col]\n",
|
||
" train_dataset = lgb.Dataset(\n",
|
||
" X_train, label=y_train,\n",
|
||
" categorical_feature=categorical_feature\n",
|
||
" )\n",
|
||
" val_dataset = lgb.Dataset(\n",
|
||
" X_val, label=y_val,\n",
|
||
" categorical_feature=categorical_feature\n",
|
||
" )\n",
|
||
"\n",
|
||
" evals = {}\n",
|
||
" callbacks = [lgb.log_evaluation(period=1000),\n",
|
||
" lgb.callback.record_evaluation(evals),\n",
|
||
" lgb.early_stopping(100, first_metric_only=True)\n",
|
||
" ]\n",
|
||
" # 训练模型\n",
|
||
" model = lgb.train(\n",
|
||
" params, train_dataset, num_boost_round=1000,\n",
|
||
" valid_sets=[train_dataset, val_dataset], valid_names=['train', 'valid'],\n",
|
||
" callbacks=callbacks\n",
|
||
" )\n",
|
||
"\n",
|
||
" # 打印特征重要性(如果需要)\n",
|
||
" if True:\n",
|
||
" lgb.plot_metric(evals)\n",
|
||
" lgb.plot_importance(model, importance_type='split', max_num_features=20)\n",
|
||
" plt.show()\n",
|
||
"\n",
|
||
"\n",
|
||
" return model, scaler, None # 返回训练好的模型、scaler 和 pca 对象"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 18,
|
||
"id": "c6eb5cd4-e714-420a-ac48-39af3e11ee81",
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-04-03T15:03:18.426481Z",
|
||
"start_time": "2025-04-03T15:02:19.926352Z"
|
||
}
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"train data size: 36400\n",
|
||
" ts_code trade_date log_circ_mv\n",
|
||
"0 600306.SH 2020-01-02 11.552040\n",
|
||
"1 603269.SH 2020-01-02 11.324801\n",
|
||
"2 002633.SZ 2020-01-02 11.759023\n",
|
||
"3 603991.SH 2020-01-02 11.181150\n",
|
||
"4 000691.SZ 2020-01-02 11.677910\n",
|
||
"... ... ... ...\n",
|
||
"36395 600615.SH 2022-12-30 12.027909\n",
|
||
"36396 603829.SH 2022-12-30 12.034572\n",
|
||
"36397 603037.SH 2022-12-30 12.035767\n",
|
||
"36398 002767.SZ 2022-12-30 11.896427\n",
|
||
"36399 600561.SH 2022-12-30 11.858571\n",
|
||
"\n",
|
||
"[36400 rows x 3 columns]\n",
|
||
"原始样本数: 36400, 去除标签为空后样本数: 36400\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"application/vnd.jupyter.widget-view+json": {
|
||
"model_id": "b132fdab733b4d4e856d4688f2997dac",
|
||
"version_major": 2,
|
||
"version_minor": 0
|
||
},
|
||
"text/plain": [
|
||
"MetricVisualizer(layout=Layout(align_self='stretch', height='500px'))"
|
||
]
|
||
},
|
||
"metadata": {},
|
||
"output_type": "display_data"
|
||
},
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"0:\tlearn: 0.6887138\ttest: 0.6894516\tbest: 0.6894516 (0)\ttotal: 328ms\tremaining: 8m 12s\n",
|
||
"bestTest = 0.5217666894\n",
|
||
"bestIteration = 487\n",
|
||
"Shrink model to first 488 iterations.\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"\n",
|
||
"gc.collect()\n",
|
||
"\n",
|
||
"use_pca = False\n",
|
||
"type = 'cat'\n",
|
||
"# feature_contri = [2 if feat.startswith('act_factor') or 'buy' in feat or 'sell' in feat else 1 for feat in feature_columns]\n",
|
||
"# light_params['feature_contri'] = feature_contri\n",
|
||
"# print(f'feature_contri: {feature_contri}')\n",
|
||
"model, scaler, pca = train_model(train_data\n",
|
||
" .dropna(subset=['label']).groupby('trade_date', group_keys=False)\n",
|
||
" .apply(lambda x: x.nsmallest(50, 'total_mv'))\n",
|
||
" .merge(industry_df, on=['cat_l2_code', 'trade_date'], how='left')\n",
|
||
" .merge(index_data, on='trade_date', how='left'), feature_columns, type=type)\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 19,
|
||
"id": "5d1522a7538db91b",
|
||
"metadata": {
|
||
"ExecuteTime": {
|
||
"end_time": "2025-04-03T15:04:39.656944Z",
|
||
"start_time": "2025-04-03T15:04:39.298483Z"
|
||
}
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"score_df = test_data.groupby('trade_date', group_keys=False).apply(lambda x: x.nsmallest(300, 'total_mv'))\n",
|
||
"# score_df = fill_nan_with_daily_median(score_df, ['pe_ttm'])\n",
|
||
"# score_df = score_df[score_df['pe_ttm'] > 0]\n",
|
||
"score_df = score_df.merge(industry_df, on=['cat_l2_code', 'trade_date'], how='left')\n",
|
||
"score_df = score_df.merge(index_data, on='trade_date', how='left')\n",
|
||
"# score_df = score_df.groupby('trade_date', group_keys=False).apply(lambda x: x.nsmallest(50, 'total_mv')).reset_index()\n",
|
||
"numeric_columns = score_df.select_dtypes(include=['float64', 'int64']).columns\n",
|
||
"numeric_columns = [col for col in feature_columns if col in numeric_columns]\n",
|
||
"# score_df.loc[:, numeric_columns] = scaler.transform(score_df[numeric_columns])\n",
|
||
"# score_df = cross_sectional_standardization(score_df, numeric_columns)\n",
|
||
"\n",
|
||
"if type == 'cat':\n",
|
||
" score_df['score'] = model.predict_proba(score_df[feature_columns])[:, 1]\n",
|
||
"elif type == 'light':\n",
|
||
" score_df['score'] = model.predict(score_df[feature_columns])\n",
|
||
"score_df['score_ranks'] = score_df.groupby('trade_date')['score'].rank(ascending=True)\n",
|
||
"\n",
|
||
"score_df = score_df.groupby('trade_date', group_keys=False).apply(\n",
|
||
" lambda x: x[x['score'] >= x['score'].quantile(0.90)] # 计算90%分位数作为阈值,筛选分数>=阈值的行\n",
|
||
").reset_index(drop=True) # drop=True 避免添加旧索引列\n",
|
||
"# save_df = score_df.groupby('trade_date', group_keys=False).apply(lambda x: x.nlargest(1, 'score')).reset_index()\n",
|
||
"save_df = score_df.groupby('trade_date', group_keys=False).apply(lambda x: x.nsmallest(2, 'total_mv')).reset_index()\n",
|
||
"save_df = save_df.sort_values(['trade_date', 'score'])\n",
|
||
"save_df[['trade_date', 'score', 'ts_code']].to_csv('predictions_test.tsv', index=False)\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 20,
|
||
"id": "09b1799e",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"191\n",
|
||
"['vol', 'pct_chg', 'turnover_rate', 'volume_ratio', 'winner_rate', 'undist_profit_ps', 'ocfps', 'AR', 'BR', 'AR_BR', 'cashflow_to_ev_factor', 'book_to_price_ratio', 'turnover_rate_mean_5', 'variance_20', 'bbi_ratio_factor', 'daily_deviation', 'lg_elg_net_buy_vol', 'flow_lg_elg_intensity', 'sm_net_buy_vol', 'total_buy_vol', 'lg_elg_buy_prop', 'flow_struct_buy_change', 'lg_elg_net_buy_vol_change', 'flow_lg_elg_accel', 'chip_concentration_range', 'chip_skewness', 'floating_chip_proxy', 'cost_support_15pct_change', 'cat_winner_price_zone', 'flow_chip_consistency', 'profit_taking_vs_absorb', 'cat_is_positive', 'upside_vol', 'downside_vol', 'vol_ratio', 'return_skew', 'return_kurtosis', 'volume_change_rate', 'cat_volume_breakout', 'turnover_deviation', 'cat_turnover_spike', 'avg_volume_ratio', 'cat_volume_ratio_breakout', 'vol_spike', 'vol_std_5', 'atr_14', 'atr_6', 'obv', 'maobv_6', 'rsi_3', 'return_5', 'return_20', 'std_return_5', 'std_return_90', 'std_return_90_2', 'act_factor1', 'act_factor2', 'act_factor3', 'act_factor4', 'rank_act_factor1', 'rank_act_factor2', 'rank_act_factor3', 'cov', 'delta_cov', 'alpha_22_improved', 'alpha_003', 'alpha_007', 'alpha_013', 'vol_break', 'weight_roc5', 'smallcap_concentration', 'cost_stability', 'high_cost_break_days', 'liquidity_risk', 'turnover_std', 'mv_volatility', 'volume_growth', 'mv_growth', 'momentum_factor', 'resonance_factor', 'log_close', 'cat_vol_spike', 'up', 'down', 'obv_maobv_6', 'std_return_5_over_std_return_90', 'std_return_90_minus_std_return_90_2', 'cat_af2', 'cat_af3', 'cat_af4', 'act_factor5', 'act_factor6', 'active_buy_volume_large', 'active_buy_volume_big', 'active_buy_volume_small', 'buy_lg_vol_minus_sell_lg_vol', 'buy_elg_vol_minus_sell_elg_vol', 'ctrl_strength', 'low_cost_dev', 'asymmetry', 'lock_factor', 'cat_vol_break', 'cost_atr_adj', 'cat_golden_resonance', 'mv_turnover_ratio', 'mv_adjusted_volume', 'mv_weighted_turnover', 'nonlinear_mv_volume', 'mv_volume_ratio', 'mv_momentum', 'lg_flow_mom_corr_20_60', 'lg_flow_accel', 'profit_pressure', 'underwater_resistance', 'cost_conc_std_20', 'profit_decay_20', 'vol_amp_loss_20', 'vol_drop_profit_cnt_5', 'lg_flow_vol_interact_20', 'cost_break_confirm_cnt_5', 'atr_norm_channel_pos_14', 'turnover_diff_skew_20', 'lg_sm_flow_diverge_20', 'pullback_strong_20_20', 'vol_wgt_hist_pos_20', 'vol_adj_roc_20', 'cs_rank_net_lg_flow_val', 'cs_rank_elg_buy_ratio', 'cs_rank_rel_profit_margin', 'cs_rank_cost_breadth', 'cs_rank_dist_to_upper_cost', 'cs_rank_winner_rate', 'cs_rank_intraday_range', 'cs_rank_close_pos_in_range', 'cs_rank_pos_in_hist_range', 'cs_rank_vol_x_profit_margin', 'cs_rank_lg_flow_price_concordance', 'cs_rank_turnover_per_winner', 'cs_rank_volume_ratio', 'cs_rank_elg_buy_sell_sm_ratio', 'cs_rank_cost_dist_vol_ratio', 'cs_rank_size', 'cat_up_limit', 'industry_obv', 'industry_return_5', 'industry_return_20', 'industry__ema_5', 'industry__ema_13', 'industry__ema_20', 'industry__ema_60', 'industry_act_factor1', 'industry_act_factor2', 'industry_act_factor3', 'industry_act_factor4', 'industry_act_factor5', 'industry_act_factor6', 'industry_rank_act_factor1', 'industry_rank_act_factor2', 'industry_rank_act_factor3', 'industry_return_5_percentile', 'industry_return_20_percentile', '000852.SH_MACD', '000905.SH_MACD', '399006.SZ_MACD', '000852.SH_MACD_hist', '000905.SH_MACD_hist', '399006.SZ_MACD_hist', '000852.SH_RSI', '000905.SH_RSI', '399006.SZ_RSI', '000852.SH_Signal_line', '000905.SH_Signal_line', '399006.SZ_Signal_line', '000852.SH_amount_change_rate', '000905.SH_amount_change_rate', '399006.SZ_amount_change_rate', '000852.SH_amount_mean', '000905.SH_amount_mean', '399006.SZ_amount_mean', '000852.SH_daily_return', '000905.SH_daily_return', '399006.SZ_daily_return', '000852.SH_up_ratio_20d', '000905.SH_up_ratio_20d', '399006.SZ_up_ratio_20d', '000852.SH_volatility', '000905.SH_volatility', '399006.SZ_volatility', '000852.SH_volume_change_rate', '000905.SH_volume_change_rate', '399006.SZ_volume_change_rate']\n",
|
||
"[]\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"print(len(feature_columns))\n",
|
||
"print(feature_columns)\n",
|
||
"print([col for col in feature_columns if 'total_mv' in col])"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 21,
|
||
"id": "e53b209a",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"5595 2057539\n",
|
||
" ts_code trade_date turnover_rate\n",
|
||
"0 000001.SZ 2023-01-03 1.1307\n",
|
||
"1 000001.SZ 2023-01-04 1.1284\n",
|
||
"2 000001.SZ 2023-01-05 0.8582\n",
|
||
"3 000001.SZ 2023-01-06 0.6162\n",
|
||
"4 000001.SZ 2023-01-09 0.5450\n",
|
||
"... ... ... ...\n",
|
||
"1766689 605599.SH 2025-05-19 0.4952\n",
|
||
"1766690 605599.SH 2025-05-20 1.6447\n",
|
||
"1766691 605599.SH 2025-05-21 1.2658\n",
|
||
"1766692 605599.SH 2025-05-22 0.7522\n",
|
||
"1766693 605599.SH 2025-05-23 0.6051\n",
|
||
"\n",
|
||
"[1766694 rows x 3 columns]\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"print(len(train_data[train_data['pct_chg'] > 7]), len(train_data))\n",
|
||
"print(test_data[['ts_code', 'trade_date', 'turnover_rate']])"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 22,
|
||
"id": "364e821a",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import pandas as pd\n",
|
||
"from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, roc_curve\n",
|
||
"import matplotlib.pyplot as plt\n",
|
||
"import numpy as np\n",
|
||
"\n",
|
||
"def calculate_binary_classification_metrics(df: pd.DataFrame, score_col: str, label_col: str, future_return_col: str = None, total_mv_col: str = None, n_mv_bins: int = 10, threshold: float = 0.5):\n",
|
||
" \"\"\"\n",
|
||
" 计算二分类模型的评估指标,可选择计算 score 和 future_return 的相关性,\n",
|
||
" 并可选择计算 score 在按总市值 (total_mv) 分为 n 份后的每个分组上的预测性能(ROC AUC)。\n",
|
||
"\n",
|
||
" Args:\n",
|
||
" df: 包含 score (预测概率或置信度), label (真实二分类标签), 可选的 future_return 和 total_mv 的 Pandas DataFrame。\n",
|
||
" score_col: 包含模型预测 score 的列名。\n",
|
||
" label_col: 包含真实二分类标签 (0 或 1) 的列名。\n",
|
||
" future_return_col: (可选) 包含未来收益率的列名,用于计算相关性。\n",
|
||
" total_mv_col: (可选) 包含总市值的列名,用于按市值分 n 份分析预测性能。\n",
|
||
" n_mv_bins: (可选) 将总市值分为多少份,默认为 5。\n",
|
||
" threshold: 将 score 转换为预测类别的阈值,默认为 0.5。\n",
|
||
"\n",
|
||
" Returns:\n",
|
||
" 一个包含以下评估指标的字典:\n",
|
||
" - accuracy: 准确率\n",
|
||
" - precision: 精确率\n",
|
||
" - recall: 召回率\n",
|
||
" - f1: F1 分数\n",
|
||
" - roc_auc: ROC AUC 值\n",
|
||
" - fpr: ROC 曲线的假正率 (False Positive Rate)\n",
|
||
" - tpr: ROC 曲线的真正率 (True Positive Rate)\n",
|
||
" - thresholds: ROC 曲线的阈值\n",
|
||
" - score_return_correlation: (如果 future_return_col 提供) score 和 future_return 的皮尔逊相关系数\n",
|
||
" - mv_roc_auc: (如果 total_mv_col 提供) 一个字典,包含按总市值分为 n 份后的每个市值分组对应的 ROC AUC 值\n",
|
||
" \"\"\"\n",
|
||
" y_true = df[label_col].values\n",
|
||
" y_score = df[score_col].values\n",
|
||
" y_pred = (y_score >= threshold).astype(int)\n",
|
||
"\n",
|
||
" metrics = {}\n",
|
||
" metrics['accuracy'] = accuracy_score(y_true, y_pred)\n",
|
||
" metrics['precision'] = precision_score(y_true, y_pred)\n",
|
||
" metrics['recall'] = recall_score(y_true, y_pred)\n",
|
||
" metrics['f1'] = f1_score(y_true, y_pred)\n",
|
||
" metrics['roc_auc'] = roc_auc_score(y_true, y_score)\n",
|
||
" metrics['fpr'], metrics['tpr'], metrics['thresholds'] = roc_curve(y_true, y_score)\n",
|
||
"\n",
|
||
" if future_return_col in df.columns:\n",
|
||
" metrics['score_return_correlation'] = df[score_col].corr(df[future_return_col])\n",
|
||
"\n",
|
||
" if total_mv_col in df.columns and n_mv_bins > 1:\n",
|
||
" metrics['mv_roc_auc'] = {}\n",
|
||
" df['mv_quantile'] = pd.cut(df[total_mv_col], bins=n_mv_bins, labels=False, duplicates='drop')\n",
|
||
" for i in range(df['mv_quantile'].nunique()):\n",
|
||
" mv_group = df[df['mv_quantile'] == i]\n",
|
||
" if len(mv_group) > 0 and len(np.unique(mv_group[label_col])) > 1 and len(np.unique(mv_group[score_col])) > 1:\n",
|
||
" roc_auc_mv = roc_auc_score(mv_group[label_col], mv_group[score_col])\n",
|
||
" lower_bound = df[total_mv_col][df['mv_quantile'] == i].min()\n",
|
||
" upper_bound = df[total_mv_col][df['mv_quantile'] == i].max()\n",
|
||
" metrics['mv_roc_auc'][f'{lower_bound:.0e}-{upper_bound:.0e}'] = roc_auc_mv\n",
|
||
" else:\n",
|
||
" lower_bound = df[total_mv_col][df['mv_quantile'] == i].min()\n",
|
||
" upper_bound = df[total_mv_col][df['mv_quantile'] == i].max()\n",
|
||
" metrics['mv_roc_auc'][f'{lower_bound:.0e}-{upper_bound:.0e}'] = np.nan\n",
|
||
" print(f'{lower_bound:.0e}-{upper_bound:.0e}')\n",
|
||
" df.drop(columns=['mv_quantile'], inplace=True)\n",
|
||
"\n",
|
||
" return metrics\n",
|
||
"\n",
|
||
"def plot_roc_curve(metrics: dict):\n",
|
||
" plt.figure(figsize=(8, 6))\n",
|
||
" plt.plot(metrics['fpr'], metrics['tpr'], color='darkorange', lw=2, label=f'ROC curve (AUC = {metrics[\"roc_auc\"]:.2f})')\n",
|
||
" plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')\n",
|
||
" plt.xlabel('False Positive Rate')\n",
|
||
" plt.ylabel('True Positive Rate')\n",
|
||
" plt.title('Receiver Operating Characteristic (ROC)')\n",
|
||
" plt.legend(loc=\"lower right\")\n",
|
||
" plt.grid(True)\n",
|
||
" plt.show()\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 23,
|
||
"id": "1f6e6336",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"6e+04-9e+04\n",
|
||
"9e+04-1e+05\n",
|
||
"1e+05-1e+05\n",
|
||
"1e+05-1e+05\n",
|
||
"1e+05-2e+05\n",
|
||
"2e+05-2e+05\n",
|
||
"2e+05-2e+05\n",
|
||
"2e+05-2e+05\n",
|
||
"2e+05-3e+05\n",
|
||
"3e+05-3e+05\n",
|
||
"二分类评估指标:\n",
|
||
"accuracy: 0.6585\n",
|
||
"precision: 0.4597\n",
|
||
"recall: 0.1487\n",
|
||
"f1: 0.2247\n",
|
||
"roc_auc: 0.6122\n",
|
||
"fpr: (array of length 7368)\n",
|
||
"tpr: (array of length 7368)\n",
|
||
"thresholds: (array of length 7368)\n",
|
||
"score_return_correlation: -0.0355\n",
|
||
"mv_roc_auc: {'6e+04-9e+04': 0.5986394557823129, '9e+04-1e+05': 0.5806043519120943, '1e+05-1e+05': 0.5902634156155784, '1e+05-2e+05': 0.5821412014040664, '2e+05-2e+05': 0.5919906015824924, '2e+05-3e+05': 0.6058984399066272, '3e+05-3e+05': 0.5884844322344323}\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"image/png": "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",
|
||
"text/plain": [
|
||
"<Figure size 800x600 with 1 Axes>"
|
||
]
|
||
},
|
||
"metadata": {},
|
||
"output_type": "display_data"
|
||
}
|
||
],
|
||
"source": [
|
||
"\n",
|
||
"for sd in [\n",
|
||
" # save_df1, \n",
|
||
" # save_df2, \n",
|
||
" score_df\n",
|
||
" ]:\n",
|
||
" # 计算二分类指标\n",
|
||
" evaluation_metrics = calculate_binary_classification_metrics(sd, score_col='score', label_col='label', threshold=0.6,\n",
|
||
" future_return_col='future_return', total_mv_col='total_mv')\n",
|
||
"\n",
|
||
" # 打印指标\n",
|
||
" print(\"二分类评估指标:\")\n",
|
||
" for metric, value in evaluation_metrics.items():\n",
|
||
" if isinstance(value, (float, int)):\n",
|
||
" print(f\"{metric}: {value:.4f}\")\n",
|
||
" elif isinstance(value, (list, tuple, np.ndarray)):\n",
|
||
" print(f\"{metric}: (array of length {len(value)})\")\n",
|
||
" else:\n",
|
||
" print(f\"{metric}: {value}\")\n",
|
||
"\n",
|
||
" # 绘制 ROC 曲线\n",
|
||
" plot_roc_curve(evaluation_metrics)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 24,
|
||
"id": "7e9023cc",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"def analyze_factors(\n",
|
||
" df: pd.DataFrame,\n",
|
||
" feature_columns: list[str],\n",
|
||
" target_column: str = 'target', # 假设目标列默认为 'target'\n",
|
||
" trade_date_col: str = 'trade_date', # 假设日期列默认为 'trade_date'\n",
|
||
" mcap_col: str = 'total_mv', # 新增: 市值列名称\n",
|
||
" mcap_bins: int = 5 # 新增: 市值分位数的数量 (例如 5 表示五分位数)\n",
|
||
") -> pd.DataFrame:\n",
|
||
" \"\"\"\n",
|
||
" 分析DataFrame中指定特征列的各种指标,包括基本统计、相关性、日间IC、ICIR以及在不同市值分位数上的IC。\n",
|
||
"\n",
|
||
" Args:\n",
|
||
" df (pd.DataFrame): 包含日期、目标列、特征列和市值列的DataFrame。\n",
|
||
" 需要包含 trade_date_col, target_column, feature_columns 和 mcap_col 中的所有列。\n",
|
||
" feature_columns (list[str]): 需要分析的特征列名称列表。\n",
|
||
" target_column (str): 目标变量列的名称。\n",
|
||
" trade_date_col (str): 交易日期列的名称。\n",
|
||
" mcap_col (str): 市值列的名称。\n",
|
||
" mcap_bins (int): 市值分位数的数量 (例如 5 表示五分位数)。\n",
|
||
"\n",
|
||
" Returns:\n",
|
||
" pd.DataFrame: 包含各个因子分析指标的汇总DataFrame。\n",
|
||
" 同时打印因子在不同市值分位数上的平均IC表格。\n",
|
||
" 如果输入数据或列有问题,可能返回空或包含NaN的DataFrame。\n",
|
||
" \"\"\"\n",
|
||
"\n",
|
||
" # --- 数据校验 ---\n",
|
||
" required_cols = [trade_date_col, target_column, mcap_col] + feature_columns\n",
|
||
" if not all(col in df.columns for col in required_cols):\n",
|
||
" missing = [col for col in required_cols if col not in df.columns]\n",
|
||
" print(f\"错误: 输入DataFrame缺少必需的列: {missing}\")\n",
|
||
" return pd.DataFrame() # 返回空DataFrame\n",
|
||
"\n",
|
||
" # 确保日期列是 datetime 类型\n",
|
||
" df = df.copy() # 在副本上操作\n",
|
||
" df[trade_date_col] = pd.to_datetime(df[trade_date_col], errors='coerce')\n",
|
||
" df.dropna(subset=[trade_date_col], inplace=True) # 移除日期转换失败的行\n",
|
||
"\n",
|
||
" # 过滤掉那些在 feature_columns, target_column, mcap_col 上有 NaN 的行,以确保后续计算是在完整数据上\n",
|
||
" # 直接在 df 副本上进行清洗\n",
|
||
" initial_rows_before_clean = len(df)\n",
|
||
" df.dropna(subset=feature_columns + [target_column, mcap_col], inplace=True)\n",
|
||
" rows_dropped_clean = initial_rows_before_clean - len(df)\n",
|
||
" if rows_dropped_clean > 0:\n",
|
||
" print(f\"警告: 移除了 {rows_dropped_clean} 行,因为其特征、目标或市值列存在空值。\")\n",
|
||
"\n",
|
||
" if df.empty:\n",
|
||
" print(\"错误: 清理缺失值后数据为空,无法进行因子分析。\")\n",
|
||
" return pd.DataFrame() # 返回空DataFrame\n",
|
||
"\n",
|
||
"\n",
|
||
" print(f\"开始分析 {len(feature_columns)} 个因子指标...\")\n",
|
||
"\n",
|
||
" # --- 1. 基本因子统计量 ---\n",
|
||
" basic_stats = df[feature_columns].describe().T\n",
|
||
"\n",
|
||
" print(\"\\n--- 基本因子统计量 ---\")\n",
|
||
" print(basic_stats)\n",
|
||
"\n",
|
||
" # --- 2. 因子与目标变量的整体相关性 ---\n",
|
||
" overall_correlation = {}\n",
|
||
" for feature in feature_columns:\n",
|
||
" # 在清理后的 df 上计算相关性\n",
|
||
" if df[[feature, target_column]].dropna().shape[0] > 1: # 确保至少有两个有效数据点\n",
|
||
" overall_correlation[feature] = {\n",
|
||
" 'Pearson_Correlation_with_Target': df[feature].corr(df[target_column], method='pearson'),\n",
|
||
" 'Spearman_Correlation_with_Target': df[feature].corr(df[target_column], method='spearman')\n",
|
||
" }\n",
|
||
" else:\n",
|
||
" overall_correlation[feature] = {\n",
|
||
" 'Pearson_Correlation_with_Target': np.nan,\n",
|
||
" 'Spearman_Correlation_with_Target': np.nan\n",
|
||
" }\n",
|
||
" overall_corr_df = pd.DataFrame.from_dict(overall_correlation, orient='index')\n",
|
||
"\n",
|
||
" print(\"\\n--- 因子与目标变量的整体相关性 ---\")\n",
|
||
" print(overall_corr_df)\n",
|
||
"\n",
|
||
" # --- 3. 因子之间的相关性矩阵 ---\n",
|
||
" # 在清理后的 df 上计算相关性\n",
|
||
" factor_correlation_matrix = df[feature_columns].corr(method='spearman') # 改回 Spearman\n",
|
||
"\n",
|
||
" print(\"\\n--- 因子之间的相关性矩阵 (Spearman) ---\") # 修正打印信息\n",
|
||
" print(factor_correlation_matrix)\n",
|
||
"\n",
|
||
" # --- 4. 日间 IC 和 ICIR ---\n",
|
||
" print(\"\\n--- 计算日间 IC (Spearman 相关性) 和 ICIR ---\")\n",
|
||
"\n",
|
||
" # 直接在清理后的 df 上计算每日 IC\n",
|
||
" if df.empty: # 理论上上面已经检查过,这里再检查一次更安全\n",
|
||
" daily_ic_series = pd.Series(dtype=float) # 空 Series\n",
|
||
" ic_stats = pd.DataFrame({\n",
|
||
" 'Mean_IC (Spearman)': np.nan, 'Std_Dev_IC': np.nan, 'ICIR': np.nan\n",
|
||
" }, index=feature_columns)\n",
|
||
" else:\n",
|
||
" daily_ic_series = df.groupby(trade_date_col).apply(\n",
|
||
" lambda day_group: {\n",
|
||
" feature: day_group[feature].corr(day_group[target_column], method='spearman')\n",
|
||
" for feature in feature_columns if day_group.shape[0] > 1 # 确保每日数据点多于1才能计算相关性\n",
|
||
" }\n",
|
||
" ).apply(pd.Series) # 将字典结果转换为 DataFrame\n",
|
||
"\n",
|
||
" # 计算 IC 的统计量\n",
|
||
" if not daily_ic_series.empty:\n",
|
||
" ic_mean = daily_ic_series.mean()\n",
|
||
" ic_std = daily_ic_series.std()\n",
|
||
" # 避免除以零\n",
|
||
" ic_ir = ic_mean / ic_std.replace(0, np.nan) # 使用 replace 0 为 NaN\n",
|
||
"\n",
|
||
" ic_stats = pd.DataFrame({\n",
|
||
" 'Mean_IC (Spearman)': ic_mean,\n",
|
||
" 'Std_Dev_IC': ic_std,\n",
|
||
" 'ICIR': ic_ir\n",
|
||
" })\n",
|
||
" print(\"\\n--- 日间 IC 和 ICIR (Spearman) ---\")\n",
|
||
" print(ic_stats)\n",
|
||
" else:\n",
|
||
" ic_stats = pd.DataFrame({\n",
|
||
" 'Mean_IC (Spearman)': np.nan, 'Std_Dev_IC': np.nan, 'ICIR': np.nan\n",
|
||
" }, index=feature_columns)\n",
|
||
"\n",
|
||
"\n",
|
||
" # --- 5. 因子在不同市值分位数上的平均 IC ---\n",
|
||
" print(f\"\\n--- 计算因子在 {mcap_bins} 个市值分位数上的平均 IC (Spearman) ---\")\n",
|
||
"\n",
|
||
" # 在清理后的 df 上计算每日市值分位数,直接添加到 df 中\n",
|
||
" # 使用 transform() 和 qcut() 在每个日期分组内计算分位数\n",
|
||
" # labels=False 返回整数 0 to mcap_bins-1\n",
|
||
" # duplicates='drop' 处理在某些日期股票数量少于 bins 导致分位数边缘重复的情况,会返回 NaN\n",
|
||
" # 添加一个临时列来存储分位数\n",
|
||
" mcap_bin_col_name = f'_mcap_bin_{mcap_bins}'\n",
|
||
" df[mcap_bin_col_name] = df.groupby(trade_date_col)[mcap_col].transform(\n",
|
||
" lambda x: pd.qcut(x, q=mcap_bins, labels=False, duplicates='drop') if len(x) >= mcap_bins else np.nan # 确保股票数量足够进行分位数划分\n",
|
||
" )\n",
|
||
"\n",
|
||
" # 过滤掉无法划分分位数 (NaN) 的行,进行分位数 IC 计算\n",
|
||
" # 创建一个临时 DataFrame df_binned_analysis\n",
|
||
" df_binned_analysis = df.dropna(subset=[mcap_bin_col_name]).copy()\n",
|
||
"\n",
|
||
" if df_binned_analysis.empty:\n",
|
||
" print(\"错误: 划分市值分位数后数据为空,无法计算分位数上的 IC。\")\n",
|
||
" avg_ic_by_bin = pd.DataFrame(index=range(mcap_bins), columns=feature_columns) # Placeholder\n",
|
||
" else:\n",
|
||
" # 按日期和市值分位数分组,计算每个分组内的因子与目标变量的截面相关性 (分位数IC)\n",
|
||
" binned_ic_by_day = df_binned_analysis.groupby([trade_date_col, mcap_bin_col_name]).apply(\n",
|
||
" lambda group: {\n",
|
||
" feature: group[feature].corr(group[target_column], method='spearman')\n",
|
||
" for feature in feature_columns if group.shape[0] > 1 # 确保分位数组内数据点多于1\n",
|
||
" }\n",
|
||
" ).apply(pd.Series) # 将嵌套结果转为 DataFrame\n",
|
||
"\n",
|
||
" # 对每个分位数组的每日 IC 求平均\n",
|
||
" # unstack(level=mcap_bin_col_name) 将 mcap_bin 作为列\n",
|
||
" # mean(axis=0) 对日期索引求平均\n",
|
||
" avg_ic_by_bin = binned_ic_by_day.unstack(level=mcap_bin_col_name).mean(axis=0).unstack()\n",
|
||
"\n",
|
||
" # 重命名索引和列,使表格更清晰\n",
|
||
" if not avg_ic_by_bin.empty:\n",
|
||
" # Index name will be the original column name used for grouping ('_mcap_bin_X')\n",
|
||
" # Rename the index name explicitly\n",
|
||
" avg_ic_by_bin.index.name = 'MarketCap_Bin'\n",
|
||
" avg_ic_by_bin.columns.name = 'Feature'\n",
|
||
" # 可以根据需要对分位数 bin 索引进行排序 (虽然 pd.qcut labels=False usually sorts)\n",
|
||
" avg_ic_by_bin = avg_ic_by_bin.sort_index()\n",
|
||
"\n",
|
||
" print(avg_ic_by_bin)\n",
|
||
"\n",
|
||
"\n",
|
||
" # --- 6. 汇总所有指标 ---\n",
|
||
" # 将基本统计、整体相关性、IC/ICIR 合并到一个 DataFrame\n",
|
||
" # 注意:合并时需要根据索引进行对齐 (因子名称)\n",
|
||
" summary_df = basic_stats\n",
|
||
" summary_df = summary_df.merge(overall_corr_df, left_index=True, right_index=True, how='left')\n",
|
||
" summary_df = summary_df.merge(ic_stats, left_index=True, right_index=True, how='left')\n",
|
||
"\n",
|
||
" # print(\"\\n--- 因子分析汇总报告 ---\")\n",
|
||
" # print(summary_df)\n",
|
||
"\n",
|
||
" # --- 清理临时列 'mcap_bin' ---\n",
|
||
" # 修正:在函数结束时从我们一直在操作的 df 副本中删除临时列\n",
|
||
" if mcap_bin_col_name in df.columns:\n",
|
||
" df.drop(columns=[mcap_bin_col_name], inplace=True)\n",
|
||
"\n",
|
||
"\n",
|
||
" return summary_df # 主要返回汇总报告,分位数IC单独打印\n",
|
||
"\n",
|
||
"# # 运行分析函数\n",
|
||
"# factor_analysis_report = analyze_factors(test_data.copy(), feature_columns, 'future_return')\n",
|
||
"\n",
|
||
"# print(\"\\n--- 最终汇总报告 DataFrame ---\")\n",
|
||
"# print(factor_analysis_report)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 25,
|
||
"id": "a0000d75",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"开始分析 'score' 在 'circ_mv' 和 'future_return' 下的表现...\n",
|
||
"准备数据,处理 NaN 值...\n",
|
||
"原始数据 17280 行,移除 NaN 后剩余 16976 行用于分析。\n",
|
||
"对 'circ_mv' 和 'future_return' 进行 100 分位数分箱...\n",
|
||
"按二维分箱分组计算 Spearman Rank IC...\n",
|
||
"整理结果用于绘图...\n",
|
||
"circ_mv_bin 0 1 2 3 4 5 6 7 8 9 ... 90 91 92 \\\n",
|
||
"future_return_bin ... \n",
|
||
"0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN \n",
|
||
"1 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN \n",
|
||
"2 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN \n",
|
||
"3 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN \n",
|
||
"4 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN \n",
|
||
"... .. .. .. .. .. .. .. .. .. .. ... .. .. .. \n",
|
||
"95 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN \n",
|
||
"96 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN \n",
|
||
"97 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN \n",
|
||
"98 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN \n",
|
||
"99 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN \n",
|
||
"\n",
|
||
"circ_mv_bin 93 94 95 96 97 98 99 \n",
|
||
"future_return_bin \n",
|
||
"0 NaN NaN NaN NaN NaN NaN NaN \n",
|
||
"1 NaN NaN NaN NaN NaN NaN NaN \n",
|
||
"2 NaN NaN NaN NaN NaN NaN NaN \n",
|
||
"3 NaN NaN NaN NaN NaN NaN NaN \n",
|
||
"4 NaN NaN NaN NaN NaN NaN NaN \n",
|
||
"... .. .. .. .. .. .. .. \n",
|
||
"95 NaN NaN NaN NaN NaN NaN NaN \n",
|
||
"96 NaN NaN NaN NaN NaN NaN NaN \n",
|
||
"97 NaN NaN NaN NaN NaN NaN NaN \n",
|
||
"98 NaN NaN NaN NaN NaN NaN NaN \n",
|
||
"99 NaN NaN NaN NaN NaN NaN NaN \n",
|
||
"\n",
|
||
"[100 rows x 100 columns]\n",
|
||
"生成热力图...\n",
|
||
"分析完成。\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"image/png": "iVBORw0KGgoAAAANSUhEUgAABdEAAASgCAYAAAAXXAHaAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvc2/+5QAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Xd0VFX79vErvdC7UqQL0YggUkRBaSJRiiigYhAUpUtRFBBQUUEUAQFRBBEEeWjSVASkiApIFzIQioAYihRpIQNJmJz3D97Mj5BMSIbJnJnM97NW1vPjzLVn3yd3nHe9Ozv7+BmGYQgAAAAAAAAAAKTjb3YBAAAAAAAAAAB4KhbRAQAAAAAAAABwgEV0AAAAAAAAAAAcYBEdAAAAAAAAAAAHWEQHAAAAAAAAAMABFtEBAAAAAAAAAHCARXQAAAAAAAAAABxgER0AAAAAAAAAAAdYRAcAAF6lTZs2atasmVJSUswuxaELFy5o2bJlMgzDfm3Xrl3q0qWLfvnll2y/3+rVqzVw4EBt27Yty2MMw9CePXuyPRcAAAAAIK1AswsAAADIjv/++0/Jycny98/6XoDExEQlJycrPDw8zbirV6/qypUryps3r/r16yeLxXLT91qwYIEKFCiQaWbevHkaPXq09uzZo9dff12SVKBAAW3YsEGXLl3SI488kuXaJenw4cNatGiRoqKiMnw9OTlZp0+f1pEjR3To0CHt3LlTmzZt0r///qtPP/1Ujz32WLbmy4p///1Xc+fO1auvvio/Pz+Xvz9ghmnTpunBBx9UlSpVzC4FAAAAHoRFdAAA4FHi4uL0ww8/qECBAgoKClJAQECa15OSkpSSkqKFCxemG5uSkqLExEQVLFhQjz/+uP360qVLNWTIkAznK1q0qNavX6+LFy/q3Llz6tChQ4a5DRs2aNeuXQoKCsq0fqvVqhkzZig0NFTR0dH262XLltWTTz6pBQsWaNGiRXryySczfZ/rhYWFpfnfVGfPnlXz5s114cIF+653f39/lShRQmXLllX9+vUVFxcnm82W7vt4K+Lj49W5c2cFBATopZdeUt68eV323oBZrl69qrVr12ratGmaP3++br/9drNLAgAAgIdgER0AAHiU48ePa9y4cTfNDRo0yOFr9957b5pF9CpVqqhnz54KCgrSzz//rL/++kvdunVTUlKSgoODJUmBgYHKnz+/+vXrl+F7Wq1W7dq1SyEhIZnWNW3aNJ0+fVovv/yySpQokea1vn37avny5RoxYoTuv/9+lSlT5qb3Kcm+e/7GHd8BAQE6f/68ateurR49eqhUqVK67bbb7PeUUwYMGKCkpCTNnj3bvoB+487dwMBAFS1aVPXq1VPXrl1Vrly5HK1JkjZt2qSOHTuqV69e6t27d47N06hRIx07dkyrV69W6dKlHeaWLFmiGTNm6ODBg8qbN68aN26svn37qnDhwjlWm7tFR0dr8+bN2rdv3y2/V5UqVXTvvfdq3rx5LqgsY0ePHlXjxo315JNP6sMPP0zzWmBgoD7//HP7z9CcOXNu+kszAAAA+AYW0QEAgEepVq2afv31VxUoUEDBwcHpjm1p2rSpbDab1qxZk25sSkqKrFarEhIS0r1ntWrVJElHjhzRP//8ox49eqTJ3LhTe/78+QoKClLDhg3THN+S2Y7uffv26YsvvlDhwoXVtWvXdK8XK1ZMb7zxhoYNG6aePXvq22+/Vb58+Ry+V1JSkoKCgnTmzBlJ137BsH//fiUmJqps2bL2xfJSpUrpgQcecFiXKy1evFi//vqr5s2bl+6XBJLUrVs3SdK5c+e0fft2LVy4UCtWrNDs2bNVtWpVt9ToCcaMGaPJkyerZMmSateunY4dO6Z58+Zp8+bNmj9/vsO+w1x58+bVZ599pscff1xTp05V9+7dzS4JAAAAHoBFdAAA4FHCwsIUHx+v+fPnKzQ0NN2idUJCgmw2W7rjXGw2m5KSkhQWFqY2bdrcch2zZs3S3r17tXHjxizlk5OT9eabbyo5OVl9+/Z1uEjavn17rV+/XitWrFCXLl30+eefZ7gzedCgQdq9e3eaawMGDLD/31OmTFHdunWzcUe3LikpSZ988ok6deqkyMjIDDPX7+RPSUnRkCFD9N1332ncuHH64osv3FWqqbZs2aLJkyerfPnymjdvnvLnzy/pWs9Gjx6tzz77TAMHDjS5Sjhy++2367XXXtPo0aP1zDPPqFChQmaXBAAAAJOxiA4AADzO8ePH9f7772eacXScS+XKlV2yiG6z2VS4cOEsH70xbtw4xcbG6qGHHlK7du0yzY4aNUrnzp3T5s2b1bZtW40ZM0b33ntvmszgwYN1+fJlhYSE6JNPPtGff/6pd955R5UrV9bly5d11113uf2oiVWrVuncuXN6+eWXs5T39/dX9+7d9d1332nHjh05XJ3nmDJliiSpT58+9gV0SXruuec0YcIE/fjjjyyie7j27dtr4sSJWrhwoV566SWzywEAAIDJWEQHAAAeJzIyUn/++aeCg4PT7UR3dJyLYRhKTk5WcnKyS2qw2WwqVqxYlrLz5s3T1KlTVbRoUY0aNSrd2eU3CgsL05QpU9S3b1+tXbtWzz77rF544QV1797dvuh6//33S5IuXbqk2NhYSdd+QZB6PZW/v7/279+vL7/8MsO5rl69qitXrqh///5ZupfMrFmzRg899FC2duYWKVJEknTlypVbnt8bJCYmasOGDfL391f9+vXTvJYnTx6VKFFC//zzjy5cuJDmmCB4lsDAQEVFRWn16tUsogMAAIBFdAAAPMG///6rCRMmaOPGjTpz5oyKFi2qBg0aqHfv3vZFyFQ2m00zZ87UggULdOTIERUpUkTVqlVTnz59VLFixXTvvXTpUs2YMUMHDhxQWFiYHnjgAb366quqUKFCmtyND2VctmyZ5syZo3379mnGjBnpzrPes2ePJk2apC1btighIUHly5dXx44d1bZtW6e/DwcPHlRwcLACAwMdLkTbbDbZbDb9+++/Dl8/efKkkpKS7DXv2bNHc+bMUXBwsCwWixITE/XBBx8oKSlJderUUVRUVLr3SUlJydIi+rx58/T2228rKChIbdu21bvvvquQkBAFBATcdDG9atWqqlSpkqZMmaJp06YpJCREffv2TZNZvHixEhMT7f/+559/VKZMGft7G4ahgwcPasaMGfbMhQsXZLPZlD9/fiUmJiopKckli+gWi+Wmu+xvtGfPHklK9xDVhIQETZs2TT/++KNOnDihggULqkaNGurXr5/Kli1rzy1cuFCDBg3SyJEjVa1aNX388cfatm2bAgMD9eCDD+qtt97K9K8FUlJSNGjQIC1evFjdu3dP9/11tYMHDyo5OVklS5a0P3T1ekOGDNH58+ez/fBXm82mhx9+WH5+flq3bl26ZwU0atRIVqtVv/32m/0vFK5cuaJp06bphx9+0PHjx5UnTx7dc8896tOnjyIiIpy/yWzau3evJk2apB07dig+Pl4lS5ZUq1at1Llz5wy/D+vXr9fo0aP1119/qUiRImrdurV69OiRLpucnKyvv/5aS5cu1ZEjRxQeHq6HHnpI/fv3V6lSpW657lq1amnhwoUyDOOm/y0DAAAgd2MRHQAAk8XHx6tDhw46duyYGjVqpHLlyuno0aOaO3eudu3apQULFtgXzGw2m3r27Km1a9eqXLlyat++vc6ePauVK1fql19+0cyZM9McCzJixAjNmDFDxYsX15NPPqmzZ89qxYoVWrdunaZOnaqaNWtmWNOQIUM0f/58lS5dWmXLllVoaGia19etW6devXopPDxcTZs2VWhoqH755RcNGTJE//77r3r37u3U96Jjx472h2jezMMPP3zTzL59+yRdOx5m4cKFCgoKUnJysq5evaqFCxcqKSlJefLkyXAR/ezZs6pevXqm72+1WjVnzhz5+fnpo48+0oULF/T5559nqX5JeuSRRzR58mTdd999mjdvnl599dU0rxuGoZkzZ9r//dNPP+l///uf3n33XbVt21Y2m02GYahly5Z677337Ll27drpzJkzGT589VacPHkyw4eJZiQpKUk7d+7UkCFD7DWlunr1qrp166bNmzerVq1aatiwoc6cOaOffvpJO3bs0Pfff5/mGBRJOnDggD744ANVrFhRTz31lH7//Xf98MMPSkhIcHjWumEYGjZsmNsW0CXpxIkTkpTul1+psvJzm5GAgAC1aNFC06ZN09atW1W7dm37a3/++aeOHTum559/Ps0RP6+//rp+/vln3XvvvXr22WcVHx+v5cuXq2PHjlq8eLFLFppvZs+ePerQoYNSUlL02GOPqVChQtq5c6fGjBmjU6dOaejQoWnycXFx6tq1q+677z61bdtWGzZs0Oeff669e/fq888/ty9mJycn6+WXX9bGjRtVq1Yt1a9fX0ePHtVPP/2kTZs2adGiRVn+SxJHSpQoIavVqvPnz3MuOgAAgI9jER0AAJNt2rRJR48e1dNPP60PPvjAfv2jjz7S8uXLdfToUd1xxx2Srj3scu3atXr44Yc1ceJE+87MH374Qa+99pomTpxoP4953bp1mjFjhqpUqaKZM2faj4749ddf9corr+j111/XihUr0u3uXLJkieLj4zV16tR0x1FI0uXLlzVw4EDlyZNHixYt0u233y5J6tu3r5544gl9+eWXio6OVsGCBbP9vXjttdfk7++vvHnzKjw83H7darVq6NChOnv2rF5//XWVK1dOSUlJGS5s2Ww2JSYm6ty5c/ZrTZo0kcVikSQNHDhQq1at0tatW9ONS3Xx4kVdvHgx3e5p6drO5tRfaoSHh2v27NnaunWrHnroISUmJqpFixYKDQ3Vli1b1KlTJ3Xu3Dnd+dcxMTF6+umn7QvSjRo1UqNGjdLN9f333+vvv/9WoUKFdO7cOdWtW1eLFi3SJ598ombNmunq1auSrh0P4w6JiYk3PYKkSpUqaf7t5+en559/Xh06dLBf++OPP7R582b7LxFSVa9eXcOHD9e6devUokWLNO/z9ddf6/nnn9dbb70lPz8/Wa1WPfroo1q3bp2uXLmS7hc9kjR8+HDNnz/fbQvo0rWfVUkKCQlx+Xu3atXKvnv/+kX0H3/80f56qosXL+rnn39WuXLlNGfOHPvPbIMGDfThhx9q27ZtbllE/+qrr2S1WjVu3Dg1b97cfv2pp57S/Pnz9dZbb6XZVX/27FlFR0fbf/litVr1/PPPa+3atVq9erWaNGkiSfrmm2+0ceNGvfzyy3r99dft46dPn66RI0dq2rRpevPNN2+p9tSf9ev/EgQAAAC+iUV0AABMlrpb8tChQ4qPj1e+fPkkSW+88YbeeOONNNnFixdLurYQfP3id9OmTTVmzJg0x0d89913kqT+/funWfhs0KCBmjZtqpUrV2rDhg165JFH0swRFxen6dOn64EHHsiw3vXr1+vs2bOKiIjQnDlz0ryWL18+nThxQtu3b89wUfhmMnog6OXLl9WzZ0+dPXtWvXv3VocOHdSwYUNFRUXp7bfftucuXbqkDz74QAMGDMjyw0Cvd/1Z6qlnkN945I10bYf19Qu2oaGheuihhyRdWzhNXTw9evSoJKlcuXLp3uP06dOSlOmu7qSkJI0bN05BQUFq166dJk+erEKFCumFF17QpEmTNGnSJD399NOSpKJFi2bnVp0WGhqq//77L9NMt27dJF07P33//v0aNWpUmsVdSXrooYfsfyWQ6siRI9q1a5eka0fW3KhkyZJ644037DuRw8PDVbNmTS1fvlz//fdfugXhESNGaPbs2apatarbFtAl2eu7/pcyrlK1alVVqVJFK1eu1LBhwxQQEKCUlBQtX75cFSpUULVq1ezZPHnyKDw8XOfOndORI0dUvnx5SdKjjz6qRx991OW1OfLJJ5/ok08+sf87KSlJ27Zt04ULF5SYmKiTJ0/afxEnScHBwWn+IiM8PFydO3fW66+/rjVr1tgX0ZcuXSrp2pE1Y8eOtefj4+MlXfvl5K06e/asJPf9kgoAAACei0V0AABMdu+996ply5ZaunSpHnzwQVWpUkURERGqU6eOmjZtmmax/NChQwoJCbEviKUKCQnR448/nubaX3/9JUkZnn189913a+XKlfrrr7/SLaI3atTI4QK6JB0+fFjStYXm1MXmG508edLxDWdDXFycevbsqX379qlevXrq1auXpGtnFS9atEjdunVTiRIlZLPZ9Nprr+mXX37R3r17NXv27GwvfN1zzz32Hf+//PKLpGu7WmfMmKG5c+fq/vvvl81mU0pKSpbeLyYmRlL6ndlS1hbRJ0yYoGPHjqlDhw5pFhk7duyov//+W23btrX34rbbbstSTbeqVKlSN+1tv379JEk1a9bUyy+/rEWLFqVbRJeufQ/mzp2rLVu2aO/evTp//rx9R3JG3+NmzZql+6uJ1B4bhpHm+vz583Xy5ElVrFhRe/fu1S+//JLu5zynpP4SLHUx90bt27fXnj17tGbNGqeOG2ndurVGjRqljRs36qGHHtLWrVt16tSpNDv9pWvHv7z55psaPny4oqKiVL58eUVERKhGjRqKiopy6hdNztq0aZOWLVumHTt26NChQ0pOTk5zRNX1SpYsme4on0qVKkn6v19MSdLff/8tSWmOO7qeKz6DTp48qXz58vEAWAAAAMj/5hEAAJDTPv74Yy1YsEC9evVS2bJl9fvvv6t///5q2bKlLly4kKX3uHLlihISEtItKGb3gXh33XVXpq+nvv/gwYO1b9++DL+effbZbM2Zke+++05PPfWUDhw4IElpjofp2bOnrly5omHDhikxMVF9+vTRL7/8oqZNm+qbb75xaudov3799M4778hms2nFihWqVq2aAgICdOzYMUnXFnGHDRuW5pgZR1JSUvT7778rLCxMkZGR6V5PXUQvXrx4huPXrVunKVOmqGDBgunOly9UqJDGjh2rihUr2h/ambrImNPuuecebdiwIUvZBg0a6O6779bGjRu1Y8eONK/FxMSoWbNm+vLLL1WoUCG9/PLLmjJlisOzzaWMd/Q7cvLkSb3yyiuaN2+eihUrpvfff99tR3Kk/vXCsWPHMtyNfvr0aSUnJ6dbKM6qFi1aKCAgwH6Ey48//ig/Pz+1bNkyXfaZZ57Rzz//rCFDhuj+++/X4cOH9d5776lJkybauXOn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7LHHVKxYMc2ePVvPPfecYmJi9MYbb6h27dqKiopSr1697At0jz32mOrWrauPP/5YGzZs0IQJE/T000/bF9D//fdfdenSRb/++qsGDBig2rVr2+e/fpfrnj17NGfOHPtDW6935MgRPfvss9q0aZPq1auX4SKeYRjavHmz9u3bl2a39M0kJydL+r8Hbm7btk1z5sxReHi4/diZ69lstkyPRrkVjRs3Vrt27dS/f3/t3bv3pvmWLVuqVKlSWrt2rf0Yob59+6p79+5KSkrSrFmztHv3bj3//POaOXOm/P39tWbNGpeeYX7//ferefPm+u2337Ry5UqXva8j/v7+GjdunIYMGaLg4GB9++23OnLkiJ5//nnNnj37lhdmw8LC7AvKTZo0yfD9goODNWvWLPXq1cv+ENelS5eqRIkSeu+999SjR49bqiErgoODNW3aNDVp0kRbt27VnDlzFBAQoLFjx9r/SmPZsmVpxpQrV06ffvqpveZLly6pS5cu9p3h17/3V199pf79+8tms2nu3Ln6448/1LRpUy1atEj33nuvUzUfP35c3bp1U926dfXcc885f/MAAADIVfyMnPr/YQEAALjAqlWr1LNnT0nSnXfeqWHDhqlWrVrpcgcPHtS3336r77//XhcvXtSTTz6pDz/8UNK1xeeMzjY/e/asZs6cqenTpys5OVnDhg1Tu3bt7K937txZGzdu1NChQxUSEqKPP/5YSUlJ+vXXX+1nr584cUIzZszQt99+q6SkJDVr1izDXf7R0dHatm2bfVH+jTfe0EsvvZSl78HUqVP18ccfa9q0aSpVqpSeffZZnT17Vj169FCfPn3S5Vu0aKFz587p999/z9L7Z1dSUpK6dOmiv/76S8uXL3f6IZmAJ7l69apatmypgIAAzZ49O8vPVwAAAEDux3EuAADAozVo0ED33nuvatWqpT59+jg8gqZixYoaNmyYBg8erD///DPNUSmOHg76yy+/aOrUqapSpYref//9NEeoSFKHDh104MABDR8+XNK1vxro169fmsW1lJQU/frrrwoMDNSbb76p559/PsO5OnTooMOHDysyMlItW7ZUVFRUlr8HqeffJyYmqmzZsho+fLi+/PJL+4M7M8pfvnw5y++fXcHBwfriiy+0YcMGFtCRawQGBuqtt95S1apVWUAHAABAGuxEBwAAHi8pKSnHHp55+PBhlStXzuFDSbPi6NGjCg4OVvHixV1Y2f85e/aszpw5o5IlS9qP7jAM45ZqBgAAAABkDYvoAAAAAAAAAAA4wINFAQAAAAAAAABwgEV0AAAAAAAAAAAcYBEdAAAAALxMYmLiTTOc3AkAAOAaLKIDAHKVn376Sd99953D1xcvXqwffvghS++VkJCgpKQkpaSkZHn+q1evKiEhQefPn8/yGHiu5ORkXblyxewyAOQCP/zwgy5cuGD/95UrVzR8+HC99dZb2X6v8+fP68EHH1Tnzp0VFxeXYcZms+nxxx9Xt27d9N9//zldNwAAAKRAswsAAMCVPv/8c8XFxempp57K8PWRI0eqQIECeuKJJ276XkOGDNGyZcucqqNo0aJav369U2NhjpSUFJ0+fVqHDh3Svn37tHPnTv3666/q06ePOnbsaHZ5ALzYyZMnNXDgQBUpUkSrVq1SUFCQQkNDtX//fm3fvl0vvviiKlasmOX3mzhxouLj42Wz2ZSUlKSDBw9KuvaLv+DgYFWoUEE//vijDh48qHvvvVdFihSxjzUMQ5cvX1ZoaKj8/dlTBQAAkBUsogMAvNq5c+f0999/KyQkRCEhIQoICFBQUJB9QeFGgYGB9tcNw1BiYqIKFiyoUqVKpcvWrl1bhQoVUmBgoAICAtK89vPPPysuLk7t2rVT3rx57devXr2qpKQkhYaGuvZGkSNOnz6t7t276+zZszp16pSSk5Ptr4WGhqpUqVI6cOCAiRUCyA2mTJmi5ORkde7cWUFBQfbrffv2VYcOHTR8+HDNmDEjS++1detWzZ49W5K0adMmRUVFpXm9WbNmGj16tMaNGyfp2l9gLVmyxP66zWaTJK1evVqlS5e+ldsCAADwGX4GB+UBALzYqlWr1LdvX/sCekJCgmw2mwoUKJBhPj4+Xn5+fsqbN68Mw1BSUpI6d+6sPn36ZGverl276pdffvHIRYjo6Ght3rxZ+/btM7sUr/Dqq6/KZrPp9ttvV0hIiKZOnao33nhDL774ovz8/Oy57du3a9euXRm+R0BAgKKjo2+5luTkZH355ZdavHixTpw4oTx58qhu3br69NNPb/m9gRsdPHhQzzzzjAYNGqQ2bdpkmImPj9ekSZO0YsUKnTlzRvfcc48GDRqkyMjIdNmkpCR99dVX9p/fihUr6vXXX9eDDz7ospqHDRum33//XWvWrHGY2bBhgyZOnKi9e/cqPDxcTz31lHr37q3AwPT7hywWi8aNG6ddu3bJz89PzZs314ABA5QnTx6X1fz333/riSeeULFixbRixQoFBweneb1Pnz5avny5Bg0apE6dOmX6XkePHlXbtm118eJFTZkyRdu3b9eECRP0ySefKCIiQklJSQoPD9f8+fM1ZcoUde3aVTVq1NC0adO0e/duffTRR0pOTlZiYqKaNm3q0vsEAADIzdiJDgDwak2aNJHFYrH/Ozo6WocOHXJ4lMoTTzyh8PBwzZs3z10lwsONHz/e/n8fPHhQU6dOVXh4eJoFdOnawtyECRMyfI/w8HCXLKKPGzdOU6dO1QMPPKBHH31U586d059//nnL7wvc6OzZs+rWrZsuXrzoMJOQkKDOnTsrJiZG1apVU7NmzfTrr78qOjpaCxYsSHP8iM1m06uvvqq1a9eqYsWK6tChg7Zt26aXX35Z06ZNU926dW+55unTp2vu3LkZ/uVQqh9++EEDBgxQaGionnjiCSUlJWnq1Kk6ffq0RowYkSa7efNmdenSRTabTVFRUQoLC9PixYv1999/6+uvv073GeCMlJQUDR48WMnJyerfv3+6BXTp2tFhGzdu1OjRo1WpUiU99NBDGb5XTEyMevbsqbNnz6pPnz6qV6+eQkJCNGHCBB08eNB+TNn69es1bdo03X333erbt6/8/f31zz//aPPmzSpQoIBq1ap1y/cFAADga1hEBwDkOmfOnFGVKlUcvn7vvfe6sRr3GzVqlC5fvmx2GblO6hE9P//8s0qWLGm/3qlTJ/31118umeP777/XHXfckWYBLzsPts3I0aNH1bhxYz355JP68MMPXVGmT9i0aZM6duyoXr16qXfv3maX41IHDhxQjx499M8//2SamzRpkmJiYvT4449r9OjR8vf3V+/evdW6dWu99dZbmjNnjj07d+5crV27Vvfff7++/vprBQcHy2azKTo6WoMHD9aKFSvSHGOSHSkpKRo3bpwmT56cae7MmTMaNmyYgoKCNGvWLN19992SpBo1aujtt99WVFSUfYE6MTFRAwcOVFJSkr744gs98sgjkqSmTZuqS5cumjdvntq3b+9UvdebNm2atm3bpho1ajh8FkexYsX04YcfqkePHurdu7fGjx+v+vXrp8kYhqHPP/9cJ0+eVOvWrdW9e3dJ0l133aWAgABt3bpV0rXvVeov+9599137med169aVn5+fLBYLi+gAAABO4EkyAIBcJ2/evBo5cmSGXyVKlDC7vBxXsmTJbD2gzpddunRJycnJcnS6XVJSki5duiSr1Wo/CsLf31+BgYH2Lz8/v3Rn5jvr5MmTuu2229LsgOXBf3ClQ4cOqV27dpKuHUvlyNWrVzV37lwFBQVpyJAh9p/D8PBwvfTSS9qxY4cOHTpkz3/77beSpKFDh9p3WwcEBKhHjx46duyYNm7c6HTNb7/9tiZPnqzu3btnugt98eLFSkhI0LPPPmtfQJekp59+Wrfffru+++47+7U1a9bo2LFjatKkiX0BXZLq16+vGjVqpMk6a8+ePfr0008VHh6ukSNHZrqzvVGjRho6dKisVqu6du2qKVOmpPkFmp+fnyZMmKB3333X/l6tW7fWwIEDNXXqVH311VeSJKvVquHDh+vdd99VeHi4Dh48qIMHDyowMFCfffaZGjRokKZvAAAAyBp2ogMAcp2goCCHu83DwsJuOj4hIUFBQUEKCgpy+s/5DcOQzWZTYmKiQkJCMjyLF+Z78sknM9yN+8477+idd96x//vll19W0aJF3VgZkDPOnDljX7DN7Fzx2NhYxcfHq169eipcuHCa11LPOF+/fr0qVKigs2fP6q+//lLZsmVVtWrVNNnatWsrKChIGzZsUIMGDZyqOSEhQZ999pmaNGmipUuXOsxt2bJFkvToo4+muR4YGKi6detq7dq1N82m3t9nn32mS5cupXlwdHYcP35cXbt2VVJSkoYPH67y5cvfdEyHDh0UFhamoUOHavTo0Vq9erWGDBliP38+ICBAzzzzjD1/9OhR5cmTR/Xq1bNfW7dunfr375/pPOHh4dqxY4dT9wUAAOCr+P/RAwBynXPnzikqKsrh6zc7zqV9+/Y6cOBAluZq3LjxTTPffPON6tSpk6X3u9H27ds1adIk7dixQ/7+/ipfvryio6P1xBNPOFzgz8qDRRs1aiTp2m7MQ4cO6auvvtKGDRv02GOP6c0330yTPX/+vD777DP9/PPPOnv2rEqWLKkGDRqoR48eKliwYLbvaeHChRo0aJAGDx6smJgYrVq1SuXLl9f48eM1ffp0LVy4UEWLFtXIkSN1//33q2/fvvrpp5/0008/qUKFCmnea+DAgVq0aJEWLVqku+66K9u19OzZU8nJyfad5IsWLdLmzZvVunVr1alTRzabTcnJybrzzjvTnL3vSjcePbR58+Y0167/+Um934weaHt9T1OPcLle6vcp1fXvkdqTkSNHpnvAZEZz3njUybJlyzRnzhzt27dPM2bMSLeQumfPHk2aNElbtmxRQkKCypcvr44dO6pt27bZ/n45uueb/RxfunRJX375pZYvX67jx4+rYMGCatSokfr166dChQqlua/rTZw4URMnTrT/+/r/riZMmKCJEydm+N94Rv8dXv99bt26tebNm6dFixbp4MGDWrt2rfLly5emjl69eqldu3YaNWqU1q9fr5SUFNWoUUNDhw5VmTJlnPqe1axZU7Vr175p7tSpU5KU4X9XpUuXVlBQkP0XUJllg4ODVbJkyTS/rJo4caImTJigzp07a+DAgfbrY8eO1RdffKFu3bqpX79+9usff/xxlv7a49SpU/Lz88uwjrJly+r8+fOKj49Xvnz57DVfv2P9+qxhGIqLi1NERMRN571RXFycXnjhBZ06dUrt27fXypUr9dtvvykwMDBLf1kyYsQIffjhh9qxY4dee+01LVmyxH6c1PUCAwPTfV9Sf1E8ZcqUDH9pER0dneX/9w0AAAD/h0V0AECuU7RoUYcPFk1ddMtMkyZNVLt27QwXKFL9/PPPiouLU7t27TLcqZiSkiKbzabLly87vYN56dKlGjhwoIKCgtSsWTPlz59fq1ev1uuvvy6LxaJBgwY59b7X++233/Tqq69KkipXrpzuuJtTp07pueeeU1xcnO6//341a9bMvlC6Zs0aLVy4UPnz53dq7nHjxum+++5T9erVtXHjRj399NMqVqyYHnvsMS1cuFBjx47Vt99+q1atWumnn37SsmXL1KtXL/v4pKQkrVq1SnfeeadTC+iS1Lp1a/v/ffXqVX366aeSpOrVq6tZs2YKCgqyH02RU4vo3bp1s//fX3zxhUqWLKmWLVvar11//npW5c+f3/6+ly5d0qxZs1SlShU1bNgwTcYVhgwZovnz56t06dIqW7ZsusW+devWqVevXgoPD1fTpk0VGhqqX375RUOGDNG///7rkvPGb/ZzHB8fr+eee04HDhxQ/fr11bhxY+3fv19z587Vtm3btGDBAoWFhalkyZL279vx48e1dOlS3X///br//vtvucbrXb16Va+88op+++03VahQQRUqVMhwcfX06dP2z5jWrVtr+/btWrdunU6ePKlFixY5ddRPVo8eunTpkiQ5/PzKly+fTp48maVs/vz57Vnp2s/8mjVrNHPmTLVp00Z33nmnDh8+rK+++kp33nmnevbs6XTN4eHhGf7FUerP+7///qt8+fLZay5SpEi6bIECBSRdO17JmUX0n376yX5UzJtvvqn77rsvW+PfeOMN1a1bV4MGDdIbb7xh/29qyZIl+vjjjxUcHCx/f39dvHhRO3fuVMOGDZWUlKTo6Gj7Z+HFixd1+vTpdO99/S8NAQAAkHUsogMAvNY///yjCxcu2I9ekaTLly/LZrPp4MGDGY5JTk5WYmJimteTk5Nls9nsOxL79u1707kPHTqkuLg4de3aNd2OYFc4deqUhg4dqpCQEC1YsMB+xvmrr76q5s2b65tvvlGXLl1UrFgxp+eIj4/Xa6+9pg4dOqh79+7KkydPusy7776ruLg49enTRz169LBfHzZsmObOnau5c+fq5Zdfdmr+atWq6auvvtLevXvVqlUrSdLs2bPtvyw4ceKEpGtnFBcuXDjdIvqvv/6q+Pj4NIvQt2LBggX6999/JV078uKZZ57R3XffneMP47x+x+0XX3yh0qVLp7nmjPz589vf4+jRo5o1a5buuuuuW37fGy1ZskTx8fGaOnVqugchStf+exw4cKDy5MmjRYsW6fbbb5d07b+xJ554Ql9++aWio6Od+ouGVFn5OR47dqz279+vd955R88++6z9+gcffKBvvvlGCxcuVIcOHVSmTBn792jTpk1aunSp6tat6/IHi3755ZcyDEPz589XtWrVHObmzZunpk2basyYMQoKCpLNZtNTTz2l2NhY/f333+n+MsOVUhdaM9oBLV3bYX7lypUsZy9cuGD/d2BgoD788EO1adNGw4cP18yZM/Xee+/JMAyNGjXK/our7PL393d4ZFfqe6Y+dDm15ozyN2az65VXXlHVqlVVt25dBQcHa/PmzQoLC1NgYKAaN24sq9WqTZs2pRv3xBNP6MiRI/bjc6ZNm5bm9Vq1aumDDz5QcHCwfvzxR82fP18REREaPHiwrl69qhIlSth3/L/22msO6+NoKgAAgOxjER0A4LUmT56sBQsWZPhaZse5nDp1Kt3rme1eN8Py5ct15coVvfLKK2keEpo/f36NHTtWp0+ftv/iwFkXL15Uq1at9Prrr2f4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5+zZoKAgffTRR2mORvn333/17rvvqmjRoho8eLAk6c0339Svv/6q999/X7Vq1VLp0qWdqvnDDz9Up06dNHHiRH355Zey2Wyy2Wxq3LixnnvuuTTZgQMH6tChQ5o3b54WLVokwzB09epVVatWTX369HFq/ozMnTtXM2fOVIkSJRy+b+rxP44W0Q3D0JgxYxQUFKT3339ffn5+Gf6iJiu/vLl+5z0AAACyhkV0AIDXSl0AvV7qYnqqoKAgBQQE2B98l7q7NFWePHn0yiuvZGm+/fv3288iT915mpUjH5zVunVrlS5dWp9//rlWrVqlq1evqlKlSurRo4eefvrpHJv3eiVKlNCCBQs0adIkrVy5Ujt27FDRokX1/PPPq1evXm4/S75169b6448/VKJECdWtW/eW3itPnjwaN25cmgftZbYAlXr+u7+/f5oxfn5+SkxMvKVasuree+/V6NGjNWnSJPXu3VuFCxdW8+bN1bt3bz3xxBMOxw0dOlQFCxbUihUrtHbtWuXPnz9NPm/evPryyy/1wQcfaOTIkQoODtYDDzygBQsW6LPPPnN6EV2SGjZsqDlz5mjSpElav369kpKSVL58eQ0fPtxtP8f58+fX3Llz9cUXX2j58uWaPXu2ihcvrrZt26pnz54Oz6EeO3asxowZo3Xr1um///5TkSJFFB0dbX+9VKlSmjRpkkaPHq1BgwYpb968atiwoebNm+fSRVgzvfDCC6pZs6a++eYbxcXFqVKlSurWrVu6z1JJat68ue666y599dVX+uuvv1SyZEn72dypDMPQwIEDdeHCBY0fP95+vEq+fPn07rvvqlu3bnrjjTc0a9Yspz5fixYtqoULF2rmzJn69ddfFRgYqKioKLVp08a+SztVWFiYZsyYoQULFmjFihW6evWqGjZsqOeff94lD+BMSkrSp59+qqlTpypfvnyaPHlyhg8rPXfunL7//ntJcvjLAz8/P40bN067du1S5cqVJcn+C4Lrpf77+uNebpT6PQcAAEDW+Rm54W9NAQA+KykpSRMnTlRISIh99/n1Zs+erYSEhHQLCikpKbLZbEpKStLTTz+tMmXK3HSulStXqnfv3vZ/BwcHa8uWLQoNDXXNzcB0FotFTz31lIYOHarnn38+zWv//POPjh49qpo1ayokJMR+/fz587p69arDneAAfEtSUpK+//57ffHF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cZwLAAAAAAAAAHipxo0bZ/r66tWrM7weHx+vO+64I821AgUKKDY2NtP3i4+PV0BAgMqUKWO/lj9/fp08eTLD/KRJk1SyZEk1bNhQ0rVF+AYNGujFF19UcnKy3nvvPfXr108LFizIdN6s2LVrl5YvX659+/bp1KlTCggIULFixRQZGanHHntMVapUcep9WUQHAAAAAAAA4BM4E/3/BAYGKiQkJM210NBQWa3Wm44LDg5Ocy0sLCzDcRs3btTMmTP19ddfKzDw2lJ06rEvqd577z01adJEhw4dsu9Wz659+/bpvffe0/nz5/XYY4+pS5cuKlasmGw2m06fPq3NmzerZ8+eqly5st566y2VLl06W+/PIjoAAAAAAAAAeClHO81vplChQjpz5kyaa5cuXUq3QJ7RuMuXL+vSpUvKmzevpGu7028cFxcXp/79+6t///6qVauWw/crXry4JOno0aNOLaLPnz9f48aNU79+/fT000+ne71KlSp66KGH1KdPH3399dd65plnNGLECDVo0CDLc3AmOgAAAAAAAAD4mOrVq+vAgQO6ePGi/VpMTIxuv/32TMeVKVNGRYsW1bZt2xyOO3v2rLp27aqGDRvqxRdftF//77//1Lx58zS71lPfp2TJktm+hwMHDujrr7/W7NmzM1xAv15AQIC6dOmiiRMn6oMPPkhz3zfDIjoAAAAAAAAA+JjKlSurQoUKGjNmjFJSUrR7926tXLlSjRo1UkpKii5evCibzZZunL+/v6KiojR+/HhdunRJ586d0/Tp09WoUSNJUmJiorp06aLChQtr4MCBSkhIUEJCgpKSklSkSBHlzZtXb731liwWi9auXashQ4aoXr16aR5wmp17+OGHH1S2bNksj6levbp++ukn5c+fP8tjWEQHAAAAAAAA4BOMXPh1K0aOHKnVq1erXr16ateunVq2bKkGDRro+PHjqlWrlvbv35/huFdffVVBQUFq0KCBGjVqpNDQUHXr1k2S9Pvvv2v37t3asmWLatWqpfvuu0/33Xefhv0/9u49zsZ6////c81gWBhGTskg0bZYI5JDSQoVaqSDDk7ZqcghKeWQQyEiKaWDdnaF7N2mHZ3syuHbrvZWk1MulkNJiSK7jRlrGoz1+6Nf89ljrDHXmmvW+1qzHvfbbW41az3f7/fr6tXt+uPt8r4mTpQkPfPMMzpy5Ih69+6tCRMm6Oqrr9bcuXMjvoaEBPtb3HbHeEKhUHH/W8cUy7JMlwAAAAAAAAA4wu/3my4hpvzh75NNl+C47TdMLNb4YDCojIwMpaSkqHnz5kUed/LkSa1bt07Hjh1TmzZtVLZs2WLV4WZx+WLRU28uwWBQgUBAPp9PXq+3QN6yLFs3JPJm8+HGFNZnt10D+cjG0GP35p1agx5HL2+qJnoced6NNdFj82vQY7N5N9ZEj82vEQv5WOqxG2uKhbzJHkdjjXjLA8Xl9XrVsWNH2+MSEhIKfWFoaRKXm+gAAAAAAAAAgNKhU6dO8ng8Rc6vWrXK1vxsogMAAAAAAACIC6FQ0TdaETuGDx9eovOziQ4AAAAAAAAAiFnXX399ic5v/9WlAAAAAAAAAADECTbRAQAAAAAAAAAII6LjXA4ePKgPPvhA27dv14EDB5SYmKgaNWrI7/fryiuvVJUqVZyuEwAAAAAAAACKJ2S6AETT8ePHtW/fPp199tk6cuSIqlevHtE8tp5EP3jwoB566CGlp6dry5YtSktL02233aYbb7xRPp9P//73v3XVVVfpscceU1ZWVkQFAQAAAAAAAAAQqezsbI0ePVoXXnihunXrpl27dmnGjBnq2bOnDhw4YHu+Im+if/rpp+rRo4fOOeccrVmzRtOmTVOvXr3UsWNHderUSbfccouefPJJffTRRzpx4oTS09O1bds22wUBAAAAAAAAABCpxx9/XP/+97/10EMP6eTJk5Kku+66SwkJCZoxY4bt+Yq0ib5z506NGTNGzz33nEaMGKHy5cuHzSYnJ2vSpEkaN26c7r77bh05csR2UQAAAAAAAAAAROKjjz7ShAkT1K9fv7zPzj//fI0cOVKffvqp7fk8oVCoSCcBZWVlqVKlSrYmj2RMSbMsy3QJAAAAAAAAgCP8fr/pEmJK4yVTTZfguJ29xpsuwXXatGmjWbNm6bLLLlOTJk20bNkyNWnSRCtXrtS4ceP0xRdf2JqvyC8WTUxM1Nq1a9WuXbsiT+62DfTfnXpzCQaDCgQC8vl88nq9BfKWZdm6IZE3mw83prA+u+0ayEc2hh67N+/UGvQ4enlTNdHjyPNurIkem1+DHpvNu7Ememx+jVjIx1KP3VhTLORN9jgaa8RbHkBBV1xxhZ544gnVrFkz77MdO3Zozpw5uuKKK2zPV+RN9H379mnQoEHatGlTge/atm2rChUqKDEx8f8mLlNGPXv21D333GO7KAAAAAAAAAAAIjFu3DgNGzZMPXv2lCTddNNNys3N1UUXXaSxY8fanq/Im+hJSUkqU+b08cOHD2vSpEn5Plu7dq3mz5/PJjoAAAAAAAAAIGqqVKmihQsX6osvvtCOHTskSY0bN1bbtm0jmq/Im+gejyfvSfOnnnpKSUlJOn78uO69915JUvfu3fPl69atq2PHjunYsWMqV65cRMUBAAAAAAAAgFOK9nZIlBZt2rRRmzZtij1PkTfR/9e8efPUuXNnffbZZxo2bFje52+88YbKli2rnJwc3XbbbWrevHmxCwQAAAAAAAAAwI5PP/1Ur732mr777jvl5uaqfv366tu3rzp16mR7roRICvB4PHruuedUpUqVvN8l6fHHH9frr7+uxx57LJJpAQAAAAAAAAAolsWLF+vOO+9UVlaWrrjiCl111VU6fvy4hg4dqr/97W+254voSfTf/b55/rvatWvrzTffVOvWrYszLQAAAAAAAAAAEZk3b57uvPNOjRo1Kt/nTz31lF5++WXdfPPNtuYr0ib6G2+8UaQd+lM31QEAAAAAAADALUIh9i/jQWZmptq3b1/g83bt2mnBggW25yvScS6bN2/W8ePHbU8OAAAAAAAAAEA03XDDDXrllVd09OjRvM+ys7O1aNEidevWzfZ8nlDozO+kzc3N1Y8//qgbbrhBX3zxhXw+nwKBgC6//HKtWrVKfr9fgUBA3bt31/vvv682bdroiy++sF2MJC1fvlxz5szR6tWrJUlvvfWWnn32WR09elQ333yzRo4cqYSEiI5ylyRZlhXxWAAAAAAAAMBN/H6/6RJiynlvlL53OX5zy8OmSzBu8ODB+X4PhUL67LPPVL58eTVp0kQej0c7duxQVlaWLr74Yr388su25i/ScS6JiYlFOqrlu+++k9/vV25urq0ifvfTTz9p6tSpqly5siTpn//8px5++GE9+uijat26tcaMGaNFixapf//+Ec3/u1NvLsFgUIFAQD6fT16vt0DesixbNyTyZvPhxhTWZ7ddA/nIxtBj9+adWoMeRy9vqiZ6HHnejTXRY/Nr0GOzeTfWRI/NrxEL+VjqsRtrioW8yR5HY414ywOQUlJSCnyWnp6e7/e6detGPH9ELxYNhUIaNmyYDh06lPe7JP3jH/9QcnKyDh8+HNGcY8eOVe3atfMes3/llVd01VVXqVevXpKkMWPG6KGHHir2JjoAAAAAAAAAoHSYPn16ic4f0Sb6LbfcogoVKuimm25SQkJC3lPqqampkqQqVarYnnPRokXat2+fxo4dq8mTJ0uStm7dqoceeigvk5aWpn379umXX35RtWrVIikdAAAAAAAAQLzixaJxIxgM6ptvvtGvv/6a99mJEye0bt06DRs2zNZcRd5ED4VCOnnypCTp0UcfLfDdhAkTTjtuypQpZ5z722+/1dNPP61XXnlF2dnZeZ9nZmaqfv36eb8nJiaqYsWKOnDgQLE20YPBYL7ff1/zf9c+0xi7a5CPbv50Y87UZ7ddA3n7Y+ixu/NOrEGPo5uPxhr02Nl8NNagx87mo7EGPTabj8Ya9NjZfDTWcHs+1nocjTVKW950j6OxRjzlT3c0MRDv/v3vf+vee+9VVlaWpP87ScXj8SglJcX2JnqRXiwqSd98842uu+66076Y87777lO5cuXynkoPhULKzc3V8ePH9fTTTxc6b25urm677TZ17NhRQ4cO1eeff66xY8dq9erVSktL0+uvv67mzZvn5Tt27KjZs2erVatWti70d5ZlKScnx9aYpKQkW2PIm827sSbyzubdWBN582uQdzbvxprIm1+DvLN5N9ZE3tm8G2sib34N8s7m3VgTefNrxFs+0j2yeHXeX6eZLsFx39w6znQJrtOzZ09deOGF6tmzp3r37q1//vOf+u9//6s//vGPGjVqlHr06GFrviI/iV6vXj2tWLHitN+daaO8MC+++KISEhIKvEFV+u1A+IMHD+b7LCsrS+XKlYt4PUny+Xz5fs/Oztbu3bvVoEEDVahQoUB+165dBcYUhrzZfLgxhfXZbddAPrIx9Ni9eafWoMfRy5uqiR5HnndjTfTY/Br02GzejTXRY/NrxEI+lnrsxppiIW+yx9FYI97yAAravXu3Jk6cqObNm6t+/fpav369unTpomHDhumFF14ouU30smXL5p157qQ333xT//nPf9S2bVtJvz2Znp2drYsuukg+n0/r1q1Tp06dJP12E8nKytLZZ59drDXD/TWXChUqhP3O7l+NIW82X9iYcH122zWQj3wMPXZn3sk16HF08tFYgx47m4/GGvTY2Xw01qDHZvPRWIMeO5uPxhqxko+VHkdjjdKaN9XjaKwRb3kU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bdmyRf/617+UlJSkiy66SMePH1e1atV02WWX6e6779YVV1yhihUrSpIOHDig++67T3/5y19sFQQAAAAAAAAAQKSef/55DRo0SCNGjMj77LrrrlPt2rU1d+7cktlEr1ixovx+v9q1a6cLL7xQ27Ztk9fr1d///ndlZGSoRo0auvbaa/PyOTk5GjVqlHr16qUlS5aoV69etooCAAAAAAAAAMeFTBeAaPjxxx/Vpk2bAp+3adNG8+fPtz2fJxQKnfF/nWAwqMsvv1zdu3dXlSpV9PXXX+u5557TY489pnPOOUcpKSl6+eWX1b17d/Xp00d///vf9cknn6hz585q0KCBLrnkEtuFlRTLskyXAAAAAAAAADjC7/ebLiGmNHhxlukSHLd78CjTJbjOrbfeqlq1amn27NlKTEyU9Ns7PUeOHKn9+/frr3/9q635irSJLknfffedXn31VZUvX1579+7V2WefrYyMDL3wwguSpM6dO6tly5baunWrkpKSNH78eHXv3t3m5ZU8y7IK3FyCwaACgYB8Pp+8Xm+Rxthdg3z08uHGFNZnt10D+cjG0GP35p1agx5HL2+qJnoced6NNdFj82vQY7N5N9ZEj82vEQv5WOqxG2uKhbzJHkdjjXjLwx420ePDpk2bNGDAANWoUUOtWrWSJK1fv14HDhzQa6+9pubNm9uaL6EooW3btmnt2rVKS0vTf//7X/34448qX768evTooe+//16SVK5cOS1cuFCPPPKIfvnlF23evNnmpQEAAAAAAAAAUDwXXHCBlixZopYtW2rz5s366quv1LJlSy1dutT2BrpUxDPRLcvSa6+9pjJlyigzM1M//fSTfv75Z6WkpGjbtm367rvv8rK5ublq27atVqxYoXbt2qljx462iwIAAAAAAAAAx3Emetxo1KiRZsyY4chcRdpEv+mmm3TTTTcpIyND33zzjZ566in9+uuvmjp1qrxer5566ilt3LhRN998s5KSktSjRw9Vq1ZN06dP12WXXSaPx+NIsQAAAAAAAAAAnMnhw4f1448/qkmTJtq7d6/+3//7f7r66qtVvXp123MV6TiXkydPasKECbr//vv1n//8RxdeeKFGjRqlu+++W+XLl9eDDz6Yt3n+3XffqX379urcubM8Ho9WrlxpuygAAAAAAAAAACKxZcsWde3aNe99nr/88otmzJiha6+9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r18/x+dNiHTgyZMnde+99+q+++5zsBwAAAAAAAAAANwj4k30F154QVu2bNFVV13lZD0AAAAAAAAAALhGRJvoK1as0HPPPadevXrJ4/Fo48aN+umnn3Ty5Emn6wMAAAAAAAAAR3hCpe+nOA4ePKghQ4aoZcuWuuGGG7Rt27YijcvNzdWMGTPUtm1bXXHFFXr//ffzfb97927169dPLVu2VP/+/bVv375837/11lvq1KmT2rZtqyeffNL1+8q2z0RfuHChHn/8cV133XUaMGCArrnmGnk8HklSQkKCatSoodq1a6tFixYaNmyYKlWq5HjRAAAAAAAAAIDIhUIhDRs2TJK0dOlSffXVVxoyZIjeeecdVaxYsdCxTz/9tJYvX65nn31WZcuW1fDhw1W/fn01a9ZMOTk5GjhwoJo0aaLly5drxYoVGjZsmJYuXaqEhAT985//1MMPP6xHH31UrVu31pgxY7Ro0SL1798/GpcdkSI/ib57924NHDhQ06dP15133qnp06fnbZ6vWbNGf/nLX/TUU09p4MCBatq0qd544w3Nnj27xAoHAAAAAAAAAERm/fr12rBhg6ZOnarzzjtP119/vc4991ytXLmy0HHHjh3TokWLNGzYMLVp0ybvafPFixdLkj744AMdPHhQU6dOVb169TRo0CBlZ2dr/fr1kqRXXnlFV111lXr16qUGDRrkbaK7WZGeRP/73/+u8ePHy+/3669//auaN2+e953H41Ht2rVVu3btfGMSEhK0evVqTZw40dmKAQAAAAAAAACSpM6dOxf6/apVq077+datW1WnTh01atQo77OWLVtq06ZNuu6668LO9+233yoYDKpDhw75xr399tt586alpSklJSXv+xYtWmjTpk266KKLtHXrVj300EN536WlpWnfvn365ZdfVK1atcIv1hBPKBQ648k5e/bs0TfffKPLL7883+e7du3SNddco0AgUGDMggULFAwGNXjw4DMWsXDhQk2dOjXfZ2PHjtWAAQP01Vdf6dFHH9WuXbvUvn17TZ06VVWrVj3jnOFYlhXxWAAAAAAAAMBN/H6/6RJiSsM5pe/kjHPffq/Q78Ntoj///PP6/PPP9dprr+V9tnDhQq1du1bPPfdc2Pm+/PJL9e/fX1u3bs37bPv27erbt68yMjL08MMPKxQKadq0aXnfP/744zp58qTGjRun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NeIt36pVqyJnITUbUwrPRH+cM9FP9Z///EcPPvig0tPTdeWVV+q6667Tvn37JEkjRozQ4MGDbc1XrE300/nqq680btw4jRw5Up07dy7SmHfeeUdjxozRV199pcTEREnS/v37ddlllykhIUHjxo3Lt2Gfnp6uoUOHqmvXrrbrsyxLDRs2zPdZdna2du/erQYNGqhChQoFxuzatavAmMKQN5sPN6awPrvtGshHNoYeuzfv1Br0OHp5UzXR48jzbqyJHptfgx6bzbuxJnpsfo1YyMdSj91YUyzkTfY4GmvEW54n0e1pNroUbqLPYBP9TI4ePaq1a9cqJSVFF154oe3xxTrO5XSaN2+uZ555Rvfee2+RN9Hr1KmjkydP6tdff817nP6HH36QJPXs2VPr1q3L20Q/evSovv32W9WpUyfiGsPdXCpUqBD2O7s3JPJm84WNCddnt10D+cjH0GN35p1cgx5HJx+NNeixs/lorEGPnc1HYw16bDYfjTXosbP5aKwRK/lY6XE01iiteVM9jsYa8ZYHULiKFSsWea/6dIr1YtFwzj77bP34449Fzl9wwQU699xzNXHiRO3Zs0dbtmzRY489pksuuUT9+vXTypUrlZGRIem3c9hTUlLk9/tLonQAAAAAAAAAQIx788031a9fP3Xs2FFff/21pkyZomnTpunEiRO25yrWk+jLli0r8Fl2drbeffddnX/++UUvokwZzZ8/XzNmzNBNN92kY8eO6eKLL9aUKVN01llnafjw4RowYICqVq2qYDCoOXPmKCGhRPb/AQAAAAAAAAAx7LXXXtPMmTPVqVMnffnllzpx4oRatWqlyZMnq3z58rr//vttzVesTfRnnnmm4IRlyqhJkyZ68MEHbc119tln6+mnnz7td4MGDVL37t21fft2paWlqVatWpGUCwAAAAAAACCOeRx9OyTcauHChRo7dqz69u2rJk2aSJK6d++ukydP6oknnrC9ie74i0XdzrIs0yUAAAAAAAAAjuDIY3v8D5W+F4taM3mx6KlatGihl19+WRdddJGaNGmiZcuWqUmTJvr3v/+te+65Rxs3brQ1n60n0T/55BMlJyfrggsusLWI25x6cwkGgwoEAvL5fKd9cYNlWbZuSOTN5sONKazPbrsG8pGNocfuzTu1Bj2OXt5UTfQ48rwba6LH5tegx2bzbqyJHptfIxby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OXKkli5dqszMTCUmJio1NTVvbHJyct5YkzyhUKgETs5xL8uyTJcAAAAAAAAAOMLv95suIaZcesMs0yU4LunwikK/P90m+n/+8x9lZ2efNn/HHXdo5MiR6tatW95nvXv31q233qoePXoUutbEiROVmJioSZMm5X329NNP5x3xcqo9e/aoS5cuWrFihY4cOaIBAwZo48aNed9///336tatm/Fz0ePySfRTby7BYFCBQEA+n09er7dA3rIsWzck8mbz4cYU1me3XQP5yMbQY/fmnVqDHkcvb6omehx53o010WPza9Bjs3k31kSPza8RC/lY6rEba4qFvMkeR2ONeMsDp9skP5Ozzjqr0O8OHjyY77PMzEyVK1fujPOmpKRo165d+T7LysoKO7ZmzZqSpB9++EH169dXdna2srKyVKlSJVvrljTORAcAAAAAAAAASPrtDPN169bl/X706FF9++23qlOnzhnHtmzZUhs2bFBubm7eZ5s3b9bZZ5+t//znP+rWrZuCwWDed7+vU6dOHaWmpqp69er51v59rGlsogMAAAAAAAAAJEnp6elauXKlMjIyJElz585VSkpK3t+QyMnJUVZW1mnHtm/fXidOnND8+fMlSWvWrNFXX32lTp066ayzzlKlSpX08MMPy7IsrVmzRuPHj9cll1yiRo0aKSEhQd27d9czzzyjrKws/fe//9Wrr76qTp06RefCCxGXx7kAAAAAAAAAiD+euHo7ZGSaNm2q4cOHa8CAAapataqCwaDmzJmjhITfnsd+6aWXtHLlSi1fvrzA2LJly2rWrFkaOXKkXn31VR06dEhDhw7VeeedJ0l65plnNH78ePXu3VvJyclKT0/XsGHD8sbfe++9uuuuu3TZZZcpFAqpfv36Gjx4cHQuvBBsogMAAAAAAAAA8gwaNEjdu3fX9u3blZaWplq1auV9N3z4cA0fPjzs2EsvvVQfffSR1q1bp3r16qlx48Z535199tl5T6mfTuXKlbV48WKtW7dOx44dU5s2bVS2bFlnLqoY2EQHAAAAAAAAAOSTmpqq1NTUiMZWrVpVnTt3jmhsQkKCWrduHdHYksKZ6AAAAAAAAAAAhMGT6AAAAAAAAADiQ4hD0WEfT6IDAAAAAAAAABCGJxSKrz9+sSzLdAkAAAAAAACAI/x+v+kSYkqHnk+YLsFxnyx70HQJpV5cHudy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Ffvv45M6duyou+66S2vWrLGveX8N92rXEhkZqQEDBuj555/XpEmTFBAQoIiICPXp00ePPPKIFi5cyOsuF5eQkKDBgwfriSee0Jdffqlx48ZpyZIlCgsLU6dOnfTJJ59o48aNOd74pMauJSkpSWPHjlWvXr20ePFiffDBBypdurRq1qypWrVqKTw8XBs3buTnGACcyG0fLHpNYGCgRowYoWXLlumVV17RN998Iw8PD+3bt0/Dhg2Tj4+P0Snib5hMJnl7eys2NlZHjhxRiRIl1Lt3bwUGBmrGjBl68cUXNW/ePPXo0UMhISGSrr6oSExMZKkeF5GZmSlfX1/17t1bktS5c2ft3r1bn332mSIiIpSWlqavv/5aO3bs0MMPP6zU1FRFR0drwIABBmeOvLBarapevbrat28v6WpD5pNPPtG2bdvUtm1bHTt2TJs2bdKOHTvUsmVLWSwW6uxisrKyZDKZVK5cOXXv3l0XLlxQWFiYFi9erIoVK+qNN95QSEiImjRpovPnz+v06dNq1KiR0WnjFpnNZtWrV0/9+vWT1WrVwIEDlZKSosTERLVr105paWk6c+aMJkyYoJCQEGVkZGjXrl2aOHEinwpzQbVr11ZERIQaNmyo7du3a8eOHbLZbIqPj1dCQoJ27NihFi1aKC0tjXu1Czl37pzCwsIUGhqqESNGSJJatmypxYsXS7r6KaH333+f110u7syZMwoKCtLo0aMlXX1WxbJly7R79249+OCDOnTokH766Sdec7mwc+fOqUyZMgoNDZWPj4/atGmj5ORkvf3221q9erVWr16tFStW8HMMAE7k9k10SfYnzbdp00Y//vijkpOT9c4776hFixZGp4ZbYLVa5eHhoWrVqikzM1Pbt2+XyWRS7969VbVqVc2aNUvvvvuuli5dqrlz5yooKEgbNmzQiy++yJskLiIzM1OJiYm6dOmSfVuzZs3syzCNGTNGW7Zs0d69ezV//nyZTCaNHDlSrVu3Nipl5FN6erpOnjypWrVqaenSpfr999+VkpIiSapcubKqVasmPz8/hYeHy9PTkzq7mGuzVu+//34lJSXp+eef19SpUzVkyBANGjRINptNp0+f1urVq+Xh4aGhQ4eqWbNmBmeNW9WsWTOtXbtWixcv1h9//KHSpUvr3Xffldls1t69e2WxWHTvvfcqKChIq1atkslk0ssv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8X/9mw7/+c9/5OnpecufWgAAAABwczTRAQCA26hbt64k6bfffrNv+/PPP/XUU0/p8OHDLjPGNV27dtXWrVu1detWtWjRQv7+/vk+1+XLlzVv3jw9+uij9tnp1yQkJOSriX7+/HnFxcXp999/V82aNRUQEHDD2GXLlunChQsym8369ddfNWvWLA0cOFDe3t6SpAEDBujw4cOKjIz827Xb/86jjz6q1atX59q2c+dORUVFSbpar82bN992w/56p06d0tNPP63Q0FCFhYXl+fjrm+ilS5fW008/re+//97+NXny5L9dHmbNmjV69NFHc50PAAAAQP6wnAsAAHAb9957rzp37qy3335bVqtVd911l+bNm6fq1avroYcecpkxruncubNmzJihtLQ0Pf744/k+z8WLFzV06FClpKRo7NixOfYlJSXp4sWLt7R8iNVqVVZWlooXL66GDRvqxx9/VPXq1bV79+4cs9uzs7Nls9nszd7+/furdu3a2rVrl1544QVZLBaVLl1ahw8fVoMGDXT58mV5e3vLZrMpOTnZYTP+VvTp00ddu3bVoUOH1LBhQ0lSu3bt1KxZMz3zzDMKCQnRzp07Va5cOfXt2zff41wvOjpaw4YN04QJE/L9iYHr13NPTk7WV199pcWLF9u3ZWdnq2TJkg7P8ddff+nLL7/UypUr85UDAAAAgNxoogMAALcyffp0zZw5U9OnT1d2draaNm2qqVOn2mc9u8oY0tW1vBs1aqR9+/Zp7ty5eT7earVq48aNev/993XhwgWFh4fbZ5zHxMTou+++0969e1WsWDHVr1/f4XmysrIkXZ1p3alTpxvG7Nu3T1999ZX9+2nTptkb/8WLF1ft2rW1du1alSlTRu+//75+/vln7du3T+fOndPEiRPVs2dPxcbGKjQ0VAsWLLhpPjdTunRpTZgwQePGjdOyZcsUEBAgk8mk+fPn66OPPtL27dvVvHlzjR07NteM/Nvx4Ycf6q233lKLFi3yfQ6r1Zrj+xkzZjiMPXfunI4ePaqzZ8/al3NJT0/Xyy+/rOeeey7fD2cFAAAAkJvJdv2UFwAAAOSb1WrN1Qz9Xx4eHre81vrt2rt3r5599lk99NBDmjx5sqpVq2bfl56ervbt26t69er65z//qZCQEIfneffdd3Xo0CEtXLhQx48fV4kSJW66VEhmZqYqVKigcuXKadu2bfr222+1bds2de/eXS+//LLKli2rU6dO6a233tKuXbs0duxYhYaGKjU1VUOGDNGBAwc0bdo09ejRI9/XPnPmTJ09e1Zvv/12vs9R0I4fP65u3brd0oNF//jjD3Xt2lU1a9bU3LlzVbVqVc2ePVtxcXGaMWMGS7kAAAAATkQTHQAAwInGjx+vb7/91uH+0NBQvfbaawWWzx9//KG77777ts7x+uuv6+jRo1q+fHmej42JidG6dev05JNPqmrVqvbtmZmZmjNnjp544glVr17dvj07O1vLly9X37595eXlle+cbTabLl26JD8/v3yfo7BLS0tTqVKlcnzv6emp4sWLG5gVAAAA4H5oogMAADjR6dOndfHiRYf7AwICFBgYWIAZAQAAAABuB010AAAAAAAAAAAcKJgFOQEAAAAAAAAAcEE00QEAAAAAAAAAcIAmOgAAAAAAAAAADtBEBwAAAAAAAADAAZroAAAAAAAAAAA4QBMdAAAAAAAAAAAHaKIDAAAAAAAAAOAATXQAAAAAAAAAABz4f+vX7CVnnsudAAAAAElFTkSuQmCC",
|
||
"text/plain": [
|
||
"<Figure size 1600x1200 with 2 Axes>"
|
||
]
|
||
},
|
||
"metadata": {},
|
||
"output_type": "display_data"
|
||
}
|
||
],
|
||
"source": [
|
||
"import pandas as pd\n",
|
||
"import numpy as np\n",
|
||
"import matplotlib.pyplot as plt\n",
|
||
"import seaborn as sns\n",
|
||
"from scipy.stats import spearmanr\n",
|
||
"from tqdm import tqdm # 用于显示进度条 (可选)\n",
|
||
"\n",
|
||
"# 设置 Matplotlib/Seaborn 样式 (可选)\n",
|
||
"sns.set_theme(style=\"whitegrid\")\n",
|
||
"plt.rcParams['font.sans-serif'] = ['SimHei'] # 或者其他支持中文的字体\n",
|
||
"plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题\n",
|
||
"\n",
|
||
"def analyze_score_performance_2d(score_df: pd.DataFrame,\n",
|
||
" score_col: str = 'score',\n",
|
||
" label_col: str = 'label',\n",
|
||
" condition1_col: str = 'circ_mv',\n",
|
||
" condition2_col: str = 'future_return',\n",
|
||
" n_bins: int = 100,\n",
|
||
" min_samples_per_bin: int = 30): # 每个格子最少样本数\n",
|
||
" \"\"\"\n",
|
||
" 分析 score 在两个条件下 (如市值、未来收益) 的二维分箱表现。\n",
|
||
"\n",
|
||
" Args:\n",
|
||
" score_df (pd.DataFrame): 包含分数、标签和条件列的 DataFrame。\n",
|
||
" score_col (str): 预测分数所在的列名。\n",
|
||
" label_col (str): 目标标签所在的列名 (应为数值或可排序类别)。\n",
|
||
" condition1_col (str): 第一个条件列名 (例如 'circ_mv')。\n",
|
||
" condition2_col (str): 第二个条件列名 (例如 'future_return')。\n",
|
||
" n_bins (int): 每个条件划分的箱数 (分位数数量)。\n",
|
||
" min_samples_per_bin (int): 计算指标所需的最小样本数,小于此数目的格子结果将被屏蔽。\n",
|
||
"\n",
|
||
" Returns:\n",
|
||
" tuple: 包含 (performance_pivot, count_pivot, fig)\n",
|
||
" performance_pivot: 以二维分箱为索引/列的 Spearman 相关系数矩阵。\n",
|
||
" count_pivot: 每个二维分箱的样本数量矩阵。\n",
|
||
" fig: 生成的热力图 Matplotlib Figure 对象。\n",
|
||
" \"\"\"\n",
|
||
" print(f\"开始分析 '{score_col}' 在 '{condition1_col}' 和 '{condition2_col}' 下的表现...\")\n",
|
||
"\n",
|
||
" required_cols = [score_col, label_col, condition1_col, condition2_col]\n",
|
||
" if not all(col in score_df.columns for col in required_cols):\n",
|
||
" missing = [col for col in required_cols if col not in score_df.columns]\n",
|
||
" raise ValueError(f\"输入 DataFrame 缺少必需列: {missing}\")\n",
|
||
"\n",
|
||
" # --- 1. 数据准备和清洗 ---\n",
|
||
" print(\"准备数据,处理 NaN 值...\")\n",
|
||
" # 只保留需要的列,并移除包含 NaN 的行,避免影响分箱和计算\n",
|
||
" analysis_df = score_df[required_cols].dropna().copy()\n",
|
||
" n_original = len(score_df)\n",
|
||
" n_after_drop = len(analysis_df)\n",
|
||
" print(f\"原始数据 {n_original} 行,移除 NaN 后剩余 {n_after_drop} 行用于分析。\")\n",
|
||
"\n",
|
||
" if n_after_drop < min_samples_per_bin * n_bins: # 检查数据量是否过少\n",
|
||
" print(f\"警告: 清理 NaN 后数据量 ({n_after_drop}) 可能不足以支持 {n_bins}x{n_bins} 的精细分箱分析。\")\n",
|
||
" if n_after_drop < min_samples_per_bin:\n",
|
||
" print(\"错误: 有效数据过少,无法进行分析。\")\n",
|
||
" return None, None, None\n",
|
||
"\n",
|
||
" # --- 2. 二维分箱 ---\n",
|
||
" print(f\"对 '{condition1_col}' 和 '{condition2_col}' 进行 {n_bins} 分位数分箱...\")\n",
|
||
" bin1_col = f'{condition1_col}_bin'\n",
|
||
" bin2_col = f'{condition2_col}_bin'\n",
|
||
"\n",
|
||
" try:\n",
|
||
" # 使用 qcut 进行分位数分箱,labels=False 返回 0 到 n_bins-1 的整数标签\n",
|
||
" # duplicates='drop' 会丢弃导致边界不唯一的重复值所在的箱子,可能导致某些箱号缺失\n",
|
||
" # 对于可视化,这通常可以接受,但如果需要严格的等分,需先 rank\n",
|
||
" analysis_df[bin1_col] = pd.qcut(analysis_df[condition1_col], q=n_bins, labels=False, duplicates='drop')\n",
|
||
" analysis_df[bin2_col] = pd.qcut(analysis_df[condition2_col], q=n_bins, labels=False, duplicates='drop')\n",
|
||
" except Exception as e:\n",
|
||
" print(f\"错误: 分箱失败,请检查数据分布或减少 n_bins。错误信息: {e}\")\n",
|
||
" # 可以尝试先 rank 再 qcut\n",
|
||
" # analysis_df[bin1_col] = pd.qcut(analysis_df[condition1_col].rank(method='first'), q=n_bins, labels=False, duplicates='raise')\n",
|
||
" # analysis_df[bin2_col] = pd.qcut(analysis_df[condition2_col].rank(method='first'), q=n_bins, labels=False, duplicates='raise')\n",
|
||
" return None, None, None\n",
|
||
"\n",
|
||
" # --- 3. 分组计算表现指标 (Spearman Rank IC) ---\n",
|
||
" print(\"按二维分箱分组计算 Spearman Rank IC...\")\n",
|
||
"\n",
|
||
" def safe_spearmanr(x, y):\n",
|
||
" \"\"\"安全计算 Spearman 相关性,处理数据量过少的情况\"\"\"\n",
|
||
" if len(x) < max(2, min_samples_per_bin): # 要求至少有 min_samples_per_bin 个点才计算\n",
|
||
" return np.nan\n",
|
||
" corr, p_value = spearmanr(x, y)\n",
|
||
" return corr if not np.isnan(corr) else np.nan # 确保返回 NaN 而不是 None 或其他\n",
|
||
"\n",
|
||
" # 按两个分箱列分组\n",
|
||
" grouped = analysis_df.groupby([bin1_col, bin2_col])\n",
|
||
"\n",
|
||
" # 计算每个格子的 Spearman 相关系数\n",
|
||
" # apply 可能较慢,但计算相关性通常需要 apply\n",
|
||
" performance_series = grouped.apply(lambda sub: safe_spearmanr(sub[score_col], sub[label_col]))\n",
|
||
"\n",
|
||
" # 计算每个格子的样本数量\n",
|
||
" count_series = grouped.size()\n",
|
||
"\n",
|
||
" # --- 4. 结果整理成 Pivot Table (用于绘图) ---\n",
|
||
" print(\"整理结果用于绘图...\")\n",
|
||
" try:\n",
|
||
" # 将 performance_series 转换成二维矩阵\n",
|
||
" # index 为 condition1_bin, columns 为 condition2_bin\n",
|
||
" performance_pivot = performance_series.unstack(level=0) # level=0 对应第一个 groupby key (bin1_col)\n",
|
||
" count_pivot = count_series.unstack(level=0)\n",
|
||
"\n",
|
||
" # 可选:按列和索引排序,确保顺序正确\n",
|
||
" performance_pivot = performance_pivot.sort_index(axis=0).sort_index(axis=1)\n",
|
||
" count_pivot = count_pivot.sort_index(axis=0).sort_index(axis=1)\n",
|
||
" \n",
|
||
" print(performance_pivot)\n",
|
||
"\n",
|
||
" except Exception as e:\n",
|
||
" print(f\"错误: 无法将结果转换为二维矩阵,可能因为分箱不均匀或数据问题: {e}\")\n",
|
||
" return None, None, None\n",
|
||
"\n",
|
||
" # --- 5. 可视化:绘制热力图 ---\n",
|
||
" print(\"生成热力图...\")\n",
|
||
" fig, ax = plt.subplots(figsize=(16, 12)) # 调整图像大小\n",
|
||
"\n",
|
||
" # 使用 count_pivot 创建一个 mask,屏蔽掉样本量过小的格子\n",
|
||
" mask = count_pivot < min_samples_per_bin\n",
|
||
"\n",
|
||
" # 绘制热力图\n",
|
||
" sns.heatmap(performance_pivot,\n",
|
||
" annot=False, # 100x100 个格子加注释会太密集\n",
|
||
" fmt=\".2f\",\n",
|
||
" cmap=\"viridis\", # 选择颜色映射, 'viridis', 'coolwarm', 'RdYlGn' 等都不错\n",
|
||
" linewidths=.5,\n",
|
||
" linecolor='lightgray',\n",
|
||
" # mask=mask, # 应用 mask\n",
|
||
" ax=ax,\n",
|
||
" cbar_kws={'label': f'Spearman Rank IC ({score_col} vs {label_col})'}) # 颜色条标签\n",
|
||
"\n",
|
||
" # 设置标题和轴标签\n",
|
||
" ax.set_title(f'{score_col} 表现分析 (Rank IC vs {label_col})\\n基于 {condition1_col} 和 {condition2_col} {n_bins}x{n_bins} 分箱', fontsize=16)\n",
|
||
" ax.set_xlabel(f'{condition1_col} 分位数 (0 -> 高)', fontsize=12)\n",
|
||
" ax.set_ylabel(f'{condition2_col} 分位数 (0 -> 高)', fontsize=12)\n",
|
||
"\n",
|
||
" # 可选:调整刻度标签,避免显示所有 100 个刻度\n",
|
||
" if n_bins > 20:\n",
|
||
" tick_interval = n_bins // 10 # 大约显示 10 个刻度\n",
|
||
" ax.set_xticks(np.arange(0, n_bins, tick_interval) + 0.5)\n",
|
||
" ax.set_yticks(np.arange(0, n_bins, tick_interval) + 0.5)\n",
|
||
" ax.set_xticklabels(np.arange(0, n_bins, tick_interval))\n",
|
||
" ax.set_yticklabels(np.arange(0, n_bins, tick_interval))\n",
|
||
"\n",
|
||
" plt.xticks(rotation=45, ha='right')\n",
|
||
" plt.yticks(rotation=0)\n",
|
||
" plt.tight_layout() # 调整布局\n",
|
||
"\n",
|
||
" print(\"分析完成。\")\n",
|
||
" return performance_pivot, count_pivot, fig\n",
|
||
"\n",
|
||
"# --- 如何使用 ---\n",
|
||
"# 假设你的包含预测结果和所需列的 DataFrame 是 final_predictions_df\n",
|
||
"# 确保它包含 'score', 'label', 'circ_mv', 'future_return'\n",
|
||
"\n",
|
||
"# # 示例调用 (你需要有实际的 score_df)\n",
|
||
"try:\n",
|
||
" # 确保数据类型正确\n",
|
||
" cols_to_numeric = ['score', 'label', 'circ_mv', 'future_return']\n",
|
||
" for col in cols_to_numeric:\n",
|
||
" if col in score_df.columns:\n",
|
||
" score_df[col] = pd.to_numeric(score_df[col], errors='coerce')\n",
|
||
"\n",
|
||
" # 调用分析函数\n",
|
||
" performance_matrix, count_matrix, heatmap_figure = analyze_score_performance_2d(\n",
|
||
" score_df,\n",
|
||
" n_bins=100, # 你要求的100分箱\n",
|
||
" min_samples_per_bin=50 # 每个格子至少需要50个样本才显示IC,可以调整\n",
|
||
" )\n",
|
||
"\n",
|
||
" # 显示图像\n",
|
||
" if heatmap_figure:\n",
|
||
" plt.show()\n",
|
||
"\n",
|
||
" # 可以查看具体的 performance_matrix 和 count_matrix\n",
|
||
" # print(\"\\nPerformance Matrix (Spearman IC):\")\n",
|
||
" # print(performance_matrix)\n",
|
||
" # print(\"\\nCount Matrix:\")\n",
|
||
" # print(count_matrix)\n",
|
||
"\n",
|
||
"except ValueError as ve:\n",
|
||
" print(f\"数据错误: {ve}\")\n",
|
||
"except Exception as e:\n",
|
||
" print(f\"发生未知错误: {e}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 26,
|
||
"id": "a436dba4",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Empty DataFrame\n",
|
||
"Columns: [ts_code, trade_date, is_st]\n",
|
||
"Index: []\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"print(df[(df['ts_code'] == '600242.SH') & (df['trade_date'] >= '2023-06-01')][['ts_code', 'trade_date', 'is_st']])"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"kernelspec": {
|
||
"display_name": "new_trader",
|
||
"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.11"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 5
|
||
}
|