{ "cells": [ { "metadata": {}, "cell_type": "markdown", "source": [ "\n", "\n", "def get_technical_factor(df):\n", " # 按股票和日期排序\n", " df = df.sort_values(by=['ts_code', 'trade_date'])\n", " grouped = df.groupby('ts_code', group_keys=False)\n", "\n", " df['return_skew'] = grouped['pct_chg'].rolling(window=5).skew().reset_index(0, drop=True)\n", " df['return_kurtosis'] = grouped['pct_chg'].rolling(window=5).kurt().reset_index(0, drop=True)\n", "\n", " # 因子 1:短期成交量变化率\n", " df['volume_change_rate'] = (\n", " grouped['vol'].rolling(window=2).mean() /\n", " grouped['vol'].rolling(window=5).mean() - 1\n", " ).reset_index(level=0, drop=True) # 确保索引对齐\n", "\n", " # 因子 2:成交量突破信号\n", " max_volume = grouped['vol'].rolling(window=5).max().reset_index(level=0, drop=True) # 确保索引对齐\n", " df['cat_volume_breakout'] = (df['vol'] > max_volume)\n", "\n", " # 因子 3:换手率均线偏离度\n", " mean_turnover = grouped['turnover_rate'].rolling(window=3).mean().reset_index(level=0, drop=True)\n", " std_turnover = grouped['turnover_rate'].rolling(window=3).std().reset_index(level=0, drop=True)\n", " df['turnover_deviation'] = (df['turnover_rate'] - mean_turnover) / std_turnover\n", "\n", " # 因子 4:换手率激增信号\n", " df['cat_turnover_spike'] = (df['turnover_rate'] > mean_turnover + 2 * std_turnover)\n", "\n", " # 因子 5:量比均值\n", " df['avg_volume_ratio'] = grouped['volume_ratio'].rolling(window=3).mean().reset_index(level=0, drop=True)\n", "\n", " # 因子 6:量比突破信号\n", " max_volume_ratio = grouped['volume_ratio'].rolling(window=5).max().reset_index(level=0, drop=True)\n", " df['cat_volume_ratio_breakout'] = (df['volume_ratio'] > max_volume_ratio)\n", "\n", " # 因子 7:成交量与换手率的综合动量因子\n", " alpha = 0.5\n", " df['momentum_factor'] = df['volume_change_rate'] + alpha * df['turnover_deviation']\n", "\n", " # 因子 8:量价共振因子\n", " df['price_change_rate'] = grouped['close'].pct_change()\n", " df['resonance_factor'] = df['volume_ratio'] * df['price_change_rate']\n", "\n", " # 计算 up 和 down\n", " df['log_close'] = np.log(df['close'])\n", "\n", " df['vol_spike'] = grouped.apply(\n", " lambda x: pd.Series(x['vol'].rolling(20).mean(), index=x.index)\n", " )\n", " df['cat_vol_spike'] = df['vol'] > 2 * df['vol_spike']\n", " df['vol_std_5'] = df['vol'].pct_change().rolling(5).std()\n", "\n", " df['up'] = (df['high'] - df[['close', 'open']].max(axis=1)) / df['close']\n", " df['down'] = (df[['close', 'open']].min(axis=1) - df['low']) / df['close']\n", "\n", " # 计算 ATR\n", " df['atr_14'] = grouped.apply(\n", " lambda x: pd.Series(talib.ATR(x['high'].values, x['low'].values, x['close'].values, timeperiod=14),\n", " index=x.index)\n", " )\n", " df['atr_6'] = grouped.apply(\n", " lambda x: pd.Series(talib.ATR(x['high'].values, x['low'].values, x['close'].values, timeperiod=6),\n", " index=x.index)\n", " )\n", "\n", " # 计算 OBV 及其均线\n", " df['obv'] = grouped.apply(\n", " lambda x: pd.Series(talib.OBV(x['close'].values, x['vol'].values), index=x.index)\n", " )\n", " df['maobv_6'] = grouped.apply(\n", " lambda x: pd.Series(talib.SMA(x['obv'].values, timeperiod=6), index=x.index)\n", " )\n", " df['obv-maobv_6'] = df['obv'] - df['maobv_6']\n", "\n", " # 计算 RSI\n", " df['rsi_3'] = grouped.apply(\n", " lambda x: pd.Series(talib.RSI(x['close'].values, timeperiod=3), index=x.index)\n", " )\n", " df['rsi_6'] = grouped.apply(\n", " lambda x: pd.Series(talib.RSI(x['close'].values, timeperiod=6), index=x.index)\n", " )\n", " df['rsi_9'] = grouped.apply(\n", " lambda x: pd.Series(talib.RSI(x['close'].values, timeperiod=9), index=x.index)\n", " )\n", "\n", " # 计算 return_10 和 return_20\n", " df['return_5'] = grouped['close'].apply(lambda x: x / x.shift(5) - 1)\n", " df['return_10'] = grouped['close'].apply(lambda x: x / x.shift(10) - 1)\n", " df['return_20'] = grouped['close'].apply(lambda x: x / x.shift(20) - 1)\n", "\n", " # df['avg_close_5'] = grouped['close'].apply(lambda x: x.rolling(window=5).mean() / x)\n", "\n", " # 计算标准差指标\n", " df['std_return_5'] = grouped['close'].apply(lambda x: x.pct_change().rolling(window=5).std())\n", " df['std_return_15'] = grouped['close'].apply(lambda x: x.pct_change().rolling(window=15).std())\n", " df['std_return_25'] = grouped['close'].apply(lambda x: x.pct_change().rolling(window=25).std())\n", " df['std_return_90'] = grouped['close'].apply(lambda x: x.pct_change().rolling(window=90).std())\n", " df['std_return_90_2'] = grouped['close'].apply(lambda x: x.shift(10).pct_change().rolling(window=90).std())\n", "\n", " # 计算比值指标\n", " df['std_return_5 / std_return_90'] = df['std_return_5'] / df['std_return_90']\n", " df['std_return_5 / std_return_25'] = df['std_return_5'] / df['std_return_25']\n", "\n", " # 计算标准差差值\n", " df['std_return_90 - std_return_90_2'] = df['std_return_90'] - df['std_return_90_2']\n", "\n", " return df\n", "\n", "\n", "def get_act_factor(df, cat=True):\n", " # 按股票和日期排序\n", " df = df.sort_values(by=['ts_code', 'trade_date'])\n", " grouped = df.groupby('ts_code', group_keys=False)\n", " # 计算 EMA 指标\n", " df['_ema_5'] = grouped['close'].apply(\n", " lambda x: pd.Series(talib.EMA(x.values, timeperiod=5), index=x.index)\n", " )\n", " df['_ema_13'] = grouped['close'].apply(\n", " lambda x: pd.Series(talib.EMA(x.values, timeperiod=13), index=x.index)\n", " )\n", " df['_ema_20'] = grouped['close'].apply(\n", " lambda x: pd.Series(talib.EMA(x.values, timeperiod=20), index=x.index)\n", " )\n", " df['_ema_60'] = grouped['close'].apply(\n", " lambda x: pd.Series(talib.EMA(x.values, timeperiod=60), index=x.index)\n", " )\n", "\n", " # 计算 act_factor1, act_factor2, act_factor3, act_factor4\n", " df['act_factor1'] = grouped['_ema_5'].apply(\n", " lambda x: np.arctan((x / x.shift(1) - 1) * 100) * 57.3 / 50\n", " )\n", " df['act_factor2'] = grouped['_ema_13'].apply(\n", " lambda x: np.arctan((x / x.shift(1) - 1) * 100) * 57.3 / 40\n", " )\n", " df['act_factor3'] = grouped['_ema_20'].apply(\n", " lambda x: np.arctan((x / x.shift(1) - 1) * 100) * 57.3 / 21\n", " )\n", " df['act_factor4'] = grouped['_ema_60'].apply(\n", " lambda x: np.arctan((x / x.shift(1) - 1) * 100) * 57.3 / 10\n", " )\n", "\n", " if cat:\n", " df['cat_af1'] = df['act_factor1'] > 0\n", " df['cat_af2'] = df['act_factor2'] > df['act_factor1']\n", " df['cat_af3'] = df['act_factor3'] > df['act_factor2']\n", " df['cat_af4'] = df['act_factor4'] > df['act_factor3']\n", "\n", " # 计算 act_factor5 和 act_factor6\n", " df['act_factor5'] = df['act_factor1'] + df['act_factor2'] + df['act_factor3'] + df['act_factor4']\n", " df['act_factor6'] = (df['act_factor1'] - df['act_factor2']) / np.sqrt(\n", " df['act_factor1'] ** 2 + df['act_factor2'] ** 2)\n", "\n", " # 根据 trade_date 截面计算排名\n", " df['rank_act_factor1'] = df.groupby('trade_date', group_keys=False)['act_factor1'].rank(ascending=False, pct=True)\n", " df['rank_act_factor2'] = df.groupby('trade_date', group_keys=False)['act_factor2'].rank(ascending=False, pct=True)\n", " df['rank_act_factor3'] = df.groupby('trade_date', group_keys=False)['act_factor3'].rank(ascending=False, pct=True)\n", "\n", " return df\n", "\n", "\n", "def get_money_flow_factor(df):\n", " # 计算资金流相关因子(字段名称见 tushare 数据说明)\n", " df['active_buy_volume_large'] = df['buy_lg_vol'] / df['net_mf_vol']\n", " df['active_buy_volume_big'] = df['buy_elg_vol'] / df['net_mf_vol']\n", " df['active_buy_volume_small'] = df['buy_sm_vol'] / df['net_mf_vol']\n", "\n", " df['buy_lg_vol_minus_sell_lg_vol'] = (df['buy_lg_vol'] - df['sell_lg_vol']) / df['net_mf_vol']\n", " df['buy_elg_vol_minus_sell_elg_vol'] = (df['buy_elg_vol'] - df['sell_elg_vol']) / df['net_mf_vol']\n", "\n", " df['log(circ_mv)'] = np.log(df['circ_mv'])\n", " return df\n", "\n", "\n", "def get_alpha_factor(df):\n", " df = df.sort_values(by=['ts_code', 'trade_date'])\n", " grouped = df.groupby('ts_code')\n", "\n", " # alpha_022: 当前 close 与 5 日前 close 差值\n", " df['alpha_022'] = grouped['close'].transform(lambda x: x - x.shift(5))\n", "\n", " # alpha_003: (close - open) / (high - low)\n", " df['alpha_003'] = np.where(df['high'] != df['low'],\n", " (df['close'] - df['open']) / (df['high'] - df['low']),\n", " 0)\n", "\n", " # alpha_007: 计算过去5日 close 与 vol 的相关性,并按 trade_date 排名\n", " df['alpha_007'] = grouped.apply(lambda x: x['close'].rolling(5).corr(x['vol'])).reset_index(level=0, drop=True)\n", " df['alpha_007'] = df.groupby('trade_date', group_keys=False)['alpha_007'].rank(ascending=True, pct=True)\n", "\n", " # alpha_013: 计算过去5日 close 之和 - 20日 close 之和,并按 trade_date 排名\n", " df['alpha_013'] = grouped['close'].transform(lambda x: x.rolling(5).sum() - x.rolling(20).sum())\n", " df['alpha_013'] = df.groupby('trade_date', group_keys=False)['alpha_013'].rank(ascending=True, pct=True)\n", "\n", " return df\n", "\n", "\n", "def get_limit_factor(df):\n", " # 按股票和日期排序\n", " df = df.sort_values(by=['ts_code', 'trade_date'])\n", "\n", " # 分组处理\n", " grouped = df.groupby('ts_code', group_keys=False)\n", "\n", " # 1. 今日是否涨停/跌停\n", " df['cat_up_limit'] = (df['close'] == df['up_limit']).astype(int) # 是否涨停(1表示涨停,0表示未涨停)\n", " df['cat_down_limit'] = (df['close'] == df['down_limit']).astype(int) # 是否跌停(1表示跌停,0表示未跌停)\n", "\n", " # 2. 最近涨跌停次数(过去20个交易日)\n", " df['up_limit_count_10d'] = grouped['cat_up_limit'].rolling(window=10, min_periods=1).sum().reset_index(level=0,\n", " drop=True)\n", " df['down_limit_count_10d'] = grouped['cat_down_limit'].rolling(window=10, min_periods=1).sum().reset_index(level=0,\n", " drop=True)\n", "\n", " # 3. 最近连续涨跌停天数\n", " def calculate_consecutive_limits(series):\n", " \"\"\"\n", " 计算连续涨停/跌停天数。\n", " \"\"\"\n", " consecutive_up = series * (series.groupby((series != series.shift()).cumsum()).cumcount() + 1)\n", " consecutive_down = series * (series.groupby((series != series.shift()).cumsum()).cumcount() + 1)\n", " return consecutive_up, consecutive_down\n", "\n", " # 连续涨停天数\n", " df['consecutive_up_limit'] = grouped['cat_up_limit'].apply(\n", " lambda x: calculate_consecutive_limits(x)[0]\n", " ).reset_index(level=0, drop=True)\n", "\n", " # 连续跌停天数\n", " # df['consecutive_down_limit'] = grouped['cat_down_limit'].apply(\n", " # lambda x: calculate_consecutive_limits(x)[1]\n", " # ).reset_index(level=0, drop=True)\n", "\n", " return df\n", "\n", "\n", "def get_cyp_perf_factor(df):\n", " # 预处理:按股票代码和时间排序\n", " df = df.sort_values(by=['ts_code', 'trade_date'])\n", "\n", " # 按股票代码分组处理\n", " grouped = df.groupby('ts_code', group_keys=False)\n", "\n", " df['ctrl_strength'] = (df['cost_85pct'] - df['cost_15pct']) / (df['his_high'] - df['his_low'])\n", "\n", " df['low_cost_dev'] = (df['close'] - df['cost_5pct']) / (df['cost_50pct'] - df['cost_5pct'])\n", "\n", " df['asymmetry'] = (df['cost_95pct'] - df['cost_50pct']) / (df['cost_50pct'] - df['cost_5pct'])\n", "\n", " df['lock_factor'] = df['turnover_rate'] * (\n", " 1 - (df['cost_95pct'] - df['cost_5pct']) / (df['his_high'] - df['his_low']))\n", "\n", " df['vol_break'] = np.where((df['close'] > df['cost_85pct']) & (df['volume_ratio'] > 2), 1, 0)\n", "\n", " df['weight_roc5'] = grouped['weight_avg'].apply(lambda x: x.pct_change(5))\n", "\n", " def rolling_corr(group):\n", " roc_close = group['close'].pct_change()\n", " roc_weight = group['weight_avg'].pct_change()\n", " return roc_close.rolling(10).corr(roc_weight)\n", "\n", " df['price_cost_divergence'] = grouped.apply(rolling_corr)\n", "\n", " def calc_atr(group):\n", " high, low, close = group['high'], group['low'], group['close']\n", " tr = np.maximum(high - low,\n", " np.maximum(abs(high - close.shift()),\n", " abs(low - close.shift())))\n", " return tr.rolling(14).mean()\n", "\n", " df['atr_14'] = grouped.apply(calc_atr)\n", " df['cost_atr_adj'] = (df['cost_95pct'] - df['cost_5pct']) / df['atr_14']\n", "\n", " # 12. 小盘股筹码集中度\n", " df['smallcap_concentration'] = (1 / df['circ_mv']) * (df['cost_85pct'] - df['cost_15pct'])\n", "\n", " # 16. 筹码稳定性指数 (20日波动率)\n", " df['weight_std20'] = grouped['weight_avg'].apply(lambda x: x.rolling(20).std())\n", " df['cost_stability'] = df['weight_std20'] / grouped['weight_avg'].transform(lambda x: x.rolling(20).mean())\n", "\n", " # 17. 成本区间突破标记\n", " df['high_cost_break_days'] = grouped.apply(lambda g: g['close'].gt(g['cost_95pct']).rolling(5).sum())\n", "\n", " # 18. 黄金筹码共振 (复合事件)\n", " df['cat_golden_resonance'] = ((df['close'] > df['weight_avg']) &\n", " (df['volume_ratio'] > 1.5) &\n", " (df['winner_rate'] > 0.7))\n", "\n", " # 20. 筹码-流动性风险\n", " df['liquidity_risk'] = (df['cost_95pct'] - df['cost_5pct']) * (\n", " 1 / grouped['vol'].transform(lambda x: x.rolling(10).mean()))\n", "\n", " df.drop(columns=['weight_std20'], inplace=True, errors='ignore')\n", "\n", " return df\n", "\n", "\n", "def get_mv_factors(df):\n", " \"\"\"\n", " 计算多个因子并生成最终的综合因子。\n", "\n", " 参数:\n", " df (pd.DataFrame): 包含 ts_code, trade_date, turnover_rate, pe_ttm, pb, ps, circ_mv, volume_ratio, vol 等列的数据框。\n", "\n", " 返回:\n", " pd.DataFrame: 包含新增因子和最终综合因子的数据框。\n", " \"\"\"\n", " # 按 ts_code 和 trade_date 排序\n", " df = df.sort_values(by=['ts_code', 'trade_date'])\n", "\n", " # 按 ts_code 分组\n", " grouped = df.groupby('ts_code', group_keys=False)\n", "\n", " # 1. 市值流动比因子\n", " df['mv_turnover_ratio'] = df['turnover_rate'] / df['circ_mv']\n", "\n", " # 2. 市值调整成交量因子\n", " df['mv_adjusted_volume'] = df['vol'] / df['circ_mv']\n", "\n", " # 3. 市值加权换手率因子\n", " df['mv_weighted_turnover'] = df['turnover_rate'] * (1 / df['circ_mv'])\n", "\n", " # 4. 非线性市值成交量因子\n", " df['nonlinear_mv_volume'] = df['vol'] / df['circ_mv']\n", "\n", " # 5. 市值量比因子\n", " df['mv_volume_ratio'] = df['volume_ratio'] / df['circ_mv']\n", "\n", " # 6. 市值动量因子\n", " df['mv_momentum'] = df['turnover_rate'] * df['volume_ratio'] / df['circ_mv']\n", "\n", " # 7. 市值波动率因子\n", " df['turnover_std'] = grouped['turnover_rate'].rolling(window=20).std().reset_index(level=0, drop=True)\n", " df['mv_volatility'] = grouped.apply(lambda x: x['turnover_std'] / x['circ_mv']).reset_index(level=0, drop=True)\n", "\n", " # 8. 市值成长性因子\n", " df['volume_growth'] = grouped['vol'].pct_change(periods=20).reset_index(level=0, drop=True)\n", " df['mv_growth'] = grouped.apply(lambda x: x['volume_growth'] / x['circ_mv']).reset_index(level=0, drop=True)\n", "\n", " # # 标准化因子\n", " # factor_columns = [\n", " # 'mv_turnover_ratio', 'mv_adjusted_volume', 'mv_weighted_turnover',\n", " # 'nonlinear_mv_volume', 'mv_volume_ratio', 'mv_momentum',\n", " # 'mv_volatility', 'mv_growth'\n", " # ]\n", " # scaler = StandardScaler()\n", " # df[factor_columns] = scaler.fit_transform(df[factor_columns])\n", " #\n", " # # 加权合成因子\n", " # weights = [0.2, 0.15, 0.15, 0.1, 0.1, 0.1, 0.1, 0.1] # 各因子权重\n", " # df['final_combined_factor'] = df[factor_columns].dot(weights)\n", "\n", " return df" ], "id": "505e825945e4b8cf" } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 5 }