本章的主要内容如下:
决策树和随机森林的原理与使用方法。 使用决策树判断因子的重要程度。 使用随机森林模型进行选股。 编写更精细的交易策略并回测。
【生成一个数据集,并且让这个数据集不适用线性模型】
{代码部分}
#导入决策树回归器和其他必要的库
from sklearn.tree import DecisionTreeRegressor
from sklearn.datasets import make_regression
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
# 生成一个回归任务数据集,样本数量100
X, y = make_regression(n_samples=100,
# 只有一个特征
n_features=1,
# 为了使线性模型不适用
# 设置噪声参数为60
noise=60,
# 设定随机状态,便于复现
random_state=18)
# 使用散点图将样本进行可视化
plt.scatter(X, y, s=80,
# 下面几个参数都是为了美观
c=y, edgecolor='grey',
alpha=0.6)
# 添加网格并展示图像
plt.grid()
plt.show()
{返回结果}

【决策树示例】
{代码部分}
# 创建一个决策树示例,可以通过调节max_depth参数来防止模型过拟合
# 这里我们不限制决策树的max_depth
reg = DecisionTreeRegressor(max_depth=None)
# 使用模型拟合样本数据
reg.fit(X, y)
# 为了将模型进行可视化,同样生成一个沿横轴分布的数列
X_new = np.linspace(-2.5, 3.5, 100)
# 调用模型对生成数列的目标值做出预测
y_new = reg.predict(X_new.reshape(-1, 1))
# 以下是绘图的部分
plt.scatter(X, y, s=80,
c=y, edgecolor='grey',
alpha=0.4)
plt.plot(X_new, y_new, lw=2,
ls='-', c='grey')
plt.grid()
plt.show()
{返回结果}

【决策树算法逻辑】
决策树是监督学习算法,用树形结构做分类 / 回归。核心逻辑是递归分割数据集,让子集 “纯度”(同类样本占比)逐步提升,直到满足停止条件(如节点样本同类别、树深达上限等 ),过程像 “分而治之”:
节点含义:内部节点是特征 / 属性判断(如 “天气是否晴朗” ),分支是特征取值(“晴 / 雨” ),叶节点是分类结果(“适合 / 不适合户外活动” )。
特征选择:用指标选最优分割特征,常用:
信息增益(ID3 算法):基于信息熵,选 “分割后数据集不确定性降最多” 的特征;
信息增益率(C4.5 算法):修正信息增益,避免偏好多取值特征;
基尼指数(CART 算法):衡量数据集纯度,选 “分割后纯度提升最多” 的特征。
生活场景举例(是否约会)
用决策树模拟 “女生判断是否见相亲对象” 的逻辑: 特征取值决策逻辑(简化)年龄≤30 岁 />30 岁优先用 “年龄是否≤30 岁” 初步筛选颜值帅 / 一般若年龄符合,再看 “颜值是否帅”职业公务员 / 其他最后用 “是否公务员” 决定最终是否见面
简言之,决策树用 “分层判断特征” 的方式,把复杂决策拆解成简单规则,既直观又易解释,是理解机器学习分类逻辑的经典模型~
决策树的工作过程是一个树状的结构——这也是决策树名字的由来。在整个树状结构的顶端,模型先对样本特征进行判断:如果样本特征小于或等于某个值,则向左分枝,否则向右分枝;到第二层左边的节点,如果样本特征小于或等于某个值,则向左分枝,否则向右分枝……模型一直重复这个过程,直到拟合完全部的样本
【随机森林】
{代码部分}
# 导入随机森林回归器
from sklearn.ensemble import RandomForestRegressor
# 创建一个随机森林实例,指定森林中有100棵决策树
reg2 = RandomForestRegressor(n_estimators=100)
# 使用随机森林拟合数据
reg2.fit(X, y)
# 生成对数列进行预测
y_new_2 = reg2.predict(X_new.reshape(-1, 1))
# 下面是绘图部分
plt.scatter(X, y, s=80,
c=y, edgecolor='grey',
alpha=0.4)
# 将决策树和随机森林的模型进行可视化
plt.plot(X_new, y_new, lw=1.5,
ls='-', c='grey',
label='Tree')
plt.plot(X_new, y_new_2, lw=1.5,
ls='--', c='r',
label='Forest')
# 添加图注、网格并展示
plt.legend()
plt.grid()
plt.show()
{返回结果}

随机森林模型“波动”的幅度没有决策树模型大。这说明,随机森林模型要比决策树模型更简单,相对不容易受到噪声的干扰,也就更不容易出现过拟合的现象。
【什么是随机森林?】
### 随机森林算法介绍
随机森林是**集成学习**里基于 **Bagging(自助聚合)** 思想的经典算法,核心是用多棵决策树“集体投票”做决策,以提升模型的泛化能力(抗过拟合)和鲁棒性(稳定性),流程可概括为:
1. **随机抽样构建子数据集**:对原始训练集**有放回抽样**(Bootstrap 抽样),生成多组与原数据集同大小的子数据集(子集会有重复样本,也会遗漏部分样本 )。
2. **随机选特征训练决策树**:每棵决策树训练时,从所有特征里**随机选部分特征**参与分裂(进一步增加树的多样性 ),用 CART 等算法独立训练成完整决策树(不剪枝 )。
3. **多树投票决策**:预测时,新样本输入所有决策树,分类问题选**众数**(回归问题选**均值** )作为最终结果,靠“群体智慧”降低单棵树的偏差与方差。
### 生活场景举例(周末是否郊游)
假设想通过 **“天气、温度、是否周末、同伴是否有空”** 4 个特征,判断周末是否适合郊游(结果:去/不去 ),用随机森林模拟如下:
#### 1. 原始数据集(简化)
| 样本 | 天气(晴/雨) | 温度(高/中/低) | 是否周末(是/否) | 同伴是否有空(是/否) | 是否郊游(结果) |
|------|--------------|------------------|------------------|----------------------|------------------|
| 1 | 晴 | 中 | 是 | 是 | 去 |
| 2 | 雨 | 高 | 否 | 否 | 不去 |
| 3 | 晴 | 低 | 是 | 否 | 去(独自郊游) |
|... |... |... |... |... |... |
#### 2. 随机森林构建过程
- **步骤 1:抽样生成子数据集**
假设构建 3 棵决策树,对原始数据集有放回抽样,得到 3 组子数据:
- 子集 1(树 1):样本 1、1、3、2…(样本 1 重复,样本 4 未被抽中 )
- 子集 2(树 2):样本 3、2、2、1…(样本 2 重复,样本 5 未被抽中 )
- 子集 3(树 3):样本 1、3、2、3…(样本 3 重复 )
- **步骤 2:随机选特征训练决策树**
每棵树随机选部分特征训练(如树 1 选“天气、是否周末”,树 2 选“温度、同伴是否有空” ),各自生成决策规则:
- 树 1 规则(简化):天气=晴 → 再看是否周末 → 是则“去”,否则“不去”
- 树 2 规则(简化):温度=低 → 不管其他特征 → 直接“去”(因训练子集里低温样本都对应“去” )
- 树 3 规则(简化):同伴有空=是 → “去”;否则看天气 → 晴则“去”,雨则“不去”
#### 3. 新样本预测(周末、天气晴、温度中、同伴有空 )
- 树 1 预测:天气=晴+是周末 → **去**
- 树 2 预测:温度=中(不触发“温度=低→去” ),但因训练时“温度”规则简单,可能结合其他特征→ **去**(或依实际训练结果 )
- 树 3 预测:同伴有空=是 → **去**
- **最终投票**:3 棵树都预测“去”→ 随机森林输出结果 **去郊游**
### 核心优势与特点
- **抗过拟合**:多棵树“投票”抵消单棵树的极端预测,适合数据噪声大的场景。
- **易扩展**:支持分类(如上述郊游案例 )、回归(如预测房价 ),还能输出特征重要性(如判断“天气”对郊游决策的影响度 )。
- **高效**:多棵树可并行训练,适合大数据量,比单决策树更稳定。
简单说,随机森林像“一群参谋”,各自从不同样本、特征角度分析问题,最后汇总意见做决策,既保留了决策树的直观,又靠“人多力量大”解决了单棵树的不足~
【看到随机森林中的每一棵树】
{代码部分}
#查看随机森林中前两棵决策树
reg2.estimators_[:2]
{返回结果}
[DecisionTreeRegressor(criterion='mse', max_depth=None, max_features='auto',
max_leaf_nodes=None, min_impurity_decrease=0.0,
min_impurity_split=None, min_samples_leaf=1,
min_samples_split=2, min_weight_fraction_leaf=0.0,
presort=False, random_state=1188818746, splitter='best'),
DecisionTreeRegressor(criterion='mse', max_depth=None, max_features='auto',
max_leaf_nodes=None, min_impurity_decrease=0.0,
min_impurity_split=None, min_samples_leaf=1,
min_samples_split=2, min_weight_fraction_leaf=0.0,
presort=False, random_state=1263748285, splitter='best')]
系统返回了随机森林中前两棵决策树的模型。可以看到,这两棵决策树模型的大部分参数是相同的,只有random_state参数不同。当然,仅仅是random_state的差别也可以让两棵决策树的预测结果有一定的差异。在我们的随机森林中,有100棵不同的决策树,这就使得随机森林的预测结果更倾向于“中立”,从而降低过拟合的风险。
【决策树的“隐藏功能”——判断特征重要性(feature importance)】
{代码部分}
# 这里开始验证
import jqdata
from jqlib.technical_analysis import *
import datetime
# 获取股票池
stocks = get_index_stocks('000040.XSHG')
# 修正查询语句:移除 valuation.code.in_(stocks) 作为返回列,仅作为过滤条件
q = query(
valuation.code,
valuation.market_cap,
balance.total_current_assets - balance.total_current_liability,
balance.total_liability - balance.total_assets,
balance.total_liability / balance.equities_parent_company_owners,
(balance.total_assets - balance.total_current_assets) / balance.total_assets,
balance.equities_parent_company_owners / balance.total_assets,
indicator.inc_total_revenue_year_on_year,
valuation.turnover_ratio,
valuation.pe_ratio,
valuation.pb_ratio,
valuation.ps_ratio,
indicator.roa # 总资产收益率(假设您需要此字段,根据实际需求调整)
).filter(
valuation.code.in_(stocks) # 正确的过滤条件用法
)
# 获取数据并命名列(列数必须与 query 返回字段数一致)
df = get_fundamentals(q, date=None)
df.columns = ['code', '市值', '净营运资本', '净债务', '产权比率',
'非流动资产比率', '股东权益比率', '营收增长率',
'换手率', 'PE', 'PB', 'PS', '总资产收益率']
# 检查数据
df.head(20)
{返回结果}

【获取技术因子】
{代码部分}
# 这里继续验证
# 将股票代码作为数据表的 index
df.index = df.code.values
# 使用 del 也可以删除列
del df['code']
# 下面定义时间变量
today = datetime.datetime.today()
# 设定 3 个时间差,分别是 50 天、1 天和 2 天
delta50 = datetime.timedelta(days=50)
delta1 = datetime.timedelta(days=1)
delta2 = datetime.timedelta(days=2)
# 50 天前作为一个历史节点
history = today - delta50
# 再计算昨天和 2 天前的日期
yesterday = today - delta1
two_days_ago = today - delta2
# 下面获取股票的动量线、成交量、累计能量线、平均差、
# 指数移动平均、移动平均、乖离率等因子
# 时间范围都设为 10 天
df['动量线'] = list(MTM(df.index, two_days_ago,
timeperiod=10, unit='1d',
include_now=True,
fq_ref_date=None).values())
df['成交量'] = list(VOL(df.index, two_days_ago, M1=10,
unit='1d', include_now=True,
fq_ref_date=None)[0].values())
df['累计能量线'] = list(OBV(df.index, check_date=two_days_ago,
timeperiod=10).values())
df['平均差'] = list(DMA(df.index, two_days_ago, N1=10,
unit='1d', include_now=True,
fq_ref_date=None)[0].values())
df['指数移动平均'] = list(EMA(df.index, two_days_ago, timeperiod=10,
unit='1d', include_now=True,
fq_ref_date=None).values())
df['移动平均'] = list(MA(df.index, two_days_ago, timeperiod=10,
unit='1d', include_now=True,
fq_ref_date=None).values())
df['乖离率'] = list(BIAS(df.index, two_days_ago, N1=10,
unit='1d', include_now=True,
fq_ref_date=None)[0].values())
# 把数据表中的空值用 0 来代替
df.fillna(0, inplace=True)
# 检查是否成功
df.head(20)
{返回结果}


【设定目标并训练模型】
思路——先找到股票的历史收盘价(如50天前),再用前一天的收盘价除以50天前的收盘价并减1,计算出这50天来股票的收益;然后我们找到那些收益水平大于平均水平的股票,标记为1,其余标记为0,作为模型的分类标签。
{代码部分}
# 获取股票前一日的收盘价
df['close1'] = list(get_price(stocks,
end_date=yesterday,
count=1,
fq='pre',
panel=False)['close'])
# 获取股票50天前的收盘价
df['close2'] = list(get_price(stocks,
end_date=history,
count=1,
fq='pre',
panel=False)['close'])
# 计算收益
df['return'] = df['close1'] / df['close2'] - 1
# 如果收益大于平均水平,则标记为1
# 否则标记为0
df['signal'] = np.where(df['return'] < df['return'].mean(), 0, 1)
# 检查是否成功
df.head(20)
{返回结果}


close1、close2、return和signal这几列。它们分别对应的是1天前的收盘价、50天前的收盘价、该时间段内的收益,以及收益是否大于平均值。signal这一列是训练模型用的分类标签。
【开始训练模型】
{代码部分}
# 导入数据集拆分工具
from sklearn.model_selection import train_test_split
# 导入决策树分类器
from sklearn.tree import DecisionTreeClassifier
# 把因子值作为样本的特征,所以要去掉刚刚添加的几个字段(假设 df 是已处理好的 DataFrame 数据)
X = df.drop(['close1', 'close2','return','signal'], axis = 1)
# 把 signal 作为分类标签
y = df['signal']
# 将数据拆分为训练集和验证集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3)
# 创建决策树分类器实例,指定 random_state 便于复现
clf = DecisionTreeClassifier(random_state=1000)
# 拟合训练数据
clf.fit(X_train, y_train)
# 查看分类器在训练集和验证集中的准确率
print(clf.score(X_train, y_train),
clf.score(X_test, y_test))
{返回结果}
1.0 0.8888888888888888
决策树模型的表现还是比较不错的——训练集中的准确率达到了100%,在验证集中的准确率达到了88.88%左右。 【哪些因子重要】 决策树的属性——feature_importances_存储的是模型判断的样本特征的重要程度。为了便于查看,我们把这个属性存储到一个列表中。
{代码部分}
# 为了便于观察,我们创建一个数据表
# 数据表有两个字段,分别是特征名和重要性
# 特征名就是因子的名称
# 重要性就是决策树给出的feature_importances_
factor_weight = pd.DataFrame({'features': list(X.columns),
'importance': clf.feature_importances_}).sort_values(
# 这里根据重要程度降序排列,一遍遍找到重要性最高的特征
by='importance', ascending=False)
# 检查结果
factor_weight
{返回结果}

在所有的因子当中,平均差(DMA)这个因子的重要性竟然是最高的,达到了0.68左右,远远超过了其他因子。平均差因子的含义是,短期均线的数值减去长期均线的数值。这里的DMA是默认的参数,也就是10天均线减去50天均线。 我们也可以看到,次重要因子是——市值因子占0.32左右。