ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

机器学习选股实战:从数据清洗到模型验证的完整Python框架

机器学习选股实战:从数据清洗到模型验证的完整Python框架 1. 机器学习选股先搞清楚“黑箱”到底指什么很多人一听到“机器学习选股”第一反应是“高大上”和“看不懂”。模型训练出来输入一堆数据输出一个买入信号中间过程像黑箱一样难以理解。这恰恰是很多策略从回测到实盘从赚钱到亏钱的关键转折点。机器学习选股的核心价值不是找到一个“圣杯”公式而是建立一个可解释、可迭代、可风控的量化研究流程。黑箱陷阱的根源往往不是模型本身太复杂而是使用者的流程出了问题比如过度依赖单一高胜率指标、忽略了数据的前瞻性泄露、或者对模型在极端市场下的表现一无所知。这篇文章不打算给你一个“胜率98%”的选股公式那种东西在实盘里大概率是过拟合的产物。我会拆解一个更务实的路径如何用Python搭建一个基础的机器学习选股框架并在这个过程中一步步把“黑箱”打开让你知道每个决策点在哪风险在哪。适合的人群是有一定Python和pandas基础对量化感兴趣但被各种复杂模型和回测平台绕晕的开发者或独立研究者。最关键的几步是数据准备与清洗 - 特征工程的可解释性 - 模型选择与验证 - 实盘衔接的考量。我们重点关注如何避免每一步中常见的“埋雷”操作。2. 环境准备别在第一步就埋下“未来函数”的雷在跑任何一行代码之前先明确环境边界。这不是一个追求极致速度的生产系统而是一个强调可复现和研究透明度的本地分析环境。基础环境配置Python环境 建议使用Anaconda创建独立环境避免包冲突。Python版本3.8或3.9较为稳定。核心库pandas,numpy: 数据处理基石。scikit-learn: 机器学习模型的核心库我们主要用它来做特征处理、模型训练和交叉验证。jupyter lab或jupyter notebook: 用于交互式分析和可视化强烈推荐方便一步步检查数据。matplotlib,seaborn: 用于可视化特征分布、回测结果。yfinance或akshare: 用于获取股票历史数据本文示例使用yfinance因其接口相对简单。注意这类库的数据可能有时效性或完整性限制生产环境需接入更稳定的数据源。安装命令很简单conda create -n stock_ml python3.9 conda activate stock_ml pip install pandas numpy scikit-learn jupyterlab matplotlib seaborn yfinance最重要的原则避免未来函数Look-Ahead Bias。这是量化策略尤其是机器学习策略最常见的致命错误。意思是你在t时刻构建特征或训练模型时不小心使用了t时刻之后未来才能知道的信息。例如用当天的收盘价计算当天的一个技术指标这没问题但如果你用了明天才知道的财务报告数据来计算今天的特征那就是未来函数会导致回测结果严重虚高实盘一塌糊涂。我的做法是在数据处理的每一步都显式地引入“时间戳”并确保所有特征计算都严格使用该时间戳之前的已知数据。在代码里这通常体现为使用.shift()函数来滞后数据。3. 从数据到特征构建可解释的输入而不是堆砌指标数据决定了模型的上限。对于选股我们通常需要价格数据日频的OHLCV和基本面数据季报、年报。这里我们先从价格数据开始。3.1 获取与清洗数据我们以获取一支股票例如AAPL的历史数据并计算其未来收益率作为标签为例import yfinance as yf import pandas as pd # 下载苹果公司股票数据 ticker ‘AAPL‘ data yf.download(ticker, start‘2018-01-01‘, end‘2023-12-31‘) # 假设我们只使用调整后收盘价 prices data[‘Adj Close‘].to_frame(name‘close‘)数据清洗包括处理缺失值 股票数据可能因为停牌等原因缺失。通常采用前向填充ffill或直接删除。去除异常值 价格数据通常不会出现极端异常值但衍生计算出的指标如涨跌幅可能会有。可以用分位数如1%和99%进行截尾处理。计算基础特征 这里就是特征工程的起点。3.2 构建可解释的特征不要一上来就用几十个技术指标把特征空间堆满。先从几个有明确经济含义的因子开始并确保计算无未来函数。# 计算基础特征收益率、波动率等 prices[‘return_1d‘] prices[‘close‘].pct_change(1) # 1日收益率 prices[‘return_5d‘] prices[‘close‘].pct_change(5) # 5日收益率 prices[‘volume_ratio‘] data[‘Volume‘] / data[‘Volume‘].rolling(20).mean() # 成交量比 # 计算简单移动平均线注意使用.shift(1)避免未来数据 prices[‘ma_10‘] prices[‘close‘].rolling(10).mean().shift(1) prices[‘ma_30‘] prices[‘close‘].rolling(30).mean().shift(1) prices[‘ma_cross‘] (prices[‘ma_10‘] prices[‘ma_30‘]).astype(int) # 金叉信号 # 计算布林带 rolling_mean prices[‘close‘].rolling(20).mean().shift(1) rolling_std prices[‘close‘].rolling(20).std().shift(1) prices[‘boll_upper‘] rolling_mean (rolling_std * 2) prices[‘boll_lower‘] rolling_mean - (rolling_std * 2) prices[‘boll_position‘] (prices[‘close‘] - prices[‘boll_lower‘]) / (prices[‘boll_upper‘] - prices[‘boll_lower‘]) # 删除因滚动计算产生的缺失值行 prices prices.dropna()关键点 所有基于滚动窗口的计算如rolling(20).mean()后面都必须跟一个.shift(1)。这意味着我们在t日交易结束时计算特征所用到的数据最晚只到t-1日收盘。这样在t日盘后构建t1日的预测时才是合规的。3.3 定义预测目标标签选股本质是预测未来股价走势。我们通常预测未来N日的收益率并将其二值化涨/跌或多值化大涨/平/大跌作为分类问题或者直接预测收益率数值作为回归问题。# 例如预测未来5日的收益率 forward_days 5 prices[‘future_return‘] prices[‘close‘].pct_change(forward_days).shift(-forward_days) # 将回归问题转为二分类问题未来5日上涨为1下跌为0 prices[‘label‘] (prices[‘future_return‘] 0).astype(int) # 再次删除因计算未来收益而产生的缺失值行最后forward_days行 features prices.drop([‘close‘, ‘future_return‘, ‘label‘], axis1) labels prices[‘label‘]至此我们有了特征矩阵features和标签向量labels。每个样本对应一个交易日特征是该交易日盘后可知的信息标签是该交易日之后第5日盘后才能确认的结果。这个时序关系必须严格保持。4. 模型训练与验证用时间序列方法拆穿“过拟合”假象这是“黑箱”感最强的部分也是最容易掉坑的地方。绝对不能简单调用train_test_split然后看准确率。4.1 为什么不能用随机划分的交叉验证金融数据具有极强的时序自相关性和结构性变化如牛熊市。随机打乱数据再交叉验证会导致模型“看到”未来的数据模式严重过拟合历史。回测曲线会非常漂亮实盘必然失效。4.2 使用时间序列交叉验证Time Series Splitscikit-learn提供了TimeSeriesSplit它能确保训练集永远在测试集之前。from sklearn.model_selection import TimeSeriesSplit from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, accuracy_score import numpy as np # 初始化模型这里以随机森林为例因其有一定的可解释性 model RandomForestClassifier(n_estimators100, max_depth5, random_state42) # 使用时间序列分割 tscv TimeSeriesSplit(n_splits5) accuracies [] for fold, (train_index, test_index) in enumerate(tscv.split(features)): X_train, X_test features.iloc[train_index], features.iloc[test_index] y_train, y_test labels.iloc[train_index], labels.iloc[test_index] # 训练模型 model.fit(X_train, y_train) # 预测并评估 y_pred model.predict(X_test) acc accuracy_score(y_test, y_pred) accuracies.append(acc) print(f“Fold {fold}: Accuracy {acc:.4f}“) # 可以打印更详细的分类报告 # print(classification_report(y_test, y_pred)) print(f“\nAverage Accuracy across folds: {np.mean(accuracies):.4f}“)重点观察什么平均准确率 如果接近甚至低于50%二分类随机猜测水平说明当前特征和模型可能无效。稳定性 各折Fold的准确率是否波动巨大如果后面几折对应近期数据准确率骤降可能意味着市场模式发生了变化模型失效。不要追求过高准确率 在有效的A股/美股市场一个长期稳定的、略高于50%的准确率已经能产生显著收益。宣称70%、80%甚至98%胜率的策略99.9%是过拟合或包含了未来函数。4.3 尝试其他模型与特征选择模型对比 除了随机森林可以尝试逻辑回归可解释性最强、梯度提升树如XGBoost、LightGBM性能通常更好。比较它们在时序交叉验证下的稳定性和平均性能而不是单次最高分。特征重要性 随机森林和XGBoost都能输出特征重要性。这是打开“黑箱”的第一把钥匙。import matplotlib.pyplot as plt model.fit(features, labels) # 用全数据最后训练一次看重要性 importances model.feature_importances_ indices np.argsort(importances)[::-1] plt.figure(figsize(10,6)) plt.title(“Feature Importances“) plt.bar(range(features.shape[1]), importances[indices]) plt.xticks(range(features.shape[1]), features.columns[indices], rotation90) plt.tight_layout() plt.show()看看哪些特征被模型认为最有用。如果发现某个你不太理解或认为不重要的特征排名很高就要深究原因是数据泄露还是它偶然捕捉到了某种噪音特征工程迭代 根据特征重要性和业务理解增加或删除特征。例如加入价量关系特征价格变化与成交量的相关性、行业相对强弱、市场情绪指标等。5. 回测与实盘考量从“实验室”到“战场”的关键一跃模型在交叉验证中表现尚可不代表就能赚钱。需要一个简单的回测框架来模拟交易。5.1 构建简易回测逻辑回测的核心是在每一个交易日结束时用当时已有的所有数据重新训练或更新模型产生对下一个交易日的预测并根据预测执行模拟交易。这里演示一个非常简单的每日再平衡策略# 假设我们有一个函数 get_model_predictions(date, historical_data) # 它能在给定日期用该日期之前的所有数据训练模型并预测下一交易日全市场股票的涨跌。 # 这是一个复杂的工程涉及大量数据管理和计算优化此处仅描述流程。 initial_capital 1000000 capital initial_capital position 0 # 持有现金为0持有股票为1 portfolio_values [] for current_date in trading_dates[100:]: # 从第100天开始保证有足够数据训练 # 1. 准备数据获取截至 current_date 的所有历史数据 historical_data get_data_up_to_date(current_date) # 2. 训练模型使用 historical_data 训练 model train_model(historical_data) # 3. 预测预测下一交易日current_date1的涨跌 prediction model.predict_for_next_day(historical_data) # 4. 交易逻辑这里以简单的二分类为例 if prediction 1 and position 0: # 看涨且空仓则全仓买入 position 1 # 记录买入价格等 elif prediction 0 and position 1: # 看跌且持仓则全仓卖出 position 0 # 记录卖出价格计算本次盈亏更新capital # 如果预测不变则持有不动 # 5. 记录当日收盘后资产总值 daily_value calculate_portfolio_value(capital, position, current_price) portfolio_values.append(daily_value) # 计算回测指标年化收益率、夏普比率、最大回撤等回测中必须考虑的细节交易成本 必须扣除佣金和印花税。滑点 假设以收盘价交易过于理想可以设置一个固定的滑点如0.1%。仓位管理 全仓进出风险极大应考虑分仓或凯利公式。基准比较 你的策略收益必须和“买入并持有”指数如沪深300、标普500做对比。5.2 实盘衔接的陷阱即使回测完美实盘依然可能失败原因包括数据实时性 回测用的是清洗好的历史数据实盘需要实时接收、清洗、校验数据任何延迟或错误都会导致信号错误。模型衰减 市场风格会变。需要定期如每月、每季度用新数据重新训练模型或者使用在线学习模型。流动性风险 回测假设你可以随时以收盘价买卖任意数量实盘中小盘股可能无法满足。策略容量 你的策略能管理多少资金当资金量变大你的买卖行为本身就会影响市场。6. 持续迭代与风控把“黑箱”变成“灰箱”机器学习选股不是一劳永逸的而是一个需要持续监控和迭代的系统工程。监控模型表现 实盘运行时持续记录模型的预测准确率、预测概率的分布。如果发现准确率持续低于某个阈值如样本外测试的平均水平触发警报。分析错误案例 定期查看模型预测错误的交易日。是系统性错误如所有模型在某类市场环境下都失效还是随机错误错误样本的特征有什么共性特征与模型迭代 根据错误分析和新的市场理解设计新的特征尝试新的模型结构。每次迭代都必须重新进行严谨的时序交叉验证和样本外回测。设立严格风控单笔止损/止盈 不要依赖模型的卖出信号设置硬性的价格止损位。整体回撤控制 当策略整体资产回撤超过一定比例如10%应暂停策略检查原因。分散化 不要只依赖一个模型或一组特征。可以运行多个相关性较低的策略或者将机器学习信号作为传统多因子模型的一个因子来使用。最终一个可靠的机器学习选股流程应该像一个透明的管道数据从哪里来经过怎样的清洗和计算变成特征特征如何进入模型模型如何做出决策决策如何转化为交易交易结果如何反馈回来优化模型——每一个环节都应该是可记录、可检查、可解释的。这个过程没有“一键致富”的代码它需要的是对市场的理解、对数据的敬畏、对模型的耐心以及最重要的——对风险的控制。从今天起忘掉那些神秘的“黑箱”公式从构建一个可解释的单因子模型开始一步步搭建属于你自己的、经得起市场检验的量化研究体系。
返回列表