简介:资源是《4天学会Python机器学习与量化交易》系列笔记的第二部分,对应视频教程p16至p20,面向正在入门量化投资与Python机器学习的学习者。笔记聚焦多因子策略中的市值因子选股,结合RiceQuant在线量化平台以沪深300成分股为例,介绍如何通过get_fundamentals等API获取市值、财务等基本面数据,并利用query函数按市值排序筛选股票、构建并动态调整个股组合。内容还系统梳理了多因子策略的完整流程——从数据获取、因子计算、因子筛选到组合构建与交易执行,同时说明因子数据中截面数据与面板数据的区别。去极值处理是重点模块,分别讲解了中位数去极值、3倍中位数去极值和3sigma法的原理及代码实现,并配有p19、p20的具体案例,帮助读者消除极端值对因子模型的干扰。资源以单个PDF文件呈现,大小仅562KB,方便本地阅读与检索;目前已有1363人学习浏览,适合希望快速上手量化选股与常见因子预处理方法的初学者作为笔记型参考。 这是“4天学会python机器学习与量化交易”系列笔记的第二篇,接着上次的进度,今天覆盖p16到p20这5节课程。上一篇笔记写完了p1~p15,那会儿还处在python基础语法、numpy和pandas的入门阶段,代码写得再多也还是“学会了工具但不知道拿来干嘛”的状态。到p16~p20这块,课程节奏明显变了,开始真正进入量化交易的数据获取、数据处理、特征工程和机器学习建模环节,算是第一次把python、机器学习和量化交易串在了一条线上。
如果你正在跟着这套课学,或者只是想看看“机器学习做量化到底怎么落地”,这篇笔记应该能帮你省不少事。我不光会把每节课的知识点重新梳理一遍,还会把跑代码时遇到的坑、视频里没解释清楚的细节、以及我自己的一些扩展思考一并写出来,方便你复现和避雷。
1. p16~p20课程地图:学习量化的第一段完整闭环
1.1 五节课分别讲了什么
前15节还在铺python基础语法和数据分析工具的底子,到了p16~p20,课程开始动真格了。我把这几节的内容做了一张对照表,方便你一目了然地看到整个节奏安排:
| 视频 | 主题 | 核心知识点 |
|---|---|---|
| p16 | 机器学习与量化交易概述 | 机器学习在量化中的应用场景、策略类型、整体处理流程 |
| p17 | 行情数据获取 | akshare/tushare等数据接口的基本用法、数据结构 |
| p18 | 数据清洗与预处理 | 列名统一、缺失值处理、数据类型转换、时间索引设置 |
| p19 | 特征工程与标签构造 | 常用技术指标计算、训练样本构造、预测目标定义 |
| p20 | 第一个机器学习模型 | 线性回归入门、sklearn建模、训练集测试集划分与评估 |
这五节课整体看下来,其实就是在走一条“从数据到模型”的产业链:先用接口把行情数据拿到手,然后清洗成能用的标准格式,接着从价格序列中构造出特征和预测目标,最后丢给sklearn跑一个最简单的模型。整套流程走完,你就算是对“机器学习做量化”这件事有了一个全景认知——尽管此时还看不清它的全貌,但至少知道了零件长什么样、大概怎么装配。
1.2 为什么课程在这个位置切入机器学习
我翻了一下p1~p15的内容,基本是变量、循环、函数、numpy和pandas的基础操作。如果课程从这会儿就直接上随机森林、神经网络,新手大概率当场劝退,因为数据不会整理、维度对不上、概念听不懂,报错都看不懂在报什么。而p16~p20作为过渡段,把“数据处理”当成了先头部队——这其实是很符合实战节奏的安排。
实际做量化的朋友应该深有体会:真正花时间的从来不是模型调参,而是数据获取、清洗、拼接、对齐这些脏活累活。数据质量不过关,后面模型再强也是“垃圾进、垃圾出”。课程把数据处理放在建模前面,而且是分了两节课篇幅来讲,这个比例我是认可的。另外,线性回归作为第一个模型也选得很稳,它足够简单,几行代码就能跑完,能让刚接触机器学习的人先建立起“特征->模型->预测->评估”的完整心智模型,而不是一上来就被复杂算法的数学推导劝退。
2. 数据获取:别一上来就调接口
2.1 akshare和tushare到底选哪个
视频里老师用的是tushare,但我实操下来发现,tushare的新版接口需要先到官网注册申请token,部分接口还有积分门槛——不同积分等级能调用的数据范围不一样。对刚入门的朋友来说,这是一个不小的隐形门槛。相比之下,akshare不用注册、不用token,直接pip install akshare装好就能用,接口返回的就是pandas的DataFrame,刚好可以顺势复习上一篇笔记里学的pandas操作。我把两者做了个简单对比:
| 对比项 | akshare | tushare |
|---|---|---|
| 是否需要token | 不需要 | 需要注册申请 |
| 上手难度 | 较低 | 中等 |
| 数据来源 | 聚合公开网页数据 | 自建数据仓库 |
| 适合场景 | 个人学习、快速验证想 | 对数据稳定性要求更高的场景 |
当然,这只是我基于个人学习体验做的对比,不是要分个高下。数据源没有绝对的优劣,只有合不合适。如果你想快速验证一个想法、跑通一套流程,akshare足够;如果后续要做更正式的量化项目,再去研究tushare的积分体系也不迟。
2.2 一个能跑的取数demo
以获取贵州茅台的历史日线数据为例,用akshare只需要几行代码:
import akshare as ak # 获取贵州茅台历史日线数据(不复权) df = ak.stock_zh_a_hist( symbol="600519", period="daily", start_date="20200101", end_date="20241231", adjust="" ) print(df.head()) print(df.tail())第一次跑通这个接口时,我还是有点兴奋的——毕竟这是第一次真正“亲手”把真实行情数据拉到本地。但兴奋劲很快就过去了,因为紧接着就会发现一个让人头疼的问题:返回的DataFrame列名是中文的,而且包含了不少我们暂时用不上的字段,比如振幅、涨跌幅、换手率等。
2.3 清洗:从“能跑”到“能用”
如果只是打印出来看看,那确实不需要额外处理。但要喂给机器学习模型,就得先把数据整理成统一格式。这一步没什么高深技术,主要就是四件事:列名改英文、日期转成datetime类型、把日期设为索引、按时间排序。我当时的清洗代码如下:
import pandas as pd # 统一列名,只保留后续用得上的字段 df.columns = ["date", "open", "close", "high", "low", "volume", "amount", "amplitude", "pct_change", "change", "turnover"] # 日期转datetime,并设为索引,按时间正序排列 df["date"] = pd.to_datetime(df["date"]) df = df.set_index("date").sort_index() # 只保留建模需要的列 df = df[["open", "close", "high", "low", "volume"]] # 检查缺失值 print(df.isnull().sum())这里有两个细节值得多说一句。第一,sort_index()一定要做,因为有些数据源返回的顺序可能不是严格按时间排的,而后续计算移动平均线依赖数据的先后顺序,一旦顺序乱了,指标就全是错的。第二,改列名时不要只图省事直接照抄,建议提前想好你后面建模到底需要哪些字段,把多余的列尽早砍掉,数据干净了,后续的排查成本会低很多。
3. 特征工程:机器学习里的“吃鸡装备”
3.1 为什么需要特征工程
如果只把原始价格喂给模型,相当于让一个没带装备的新人直接跳伞到决赛圈——他肉眼看到的只有红红绿绿的K线,很难从中归纳出规律。特征工程就是给这个新人配上倍镜、防弹衣和医疗包,让模型从同样的数据里看到更有区分度的信息。
具体到量化场景,移动平均线、RSI、布林带这些经典技术指标,本质上就是对原始价格序列做了一次加工和压缩,把“最近一段时间的趋势强弱”这类信息显式地暴露给模型。这一步做得好不好,直接决定模型能力的上限。很多初学者喜欢把时间花在调模型超参数上,但实战经验是:特征工程对效果的提升往往比调参来得更明显。
3.2 用pandas计算常见技术指标
这里我用pandas把几个出场率最高的指标都算了一遍:
# 移动平均线 df["ma5"] = df["close"].rolling(window=5).mean() df["ma10"] = df["close"].rolling(window=10).mean() df["ma20"] = df["close"].rolling(window=20).mean() # RSI指标 delta = df["close"].diff() gain = delta.clip(lower=0) loss = -delta.clip(upper=0) avg_gain = gain.rolling(window=14).mean() avg_loss = loss.rolling(window=14).mean() rs = avg_gain / avg_loss df["rsi"] = 100 - (100 / (1 + rs)) # 布林带 df["std20"] = df["close"].rolling(window=20).std() df["boll_upper"] = df["ma20"] + 2 * df["std20"] df["boll_lower"] = df["ma20"] - 2 * df["std20"] # 把刚开始计算产生的NaN丢弃 df = df.dropna()先解释一下rolling(window=5).mean()到底做了什么:它相当于开了一个长度为5的滑窗,从数据第一行开始往后滑动,每滑到一行就取包括自己在内的前5行求平均,这个值和这一行绑定在一起。所以ma5这一列的第10行,表示第6到第10天的收盘价均值。窗口越短,指标对价格反应越灵敏,但也越容易产生噪音;窗口越长,趋势越平滑,但滞后越严重。这个“灵敏与滞后”的权衡,几乎贯穿所有技术指标的设计逻辑。
RSI的公式看起来有点唬人,其实就是计算一段时间内“涨的力量”在总波动中的占比,取值在0到100之间。通常认为RSI高于70进入超买区、低于30进入超卖区,但学习阶段先不用纠结阈值怎么选,而是要把“它是在衡量什么”想明白——衡量的是最近涨跌力量的对比。
3.3 标签构造:你到底要预测什么
特征有了,接下来最重要的问题是:模型的y是什么?机器学习本质上是找x到y的映射关系,在量化场景里,最常见的一个做法是把“明天的收盘价比今天高不高”定义为一个分类问题,明天涨记作1,跌记作0。
# 用明天的收盘价是否高于今天,作为预测目标 df["target"] = (df["close"].shift(-1) > df["close"]).astype(int) df = df.dropna()这一行代码可以说是整个p16~p20里最容易被忽视、但最值得停下来想明白的地方。shift(-1)的作用是把整列数据向上平移一行——让第t行的target值等于第t+1天的收盘价与第t天收盘价的比较结果。为什么要这么干?因为我们今天能拿到的所有特征(ma5、rsi、布林带等)都是基于截至今天的历史数据计算出来的,我们想要预测的是明天会发生什么,所以标签必须来自未来一天的信息。如果不做shift,直接用当天的close去构造target,模型就是在拿今天的特征预测今天已经发生的事——这就不叫预测了,叫“事后诸葛亮”。这也是量化里经常提到的“未来函数”问题,后面踩坑部分我会专门展开说。
4. 第一个模型:线性回归其实暴露了很多问题
4.1 为什么选线性回归而不是别的
p20一口气把线性回归的建模、训练、评估流程走完了。这里选线性回归而不是逻辑回归或者决策树,我觉得有三个原因:简单、可解释、跑得快。线性回归的预测结果本质上是对所有特征做了一次加权求和,公式就是y = w1x1 + w2x2 + ... + b,配合sklearn大概五行代码就能训练完。对于刚接触机器学习的同学,重要的不是模型多高级,而是先把“fit->predict->evaluate”这条流水线走通。
当然,线性回归本身是回归模型,输出的是连续值,而我们构造的target是0和1的分类标签。课程这里直接用“预测值大于0.5就判定为涨”的方式,把回归结果硬掰成分类结果。严格来说,更标准的做法是用逻辑回归(LogisticRegression),它对分类问题的建模方式更合理。但作为启蒙演示,线性回归够用了,它能把“训练一套模型”的流程感建立起来,逻辑回归的数学细节留到后面再深入,反而是更平滑的学习曲线。
4.2 时间序列数据不能随机切分
很多第一次跑机器学习的人会习惯性地直接调用train_test_split(),把数据随机切成训练集和测试集。但在量化场景里,这个做法有严重问题:时间序列数据有先后依赖关系,随机切分等于让模型“看了未来再做过去题”,测试集里混入了未来信息,评估结果会虚高得非常离谱。正确做法是按时间顺序,用前80%的数据训练、后20%的数据测试:
from sklearn.linear_model import LinearRegression from sklearn.metrics import classification_report # 特征列 feature_cols = ["ma5", "ma10", "ma20", "rsi", "boll_upper", "boll_lower"] X = df[feature_cols].values y = df["target"].values # 按时间切分:前80%训练,后20%测试 split_idx = int(len(X) * 0.8) X_train, X_test = X[:split_idx], X[split_idx:] y_train, y_test = y[:split_idx], y[split_idx:] # 训练线性回归模型 model = LinearRegression() model.fit(X_train, y_train) # 预测并将连续值转换为0/1 y_pred = (model.predict(X_test) > 0.5).astype(int) # 输出评估报告 print(classification_report(y_test, y_pred))这里还有个容易踩的雷:特征x和标签y必须严格对齐。如果特征计算完dropna之后,又单独对y做了一次dropna,行数就对不上了,后面的fit会直接报错。我当时就犯过这个错,来回对了好几遍索引才发现是行数不一致。建议习惯性看一眼X.shape和y.shape是不是一致。
4.3 评估结果怎么看,以及为什么赚钱没那么容易
classification_report会输出precision、recall、f1-score这些指标。第一次看到这份报告的时候,我的准确率大概在53%左右,比瞎猜的50%强了一点点,但远谈不上“找到了财富密码”。这里多说一句,很多新手容易盯住准确率不放,但涨跌分类有一个天然的不平衡问题:如果市场整体上涨天数略多,模型什么都不做、全部预测“涨”,也能拿到一个还不错的准确率。所以真正要看的其实是precision、recall和f1-score的组合,尤其对“涨”这个类别单独看。
更扎心的是,即便模型准确率做到了52%,在真实交易里还要扣掉手续费、滑点这些交易成本,52%的胜率扣完成本很可能根本不赚钱。机器学习在量化里的作用不是“点石成金”,而是帮你从大量数据里找出统计意义上的规律,再把规律包装成策略去执行。离“稳定盈利”,中间还隔着一个完整的交易系统——仓控、止损、资金管理、回撤控制,这些一个都不能少。p16~p20只是把机器学习模型的轮子转了起来,离“上路”还远。
5. 踩坑记录:这些坑比课程本身更有学习价值
5.1 未来函数:看似90%准确率的假象
我在自己动手复现时,曾经写出了这样一段错误的标签构造代码:
# 错误示范 df["target"] = (df["close"] > df["close"].shift(1)).astype(int)看起来好像没什么问题,就是把“今天比昨天涨没涨”作为标签。问题出在特征里:ma5、ma10、ma20这些指标都包含了当天的收盘价信息,而target又是由当天收盘价和昨天收盘价的比较得到的。模型训练时,特征和标签共享了同一个“今天的收盘价”,相当于考试时答案就压在卷子底下——测试集准确率冲到了90%以上,我还以为自己发现了什么不得了的规律,后来对照课程才发现标签构造逻辑搞反了方向。应该用close.shift(-1)制造“明天”的信息,而不是用close.shift(1)去提取“昨天”的信息。这类未来函数问题是量化新手最容易踩的坑,错误代码跑出来的评估结果越漂亮,越要警惕数据里是不是藏着未来。
5.2 除权除息会把股价“打出一个坑”
取数时还有一个很容易被忽视的细节:默认参数获取的往往是“不复权”数据。一旦股票在某一天除权除息,价格会突然向下跳空一大截,但跳空之前的历史价格没有做调整。这时候计算均线、RSI等指标,就会在除权日附近出现假信号——模型会以为股价暴跌了,但其实只是分红除权导致的价格修正。解决办法很简单,把取数接口的adjust参数改成"qfq"(前复权),让历史价格在除权时做统一调整。前复权的逻辑是保持当前价格不变,把历史价格按比例调整,这样算出来的技术指标才是连续的。
5.3 sklearn版本差异带来的API变更
课程视频里用的是当时某个版本的sklearn,代码在我现在的新版本环境里偶尔会碰到一些参数失效或者模块迁移的情况。比如某些老版本里的函数,在新版本中换了位置或者改了默认值,直接复制老代码会莫名其妙报错。我的处理办法是在项目一开始就创建一个虚拟环境,把依赖版本固定下来:
pip install scikit-learn==1.0.2这样做的好处是,以后不管课程代码怎么变、系统环境怎么升级,项目本身能稳定复现。如果你懒得管版本,也没问题——但每跑一步遇到报错,先看一眼报错信息里的sklearn相关字样,通常就能定位是版本问题还是代码问题。
5.4 重新理解“4天学会”这件事
把p16~p20跑通之后,我对“4天学会”这个标题的理解更清醒了。4天时间能完成的事,是搭好环境、了解接口、跑通一套标准的建模流程,让你看到一个从数据到模型的完整骨架。但距离真正“学会”,还差着大量的实操:换一只股票能不能跑通?换一组特征效果会怎样?模型参数调一调又会怎样?这些问题,靠4天是不可能得到答案的。课程的真正价值,是把一条最常用的技术路径喂到你嘴边——数据获取->清洗->特征->建模->评估,后面需要你自己反复咀嚼、消化成自己的能力。
写到这里,p16~p20的内容算是基本消化完了。一个很主观的感受:这套课程真正的价值不在那几行代码,而在把“机器学习做量化”这条路上最常见的几个坑提前暴露给你。我踩过的未来函数、复权、版本兼容,你大概率也会踩一遍,早点知道至少能少掉几根头发。接下来我会继续往下学,下一篇笔记打算整理p21~p25的回测部分,到那会儿就能看到策略完整跑起来是什么效果了。有兴趣的可以继续关注,也建议你把上一篇笔记里的环境搭建和基础语法补一补,再看本文的代码会顺畅很多。
本文还有配套的精品资源,点击获取