{ "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:38:49.168405Z", "start_time": "2026-03-13T14:38:47.799348Z" } }, "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:38:49.197280Z", "start_time": "2026-03-13T14:38:49.189632Z" } }, "cell_type": "code", "source": [ "def register_factors(\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 用 DSL 表达式注册)\"\"\"\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:38:49.207900Z", "start_time": "2026-03-13T14:38:49.204530Z" } }, "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(log(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:38:49.216389Z", "start_time": "2026-03-13T14:38:49.211732Z" } }, "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", "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:39:02.072976Z", "start_time": "2026-03-13T14:38:49.220376Z" } }, "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 = register_factors(\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(params=MODEL_PARAMS)\n", "\n", "# 7. 创建数据处理器(使用函数返回的完整特征列表)\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", "# 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", " - active_market_cap\n", "\n", "注册特征因子(表达式):\n", "\n", "注册 Label 因子(表达式):\n", " - future_return_5_rank: (ts_delay(close, -5) / ts_delay(open, -1)) - 1\n", "\n", "特征因子数: 47\n", " - 来自 metadata: 47\n", " - 来自表达式: 0\n", "Label: future_return_5_rank\n", "已注册因子总数: 48\n", "\n", "[3] 准备数据\n", "\n", "================================================================================\n", "准备数据\n", "================================================================================\n", "\n", "计算因子: 20200101 - 20251231\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, 67)\n", "数据列: ['ts_code', 'trade_date', 'low', 'high', 'open', 'turnover_rate', 'close', 'amount', 'vol', 'total_assets', 'total_mv', 'f_ann_date', 'revenue', 'n_income', 'total_cur_assets', 'total_hldr_eqy_exc_min_int', 'total_liab', 'total_cur_liab', 'n_cashflow_act', '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', 'active_market_cap', 'future_return_5_rank']\n", "\n", "前5行预览:\n", "shape: (5, 67)\n", "┌───────────┬────────────┬─────────┬─────────┬───┬──────────┬────────────┬────────────┬────────────┐\n", "│ ts_code ┆ trade_date ┆ low ┆ high ┆ … ┆ EP_rank ┆ pe_expansi ┆ active_mar ┆ future_ret │\n", "│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ on_trend ┆ ket_cap ┆ urn_5_rank │\n", "│ str ┆ str ┆ f64 ┆ f64 ┆ ┆ f64 ┆ --- ┆ --- ┆ --- │\n", "│ ┆ ┆ ┆ ┆ ┆ ┆ f64 ┆ f64 ┆ f64 │\n", "╞═══════════╪════════════╪═════════╪═════════╪═══╪══════════╪════════════╪════════════╪════════════╡\n", "│ 000001.SZ ┆ 20200102 ┆ 1806.75 ┆ 1850.42 ┆ … ┆ 0.920834 ┆ null ┆ null ┆ -0.008857 │\n", "│ 000001.SZ ┆ 20200103 ┆ 1847.15 ┆ 1889.72 ┆ … ┆ 0.918182 ┆ null ┆ null ┆ -0.01881 │\n", "│ 000001.SZ ┆ 20200106 ┆ 1846.05 ┆ 1893.0 ┆ … ┆ 0.919829 ┆ null ┆ null ┆ -0.008171 │\n", "│ 000001.SZ ┆ 20200107 ┆ 1850.42 ┆ 1886.45 ┆ … ┆ 0.921165 ┆ null ┆ null ┆ -0.014117 │\n", "│ 000001.SZ ┆ 20200108 ┆ 1815.49 ┆ 1861.34 ┆ … ┆ 0.922481 ┆ 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", "[配置] 特征数: 47\n", "[配置] 目标变量: future_return_5_rank_rank(20分位数)\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "C:\\Users\\liaozhaorun\\AppData\\Local\\Temp\\ipykernel_6736\\2929956284.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:39:26.231967Z", "start_time": "2026-03-13T14:39:02.079217Z" } }, "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, 68)\n", "\n", "执行每日独立筛选股票池...\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "C:\\Users\\liaozhaorun\\AppData\\Local\\Temp\\ipykernel_6736\\4061767669.py:57: 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, 68)\n", " 筛选后数据规模: (1455000, 68)\n", " 筛选前股票数: 5678\n", " 筛选后股票数: 1934\n", " 删除记录数: 5368808\n" ] } ], "execution_count": 6 }, { "metadata": {}, "cell_type": "markdown", "source": "### 4.2 数据划分" }, { "metadata": { "ExecuteTime": { "end_time": "2026-03-13T14:39:26.304464Z", "start_time": "2026-03-13T14:39:26.241490Z" } }, "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, 68)\n", "验证集数据规模: (242000, 68)\n", "测试集数据规模: (243000, 68)\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_6736\\2929956284.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:39:29.591092Z", "start_time": "2026-03-13T14:39:26.325582Z" } }, "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=False)\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", "[严重] 发现 1386 个全空因子:\n", " (某天的某个因子所有值都是 null,可能是数据缺失或计算错误)\n", " - 日期 20200721: net_profit_yoy (样本数: 1000)\n", " - 日期 20200721: revenue_yoy (样本数: 1000)\n", " - 日期 20200721: healthy_expansion_velocity (样本数: 1000)\n", " - 日期 20200220: drawdown_from_high_60 (样本数: 1000)\n", " - 日期 20200220: volatility_squeeze_5_60 (样本数: 1000)\n", " - 日期 20200220: roa (样本数: 1000)\n", " - 日期 20200220: net_profit_yoy (样本数: 1000)\n", " - 日期 20200220: revenue_yoy (样本数: 1000)\n", " - 日期 20200220: healthy_expansion_velocity (样本数: 1000)\n", " - 日期 20200220: pe_expansion_trend (样本数: 1000)\n", " ... 还有 1376 个\n", "\n", "--------------------------------------------------------------------------------\n", "建议处理方式:\n", " 1. 检查因子定义和数据源,确认计算逻辑是否正确\n", " 2. 如果是预期内的缺失(如新股无历史数据),考虑调整因子计算窗口\n", " 3. 如果是数据同步问题,重新同步相关数据\n", " 4. 可以使用 filter 排除问题日期或因子\n", "================================================================================\n", "\n", "检查验证集...\n", "\n", "检查测试集...\n", "[成功] 数据质量检查通过,未发现异常\n" ] } ], "execution_count": 8 }, { "metadata": {}, "cell_type": "markdown", "source": "### 4.4 数据预处理" }, { "metadata": { "ExecuteTime": { "end_time": "2026-03-13T14:39:30.132178Z", "start_time": "2026-03-13T14:39:29.595727Z" } }, "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, 68)\n", "处理后验证集形状: (242000, 68)\n", "处理后测试集形状: (243000, 68)\n" ] } ], "execution_count": 9 }, { "metadata": {}, "cell_type": "markdown", "source": "### 4.4 训练 LambdaRank 模型" }, { "metadata": { "ExecuteTime": { "end_time": "2026-03-13T14:39:31.913185Z", "start_time": "2026-03-13T14:39:30.136586Z" } }, "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", "特征数: 47\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", "[55]\ttrain's ndcg@10: 0.625716\tval's ndcg@10: 0.559889\n", "训练完成!\n" ] } ], "execution_count": 10 }, { "metadata": {}, "cell_type": "markdown", "source": "### 4.5 训练指标曲线" }, { "metadata": { "ExecuteTime": { "end_time": "2026-03-13T14:39:32.028008Z", "start_time": "2026-03-13T14:39:31.917113Z" } }, "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", " 实际训练轮数: 105\n", " 早停状态: 已触发(最佳迭代: 55)\n", "\n", "最终 NDCG 指标:\n", " ndcg@10: 训练集=0.6351, 验证集=0.5518\n", "\n", "[绘图] 使用 LightGBM 原生接口绘制训练曲线...\n" ] }, { "data": { "text/plain": [ "
" ], "image/png": "iVBORw0KGgoAAAANSUhEUgAABW0AAAPdCAYAAADxjUr8AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAA5BdJREFUeJzs3Qd81PX9x/F39l5kkwTC3htBcCAuFPderaPVumu1zrZqtY62Wkurtjhqrf61rjpQUEQBUYYgyN6EMDJIQkhC9rj7P77fDBkBE0hyl7vX8/H49ve73/3ud9+748S+/dzn6+N0Op0CAAAAAAAAALgFX1dPAAAAAAAAAADwA0JbAAAAAAAAAHAjhLYAAAAAAAAA4EYIbQEAAAAAAADAjRDaAgAAAAAAAIAbIbQFAAAAAAAAADdCaAsAAAAAAAAAboTQFgAAAAAAAADcCKEtAAAAAAAAALgRQlsAAAC4tWuvvVY+Pj529O3bVw6Ho+m+qVOnNt336quv2mPp6elNx/z8/BQWFmaPnXXWWXrjjTdUV1fX7PPs3r1bDz30kIYPH66IiAiFh4drwIABuuWWW7RmzZr9zq2srNTzzz+vCRMmKDY2VoGBgUpJSdGxxx6rhx9+WBkZGW3y2qurq3X33Xdr/PjxCg4Obnpdn332WbPnL1q0SKeffroiIyPt6z7uuOM0Y8aMNpkLAAAAOo5/Bz4XAAAAcFQ2bdqkd955R5dffnmLzjcBb3l5ubZt22aHCTBfeeUVffDBBzbYbPT999/bUDcnJ2e/x69fv94OE8pOmTLFHsvOztbkyZO1YsWK/c41x8349ttvFRAQoN/97ncHzceEueY6n376qXbu3GnnMHToUF199dW66qqr5Ou7f02Fmftf/vKXFr3WefPm6bTTTrNBb6MFCxbo7LPPtmH1FVdc0aLrAAAAwPWotAUAAECn8sQTT8jpdLboXHNeWVmZvvzyS40YMcIemz17tq6//vqmc0pLS3Xuuec2BbY//elPtWXLFlVVVWnDhg168MEHbdVq4/UuvPDCpsD2pJNOsiGtqbwtLi7WV199pdtuu63p/H39/e9/t5W7zz77rDZv3mwfk5eXpy+++MKGtuZaptp3Xyb8vfXWW23oetNNNx32tZr7TWAbFRWlJUuW2Ofo1q2bnfPtt9+uioqKFr1nAAAAcD0qbQEAANBpmHYHq1at0rRp03Teeee16DGhoaE6+eSTNXPmTPXu3VslJSV699137XWGDBmil19+2Va9Gqa9wWuvvdb0WNOO4dFHH21qqfDxxx/bkNZIS0uzlbshISH2dlBQkE488UQ7DvSPf/xDd9xxh+Li4my7hYsvvlg9e/a0IasJWE2Q+8knn9iqWBP8mspew4S/zz33nN3fuHHjIV/jsmXLtG7dOrtvqpBHjx7dFOT+5je/sWGwaalwwQUXtPCdBgAAgCtRaQsAAIBO49JLL7Xbxx9/vNWPjY+Pty0IGpkWBca+/WFNsHqosHjfxxgmfG0MbA9nx44duuuuuzR48GAtX77cVvVeeeWVSkpK0r///W/98pe/1N69e/WnP/3J9qQ1FbmtZULbRv379292f99zAAAA4N4IbQEAANBpmIDT9IE11amff/55qx9vKmcbZWZm2q3pddtcyNmcfc/t169f076paG1cJMwME8g2+tvf/maPmT66iYmJuuiii7R69WrbluHee++1LRIMs28qgk3lb2vl5+c37e/bq3ff/cbnAQAAgPsjtAUAAECnER0dbStcj7Ta1ixM1sgEqftuW6uljzOVvKYPrmnNYFo0mOD3jDPO0J49e+xiZWbbyFQCmz66poVDW9i39++Rvk4AAAB0PEJbAAAAdCqm1YDpUztv3jx98803rXrsvn1h09PT7dYs1tXIBKaH071792avNXXqVBuQ7nt/o61bt2rMmDF2f9OmTU2VuSaAvvPOO/c7t7FC11Thtrb1QyOzIFoj03ahuXMAAADg3ghtAQAA0KmY8PGGG26w+2+99VarWgi8+eabTbfPPPNMuzVVr40O1U+2cSGyfc81QW1NTU2LnruxJ+6+la/N3f7+++8VEBCg2NhYtcbIkSObDZ7Xr1/f7DkAAABwb4S2AAAA6HTuueceBQYGNoWph1NRUaE5c+bYwLWx8vSyyy6zC4MZ119/vVJTU+3+ggULdN1119nqWBPImmraBx980A7jnHPO0ejRo+2+OeeCCy7QqlWr7LlmwbHKysqDnt9U9C5dunS/nrovvPCCioqKmkJi07Zh+vTpeuqpp3TKKacoKCio6fEFBQV2lJeXNx0z7RPMscaqWhPIDhgwoCnI/u6777R582YbLBsmBN43cAYAAIB783Ee+J/3AQAAADdy7bXX6j//+Y/dX7duXdNiYTfeeKNe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AGgVQlsAAHBYeXsrNXVuht74dpuqGsJa0w/WLDQ2vlcsfWEBAAAAoI21z3LOrfD8888rPT1dwcHBGjt2rBYvXnzY84uKinTrrbcqOTlZQUFB6tu3r62kafTkk0/qmGOOUUREhBISEnT++edrw4YNHfBKAADwLPl7q/TYJ2t14p/n6JX5W21gO6p7jP7v52P17k3jdFzvOAJbAAAAAPC0Stu3335bd911l6ZOnWoD2ylTpmjSpEk2ZDWB64Gqq6t12mmn2fvee+89paSkaNu2bYqOjm4656uvvrKhrglua2tr9Zvf/Eann3661q5dq7CwsA5+hQAAdD4FpVV64asten3RNlXW1FfWjugWrTtP7asT+hDUAgAAAIBHh7bPPPOMbrjhBl133XX2tglvp0+frldeeUX333//Qeeb44WFhVqwYEFTfzpTpbuvzz77bL/br776qg15ly5dqhNPPLHZeVRVVdnRqKSkxG5ramrsAOB5Gr/bfMeBH1TV1Omf87bqlfmZqmgIa4emRuqOk3vrhN71bRDMfxDtDPiOA56P7zng2fiOA57PW7/nNS18vT5Op9MpFzBVs6GhobZi1rQwaHTNNdfYFggfffTRQY+ZPHmyunTpYh9n7o+Pj9eVV16p++67T35+fs0+z+bNm9WnTx+tWrVKgwcPbvac3//+93rkkUcOOv7mm2/a5wIAwNNtKvbR2xm+yq/0sbfTwpw6M82hgdFO+dQfAgAAAAAcpfLycptnFhcXKzIy0v0qbQsKClRXV6fExMT9jpvb69evb/YxGRkZmj17tq666irbx9YEsrfccotNqB9++OGDznc4HPrVr36l44477pCBrfHAAw/YNg37VtqmpaVp4sSJio2NParXCcA9mX9uzJo1y7ZcYWV5eLM95dX608yN+t/abHs7MSJIv53cT2cMSuzU/Wr5jgOej+854Nn4jgOez1u/5yUNv/B36/YIrWVCWNPq4MUXX7SVtaNGjVJWVpaeeuqpZkNb09t29erV+uabbw57XbOgmRkHMn9gvOkPDeCN+J7DW5kf2kxbka1HP16r3WXVtpr2J2O7654z+iky2HP+7uM7Dng+vueAZ+M7Dng+b/ueB7TwtbostI2Li7PB665du/Y7bm4nJSU1+5jk5GT7wvZthTBgwADl5ubadguBgYFNx2+77TZ98sknmjdvnlJTU9vx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N/EYAAAAAAAAAORFIe1pCwAAAAAAAABIj9AWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAAAAAAfITQFgAAAAAAAAB8hNAWAAAAAAAAAHyE0BYAAAAA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nM86DuRtVnFnExVZe4RevXppxowZeuutt7xhIiIivF2pn3jiCdWpU8f7MfjQQw95u1Z379491IsP4BQuueQSr4etTQRs7RHmzp2rF154Qddee613Oes4kPfs3btXy5cvTzf5mBVU2G9uW9dP9Xfb9qi58MILNXjwYK8dkk0ebsVYVpFv18tXAjjGK6+8EqhWrVqgQIECgTZt2gSmT5/OqwTkQfZf3PHGu+++m3KdAwcOBG6++eZAyZIlA4ULFw5cfvnlgQ0bNoR0uQFkXYcOHQK33357ymnWcSDv++qrrwKnn356IDY2NlC/fv3AW2+9le7yxMTEwEMPPRQoX768d53zzjsvsGTJkpAtL4CM2717t/d3235/FyxYMFCrVq3Agw8+GDh06FDKdVjHgbxl0qRJx/0dPnDgwAyv09u2bQv06dMnULRo0UBcXFxg0KBBgT179gTymwj7J9TBMQAAAAAAAADAoactAAAAAAAAAPgIoS0AAAAAAAAA+AihLQAAAAAAAAD4CKEtAAAAAAAAAPgIoS0AAAAAAAAA+AihLQAAAAAAAAD4CKEtAAAAAAAAAPgIoS0AAAAAAAAA+AihLQAAAJADatSooSFDhvDaAgAAINMIbQEAAJDnXXPNNerevbt3vGPHjrrjjjty7bHfe+89lShR4pjzZ86cqRtuuCHXlgMAAADhIzrUCwAAAAD40eHDh1WgQIEs375s2bJBXR4AAADkH1TaAgAAIKwqbqdMmaKXXnpJERER3vjrr7+8y/744w9ddNFFKlq0qMqXL6/+/ftr69atKbe1Ct1bb73Vq9ItU6aMunTp4p3/wgsvqHHjxipSpIiqVq2qm2++WXv37vUumzx5sgYNGqRdu3alPN6//vWv47ZHWL16tS677DLv8ePi4tSrVy9t2rQp5XK7XbNmzTRs2DDvtsWLF9dVV12lPXv2pFzns88+85alUKFCKl26tDp37qx9+/blwisLAACA3ERoCwAAgLBhYe0ZZ5yhwYMHa8OGDd6woHXnzp0699xz1bx5c82aNUvjx4/3AlMLTtN6//33veran3/+WW+88YZ3XmRkpF5++WUtWLDAu/yHH37Qvffe613Wvn17L5i1EDb58e6+++5jlisxMdELbLdv3+6FyhMmTNDKlSvVu3fvdNdbsWKFRo8era+//tobdt2nn37au8zuu0+fPrr22mu1aNEiLzDu0aOHAoFADr6iAAAACAXaIwAAACBsWHWqha6FCxdWhQoVUs5/9dVXvcD2ySefTDlv6NChXqC7dOlS1a1b1zuvTp06evbZZ9PdZ9r+uFYB+8QTT+imm27Sf//7X++x7DGtwjbt4x1t4sSJmj9/vv7880/vMc0HH3ygRo0aeb1vW7dunRLuWo/cYsWKeaetGthu++9//9sLbRMSErygtnr16t7lVnULAACA8EOlLQAAAMLeb7/9pkmTJnmtCZJH/fr1U6pbk7Vs2fKY237//fc677zzVLlyZS9MtSB127Zt2r9/f4Yf3ypjLaxNDmxNw4YNvQnM7LK0oXByYGsqVqyozZs3e8ebNm3qLYcFtVdeeaXefvtt7dixIwuvBgAAAPyO0BYAAABhz3rQXnLJJZo3b166sWzZMp1zzjkp17O+tWlZP9xu3bqpSZMm+vzzzzV79my99tprKROVBVtMTEy601bBa9W3JioqymurMG7cOC/wfeWVV1SvXj2vehcAAADhhdAWAAAAYcVaFhw5ciTdeS1atPB60lola+3atdONo4PatCyktdD0P//5j9q1a+e1UVi/fv0pH+9oDRo00Jo1a7yRbOHChV6vXQtgM8pC3DPPPFOPPvqo5s6d6z32qFGjMnx7AAAA5A2EtgAAAAgrFsz++uuvXpXs1q1bvdD1lltu8SYBs4m8rIestUT49ttvNWjQoJMGrhbqxsfHe1WtNnHYsGHDUiYoS/t4VslrvWft8Y7XNqFz585eW4O+fftqzpw5mjFjhgYMGKAOHTqoVatWGXpe9pysJ69NpLZ69Wp98cUX2rJlixcIAwAAILwQ2gIAACCs3H333V4rAatgLVu2rBdwVqpUST///LMX0F5wwQVegGoTjFlP2cjIE38ltj6yL7zwgp555hmdfvrpGj58uJ566ql012nfvr03MVnv3r29xzt6IrPkCtkxY8aoZMmSXjsGC3Fr1aqlkSNHZvh5xcXFaerUqeratatX8fvPf/7TqwC+6KKLMvkKAQAAwO8iAoFAINQLAQAAAAAAAABwqLQFAAAAAAAAAB8htAUAAAAAAAAAHyG0BQAAAAAAAAAfIbQFAAAAAAAAAB8htAUAAAAAAAAAHyG0BQAAAAAAAAAfIbQFAAAAAAAAAB8htAUAAAAAAAAAHyG0BQAAAAAAAAAfIbQFAAAAAAAAAB8htAUAAAAAAAAA+cf/AyBll7qpXVUJAAAAAElFTkSuQmCC" }, "metadata": {}, "output_type": "display_data", "jetTransient": { "display_id": null } }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "[指标分析]\n", " 各NDCG指标在验证集上的最佳值:\n", " ndcg@10: 0.5599 (迭代 55)\n", "\n", "[重要提醒] 验证集仅用于早停/调参,测试集完全独立于训练过程!\n" ] } ], "execution_count": 11 }, { "metadata": {}, "cell_type": "markdown", "source": "### 4.6 模型评估" }, { "metadata": { "ExecuteTime": { "end_time": "2026-03-13T14:39:32.393179Z", "start_time": "2026-03-13T14:39:32.041674Z" } }, "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.5399\n", " ndcg@5: 0.5424\n", " ndcg@10: 0.5346\n", " ndcg@20: 0.5299\n", "\n", "特征重要性(Top 20):\n", "----------------------------------------\n", " 1. close_vwap_deviation 8189.04\n", " 2. revenue_yoy 4009.98\n", " 3. profit_margin 3742.07\n", " 4. sharpe_ratio_20 1536.27\n", " 5. active_market_cap 1361.63\n", " 6. max_ret_20 1216.47\n", " 7. pe_expansion_trend 1215.69\n", " 8. market_cap_rank 1063.42\n", " 9. debt_to_equity 886.34\n", " 10. drawdown_from_high_60 878.19\n", " 11. volatility_20 677.31\n", " 12. return_20 675.57\n", " 13. amihud_illiq_20 515.66\n", " 14. min_ret_20 486.54\n", " 15. turnover_rank 484.78\n", " 16. ma_20 428.48\n", " 17. std_return_20 413.62\n", " 18. BP 307.34\n", " 19. turnover_cv_20 299.52\n", " 20. EP_rank 297.78\n" ] } ], "execution_count": 12 }, { "metadata": { "ExecuteTime": { "end_time": "2026-03-13T14:39:32.644581Z", "start_time": "2026-03-13T14:39:32.401777Z" } }, "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.186959 ┆ 600082.SH │\n", "│ 2025-01-02 ┆ 0.088174 ┆ 600844.SH │\n", "│ 2025-01-02 ┆ 0.079581 ┆ 600683.SH │\n", "│ 2025-01-02 ┆ 0.07805 ┆ 600193.SH │\n", "│ 2025-01-02 ┆ 0.074266 ┆ 002343.SZ │\n", "│ … ┆ … ┆ … │\n", "│ 2025-01-06 ┆ 0.185272 ┆ 600082.SH │\n", "│ 2025-01-06 ┆ 0.137456 ┆ 000668.SZ │\n", "│ 2025-01-06 ┆ 0.125342 ┆ 002848.SZ │\n", "│ 2025-01-06 ┆ 0.114527 ┆ 002343.SZ │\n", "│ 2025-01-06 ┆ 0.101892 ┆ 600193.SH │\n", "└────────────┴──────────┴───────────┘\n", "\n", "训练流程完成!\n" ] } ], "execution_count": 13 }, { "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 }