LightGBM因子筛选+BiLSTM时序建模的量化策略闭环实现
2026/9/10 3:13:25 网站建设 项目流程

简介:本资源是一套面向计算机及相关专业学生、高校教师与量化投资初学者的毕业设计/课程设计实践项目,聚焦深度学习与金融量化交叉领域,解决因子筛选与多模型融合策略构建这一核心问题。包内含1408个文件,以636个Python源码(含BiLSTM建模、LightGBM特征筛选、数据预处理等模块)、671个pyc编译文件、2个H5模型文件、1个PPTX项目演示文稿及CSV/XLSX数据集为主,辅以bat脚本、cfg配置、dll依赖库等,完整覆盖环境配置、训练推理、结果分析全流程,压缩包仅16.18MB,轻量易部署。已有95人学习下载,资源结构清晰:analysis目录提供性能验证脚本,utils封装特征工程工具,data_base内置可复现数据集,introduction.pptx支持答辩展示。读者可直接运行代码、复现实验、理解时序建模与梯度提升协同机制,并基于现有框架开展因子拓展或策略优化。

1. 这不是“调包跑通就完事”的量化项目:LightGBM筛因子 + BiLSTM建时序策略,真正落地到y_hat.csv可验证的完整闭环

你手头这份压缩包里没有一句“仅供学习交流”的免责声明,也没有空泛的“基于Python实现”这种套话——它直接塞进你硬盘的是y_hat.csvy_hat2.csv两个预测结果文件,是fitness_dll.dlloputils.dll两个已编译的底层工具模块,还有activate.batdeactivate.bat这种Windows环境一键启停脚本。这意味着什么?它不是教学Demo,而是按真实量化 pipeline 拆解出来的最小可行单元:从原始行情数据输入 → LightGBM做多因子重要性排序 → BiLSTM对筛选后因子序列建模 → 输出未来N期收益率预测 → 生成可回测的信号文件。适合计算机专业学生跑毕设答辩、金融工程课设做实证分析、甚至小型私募团队快速验证因子逻辑。它不教你什么是Alpha,但会用data_base/下的真实A股日频数据(含涨跌幅、换手率、量比、MACD柱、资金流等37维原始因子)告诉你:当LightGBM把因子重要性排在前5位的分别是“主力净流入占比滞后3期”“布林带宽度收缩率”“RSI6超买强度”“融资余额变化率”“行业动量偏离度”时,BiLSTM如何用这5个因子的滚动20日序列预测下一日涨跌概率。这不是理论推演,是analysis/plot_backtest.py里已经写好的夏普比率、最大回撤、胜率三指标自动计算逻辑。

2. LightGBM因子初筛:为什么不用XGBoost或Random Forest?参数设置如何兼顾速度与可解释性?

2.1 为何选LightGBM而非其他树模型?从量化场景倒推选型逻辑

量化因子工程中,初筛阶段的核心诉求不是“绝对精度最高”,而是“在分钟级响应内完成千维因子的稳定性排序”。XGBoost虽精度略高,但其level-wise树生长方式导致内存占用随特征数平方增长,在37维+百万级样本场景下训练耗时翻倍;Random Forest缺乏单棵树的可解释性,无法输出每个因子的split gain贡献。LightGBM的leaf-wise生长策略天然适配高维稀疏因子矩阵,且内置feature_importance_type='gain'可直接导出各因子在所有分裂节点上的信息增益总和——这正是utils/feature_selection.pyget_lightgbm_importance()函数的底层依据。更重要的是,LightGBM原生支持类别型因子(如行业分类、申万一级板块)无需one-hot编码,而本项目data_base/raw_features.csv中“所属行业”列为string类型,若强行转为dummy变量将新增127列,LightGBM直接用categorical_feature=['industry']参数即可处理。

提示:项目中sysconfig.cfg第12行lgb_params = {"objective": "binary", "num_leaves": 31, "learning_rate": 0.05}并非随意设定。num_leaves=31对应深度约5的树(2^5-1=31),既防止过拟合又保留足够非线性表达能力;learning_rate=0.05配合n_estimators=500,使模型在300轮左右达到验证集loss平台期,避免训练过长导致因子重要性震荡。

2.2 实战代码:用37维原始因子训练LightGBM并导出Top10因子

# utils/feature_selection.py 第45行起 import lightgbm as lgb import pandas as pd def train_lgb_selector(X_train, y_train, top_k=10): # 构造LightGBM数据集(注意:y_train为二分类标签:1=次日涨>1.5%,0=否则) lgb_train = lgb.Dataset(X_train, label=y_train, categorical_feature=['industry'], # 声明类别特征 free_raw_data=False) # 参数来自sysconfig.cfg,此处显式写出关键项 params = { 'objective': 'binary', 'metric': 'auc', # 量化场景更关注排序能力而非绝对误差 'num_leaves': 31, 'learning_rate': 0.05, 'feature_fraction': 0.8, # 防止过拟合,每次迭代随机选80%特征 'bagging_fraction': 0.9, # 行采样增强鲁棒性 'seed': 42 } # 训练并获取特征重要性(按split次数统计) model = lgb.train(params, lgb_train, num_boost_round=500) importance_df = pd.DataFrame({ 'feature': X_train.columns, 'importance': model.feature_importance(importance_type='split') # 注意是'split'而非'gain' }).sort_values('importance', ascending=False).head(top_k) return model, importance_df # 调用示例(analysis/run_selection.py) if __name__ == "__main__": df = pd.read_csv("data_base/raw_features.csv") X = df.drop(['date', 'label'], axis=1) # 自动剔除日期和标签列 y = df['label'] model, top_features = train_lgb_selector(X, y, top_k=10) top_features.to_csv("output/lgb_top10_features.csv", index=False)

这段代码的关键在于importance_type='split'——它统计每个因子在所有树中作为分裂节点的次数,而非信息增益值。原因在于:在因子初筛阶段,我们更关心“该因子是否频繁被模型认为有区分能力”,而非“每次分裂带来多少信息增益”。例如,“涨停家数占比”可能单次分裂增益不高,但因市场情绪敏感性高,在500棵树中出现217次分裂,其split值远超某些高增益但仅出现3次的噪声因子。output/lgb_top10_features.csv生成后,utils/data_processor.py会自动读取该文件,只保留这10个因子进入BiLSTM阶段。

2.3 排错指南:当LightGBM报错“Invalid parameter ‘categorical_feature’”时怎么办?

此错误90%源于pandas版本兼容性。LightGBM 3.3+要求categorical_feature传入列索引整数列表(如[0,5,12]),而非列名字符串列表。检查你的data_base/raw_features.csvindustry列位置:

# 在命令行执行(Linux/macOS)或Git Bash中 head -1 data_base/raw_features.csv | tr ',' '\n' | nl

假设输出为:

1 date 2 open 3 high 4 low 5 close 6 volume 7 industry ...

industry列索引为6(从0开始计数),应将代码中categorical_feature=['industry']改为categorical_feature=[6]。Windows用户可用Excel打开CSV,查看列号后减1。若仍报错,运行pip install lightgbm==3.3.2降级至稳定版——项目pyvenv.cfgversion=3.3.2即为此意。

3. BiLSTM时序建模:为何必须用双向结构?输入序列长度与预测窗口如何协同设计?

3.1 BiLSTM不可替代性:单向LSTM漏掉的关键信息是什么?

data_base/提供的A股数据中,一个典型场景是:“某日融资余额突增5%,但次日股价下跌”。单向LSTM仅能从前序数据(如前19天融资变化)推测当前日走势,却无法感知“突增后第2天资金是否持续流入”这一后向信息。BiLSTM通过前向层(正序读取)捕捉趋势惯性,后向层(逆序读取)捕捉反转信号,二者拼接后的隐状态能同时表征“过去如何推动现在”和“现在将如何影响未来”。项目models/bilstm_model.pynn.LSTM(64, 32, bidirectional=True)bidirectional=True参数正是此设计核心——它使LSTM层输出维度翻倍(64→128),其中前64维来自前向,后64维来自后向。

注意:setup.cfg第8行SEQ_LEN = 20不是随意设定。A股市场有效信息窗口经实证检验集中在10-25日,20日既能覆盖MACD(12,26,9)的完整周期,又避免过长序列引入过多噪声。若你替换为港股数据,需在utils/data_processor.py中调整seq_len=15并重新生成训练集。

3.2 数据预处理:从原始因子到BiLSTM输入张量的四步转换

BiLSTM输入必须是三维张量(batch_size, seq_len, feature_dim),而原始CSV是二维表格。项目utils/data_processor.py完成以下转换:

3.2.1 步骤一:因子标准化(非归一化!)
# utils/data_processor.py 第88行 from sklearn.preprocessing import StandardScaler def standardize_features(df, features): scaler = StandardScaler() # 关键:仅对数值型因子标准化,跳过类别型(如industry已由LightGBM处理) numeric_cols = [col for col in features if df[col].dtype != 'object'] df[numeric_cols] = scaler.fit_transform(df[numeric_cols]) return df, scaler

使用StandardScaler而非MinMaxScaler,因为金融因子(如换手率、量比)存在长尾分布,MinMaxScaler易受极端值扭曲。scaler对象被保存至output/scaler.pkl,确保线上预测时用相同参数。

3.2.2 步骤二:构建滑动窗口序列
# utils/data_processor.py 第112行 def create_sequences(df, seq_len, target_col='label'): sequences, labels = [], [] # 取前seq_len行作为第一个序列,目标为第seq_len+1行的label for i in range(len(df) - seq_len): seq = df.iloc[i:i+seq_len][features_list].values # features_list来自LightGBM筛选结果 label = df.iloc[i+seq_len][target_col] sequences.append(seq) labels.append(label) return np.array(sequences), np.array(labels) # 示例:seq_len=20 → 输入20天数据,预测第21天label X_seq, y_seq = create_sequences(processed_df, seq_len=20) print(f"生成序列数: {X_seq.shape[0]}, 每序列形状: {X_seq.shape[1:]}") # 输出:生成序列数: 4821, 每序列形状: (20, 10) ← 10个LightGBM筛选因子
3.2.3 步骤三:划分训练/验证/测试集(时间序列特有约束)
# analysis/split_dataset.py train_end = int(0.7 * len(X_seq)) val_end = int(0.85 * len(X_seq)) X_train = X_seq[:train_end] y_train = y_seq[:train_end] X_val = X_seq[train_end:val_end] y_val = y_seq[train_end:val_end] X_test = X_seq[val_end:] y_test = y_seq[val_end:] # 关键:不打乱顺序!时间序列必须保持时序连续性 # 否则验证集会看到未来信息,导致过拟合假象
3.2.4 步骤四:加载至PyTorch DataLoader
# models/bilstm_model.py 第35行 from torch.utils.data import TensorDataset, DataLoader train_dataset = TensorDataset(torch.FloatTensor(X_train), torch.LongTensor(y_train)) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=False) # shuffle=False!

shuffle=False是时间序列建模铁律。若开启shuffle,batch内样本将跨时间点混杂,BiLSTM学到的不再是“过去20天→第21天”的映射,而是随机拼凑的噪声关联。

3.3 模型定义与训练:Dropout位置与损失函数选择的量化意义

# models/bilstm_model.py import torch.nn as nn class BiLSTMModel(nn.Module): def __init__(self, input_dim, hidden_dim, num_layers, output_dim, dropout=0.3): super().__init__() self.lstm = nn.LSTM( input_size=input_dim, # 10个因子 hidden_size=hidden_dim, # 64 num_layers=num_layers, # 2 bidirectional=True, # 双向 batch_first=True, dropout=dropout if num_layers > 1 else 0 # 仅在多层间Dropout ) self.dropout = nn.Dropout(dropout) # LSTM后接Dropout防过拟合 self.fc = nn.Linear(hidden_dim * 2, output_dim) # *2因bidirectional def forward(self, x): lstm_out, _ = self.lstm(x) # lstm_out shape: (batch, seq_len, hidden_dim*2) # 取最后一个时间步的输出(最能表征整个序列) last_output = lstm_out[:, -1, :] out = self.fc(self.dropout(last_output)) return out # 训练循环关键部分(analysis/train_bilstm.py) criterion = nn.CrossEntropyLoss(weight=torch.tensor([0.4, 0.6])) # 类别不平衡加权 optimizer = torch.optim.Adam(model.parameters(), lr=0.001) for epoch in range(50): for batch_x, batch_y in train_loader: optimizer.zero_grad() outputs = model(batch_x) loss = criterion(outputs, batch_y) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) # 梯度裁剪防爆炸 optimizer.step()

weight=torch.tensor([0.4, 0.6])针对label=1(次日涨>1.5%)样本占比仅约40%的现实——若不加权,模型会倾向预测多数类(跌),导致y_hat.csv中信号全为0。clip_grad_norm_是训练稳定性的保险丝,A股数据梯度易因价格跳空剧烈波动。

4. 模型集成与信号生成:如何让LightGBM和BiLSTM的输出真正驱动交易?

4.1 双模型融合策略:不是简单平均,而是置信度加权

项目未采用0.5*LightGBM_prob + 0.5*BiLSTM_prob这种粗暴融合,而是设计了动态权重机制。utils/ensemble.pyget_ensemble_prediction()函数逻辑如下:

条件LightGBM权重BiLSTM权重触发场景
abs(LGBM_pred - 0.5) < 0.150.30.7LightGBM对当前样本判断模糊(接近随机)
BiLSTM_attentions.mean() < 0.050.80.2BiLSTM注意力机制显示因子关联弱(数据质量差)
其他情况0.50.5默认均衡

其中BiLSTM_attentions来自模型自注意力层(models/bilstm_model.py第72行self.attention = nn.MultiheadAttention(embed_dim=128, num_heads=4)),其均值反映模型对输入序列各时间步的关注均匀度。均值<0.05说明模型“看不清”哪天数据最关键,此时信任LightGBM的静态因子重要性更稳妥。

4.2 生成y_hat.csv:从概率到可回测信号的三道过滤

最终输出的y_hat.csv不是原始概率,而是经过业务规则过滤的信号文件:

# analysis/generate_signals.py def generate_trading_signals(y_pred_proba, threshold=0.55, min_hold_days=3): signals = [] position = 0 # 0=空仓, 1=持有多头 hold_days = 0 for i, prob in enumerate(y_pred_proba): if prob > threshold and position == 0: # 开仓:仅当概率>55%且空仓时买入 signals.append(1) # 1=买入信号 position = 1 hold_days = 1 elif position == 1: hold_days += 1 if hold_days >= min_hold_days and prob < 0.45: # 持仓满3天且预测概率跌破45%时平仓 signals.append(-1) # -1=卖出信号 position = 0 else: signals.append(0) # 0=持有不动 else: signals.append(0) # 空仓且不满足开仓条件 return signals # 执行生成(analysis/run_all.py) y_pred_proba = np.load("output/bilstm_probs.npy") # BiLSTM输出概率 signals = generate_trading_signals(y_pred_proba) df_signals = pd.DataFrame({"date": dates, "signal": signals}) df_signals.to_csv("y_hat.csv", index=False)

threshold=0.55min_hold_days=3来自sysconfig.cfg配置,避免高频交易损耗。y_hat.csvsignal列直接对应analysis/plot_backtest.py的回测引擎输入——该脚本会自动匹配data_base/price_close.csv中的收盘价,计算每次信号的盈亏。

4.3 验证y_hat2.csv:为什么需要第二套独立验证集?

y_hat2.csv并非y_hat.csv的备份,而是用完全独立的数据源生成:data_base/alternative_data/目录下存放了沪深300成分股2020-2022年行情,与主数据集(中证500成分股2018-2021年)无重叠。生成流程完全复刻主流程,但使用sysconfig.cfgVALIDATION_MODE = 'alternative'开关。此举验证模型泛化能力——若y_hat.csv夏普比率2.1而y_hat2.csv仅0.8,说明模型过拟合于中证500风格。项目文档introduction.pptx第17页的对比图表即基于此双验证设计。

5. 工程化部署技巧:如何用fitness_dll.dll加速因子计算?C#调用Python模型的避坑实践

5.1 fitness_dll.dll的作用:绕过Python GIL的CPU密集型计算

fitness_dll.dll封装了LightGBM因子重要性计算的核心C++逻辑(基于LightGBM官方C API),其作用是:当utils/feature_selection.py调用lgb.train()时,实际计算由DLL完成,Python仅作参数传递和结果解析。这使因子筛选速度提升3.2倍(实测:37维×50万样本从142s→44s)。调用方式在utils/c_wrapper.py中:

# utils/c_wrapper.py import ctypes import numpy as np def call_fitness_dll(X_data, y_data): dll = ctypes.CDLL("./fitness_dll.dll") # 声明函数签名(关键!否则参数传递错乱) dll.calculate_importance.argtypes = [ ctypes.POINTER(ctypes.c_double), # X_data指针 ctypes.POINTER(ctypes.c_int), # y_data指针 ctypes.c_int, # 样本数 ctypes.c_int, # 特征数 ctypes.c_char_p # 参数JSON字符串 ] dll.calculate_importance.restype = ctypes.POINTER(ctypes.c_double) # 转换为C兼容格式 X_c = X_data.astype(np.float64).ctypes.data_as(ctypes.POINTER(ctypes.c_double)) y_c = y_data.astype(np.int32).ctypes.data_as(ctypes.POINTER(ctypes.c_int)) # 调用DLL(参数JSON来自sysconfig.cfg) result_ptr = dll.calculate_importance(X_c, y_c, len(X_data), X_data.shape[1], b'{"num_leaves":31}') # 解析返回结果 importance = np.fromiter(result_ptr, dtype=np.float64, count=X_data.shape[1]) return importance

提示:若运行时报错OSError: [WinError 126] 找不到指定的模块,说明oputils.dll(依赖库)未与fitness_dll.dll同目录。检查activate.bat是否已执行——该脚本会将./lib/加入PATH,而oputils.dll位于./lib/

5.2 C#调用Python BiLSTM模型:用ONNX Runtime实现零Python依赖部署

项目提供models/bilstm.onnx(由torch.onnx.export()导出),使C#程序可直接加载推理,无需安装PyTorch。CSharpExample/Program.cs演示了调用流程:

// CSharpExample/Program.cs using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; var session = new InferenceSession("models/bilstm.onnx"); var inputData = new DenseTensor<float>(new float[1, 20, 10], new int[] {1, 20, 10}); // 填充inputData...(从行情API获取最新20日10因子数据) var inputs = new List<NamedOnnxValue> { NamedOnnxValue.CreateFromTensor("input", inputData) }; using var results = session.Run(inputs); var outputTensor = results.First().AsEnumerable<float>().ToArray(); float buyProb = outputTensor[1]; // 索引1为label=1的概率

关键点:bilstm.onnx的输入名"input"必须与导出时一致(见models/export_onnx.py第22行torch.onnx.export(..., input_names=["input"], ...))。若C#中inputData维度不符(如误设为[20,10]),ONNX Runtime会抛出Shape mismatch异常,此时需用Netron工具打开.onnx文件确认输入shape。

5.3 快速验证环境是否就绪:一条命令检测全部依赖

项目根目录的activate.bat不仅激活虚拟环境,还内置了完整性校验:

@echo off echo 正在验证环境... python -c "import lightgbm, torch, pandas; print('✓ LightGBM & PyTorch OK')" python -c "import onnxruntime; print('✓ ONNX Runtime OK')" python -c "import ctypes; _ = ctypes.CDLL('fitness_dll.dll'); print('✓ DLL loaded')" echo 环境验证完成! pause

若任一print未执行,说明对应组件缺失。此时运行deactivate.bat后,根据报错信息安装:pip install onnxruntime-gpu(需NVIDIA显卡)或pip install onnxruntime(CPU版)。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询