{ "cells": [ { "metadata": {}, "cell_type": "markdown", "source": [ "# Learn-to-Rank 排序学习训练流程\n", "#\n", "本 Notebook 实现基于 LightGBM LambdaRank 的排序学习训练,用于股票排序任务。\n", "#\n", "## 核心特点\n", "#\n", "1. **Label 转换**: 将 `future_return_5` 按每日进行 20 分位数划分(qcut)\n", "2. **排序学习**: 使用 LambdaRank 目标函数,学习每日股票排序\n", "3. **NDCG 评估**: 使用 NDCG@1/5/10/20 评估排序质量\n", "4. **策略回测**: 基于排序分数构建 Top-k 选股策略" ] }, { "metadata": {}, "cell_type": "markdown", "source": "## 1. 导入依赖" }, { "metadata": { "ExecuteTime": { "end_time": "2026-03-13T14:14:03.291580Z", "start_time": "2026-03-13T14:14:01.936601Z" } }, "cell_type": "code", "source": [ "import os\n", "from datetime import datetime\n", "from typing import List, Tuple, Optional\n", "\n", "import numpy as np\n", "import polars as pl\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.metrics import ndcg_score\n", "\n", "from src.factors import FactorEngine\n", "from src.training import (\n", " DateSplitter,\n", " STFilter,\n", " StockPoolManager,\n", " Trainer,\n", " Winsorizer,\n", " NullFiller,\n", " StandardScaler,\n", " check_data_quality,\n", ")\n", "from src.training.components.models import LightGBMLambdaRankModel\n", "from src.training.config import TrainingConfig\n", "\n" ], "outputs": [], "execution_count": 1 }, { "metadata": {}, "cell_type": "markdown", "source": "## 2. 辅助函数" }, { "metadata": { "ExecuteTime": { "end_time": "2026-03-13T14:14:03.306725Z", "start_time": "2026-03-13T14:14:03.299251Z" } }, "cell_type": "code", "source": [ "def create_factors_with_metadata(\n", " engine: FactorEngine,\n", " selected_factors: List[str],\n", " factor_definitions: dict,\n", " label_factor: dict,\n", ") -> List[str]:\n", " \"\"\"注册因子(SELECTED_FACTORS 从 metadata 查询,FACTOR_DEFINITIONS 用表达式注册)\"\"\"\n", " print(\"=\" * 80)\n", " print(\"注册因子\")\n", " print(\"=\" * 80)\n", "\n", " # 注册 SELECTED_FACTORS 中的因子(已在 metadata 中)\n", " print(\"\\n注册特征因子(从 metadata):\")\n", " for name in selected_factors:\n", " engine.add_factor(name)\n", " print(f\" - {name}\")\n", "\n", " # 注册 FACTOR_DEFINITIONS 中的因子(通过表达式,尚未在 metadata 中)\n", " print(\"\\n注册特征因子(表达式):\")\n", " for name, expr in factor_definitions.items():\n", " engine.add_factor(name, expr)\n", " print(f\" - {name}: {expr}\")\n", "\n", " # 注册 label 因子(通过表达式)\n", " print(\"\\n注册 Label 因子(表达式):\")\n", " for name, expr in label_factor.items():\n", " engine.add_factor(name, expr)\n", " print(f\" - {name}: {expr}\")\n", "\n", " # 特征列 = SELECTED_FACTORS + FACTOR_DEFINITIONS 的 keys\n", " feature_cols = selected_factors + list(factor_definitions.keys())\n", "\n", " print(f\"\\n特征因子数: {len(feature_cols)}\")\n", " print(f\" - 来自 metadata: {len(selected_factors)}\")\n", " print(f\" - 来自表达式: {len(factor_definitions)}\")\n", " print(f\"Label: {list(label_factor.keys())[0]}\")\n", " print(f\"已注册因子总数: {len(engine.list_registered())}\")\n", "\n", " return feature_cols\n", "\n", "\n", "def prepare_data(\n", " engine: FactorEngine,\n", " feature_cols: List[str],\n", " start_date: str,\n", " end_date: str,\n", ") -> pl.DataFrame:\n", " \"\"\"准备数据\"\"\"\n", " print(\"\\n\" + \"=\" * 80)\n", " print(\"准备数据\")\n", " print(\"=\" * 80)\n", "\n", " # 计算因子(全市场数据)\n", " print(f\"\\n计算因子: {start_date} - {end_date}\")\n", " factor_names = feature_cols + [LABEL_NAME] # 包含 label\n", "\n", " data = engine.compute(\n", " factor_names=factor_names,\n", " start_date=start_date,\n", " end_date=end_date,\n", " )\n", "\n", " print(f\"数据形状: {data.shape}\")\n", " print(f\"数据列: {data.columns}\")\n", " print(f\"\\n前5行预览:\")\n", " print(data.head())\n", "\n", " return data\n", "\n", "\n", "def prepare_ranking_data(\n", " df: pl.DataFrame,\n", " label_col: str = \"future_return_5\",\n", " date_col: str = \"trade_date\",\n", " n_quantiles: int = 20,\n", ") -> Tuple[pl.DataFrame, str]:\n", " \"\"\"准备排序学习数据\n", "\n", " 将连续 label 转换为分位数标签,用于排序学习任务。\n", "\n", " Args:\n", " df: 原始数据\n", " label_col: 原始标签列名\n", " date_col: 日期列名\n", " n_quantiles: 分位数数量\n", "\n", " Returns:\n", " (处理后的 DataFrame, 新的标签列名)\n", " \"\"\"\n", " print(\"\\n\" + \"=\" * 80)\n", " print(f\"准备排序学习数据(将 {label_col} 转换为 {n_quantiles} 分位数标签)\")\n", " print(\"=\" * 80)\n", "\n", " # 新的标签列名\n", " rank_col = f\"{label_col}_rank\"\n", "\n", " # 按日期分组进行分位数划分\n", " # 使用 rank 生成 0, 1, 2, ..., n_quantiles-1 的标签\n", " # 方法: 计算每天内的排名,然后映射到 n_quantiles 个分位数组\n", " df_ranked = (\n", " df.with_columns(\n", " # 计算每天内的排名 (1-based)\n", " pl.col(label_col).rank(method=\"min\").over(date_col).alias(\"_rank\")\n", " )\n", " .with_columns(\n", " # 将排名转换为分位数标签 (0 to n_quantiles-1)\n", " ((pl.col(\"_rank\") - 1) / pl.len().over(date_col) * n_quantiles)\n", " .floor()\n", " .cast(pl.Int64)\n", " .clip(0, n_quantiles - 1)\n", " .alias(rank_col)\n", " )\n", " .drop(\"_rank\")\n", " )\n", "\n", " # 检查转换结果\n", " print(f\"\\n原始 {label_col} 统计:\")\n", " print(df_ranked[label_col].describe())\n", "\n", " print(f\"\\n转换后 {rank_col} 统计:\")\n", " print(df_ranked[rank_col].describe())\n", "\n", " # 检查每日样本分布\n", " print(f\"\\n每日样本数统计:\")\n", " daily_counts = df_ranked.group_by(date_col).agg(pl.count().alias(\"count\"))\n", " print(daily_counts[\"count\"].describe())\n", "\n", " # 检查分位数分布(应该是均匀的)\n", " print(f\"\\n分位数标签分布:\")\n", " rank_dist = df_ranked[rank_col].value_counts().sort(rank_col)\n", " print(rank_dist)\n", "\n", " return df_ranked, rank_col\n", "\n", "\n", "def compute_group_array(df: pl.DataFrame, date_col: str = \"trade_date\") -> np.ndarray:\n", " \"\"\"计算 group 数组用于 LambdaRank\n", "\n", " 每个日期作为一个 query,group 数组表示每个 query 的样本数。\n", "\n", " Args:\n", " df: 数据框\n", " date_col: 日期列名\n", "\n", " Returns:\n", " group 数组\n", " \"\"\"\n", " group_counts = df.group_by(date_col, maintain_order=True).agg(\n", " pl.count().alias(\"count\")\n", " )\n", " return group_counts[\"count\"].to_numpy()\n", "\n", "\n", "def evaluate_ndcg_at_k(\n", " y_true: np.ndarray,\n", " y_pred: np.ndarray,\n", " group: np.ndarray,\n", " k_list: List[int] = [1, 5, 10, 20],\n", ") -> dict:\n", " \"\"\"计算 NDCG@k 指标\n", "\n", " Args:\n", " y_true: 真实标签\n", " y_pred: 预测分数\n", " group: 分组数组\n", " k_list: 要计算的 k 值列表\n", "\n", " Returns:\n", " NDCG 指标字典\n", " \"\"\"\n", " results = {}\n", "\n", " # 按 group 拆分数据\n", " start_idx = 0\n", " y_true_groups = []\n", " y_pred_groups = []\n", "\n", " for group_size in group:\n", " end_idx = start_idx + group_size\n", " y_true_groups.append(y_true[start_idx:end_idx])\n", " y_pred_groups.append(y_pred[start_idx:end_idx])\n", " start_idx = end_idx\n", "\n", " # 计算每个 k 值的平均 NDCG\n", " for k in k_list:\n", " ndcg_scores = []\n", " for yt, yp in zip(y_true_groups, y_pred_groups):\n", " if len(yt) > 1:\n", " try:\n", " score = ndcg_score([yt], [yp], k=k)\n", " ndcg_scores.append(score)\n", " except ValueError:\n", " # 标签都相同,无法计算\n", " pass\n", "\n", " results[f\"ndcg@{k}\"] = np.mean(ndcg_scores) if ndcg_scores else 0.0\n", "\n", " return results\n", "\n" ], "outputs": [], "execution_count": 2 }, { "metadata": {}, "cell_type": "markdown", "source": [ "## 3. 配置参数\n", "#\n", "### 3.1 因子定义" ] }, { "metadata": { "ExecuteTime": { "end_time": "2026-03-13T14:14:03.317598Z", "start_time": "2026-03-13T14:14:03.314983Z" } }, "cell_type": "code", "source": [ "# 特征因子定义字典(复用 regression.ipynb 的因子定义)\n", "LABEL_NAME = \"future_return_5_rank\"\n", "\n", "# 当前选择的因子列表(从 FACTOR_DEFINITIONS 中选择要使用的因子)\n", "SELECTED_FACTORS = [\n", " # ================= 1. 价格、趋势与路径依赖 =================\n", " \"ma_5\",\n", " \"ma_20\",\n", " \"ma_ratio_5_20\",\n", " \"bias_10\",\n", " \"high_low_ratio\",\n", " \"bbi_ratio\",\n", " \"return_5\",\n", " \"return_20\",\n", " \"kaufman_ER_20\",\n", " \"mom_acceleration_10_20\",\n", " \"drawdown_from_high_60\",\n", " \"up_days_ratio_20\",\n", " # ================= 2. 波动率、风险调整与高阶矩 =================\n", " \"volatility_5\",\n", " \"volatility_20\",\n", " \"volatility_ratio\",\n", " \"std_return_20\",\n", " \"sharpe_ratio_20\",\n", " \"min_ret_20\",\n", " \"volatility_squeeze_5_60\",\n", " # ================= 3. 日内微观结构与异象 =================\n", " \"overnight_intraday_diff\",\n", " \"upper_shadow_ratio\",\n", " \"capital_retention_20\",\n", " \"max_ret_20\",\n", " # ================= 4. 量能、流动性与量价背离 =================\n", " \"volume_ratio_5_20\",\n", " \"turnover_rate_mean_5\",\n", " \"turnover_deviation\",\n", " \"amihud_illiq_20\",\n", " \"turnover_cv_20\",\n", " \"pv_corr_20\",\n", " \"close_vwap_deviation\",\n", " # ================= 5. 基本面财务特征 =================\n", " \"roe\",\n", " \"roa\",\n", " \"profit_margin\",\n", " \"debt_to_equity\",\n", " \"current_ratio\",\n", " \"net_profit_yoy\",\n", " \"revenue_yoy\",\n", " \"healthy_expansion_velocity\",\n", " # ================= 6. 基本面估值与截面动量共振 =================\n", " \"EP\",\n", " \"BP\",\n", " \"CP\",\n", " \"market_cap_rank\",\n", " \"turnover_rank\",\n", " \"return_5_rank\",\n", " \"EP_rank\",\n", " \"pe_expansion_trend\",\n", " \"value_price_divergence\",\n", " \"active_market_cap\",\n", " \"ebit_rank\",\n", "]\n", "\n", "# 因子定义字典(完整因子库)\n", "FACTOR_DEFINITIONS = {\n", " # \"turnover_rate_volatility\": \"ts_std(turnover_rate, 20)\"\n", "}\n", "\n", "# Label 因子定义(不参与训练,用于计算目标)\n", "LABEL_FACTOR = {\n", " LABEL_NAME: \"(ts_delay(close, -5) / ts_delay(open, -1)) - 1\",\n", "}" ], "outputs": [], "execution_count": 3 }, { "metadata": {}, "cell_type": "markdown", "source": "### 3.2 训练参数配置" }, { "metadata": { "ExecuteTime": { "end_time": "2026-03-13T14:14:03.327552Z", "start_time": "2026-03-13T14:14:03.321870Z" } }, "cell_type": "code", "source": [ "# 日期范围配置(正确的 train/val/test 三分法)\n", "TRAIN_START = \"20200101\"\n", "TRAIN_END = \"20231231\"\n", "VAL_START = \"20240101\"\n", "VAL_END = \"20241231\"\n", "TEST_START = \"20250101\"\n", "TEST_END = \"20251231\"\n", "\n", "\n", "# 分位数配置\n", "N_QUANTILES = 20 # 将 label 分为 20 组\n", "\n", "# LambdaRank 模型参数配置\n", "MODEL_PARAMS = {\n", " \"objective\": \"lambdarank\",\n", " \"metric\": \"ndcg\",\n", " \"ndcg_at\": 10, # 评估 NDCG@k\n", " \"learning_rate\": 0.01,\n", " \"num_leaves\": 31,\n", " \"max_depth\": 4,\n", " \"min_data_in_leaf\": 20,\n", " \"n_estimators\": 2000,\n", " \"early_stopping_round\": 300,\n", " \"subsample\": 0.8,\n", " \"colsample_bytree\": 0.8,\n", " \"reg_alpha\": 0.1,\n", " \"reg_lambda\": 1.0,\n", " \"verbose\": -1,\n", " \"random_state\": 42,\n", " \"lambdarank_truncation_level\": 10,\n", " \"label_gain\": [i for i in range(1, N_QUANTILES + 1)],\n", "}\n", "\n", "\n", "# 特征列(用于数据处理器)\n", "FEATURE_COLS = SELECTED_FACTORS\n", "\n", "# 数据处理器配置\n", "PROCESSORS = [\n", " NullFiller(feature_cols=FEATURE_COLS, strategy=\"mean\"),\n", " Winsorizer(feature_cols=FEATURE_COLS, lower=0.01, upper=0.99),\n", " StandardScaler(feature_cols=FEATURE_COLS),\n", "]\n", "\n", "\n", "# 股票池筛选函数\n", "def stock_pool_filter(df: pl.DataFrame) -> pl.Series:\n", " \"\"\"股票池筛选函数(单日数据)\n", "\n", " 筛选条件:\n", " 1. 排除创业板(代码以 300 开头)\n", " 2. 排除科创板(代码以 688 开头)\n", " 3. 排除北交所(代码以 8、9 或 4 开头)\n", " 4. 选取当日市值最小的500只股票\n", " \"\"\"\n", " code_filter = (\n", " ~df[\"ts_code\"].str.starts_with(\"30\")\n", " & ~df[\"ts_code\"].str.starts_with(\"68\")\n", " & ~df[\"ts_code\"].str.starts_with(\"8\")\n", " & ~df[\"ts_code\"].str.starts_with(\"9\")\n", " & ~df[\"ts_code\"].str.starts_with(\"4\")\n", " )\n", "\n", " valid_df = df.filter(code_filter)\n", " n = min(1000, len(valid_df))\n", " small_cap_codes = valid_df.sort(\"total_mv\").head(n)[\"ts_code\"]\n", "\n", " return df[\"ts_code\"].is_in(small_cap_codes)\n", "\n", "\n", "STOCK_FILTER_REQUIRED_COLUMNS = [\"total_mv\"]\n", "\n", "# 输出配置\n", "OUTPUT_DIR = \"output\"\n", "SAVE_PREDICTIONS = True\n", "PERSIST_MODEL = False\n", "\n", "# Top N 配置:每日推荐股票数量\n", "TOP_N = 5 # 可调整为 10, 20 等" ], "outputs": [], "execution_count": 4 }, { "metadata": {}, "cell_type": "markdown", "source": "## 4. 训练流程" }, { "metadata": { "ExecuteTime": { "end_time": "2026-03-13T14:14:25.239464Z", "start_time": "2026-03-13T14:14:03.331272Z" } }, "cell_type": "code", "source": [ "print(\"\\n\" + \"=\" * 80)\n", "print(\"LightGBM LambdaRank 排序学习训练\")\n", "print(\"=\" * 80)\n", "\n", "# 1. 创建 FactorEngine(启用 metadata 功能)\n", "print(\"\\n[1] 创建 FactorEngine\")\n", "engine = FactorEngine()\n", "\n", "# 2. 使用 metadata 定义因子\n", "print(\"\\n[2] 定义因子(从 metadata 注册)\")\n", "feature_cols = create_factors_with_metadata(\n", " engine, SELECTED_FACTORS, FACTOR_DEFINITIONS, LABEL_FACTOR\n", ")\n", "\n", "# 3. 准备数据\n", "print(\"\\n[3] 准备数据\")\n", "data = prepare_data(\n", " engine=engine,\n", " feature_cols=feature_cols,\n", " start_date=TRAIN_START,\n", " end_date=TEST_END,\n", ")\n", "\n", "# 4. 转换为排序学习格式(分位数标签)\n", "print(\"\\n[4] 转换为排序学习格式\")\n", "data, target_col = prepare_ranking_data(\n", " df=data,\n", " label_col=LABEL_NAME,\n", " n_quantiles=N_QUANTILES,\n", ")\n", "\n", "# 5. 打印配置信息\n", "print(f\"\\n[配置] 训练期: {TRAIN_START} - {TRAIN_END}\")\n", "print(f\"[配置] 验证期: {VAL_START} - {VAL_END}\")\n", "print(f\"[配置] 测试期: {TEST_START} - {TEST_END}\")\n", "print(f\"[配置] 特征数: {len(feature_cols)}\")\n", "print(f\"[配置] 目标变量: {target_col}({N_QUANTILES}分位数)\")\n", "\n", "# 6. 创建排序学习模型\n", "model: LightGBMLambdaRankModel = LightGBMLambdaRankModel(params=MODEL_PARAMS)\n", "\n", "# 7. 创建数据处理器\n", "processors = PROCESSORS\n", "\n", "# 8. 创建数据划分器\n", "splitter = DateSplitter(\n", " train_start=TRAIN_START,\n", " train_end=TRAIN_END,\n", " val_start=VAL_START,\n", " val_end=VAL_END,\n", " test_start=TEST_START,\n", " test_end=TEST_END,\n", ")\n", "\n", "# 9. 创建股票池管理器\n", "pool_manager = StockPoolManager(\n", " filter_func=stock_pool_filter,\n", " required_columns=STOCK_FILTER_REQUIRED_COLUMNS,\n", " data_router=engine.router,\n", ")\n", "\n", "# 10. 创建 ST 过滤器\n", "st_filter = STFilter(data_router=engine.router)\n", "\n", "# 11. 创建训练器\n", "trainer = Trainer(\n", " model=model,\n", " pool_manager=pool_manager,\n", " processors=processors,\n", " filters=[st_filter],\n", " splitter=splitter,\n", " target_col=target_col,\n", " feature_cols=feature_cols,\n", " persist_model=PERSIST_MODEL,\n", ")" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "================================================================================\n", "LightGBM LambdaRank 排序学习训练\n", "================================================================================\n", "\n", "[1] 创建 FactorEngine\n", "\n", "[2] 定义因子(从 metadata 注册)\n", "================================================================================\n", "注册因子\n", "================================================================================\n", "\n", "注册特征因子(从 metadata):\n", " - ma_5\n", " - ma_20\n", " - ma_ratio_5_20\n", " - bias_10\n", " - high_low_ratio\n", " - bbi_ratio\n", " - return_5\n", " - return_20\n", " - kaufman_ER_20\n", " - mom_acceleration_10_20\n", " - drawdown_from_high_60\n", " - up_days_ratio_20\n", " - volatility_5\n", " - volatility_20\n", " - volatility_ratio\n", " - std_return_20\n", " - sharpe_ratio_20\n", " - min_ret_20\n", " - volatility_squeeze_5_60\n", " - overnight_intraday_diff\n", " - upper_shadow_ratio\n", " - capital_retention_20\n", " - max_ret_20\n", " - volume_ratio_5_20\n", " - turnover_rate_mean_5\n", " - turnover_deviation\n", " - amihud_illiq_20\n", " - turnover_cv_20\n", " - pv_corr_20\n", " - close_vwap_deviation\n", " - roe\n", " - roa\n", " - profit_margin\n", " - debt_to_equity\n", " - current_ratio\n", " - net_profit_yoy\n", " - revenue_yoy\n", " - healthy_expansion_velocity\n", " - EP\n", " - BP\n", " - CP\n", " - market_cap_rank\n", " - turnover_rank\n", " - return_5_rank\n", " - EP_rank\n", " - pe_expansion_trend\n", " - value_price_divergence\n", " - active_market_cap\n", " - ebit_rank\n", "\n", "注册特征因子(表达式):\n", "\n", "注册 Label 因子(表达式):\n", " - future_return_5_rank: (ts_delay(close, -5) / ts_delay(open, -1)) - 1\n", "\n", "特征因子数: 49\n", " - 来自 metadata: 49\n", " - 来自表达式: 0\n", "Label: future_return_5_rank\n", "已注册因子总数: 50\n", "\n", "[3] 准备数据\n", "\n", "================================================================================\n", "准备数据\n", "================================================================================\n", "\n", "计算因子: 20200101 - 20251231\n", "[FinancialLoader] 加载 financial_fina_indicator 失败: Binder Error: Referenced column \"f_ann_date\" not found in FROM clause!\n", "Candidate bindings: \"ann_date\", \"end_date\", \"ocf_to_debt\", \"arturn_days\", \"nca_to_assets\"\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "D:\\PyProject\\ProStock\\src\\data\\financial_loader.py:148: UserWarning: Sortedness of columns cannot be checked when 'by' groups provided\n", " merged = df_price.join_asof(\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "数据形状: (7044952, 70)\n", "数据列: ['ts_code', 'trade_date', 'high', 'close', 'amount', 'open', 'turnover_rate', 'low', 'vol', 'total_assets', 'total_mv', 'f_ann_date', 'n_income', 'revenue', 'total_liab', 'total_cur_assets', 'total_cur_liab', 'total_hldr_eqy_exc_min_int', 'n_cashflow_act', 'ebit', 'ma_5', 'ma_20', 'ma_ratio_5_20', 'bias_10', 'high_low_ratio', 'bbi_ratio', 'return_5', 'return_20', 'kaufman_ER_20', 'mom_acceleration_10_20', 'drawdown_from_high_60', 'up_days_ratio_20', 'volatility_5', 'volatility_20', 'volatility_ratio', 'std_return_20', 'sharpe_ratio_20', 'min_ret_20', 'volatility_squeeze_5_60', 'overnight_intraday_diff', 'upper_shadow_ratio', 'capital_retention_20', 'max_ret_20', 'volume_ratio_5_20', 'turnover_rate_mean_5', 'turnover_deviation', 'amihud_illiq_20', 'turnover_cv_20', 'pv_corr_20', 'close_vwap_deviation', 'roe', 'roa', 'profit_margin', 'debt_to_equity', 'current_ratio', 'net_profit_yoy', 'revenue_yoy', 'healthy_expansion_velocity', 'EP', 'BP', 'CP', 'market_cap_rank', 'turnover_rank', 'return_5_rank', 'EP_rank', 'pe_expansion_trend', 'value_price_divergence', 'active_market_cap', 'ebit_rank', 'future_return_5_rank']\n", "\n", "前5行预览:\n", "shape: (5, 70)\n", "┌───────────┬────────────┬─────────┬─────────┬───┬────────────┬────────────┬───────────┬───────────┐\n", "│ ts_code ┆ trade_date ┆ high ┆ close ┆ … ┆ value_pric ┆ active_mar ┆ ebit_rank ┆ future_re │\n", "│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ e_divergen ┆ ket_cap ┆ --- ┆ turn_5_ra │\n", "│ str ┆ str ┆ f64 ┆ f64 ┆ ┆ ce ┆ --- ┆ f64 ┆ nk │\n", "│ ┆ ┆ ┆ ┆ ┆ --- ┆ f64 ┆ ┆ --- │\n", "│ ┆ ┆ ┆ ┆ ┆ f64 ┆ ┆ ┆ f64 │\n", "╞═══════════╪════════════╪═════════╪═════════╪═══╪════════════╪════════════╪═══════════╪═══════════╡\n", "│ 000001.SZ ┆ 20200102 ┆ 1850.42 ┆ 1841.69 ┆ … ┆ null ┆ null ┆ null ┆ -0.008857 │\n", "│ 000001.SZ ┆ 20200103 ┆ 1889.72 ┆ 1875.53 ┆ … ┆ null ┆ null ┆ null ┆ -0.01881 │\n", "│ 000001.SZ ┆ 20200106 ┆ 1893.0 ┆ 1863.52 ┆ … ┆ null ┆ null ┆ null ┆ -0.008171 │\n", "│ 000001.SZ ┆ 20200107 ┆ 1886.45 ┆ 1872.26 ┆ … ┆ null ┆ null ┆ null ┆ -0.014117 │\n", "│ 000001.SZ ┆ 20200108 ┆ 1861.34 ┆ 1818.76 ┆ … ┆ null ┆ null ┆ null ┆ -0.017252 │\n", "└───────────┴────────────┴─────────┴─────────┴───┴────────────┴────────────┴───────────┴───────────┘\n", "\n", "[4] 转换为排序学习格式\n", "\n", "================================================================================\n", "准备排序学习数据(将 future_return_5_rank 转换为 20 分位数标签)\n", "================================================================================\n", "\n", "原始 future_return_5_rank 统计:\n", "shape: (9, 2)\n", "┌────────────┬───────────┐\n", "│ statistic ┆ value │\n", "│ --- ┆ --- │\n", "│ str ┆ f64 │\n", "╞════════════╪═══════════╡\n", "│ count ┆ 7.01659e6 │\n", "│ null_count ┆ 28362.0 │\n", "│ mean ┆ 0.003779 │\n", "│ std ┆ 0.073221 │\n", "│ min ┆ -0.969459 │\n", "│ 25% ┆ -0.033163 │\n", "│ 50% ┆ -0.001483 │\n", "│ 75% ┆ 0.032547 │\n", "│ max ┆ 10.361925 │\n", "└────────────┴───────────┘\n", "\n", "转换后 future_return_5_rank_rank 统计:\n", "shape: (9, 2)\n", "┌────────────┬───────────┐\n", "│ statistic ┆ value │\n", "│ --- ┆ --- │\n", "│ str ┆ f64 │\n", "╞════════════╪═══════════╡\n", "│ count ┆ 7.01659e6 │\n", "│ null_count ┆ 28362.0 │\n", "│ mean ┆ 9.495412 │\n", "│ std ┆ 5.765668 │\n", "│ min ┆ 0.0 │\n", "│ 25% ┆ 4.0 │\n", "│ 50% ┆ 9.0 │\n", "│ 75% ┆ 14.0 │\n", "│ max ┆ 19.0 │\n", "└────────────┴───────────┘\n", "\n", "每日样本数统计:\n", "shape: (9, 2)\n", "┌────────────┬─────────────┐\n", "│ statistic ┆ value │\n", "│ --- ┆ --- │\n", "│ str ┆ f64 │\n", "╞════════════╪═════════════╡\n", "│ count ┆ 1455.0 │\n", "│ null_count ┆ 0.0 │\n", "│ mean ┆ 4841.891409 │\n", "│ std ┆ 560.948186 │\n", "│ min ┆ 3740.0 │\n", "│ 25% ┆ 4369.0 │\n", "│ 50% ┆ 5060.0 │\n", "│ 75% ┆ 5344.0 │\n", "│ max ┆ 5458.0 │\n", "└────────────┴─────────────┘\n", "\n", "分位数标签分布:\n", "shape: (21, 2)\n", "┌───────────────────────────┬────────┐\n", "│ future_return_5_rank_rank ┆ count │\n", "│ --- ┆ --- │\n", "│ i64 ┆ u32 │\n", "╞═══════════════════════════╪════════╡\n", "│ null ┆ 28362 │\n", "│ 0 ┆ 351599 │\n", "│ 1 ┆ 350894 │\n", "│ 2 ┆ 350944 │\n", "│ 3 ┆ 351077 │\n", "│ … ┆ … │\n", "│ 15 ┆ 350910 │\n", "│ 16 ┆ 350835 │\n", "│ 17 ┆ 350848 │\n", "│ 18 ┆ 350871 │\n", "│ 19 ┆ 349137 │\n", "└───────────────────────────┴────────┘\n", "\n", "[配置] 训练期: 20200101 - 20231231\n", "[配置] 验证期: 20240101 - 20241231\n", "[配置] 测试期: 20250101 - 20251231\n", "[配置] 特征数: 49\n", "[配置] 目标变量: future_return_5_rank_rank(20分位数)\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "C:\\Users\\liaozhaorun\\AppData\\Local\\Temp\\ipykernel_4168\\966642096.py:125: DeprecationWarning: `pl.count()` is deprecated. Please use `pl.len()` instead.\n", "(Deprecated in version 0.20.5)\n", " daily_counts = df_ranked.group_by(date_col).agg(pl.count().alias(\"count\"))\n" ] } ], "execution_count": 5 }, { "metadata": {}, "cell_type": "markdown", "source": "### 4.1 股票池筛选" }, { "metadata": { "ExecuteTime": { "end_time": "2026-03-13T14:14:48.474674Z", "start_time": "2026-03-13T14:14:25.243457Z" } }, "cell_type": "code", "source": [ "print(\"\\n\" + \"=\" * 80)\n", "print(\"股票池筛选\")\n", "print(\"=\" * 80)\n", "\n", "# 先执行 ST 过滤(在股票池筛选之前,与 Trainer.train() 保持一致)\n", "if st_filter:\n", " print(\"\\n[过滤] 应用 ST 过滤器...\")\n", " data = st_filter.filter(data)\n", " print(f\" ST 过滤后数据规模: {data.shape}\")\n", "\n", "if pool_manager:\n", " print(\"\\n执行每日独立筛选股票池...\")\n", " filtered_data = pool_manager.filter_and_select_daily(data)\n", " print(f\" 筛选前数据规模: {data.shape}\")\n", " print(f\" 筛选后数据规模: {filtered_data.shape}\")\n", " print(f\" 筛选前股票数: {data['ts_code'].n_unique()}\")\n", " print(f\" 筛选后股票数: {filtered_data['ts_code'].n_unique()}\")\n", " print(f\" 删除记录数: {len(data) - len(filtered_data)}\")\n", "else:\n", " filtered_data = data\n", " print(\" 未配置股票池管理器,跳过筛选\")" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "================================================================================\n", "股票池筛选\n", "================================================================================\n", "\n", "[过滤] 应用 ST 过滤器...\n", " ST 过滤后数据规模: (6823808, 71)\n", "\n", "执行每日独立筛选股票池...\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "C:\\Users\\liaozhaorun\\AppData\\Local\\Temp\\ipykernel_4168\\2439474879.py:68: DeprecationWarning: `is_in` with a collection of the same datatype is ambiguous and deprecated.\n", "Please use `implode` to return to previous behavior.\n", "\n", "See https://github.com/pola-rs/polars/issues/22149 for more information.\n", " return df[\"ts_code\"].is_in(small_cap_codes)\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " 筛选前数据规模: (6823808, 71)\n", " 筛选后数据规模: (1455000, 71)\n", " 筛选前股票数: 5678\n", " 筛选后股票数: 1934\n", " 删除记录数: 5368808\n" ] } ], "execution_count": 6 }, { "metadata": {}, "cell_type": "markdown", "source": "### 4.2 数据划分" }, { "metadata": { "ExecuteTime": { "end_time": "2026-03-13T14:14:48.541185Z", "start_time": "2026-03-13T14:14:48.480047Z" } }, "cell_type": "code", "source": [ "print(\"\\n\" + \"=\" * 80)\n", "print(\"数据划分\")\n", "print(\"=\" * 80)\n", "\n", "if splitter:\n", " train_data, val_data, test_data = splitter.split(filtered_data)\n", " print(f\"\\n训练集数据规模: {train_data.shape}\")\n", " print(f\"验证集数据规模: {val_data.shape}\")\n", " print(f\"测试集数据规模: {test_data.shape}\")\n", "\n", " # 计算各集的 group 数组\n", " train_group = compute_group_array(train_data)\n", " val_group = compute_group_array(val_data)\n", " test_group = compute_group_array(test_data)\n", "\n", " print(f\"\\n训练集 group 数量: {len(train_group)}\")\n", " print(f\"验证集 group 数量: {len(val_group)}\")\n", " print(f\"测试集 group 数量: {len(test_group)}\")\n", " print(f\"训练集日均样本数: {np.mean(train_group):.1f}\")\n", " print(f\"验证集日均样本数: {np.mean(val_group):.1f}\")\n", " print(f\"测试集日均样本数: {np.mean(test_group):.1f}\")\n", "else:\n", " raise ValueError(\"必须配置数据划分器\")" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "================================================================================\n", "数据划分\n", "================================================================================\n", "\n", "训练集数据规模: (970000, 71)\n", "验证集数据规模: (242000, 71)\n", "测试集数据规模: (243000, 71)\n", "\n", "训练集 group 数量: 970\n", "验证集 group 数量: 242\n", "测试集 group 数量: 243\n", "训练集日均样本数: 1000.0\n", "验证集日均样本数: 1000.0\n", "测试集日均样本数: 1000.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "C:\\Users\\liaozhaorun\\AppData\\Local\\Temp\\ipykernel_4168\\966642096.py:149: DeprecationWarning: `pl.count()` is deprecated. Please use `pl.len()` instead.\n", "(Deprecated in version 0.20.5)\n", " pl.count().alias(\"count\")\n" ] } ], "execution_count": 7 }, { "metadata": {}, "cell_type": "markdown", "source": "### 4.3 数据质量检查" }, { "metadata": { "ExecuteTime": { "end_time": "2026-03-13T14:15:07.312974Z", "start_time": "2026-03-13T14:14:48.544339Z" } }, "cell_type": "code", "source": [ "print(\"\\n\" + \"=\" * 80)\n", "print(\"数据质量检查(必须在预处理之前)\")\n", "print(\"=\" * 80)\n", "\n", "print(\"\\n检查训练集...\")\n", "check_data_quality(train_data, feature_cols, raise_on_error=True)\n", "\n", "print(\"\\n检查验证集...\")\n", "check_data_quality(val_data, feature_cols, raise_on_error=True)\n", "\n", "print(\"\\n检查测试集...\")\n", "check_data_quality(test_data, feature_cols, raise_on_error=True)\n", "\n", "print(\"[成功] 数据质量检查通过,未发现异常\")\n" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "================================================================================\n", "数据质量检查(必须在预处理之前)\n", "================================================================================\n", "\n", "检查训练集...\n", "\n", "================================================================================\n", "数据质量检查报告\n", "================================================================================\n", "\n", "[严重] 发现 3326 个全空因子:\n", " (某天的某个因子所有值都是 null,可能是数据缺失或计算错误)\n", " - 日期 20200910: net_profit_yoy (样本数: 1000)\n", " - 日期 20200910: revenue_yoy (样本数: 1000)\n", " - 日期 20200910: healthy_expansion_velocity (样本数: 1000)\n", " - 日期 20200910: value_price_divergence (样本数: 1000)\n", " - 日期 20200910: ebit_rank (样本数: 1000)\n", " - 日期 20230620: value_price_divergence (样本数: 1000)\n", " - 日期 20230620: ebit_rank (样本数: 1000)\n", " - 日期 20230802: value_price_divergence (样本数: 1000)\n", " - 日期 20230802: ebit_rank (样本数: 1000)\n", " - 日期 20230512: value_price_divergence (样本数: 1000)\n", " ... 还有 3316 个\n", "\n", "--------------------------------------------------------------------------------\n", "建议处理方式:\n", " 1. 检查因子定义和数据源,确认计算逻辑是否正确\n", " 2. 如果是预期内的缺失(如新股无历史数据),考虑调整因子计算窗口\n", " 3. 如果是数据同步问题,重新同步相关数据\n", " 4. 可以使用 filter 排除问题日期或因子\n", "================================================================================\n" ] }, { "ename": "ValueError", "evalue": "数据质量检查失败: 发现 3326 个问题,详见上方报告。如需忽略,请设置 raise_on_error=False", "output_type": "error", "traceback": [ "\u001B[31m---------------------------------------------------------------------------\u001B[39m", "\u001B[31mValueError\u001B[39m Traceback (most recent call last)", "\u001B[36mCell\u001B[39m\u001B[36m \u001B[39m\u001B[32mIn[8]\u001B[39m\u001B[32m, line 6\u001B[39m\n\u001B[32m 3\u001B[39m \u001B[38;5;28mprint\u001B[39m(\u001B[33m\"\u001B[39m\u001B[33m=\u001B[39m\u001B[33m\"\u001B[39m * \u001B[32m80\u001B[39m)\n\u001B[32m 5\u001B[39m \u001B[38;5;28mprint\u001B[39m(\u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m检查训练集...\u001B[39m\u001B[33m\"\u001B[39m)\n\u001B[32m----> \u001B[39m\u001B[32m6\u001B[39m \u001B[43mcheck_data_quality\u001B[49m\u001B[43m(\u001B[49m\u001B[43mtrain_data\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mfeature_cols\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mraise_on_error\u001B[49m\u001B[43m=\u001B[49m\u001B[38;5;28;43;01mTrue\u001B[39;49;00m\u001B[43m)\u001B[49m\n\u001B[32m 8\u001B[39m \u001B[38;5;28mprint\u001B[39m(\u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m检查验证集...\u001B[39m\u001B[33m\"\u001B[39m)\n\u001B[32m 9\u001B[39m check_data_quality(val_data, feature_cols, raise_on_error=\u001B[38;5;28;01mTrue\u001B[39;00m)\n", "\u001B[36mFile \u001B[39m\u001B[32mD:\\PyProject\\ProStock\\src\\training\\utils.py:166\u001B[39m, in \u001B[36mcheck_data_quality\u001B[39m\u001B[34m(df, feature_cols, date_col, check_all_null, check_all_zero, check_all_nan, raise_on_error)\u001B[39m\n\u001B[32m 163\u001B[39m \u001B[38;5;28mprint\u001B[39m(report)\n\u001B[32m 165\u001B[39m \u001B[38;5;28;01mif\u001B[39;00m raise_on_error:\n\u001B[32m--> \u001B[39m\u001B[32m166\u001B[39m \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mValueError\u001B[39;00m(\n\u001B[32m 167\u001B[39m \u001B[33mf\u001B[39m\u001B[33m\"\u001B[39m\u001B[33m数据质量检查失败: 发现 \u001B[39m\u001B[38;5;132;01m{\u001B[39;00mtotal_issues\u001B[38;5;132;01m}\u001B[39;00m\u001B[33m 个问题,\u001B[39m\u001B[33m\"\u001B[39m\n\u001B[32m 168\u001B[39m \u001B[33mf\u001B[39m\u001B[33m\"\u001B[39m\u001B[33m详见上方报告。如需忽略,请设置 raise_on_error=False\u001B[39m\u001B[33m\"\u001B[39m\n\u001B[32m 169\u001B[39m )\n\u001B[32m 171\u001B[39m \u001B[38;5;28;01mreturn\u001B[39;00m issues\n", "\u001B[31mValueError\u001B[39m: 数据质量检查失败: 发现 3326 个问题,详见上方报告。如需忽略,请设置 raise_on_error=False" ] } ], "execution_count": 8 }, { "metadata": {}, "cell_type": "markdown", "source": "### 4.4 数据预处理" }, { "metadata": { "ExecuteTime": { "end_time": "2026-03-13T14:15:07.323298800Z", "start_time": "2026-03-12T15:56:24.492487Z" } }, "cell_type": "code", "source": [ "print(\"\\n\" + \"=\" * 80)\n", "print(\"数据预处理\")\n", "print(\"=\" * 80)\n", "\n", "fitted_processors = []\n", "if processors:\n", " print(\"\\n训练集处理...\")\n", " for i, processor in enumerate(processors, 1):\n", " print(f\" [{i}/{len(processors)}] {processor.__class__.__name__}\")\n", " train_data = processor.fit_transform(train_data)\n", " fitted_processors.append(processor)\n", "\n", " print(\"\\n验证集处理...\")\n", " for processor in fitted_processors:\n", " val_data = processor.transform(val_data)\n", "\n", " print(\"\\n测试集处理...\")\n", " for processor in fitted_processors:\n", " test_data = processor.transform(test_data)\n", "\n", "print(f\"\\n处理后训练集形状: {train_data.shape}\")\n", "print(f\"处理后验证集形状: {val_data.shape}\")\n", "print(f\"处理后测试集形状: {test_data.shape}\")" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "================================================================================\n", "数据预处理\n", "================================================================================\n", "\n", "训练集处理...\n", " [1/3] NullFiller\n", " [2/3] Winsorizer\n", " [3/3] StandardScaler\n", "\n", "验证集处理...\n", "\n", "测试集处理...\n", "\n", "处理后训练集形状: (970000, 71)\n", "处理后验证集形状: (242000, 71)\n", "处理后测试集形状: (243000, 71)\n" ] } ], "execution_count": 8 }, { "metadata": {}, "cell_type": "markdown", "source": "### 4.4 训练 LambdaRank 模型" }, { "metadata": { "ExecuteTime": { "end_time": "2026-03-13T14:15:07.323298800Z", "start_time": "2026-03-12T15:56:25.031866Z" } }, "cell_type": "code", "source": [ "print(\"\\n\" + \"=\" * 80)\n", "print(\"训练 LambdaRank 模型\")\n", "print(\"=\" * 80)\n", "\n", "# 准备数据\n", "X_train = train_data.select(feature_cols)\n", "y_train = train_data.select(target_col).to_series()\n", "\n", "X_val = val_data.select(feature_cols)\n", "y_val = val_data.select(target_col).to_series()\n", "\n", "print(f\"\\n训练样本数: {len(X_train)}\")\n", "print(f\"验证样本数: {len(X_val)}\")\n", "print(f\"特征数: {len(feature_cols)}\")\n", "print(f\"目标变量: {target_col}\")\n", "\n", "print(\"\\n目标变量统计(训练集):\")\n", "print(y_train.describe())\n", "\n", "print(\"\\n开始训练...\")\n", "model.fit(\n", " X=X_train,\n", " y=y_train,\n", " group=train_group,\n", " eval_set=(X_val, y_val, val_group),\n", ")\n", "print(\"训练完成!\")" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "================================================================================\n", "训练 LambdaRank 模型\n", "================================================================================\n", "\n", "训练样本数: 970000\n", "验证样本数: 242000\n", "特征数: 49\n", "目标变量: future_return_5_rank_rank\n", "\n", "目标变量统计(训练集):\n", "shape: (9, 2)\n", "┌────────────┬──────────┐\n", "│ statistic ┆ value │\n", "│ --- ┆ --- │\n", "│ str ┆ f64 │\n", "╞════════════╪══════════╡\n", "│ count ┆ 969665.0 │\n", "│ null_count ┆ 335.0 │\n", "│ mean ┆ 9.810091 │\n", "│ std ┆ 5.346526 │\n", "│ min ┆ 0.0 │\n", "│ 25% ┆ 6.0 │\n", "│ 50% ┆ 10.0 │\n", "│ 75% ┆ 14.0 │\n", "│ max ┆ 19.0 │\n", "└────────────┴──────────┘\n", "\n", "开始训练...\n", "Training until validation scores don't improve for 50 rounds\n", "Early stopping, best iteration is:\n", "[2]\ttrain's ndcg@10: 0.5742\tval's ndcg@10: 0.570382\n", "训练完成!\n" ] } ], "execution_count": 9 }, { "metadata": {}, "cell_type": "markdown", "source": "### 4.5 训练指标曲线" }, { "metadata": { "ExecuteTime": { "end_time": "2026-03-13T14:15:07.323298800Z", "start_time": "2026-03-12T15:56:26.444220Z" } }, "cell_type": "code", "source": [ "print(\"\\n\" + \"=\" * 80)\n", "print(\"训练指标曲线\")\n", "print(\"=\" * 80)\n", "\n", "# 从模型获取训练评估结果\n", "evals_result = model.get_evals_result()\n", "\n", "if evals_result is None or not evals_result:\n", " print(\"[警告] 没有可用的训练指标,请确保训练时使用了 eval_set 参数\")\n", "else:\n", " print(\"[成功] 已从模型获取训练评估结果\")\n", "\n", " # 获取评估的 NDCG 指标\n", " ndcg_metrics = [k for k in evals_result[\"train\"].keys() if \"ndcg\" in k]\n", " print(f\"\\n评估的 NDCG 指标: {ndcg_metrics}\")\n", "\n", " # 显示早停信息\n", " actual_rounds = len(list(evals_result[\"train\"].values())[0])\n", " expected_rounds = MODEL_PARAMS.get(\"n_estimators\", 1000)\n", " print(f\"\\n[早停信息]\")\n", " print(f\" 配置的最大轮数: {expected_rounds}\")\n", " print(f\" 实际训练轮数: {actual_rounds}\")\n", "\n", " best_iter = model.get_best_iteration()\n", " if best_iter is not None and best_iter < actual_rounds:\n", " print(f\" 早停状态: 已触发(最佳迭代: {best_iter})\")\n", " else:\n", " print(f\" 早停状态: 未触发(达到最大轮数)\")\n", "\n", " # 显示各 NDCG 指标的最终值\n", " print(f\"\\n最终 NDCG 指标:\")\n", " for metric in ndcg_metrics:\n", " train_ndcg = evals_result[\"train\"][metric][-1]\n", " val_ndcg = evals_result[\"val\"][metric][-1]\n", " print(f\" {metric}: 训练集={train_ndcg:.4f}, 验证集={val_ndcg:.4f}\")\n", "\n", " # 使用封装好的方法绘制所有指标\n", " print(\"\\n[绘图] 使用 LightGBM 原生接口绘制训练曲线...\")\n", " fig = model.plot_all_metrics(metrics=ndcg_metrics[:4], figsize=(14, 10))\n", " plt.show()\n", "\n", " print(f\"\\n[指标分析]\")\n", " print(f\" 各NDCG指标在验证集上的最佳值:\")\n", " for metric in ndcg_metrics:\n", " val_metric_list = evals_result[\"val\"][metric]\n", " best_iter_metric = val_metric_list.index(max(val_metric_list))\n", " best_val = max(val_metric_list)\n", " print(f\" {metric}: {best_val:.4f} (迭代 {best_iter_metric + 1})\")\n", " print(f\"\\n[重要提醒] 验证集仅用于早停/调参,测试集完全独立于训练过程!\")" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "================================================================================\n", "训练指标曲线\n", "================================================================================\n", "[成功] 已从模型获取训练评估结果\n", "\n", "评估的 NDCG 指标: ['ndcg@10']\n", "\n", "[早停信息]\n", " 配置的最大轮数: 2000\n", " 实际训练轮数: 52\n", " 早停状态: 已触发(最佳迭代: 2)\n", "\n", "最终 NDCG 指标:\n", " ndcg@10: 训练集=0.6456, 验证集=0.5425\n", "\n", "[绘图] 使用 LightGBM 原生接口绘制训练曲线...\n" ] }, { "data": { "text/plain": [ "
" ], "image/png": "iVBORw0KGgoAAAANSUhEUgAABW0AAAPdCAYAAADxjUr8AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAA175JREFUeJzs3QeYXHW5P/B3e0nvvZEQIIQQmtRLURAFEbCBoBQvFooNC+IVFAv+vSjiVa8Iir2AXFSUJh0h9BJ6eu99k022z/85ZwsJSSAJuzuzu5+Pz3lOmTOzvzmzJ5Jv3nl/eZlMJhMAAAAAAOSE/GwPAAAAAACA1whtAQAAAAByiNAWAAAAACCHCG0BAAAAAHKI0BYAAAAAIIcIbQEAAAAAcojQFgAAAAAghwhtAQAAAAByiNAWAAAAACCHCG0BAMhp55xzTuTl5aXL+PHjo6GhoeWxa6+9tuWxX//61+mx0aNHtxwrKCiIbt26pcdOPPHE+MMf/hD19fXb/DmrVq2Kyy+/PCZPnhw9evSI7t27x1577RUXXHBBvPTSS1ucW1VVFT/96U/jqKOOin79+kVxcXEMGzYsDjnkkPj6178es2fPbpX3XlNTE1/84hfjsMMOi9LS0pb3deedd27z/Mceeyze+c53Rs+ePdP3ffjhh8ftt9/eKmMBAKD9FLbjzwIAgLdkxowZcdNNN8Xpp5++Q+cnAe/GjRtj3rx56ZIEmDfccEP89a9/TYPNZs8++2wa6i5ZsmSL57/66qvpkoSy11xzTXps8eLFccIJJ8TUqVO3ODc5niyPP/54FBUVxde+9rWtxpOEucnr3HHHHbFw4cJ0DJMmTYqzzjorzjzzzMjP37KmIhn7D37wgx16rw899FAcd9xxadDbbMqUKfGe97wnDas//OEP79DrAACQfSptAQDoUK688srIZDI7dG5yXmVlZdx7772x3377pcfuu+++OO+881rO2bBhQ7z3ve9tCWw/+tGPxqxZs6K6ujqmTZsWl112WVq12vx673vf+1oC26OPPjoNaZPK23Xr1sWDDz4YF110Ucv5m/uf//mftHL3xz/+ccycOTN9zvLly+Oee+5JQ9vktZJq380l4e+FF16Yhq6f+tSn3vC9Jo8ngW2vXr3iySefTH/GyJEj0zF/+tOfjk2bNu3QNQMAIPtU2gIA0GEk7Q5eeOGFuPXWW+Pkk0/eoeeUl5fH29/+9rjrrrti3LhxUVFREX/5y1/S19lnn33iF7/4RVr1mkjaG/z2t79teW7SjuGb3/xmS0uFf/zjH2lImxgxYkRauVtWVpbul5SUxJFHHpkur/e///u/8dnPfjb69++ftlv4wAc+ELvttlsasiYBaxLk/vOf/0yrYpPgN6nsTSTh709+8pN0e/r06dt9j88880y88sor6XZShXzggQe2BLlf/epX0zA4aalw6qmn7uCVBgAgm1TaAgDQYXzoQx9K19/5znd2+rkDBgxIWxA0S1oUJDbvD5sEq9sLizd/TiIJX5sD2zeyYMGCuPjii2PixInx3HPPpVW9Z5xxRgwePDh+9atfxWc+85lYv359fO9730t70iYVuTsrCW2b7bnnntvc3vwcAABym9AWAIAOIwk4kz6wSXXqv/71r51+flI522zu3LnpOul1u62Qc1s2P3ePPfZo2U4qWpsnCUuWJJBt9qMf/Sg9lvTRHTRoULz//e+PF198MW3L8OUvfzltkZBItpOK4KTyd2etWLGiZXvzXr2bbzf/HAAAcp/QFgCADqN3795pheuuVtsmE5M1S4LUzdc7a0efl1TyJn1wk9YMSYuGJPh917veFWvWrEknK0vWzZJK4KSPbtLCoTVs3vt3V98nAADtT2gLAECHkrQaSPrUPvTQQ/Hwww/v1HM37ws7evTodJ1M1tUsCUzfyKhRo7b5Wtdee20akG7+eLM5c+bE2972tnR7xowZLZW5SQD9+c9/fotzmyt0kyrcnW390CyZEK1Z0nZhW+cAAJDbhLYAAHQoSfj48Y9/PN3+85//vFMtBP74xz+27L/73e9O10nVa7Pt9ZNtnohs83OToLa2tnaHfnZzT9zNK1+3tf/ss89GUVFR9OvXL3bG/vvvv83g+dVXX93mOQAA5DahLQAAHc6XvvSlKC4ubglT38imTZvi/vvvTwPX5srT0047LZ0YLHHeeefF8OHD0+0pU6bEueeem1bHJoFsUk172WWXpUvipJNOigMPPDDdTs459dRT44UXXkjPTSYcq6qq2urnJxW9Tz/99BY9dX/+85/H2rVrW0LipG3DbbfdFldddVW84x3viJKSkpbnr1y5Ml02btzYcixpn5Aca66qTQLZvfbaqyXIfuqpp2LmzJlpsJxIQuDNA2cAAHJbXub1/7wPAAA55Jxzzonf/OY36fYrr7zSMlnYJz/5ybjuuutazvvVr36VnpuEpJtPGPZ6SSh6yy23bDFJV1LhesIJJ8TSpUu3+ZzPfvazcc0116TbSTh7/PHHp2PZnmTCsebXSlogJONMql6HDBmS9rZtHl9SgZuMo7mvba9evdLgeMKECTvUi/aoo46KBx54IN1+8MEH453vfGfU1NRscU7y/D/84Q/x4Q9/eLuvAwBAblFpCwBAh3TJJZdEYWHhG56TBJZlZWVpr9kklE3aIySTgW0e2Cb222+/tGI2mRhs0qRJ0a1bt7Rv7h577JH2n02qcZuNGDEinnzyybQq9uCDD05fKwlfk2rWww47LL761a/Gfffd13L+Zz7zmbQS95RTTknD2b/85S9pKJv8jO9+97tpkJv0sv3IRz6SVshuHtjujCTATYLb4447Lnr06JGOPxnPP//5T4EtAEAHo9IWAADa2NVXXx1f+MIX0oA2CZuTNgtJS4akzcH8+fPTatkk/N08HAYAoOsS2gIAQDv43ve+F//1X/+1zT68SX/e66+/Ps466yyfBQAAQlsAAGgvL7/8ctob95577onFixdHnz590j60l156aUuvXgAAUGkLAAAAAJBDTEQGAAAAAJBDhLYAAAAAADmkMNsDyEUNDQ1pj7EePXpEXl5etocDAAAAAHQCmUwm1q9fH0OHDo38/O3X0wpttyEJbEeMGNGWnw8AAAAA0EUtWLAghg8fvt3HhbbbkFTYJubMmRN9+/Ztu08HyLra2tr417/+lc7cXVRUlO3hAG3I/Q5dh/sdug73O3QdtZ3k7+8VFRVpsWhz/rg9QtttaG6JkFy8nj17ts0nBOTMH/rl5eXpvd6R/9AH3pz7HboO9zt0He536DpqO9nf39+sJauJyAAAAAAAcojQFgAAAAAghwhtAQAAAAByiJ62AAAAAECLhoaGqKmpybmetoWFhVFVVRX19fWRq5J+uwUFBW/5dYS2AAAAAEAqCWvnzJmTBre5JJPJxODBg2PBggVvOolXtvXu3Tsd61sZp9AWAAAAAEiD0SVLlqSVoiNGjIj8/NzprNrQ0BAbNmyI7t2759S4Xn/9Nm7cGMuXL0/3hwwZssuvJbQFAAAAAKKuri4NHYcOHRrl5eU52bKhtLQ0Z0PbRFlZWbpOgtuBAwfucquE3H2HAAAAAEC7ae4VW1xc7Kq/Bc2Bd9KHd1cJbQEAAACAFrneM7YrXD+hLQAAAABADhHaAgAAAADkEKEtAAAAAEBEjB49Oq655pqsX4vCbA8AAAAAAGBXHX300TF58uRWCVuffPLJ6NatW9Y/DKEtAAAAANBpZTKZqK+vj8LCN49CBwwYELlAewQAAAAAYJth58aauqwsmUxmhz6Rc845Jx588MH40Y9+FHl5eeny61//Ol3fcccdccABB0RJSUk8/PDDMWvWrDj55JNj0KBB0b179zjooIPinnvuecP2CMnr/OIXv4hTTz01ysvLY/fdd49bb721zX9bVNoCAAAAAFvZVFsfEy6/KytX5uVvHh/lxW8eXSZh7fTp02PixInxzW9+Mz320ksvpeuvfOUr8f3vfz9222236NOnTyxYsCBOOOGE+M53vpMGub/97W/jpJNOimnTpsXIkSO3+zOuuOKK+O///u+46qqr4sc//nGceeaZMW/evOjbt2+0FZW2AAAAAECH1KtXryguLk6rYAcPHpwuBQUF6WNJiHvcccfF2LFj04B13333jU9+8pNpwJtUzH7rW99KH3uzytmkmvfDH/5wjBs3Lq688srYsGFDPPHEE236vlTaAgAAAABbKSsqSCtes/Wz36oDDzxwi/0kbP3GN74Rt912WyxZsiTq6upi06ZNMX/+/Dd8nUmTJrVsJ5OU9ezZM5YvXx5tSWgLAAAAAGwl6ee6Iy0KclW3bt222P/iF78Yd999d9oyIamaLSsriw984ANRU1Pzhq9TVFS01XVpaGiIttRxrzoAAAAA0OUVFxdHfX39m16HRx55JG11kEwq1lx5O3fu3Jy8fnraAgAAAAAd1ujRo+Pxxx9PA9iVK1dutwo26WN7yy23xHPPPRdTp06NM844o80rZneV0BYAAAAA6LC++MUvppOPTZgwIQYMGLDdHrVXX3119OnTJw477LA46aST4vjjj4/9998/cpH2CAAAAABAhzV+/Ph49NFHtziWtEHYVkXufffdt8WxCy+8cIv917dLyGQyW73O2rVro62ptAUAAAAAyCFCWwAAAACAHCK0BQAAAADIIUJbAAAAAIAcIrQFAAAAAMghQlsAAAAAgBwitAUAAAAAyCFCWwAAAACAHCK0BQAAAADIIUJbAAAAAKDLGj16dFxzzTWRS4S2AAAAAAA5RGgLAAAAAJBDhLYAAAAAwNYy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gFIjtAUAAEDYu+qqqzRy5EhveciQIbr55psr7b5feOEF1axZ86jLv/jiC1133XWVth4AAACIHPGhXgEAAADAjw4fPqzExMQy/3y9evXKdX0AAAAQPai0BQAAQERV3M6ZM0dPPfWUYmJivLF27VrvuiVLlujMM89UtWrV1KBBA1155ZXatm1b7s9ahe748eO9Kt26detqxIgR3uWPP/64TjrpJKWkpKhZs2b65S9/qfT0dO+62bNna+zYsdq9e3fu/d1zzz2FtkdYv369zj//fO/+U1NTdemll2rz5s2519vPdevWTf/617+8n61Ro4Yuu+wy7d27N/c2r7/+urcuVatWVZ06dTRs2DDt27evEh5ZAAAAVCZCWwAAAEQMC2v79++vcePGadOmTd6woHXXrl067bTT1L17d3355ZeaMmWKF5hacBrsn//8p1dd++mnn2rSpEneZbGxsfrTn/6kpUuXetfPnDlTt99+u3fdgAEDvGDWQtjA/d12221HrVdWVpYX2O7YscMLladNm6bvv/9eo0aNyne7NWvW6O2339b777/vDbvtQw895F1nv/vyyy/X1VdfrWXLlnmB8YUXXqjs7OwKfEQBAAAQCrRHAAAAQMSw6lQLXZOTk9WwYcPcy//85z97ge2DDz6Ye9nkyZO9QHflypVq166dd1nbtm31yCOP5Pudwf1xrQL2gQce0PXXX69nn33Wuy+7T6uwDb6/gmbMmKHFixfrhx9+8O7TvPjii+rcubPX+7Z379654a71yK1evbp33qqB7Wf/8Ic/eKHtkSNHvKC2RYsW3vVWdQsAAIDIQ6UtAAAAIt4333yjWbNmea0JAqNDhw651a0BPXv2POpnp0+frqFDh6pJkyZemGpB6vbt27V///4S379VxlpYGwhsTadOnbwJzOy64FA4ENiaRo0aacuWLd5y165dvfWwoPaSSy7RX//6V+3cubMMjwYAAAD8jtAWAAAAEc960J577rn6+uuv841Vq1Zp0KBBubezvrXBrB/uOeecoy5duuiNN97QwoUL9cwzz+ROVFbeEhIS8p23Cl6rvjVxcXFeW4UPP/zQC3yffvpptW/f3qveBQAAQGQhtAUAAEBEsZYFmZmZ+S7r0aOH15PWKlnbtGmTbxQMaoNZSGuh6R//+Ef169fPa6OwcePGY95fQR07dtSPP/7ojYDvvvvO67VrAWxJWYg7cOBA3Xvvvfrqq6+8+37rrbdK/PMAAAAID4S2AAAAiCgWzH7++edeley2bdu80PWGG27wJgGzibysh6y1RJg6darGjh1bbOBqoW5GRoZX1WoTh/3rX//KnaAs+P6sktd6z9r9FdY2YdiwYV5bgyuuuEKLFi3SggULNHr0aA0ePFi9evUq0d9lf5P15LWJ1NavX68333xTW7du9QJhAAAARBZCWwAAAESU2267zWslYBWs9erV8wLOxo0b69NPP/UC2tNPP90LUG2CMespGxtb9Edi6yP7+OOP6+GHH9aJJ56ol19+WRMnTsx3mwEDBngTk40aNcq7v4ITmQUqZN955x3VqlXLa8dgIW7r1q31yiuvlPjvSk1N1dy5c3XWWWd5Fb+///3vvQrgM888s5SPEAAAAPwuJjs7OzvUKwEAAAAAAAAAcKi0BQAAAAAAAAAfIbQFAAAAAAAAAB8htAUAAAAAAAAAHyG0BQAAAAAAAAAfIbQFAAAAAAAAAB8htAUAAAAAAAAAHyG0BQAAAAAAAAAfIbQFAAAAAAAAAB8htAUAAAAAAAAAHyG0BQAAAAAAAAAfIbQFAAAAAAAAAPnH/wcMEdDPgbJLwQAAAABJRU5ErkJggg==" }, "metadata": {}, "output_type": "display_data", "jetTransient": { "display_id": null } }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "[指标分析]\n", " 各NDCG指标在验证集上的最佳值:\n", " ndcg@10: 0.5704 (迭代 2)\n", "\n", "[重要提醒] 验证集仅用于早停/调参,测试集完全独立于训练过程!\n" ] } ], "execution_count": 10 }, { "metadata": {}, "cell_type": "markdown", "source": "### 4.6 模型评估" }, { "metadata": { "ExecuteTime": { "end_time": "2026-03-13T14:15:07.323298800Z", "start_time": "2026-03-12T15:56:26.601415Z" } }, "cell_type": "code", "source": [ "print(\"\\n\" + \"=\" * 80)\n", "print(\"模型评估\")\n", "print(\"=\" * 80)\n", "\n", "# 准备测试集\n", "X_test = test_data.select(feature_cols)\n", "y_test = test_data.select(target_col).to_series()\n", "\n", "# 预测\n", "print(\"\\n生成预测...\")\n", "predictions = model.predict(X_test)\n", "\n", "# 添加预测列\n", "test_data = test_data.with_columns([pl.Series(\"prediction\", predictions)])\n", "\n", "# 计算 NDCG 指标\n", "print(\"\\n计算 NDCG 指标...\")\n", "ndcg_results = evaluate_ndcg_at_k(\n", " y_true=y_test.to_numpy(),\n", " y_pred=predictions,\n", " group=test_group,\n", " k_list=[1, 5, 10, 20],\n", ")\n", "\n", "print(\"\\nNDCG 评估结果:\")\n", "print(\"-\" * 40)\n", "for metric, value in ndcg_results.items():\n", " print(f\" {metric}: {value:.4f}\")\n", "\n", "# 特征重要性\n", "print(\"\\n特征重要性(Top 20):\")\n", "print(\"-\" * 40)\n", "importance = model.feature_importance()\n", "if importance is not None:\n", " top_features = importance.sort_values(ascending=False).head(20)\n", " for i, (feature, score) in enumerate(top_features.items(), 1):\n", " print(f\" {i:2d}. {feature:30s} {score:10.2f}\")" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "================================================================================\n", "模型评估\n", "================================================================================\n", "\n", "生成预测...\n", "\n", "计算 NDCG 指标...\n", "\n", "NDCG 评估结果:\n", "----------------------------------------\n", " ndcg@1: 0.4869\n", " ndcg@5: 0.5123\n", " ndcg@10: 0.5211\n", " ndcg@20: 0.5237\n", "\n", "特征重要性(Top 20):\n", "----------------------------------------\n", " 1. turnover_rank 280.23\n", " 2. mom_acceleration_10_20 75.44\n", " 3. volatility_20 69.78\n", " 4. profit_margin 64.57\n", " 5. revenue_yoy 62.87\n", " 6. BP 60.64\n", " 7. std_return_20 48.58\n", " 8. EP 45.02\n", " 9. return_20 43.80\n", " 10. drawdown_from_high_60 43.42\n", " 11. pv_corr_20 43.31\n", " 12. kaufman_ER_20 39.42\n", " 13. active_market_cap 37.19\n", " 14. turnover_rate_mean_5 32.11\n", " 15. min_ret_20 29.80\n", " 16. max_ret_20 28.82\n", " 17. amihud_illiq_20 28.09\n", " 18. up_days_ratio_20 27.88\n", " 19. ma_20 26.77\n", " 20. EP_rank 24.64\n" ] } ], "execution_count": 11 }, { "metadata": { "ExecuteTime": { "end_time": "2026-03-13T14:15:07.323298800Z", "start_time": "2026-03-12T15:56:26.866785Z" } }, "cell_type": "code", "source": [ "# 确保输出目录存在\n", "os.makedirs(OUTPUT_DIR, exist_ok=True)\n", "\n", "# 生成时间戳\n", "start_dt = datetime.strptime(TEST_START, \"%Y%m%d\")\n", "end_dt = datetime.strptime(TEST_END, \"%Y%m%d\")\n", "date_str = f\"{start_dt.strftime('%Y%m%d')}_{end_dt.strftime('%Y%m%d')}\"\n", "\n", "# 保存每日 Top N\n", "print(f\"\\n[1/1] 保存每日 Top {TOP_N} 股票...\")\n", "topn_output_path = os.path.join(OUTPUT_DIR, \"rank_output.csv\")\n", "\n", "# 按日期分组,取每日 top N\n", "topn_by_date = []\n", "unique_dates = test_data[\"trade_date\"].unique().sort()\n", "for date in unique_dates:\n", " day_data = test_data.filter(test_data[\"trade_date\"] == date)\n", " # 按 prediction 降序排序,取前 N\n", " topn = day_data.sort(\"prediction\", descending=True).head(TOP_N)\n", " topn_by_date.append(topn)\n", "\n", "# 合并所有日期的 top N\n", "topn_results = pl.concat(topn_by_date)\n", "\n", "# 格式化日期并调整列顺序:日期、分数、股票\n", "topn_to_save = topn_results.select(\n", " [\n", " pl.col(\"trade_date\").str.slice(0, 4)\n", " + \"-\"\n", " + pl.col(\"trade_date\").str.slice(4, 2)\n", " + \"-\"\n", " + pl.col(\"trade_date\").str.slice(6, 2).alias(\"date\"),\n", " pl.col(\"prediction\").alias(\"score\"),\n", " pl.col(\"ts_code\"),\n", " ]\n", ")\n", "topn_to_save.write_csv(topn_output_path, include_header=True)\n", "print(f\" 保存路径: {topn_output_path}\")\n", "print(\n", " f\" 保存行数: {len(topn_to_save)}({len(unique_dates)}个交易日 x 每日top{TOP_N})\"\n", ")\n", "print(f\"\\n 预览(前15行):\")\n", "print(topn_to_save.head(15))\n", "\n", "print(\"\\n训练流程完成!\")" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "[1/1] 保存每日 Top 5 股票...\n", " 保存路径: output\\rank_output.csv\n", " 保存行数: 1215(243个交易日 x 每日top5)\n", "\n", " 预览(前15行):\n", "shape: (15, 3)\n", "┌────────────┬──────────┬───────────┐\n", "│ trade_date ┆ score ┆ ts_code │\n", "│ --- ┆ --- ┆ --- │\n", "│ str ┆ f64 ┆ str │\n", "╞════════════╪══════════╪═══════════╡\n", "│ 2025-01-02 ┆ 0.011742 ┆ 605189.SH │\n", "│ 2025-01-02 ┆ 0.010834 ┆ 002076.SZ │\n", "│ 2025-01-02 ┆ 0.010567 ┆ 603041.SH │\n", "│ 2025-01-02 ┆ 0.010191 ┆ 600493.SH │\n", "│ 2025-01-02 ┆ 0.008633 ┆ 000679.SZ │\n", "│ … ┆ … ┆ … │\n", "│ 2025-01-06 ┆ 0.016587 ┆ 002343.SZ │\n", "│ 2025-01-06 ┆ 0.011217 ┆ 002188.SZ │\n", "│ 2025-01-06 ┆ 0.010834 ┆ 002076.SZ │\n", "│ 2025-01-06 ┆ 0.007108 ┆ 000677.SZ │\n", "│ 2025-01-06 ┆ 0.006257 ┆ 603117.SH │\n", "└────────────┴──────────┴───────────┘\n", "\n", "训练流程完成!\n" ] } ], "execution_count": 12 }, { "metadata": {}, "cell_type": "markdown", "source": [ "## 5. 总结\n", "#\n", "本 Notebook 实现了完整的 Learn-to-Rank 训练流程:\n", "#\n", "### 核心步骤\n", "#\n", "1. **数据准备**: 计算 49 个特征因子,将 `future_return_5` 转换为 20 分位数标签\n", "2. **模型训练**: 使用 LightGBM LambdaRank 学习每日股票排序\n", "3. **模型评估**: 使用 NDCG@1/5/10/20 评估排序质量\n", "4. **策略分析**: 基于排序分数构建 Top-k 选股策略\n", "#\n", "### 关键参数\n", "#\n", "- **Objective**: lambdarank\n", "- **Metric**: ndcg\n", "- **Learning Rate**: 0.05\n", "- **Num Leaves**: 31\n", "- **N Quantiles**: 20\n", "#\n", "### 输出结果\n", "#\n", "- rank_output.csv: 每日Top-N推荐股票(格式:date, score, ts_code)\n", "- 特征重要性排名\n", "- Top-k 策略统计和图表\n", "- NDCG训练指标曲线\n", "#\n", "### 后续优化方向\n", "#\n", "1. **特征工程**: 尝试更多因子组合\n", "2. **超参数调优**: 使用网格搜索优化 LambdaRank 参数\n", "3. **模型集成**: 结合多个排序模型的预测\n", "4. **更复杂的分组**: 考虑按行业分组排序\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.0" } }, "nbformat": 4, "nbformat_minor": 4 }