我用1分钟给股票找出了3000个交易因子,用过的人都发财了

2,721 阅读9分钟

我用1分钟给股票找出了3000个交易因子,用过的人都发财了

炒股的人,做量化的人,甚至是不小心刷到财经视频的你……多多少少都听过“交易因子”这几个字。

image.png

听起来就很高级对吧? 什么动量因子反转因子大小盘因子情绪因子…… 每次别人提起都眉飞色舞,好像掌握了什么财富密码, 你问一句“啥是因子?” 他们:你不懂 你再问一句“怎么做因子?” 他们:你不配

得了,我忍了好几年了。

今天,花姐我就带着键盘——不,带着良心, 来给你扒开交易因子的真面目,顺便还手把手教你:

怎么用Python,一分钟干出3000个因子, 不是“瞎编的因子”,是真·能喂给模型、能配对策略、能拿去回测的那种!

要是你能跟着我整完, 下次就轮到你对别人说:“这不是因子的问题,是你不会提。”

啥是交易因子? 简单点说,它就像股票市场里的“隐藏秘籍”。别人看K线、看均线、看MACD,你看交易因子——直接抢跑三步。

那么问题来了:怎么才能快速、批量地生成交易因子?

我试过很多招,最后发现了一个宝藏库,堪称“因子工厂”,它的名字是——tsfresh

tsfresh:一台会炼金的因子生成器

tsfresh 是一个专注于时间序列特征提取的 Python 库,全称是 Time Series Feature extraction based on scalable hypothesis tests。它的核心功能,就是从一段或多段时间序列中,自动提取出大量可用于机器学习建模的统计特征

这些特征,也就是我们常说的因子(在金融量化语境下)。

更具体一点,tsfresh 内置了上百种特征计算方法,涵盖常见的统计量(如最大值、最小值、平均值、方差、偏度、峰度)、频域特征、小波变换、傅里叶变换、复杂度指标、变化率指标等,甚至还有一些你连听都没听过的“奇葩特征”——什么change_quantilesagg_autocorrelationlinear_trend_timewise,只要能用数学刻画时序,它都能榨出来。

你甚至都不用自己去定义特征函数,它会把能提的都给你提一遍,一口气喂你几千个。

它的设计初衷其实是面向工业 IoT 领域,比如设备预测维护之类的,但它天生适合金融时间序列。尤其是我们搞量化、做选股、因子挖掘这类场景,数据形态几乎一模一样。

说白了,tsfresh 就是把“写因子”这件原本需要金融知识、经验积累、血泪教训的事,变成了“数据一扔,特征全来”的自动化流程。

当然,它的输出并不是终点,你还需要后续做筛选、降维、建模、回测。但就“因子发掘”这个环节而言,它极大地降低了门槛,是初学者和实验驱动型开发者的一大利器

不过话说回来,tsfresh 也不是没有毛病—— 它有时候提的因子重复性强、冗余度高,而且默认配置下会很吃内存和计算资源。 所以用得好是一把宝剑,用不好就是因子垃圾场。怎么用,咱一会儿慢慢讲。


思路全景

今天我们以300ETF为例,教大家如何从头开始提取交易因子。

我们要干的事可以分 3 步走:

  1. 获取 300ETF 历史行情 + 衍生技术指标
  2. tsfresh 从行情+指标中自动提取“时序特征因子”
  3. 筛选出有用的因子

Step 1:获取 300ETF 数据 + 计算技术指标

我们选用 沪深300ETF(510300.SH),通过 AKShare 获取过去两年的日线数据,并计算常见的技术指标(MACD、RSI、KDJ)。

# 01_data_prep.py
import akshare as ak
import pandas as pd
import talib
from datetime import datetime, timedelta


# 获取过去两年日线数据
symbol = "510300"
start_date = (datetime.today() - timedelta(days=365 * 2)).strftime('%Y%m%d')
df = ak.fund_etf_hist_em(symbol=symbol, period="daily", start_date=start_date, end_date='20250520')

# 重命名字段
df = df.rename(columns={
    "日期": "date",
    "开盘": "open",
    "收盘": "close",
    "最高": "high",
    "最低": "low",
    "成交量": "volume",
})
df['date'] = pd.to_datetime(df['date'])
df = df.sort_values('date').reset_index(drop=True)

# 使用 TA-Lib 计算技术指标
df['rsi'] = talib.RSI(df['close'], timeperiod=14)

macd, macdsignal, macdhist = talib.MACD(df['close'], fastperiod=12, slowperiod=26, signalperiod=9)
df['macd'] = macd
df['macds'] = macdsignal
df['macdh'] = macdhist

slowk, slowd = talib.STOCH(
    df['high'], df['low'], df['close'],
    fastk_period=9, slowk_period=3, slowk_matype=0,
    slowd_period=3, slowd_matype=0
)
df['kdjk'] = slowk
df['kdjd'] = slowd
df['kdjj'] = 3 * slowk - 2 * slowd  # 手动构造 J 值(模拟 KDJ 的 J 值)

# 清洗 NaN
df = df.dropna().reset_index(drop=True)

df.to_csv("01data.csv",index=False)


Step 2:用 tsfresh 自动提取因子特征

接下来,我们将数据转换为 tsfresh 需要的格式,并从中提取“时间窗口内的统计特征”。

库安装命令

pip install tsfresh
# 02——feature_extract.py
from tsfresh import extract_features
from tsfresh.utilities.dataframe_functions import impute
from tsfresh.feature_extraction import EfficientFCParameters
from tqdm import tqdm  # 用于进度条展示(可选)
import warnings
import pandas as pd

df = pd.read_csv("01data.csv")
df['date'] = pd.to_datetime(df['date'])

warnings.filterwarnings("ignore", category=RuntimeWarning, module="tsfresh.utilities.dataframe_functions")

# ===  滑动窗口提取 tsfresh 特征 ===
WINDOW_SIZE = 20
features_list = []

print(f"开始提取滑动窗口特征,每个窗口长度为 {WINDOW_SIZE}...")

for i in tqdm(range(WINDOW_SIZE, len(df))):
    df_window = df.iloc[i - WINDOW_SIZE:i].copy()
    # 只提取 close、volume、rsi、macd 四个作为例子
    tsfresh_df = df_window[['date', 'close', 'volume', 'rsi', 'macd']].copy()
    tsfresh_df['id'] = 0 # 单一时间序列

    # 转为 long-format
    df_long = tsfresh_df.melt(id_vars=["date", "id"], var_name="kind", value_name="value")
    df_long.rename(columns={"date": "time"}, inplace=True)

    # 提取特征
    features = extract_features(
        df_long,
        column_id="id",
        column_sort="time",
        column_kind="kind",
        column_value="value",
        default_fc_parameters=EfficientFCParameters(),
        n_jobs=0,
        disable_progressbar=True
    )
    impute(features) # 缺失值(NaN)处理函数,处理缺失值

    features.columns = [f'feature_{col}' for col in range(features.shape[1])]
    features['date'] = df.iloc[i]['date']  # 当前行时间作为结果时间
    features_list.append(features)

# 合并所有窗口结果
df_all_features = pd.concat(features_list, axis=0).reset_index(drop=True)
df_all_features.to_csv("02data.csv", index=False)
    

# 展示结果
print(df_all_features.head())

print("******************特征提取完毕**********************")

运行完以后我们就得到了feature_0 feature_1 ... feature_3106 feature_3107 date 总计3107个因子了

image.png


Step 3:因子这么多,我该用哪几个?

因子提出来了,下一步当然就是——挑出“有用的因子”,也就是特征选择(feature selection) 这一步。咱不能全都上,不然模型就容易陷入高维灾难、过拟合,甚至运行慢还没提升效果。

常见的因子筛选方法主要有以下几种: 1. 过滤法(Filter Method) —— 快速初筛 典型方法:

方法原理Python 工具
方差过滤删除方差小的列(波动小,无信息)VarianceThreshold
相关系数与目标的 Pearson/Spearman 相关性df.corr()
单变量评分f-value, chi2, mutual_infosklearn.feature_selection
from sklearn.feature_selection import VarianceThreshold

# 例子:删除方差小于阈值的因子
selector = VarianceThreshold(threshold=1e-5)
X_filtered = selector.fit_transform(X)

2. 包裹法(Wrapper Method) —— 模型亲测,有点“炼丹”味 常用方法:

方法原理优点
递归特征消除(RFE)每次删掉不重要的特征,用模型重复训练比较稳
forward/backward selection从少到多/多到少加减特征直观
from sklearn.feature_selection import RFE
from sklearn.ensemble import RandomForestClassifier

rfe = RFE(estimator=RandomForestClassifier(), n_features_to_select=20)
X_selected = rfe.fit_transform(X, y)

3. 嵌入法(Embedded) —— 直接利用模型自身的特征重要性 常用方法:

模型方法
LassoL1 正则会把不重要特征权重压成 0
决策树 / GBDT / XGBoost自带 feature_importances_
LightGBM超强推荐,速度快,重要性可解释
import lightgbm as lgb

model = lgb.LGBMClassifier()
model.fit(X, y)
importance = pd.Series(model.feature_importances_, index=X.columns)
top_features = importance.sort_values(ascending=False).head(30)

4. 基于稳定性的因子选择(金融量化专属)

  • 计算每个因子对未来收益的 IC(信息系数)

  • 选取长期 IC 稳定、显著为正的因子

  • 更贴近实战!

# 举个例子(IC)
from scipy.stats import spearmanr

ic_list = []
for col in X.columns:
    ic, _ = spearmanr(X[col], y)
    ic_list.append((col, ic))

ic_df = pd.DataFrame(ic_list, columns=["feature", "IC"]).sort_values("IC", ascending=False)

实战建议:

情况推荐操作
先快点跑一遍看哪些特征是废的过滤法
做建模数据集,搞模型准确率嵌入法(LGB/XGBoost)
做因子有效性分析(金融)计算 IC、IR
自动化多因子回测/研究用因子池,每月选表现好的

tsfresh 自带特征选择!

这里我们用tsfresh 自带特征选择来实现因子筛选。 假如我们想通过因子来筛选未来5天会上涨的股票,这里花姐给出了一个简单的示例:

# 03_feature_select.py
from tsfresh import select_features
import pandas as pd
from tsfresh.utilities.dataframe_functions import impute


def main():
    df = pd.read_csv("01data.csv")
    df['date'] = pd.to_datetime(df['date'])

    df_all_features = pd.read_csv("02data.csv")
    df_all_features['date'] = pd.to_datetime(df_all_features['date'])
    
    # 设定你的特征 DataFrame(去掉 date)
    X = df_all_features.drop(columns=["date"])

    # 构造一个简单的目标变量(比如未来5天是否上涨)
    df_label = df[['date', 'close']].copy()
    df_label['target'] = df_label['close'].shift(-5) > df_label['close']
    df_label['target'] = df_label['target'].astype(int)

    # 只保留与 X 对齐的时间(注意时间是滞后的)
    y = df_label[df_label['date'].isin(df_all_features['date'])]['target'].reset_index(drop=True)

    # Step 1: 填补缺失值
    impute(X)
    print("******************使用 tsfresh 进行特征选择**********************")
    # Step 2: 使用 tsfresh 进行特征选择
    X_selected = select_features(X, y)

    print("******************输出选择后的因子**********************")
    # Step 3: 输出选择后的因子
    print(f"选出来的有效因子数量:{X_selected.shape[1]}")
    print("部分有效因子名:", X_selected.columns[:10].tolist())

    df_selected_features = df_all_features[['date']+X_selected.columns.tolist()]
    df_selected_features.to_csv("selected_features_with_data.csv", index=False)
    
if __name__=="__main__":
    main()

看到这里你可能好奇 为啥一定要加 if name == 'main':

你用的是 tsfresh.select_features(X, y),它在内部使用了 multiprocessing.Pool() 来加速计算。但是在 Windows 系统下Python 启动多进程时,会重新「导入整个主模块」,如果你没有加 if __name__ == '__main__':,Python 会直接执行整个文件——就形成了无限递归「导入-执行-导入-执行」,然后炸锅!

所以不同于 Linux/macOS,需要你用:

if __name__ == '__main__':

来保护你的主逻辑。


画图看看因子是不是在扯淡

你没看错,就是画图。眼睛一看就知道因子灵不灵。

import pandas as pd
import matplotlib.pyplot as plt

# ===== 设置中文支持 =====
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# ===== 读取数据 =====
df_price = pd.read_csv("01data.csv", parse_dates=["date"])
df_features = pd.read_csv("selected_features_with_data.csv", parse_dates=["date"])

# ===== 合并两个数据集(按日期对齐)=====
df_merged = pd.merge(df_price[['date', 'close']], df_features, on='date', how='inner')

# ===== 选取前10个特征列 =====
feature_cols = [col for col in df_features.columns if col != 'date'][:10]

# ===== 绘图开始 =====
fig, ax1 = plt.subplots(figsize=(16, 8))

# --- 主轴:收盘价 ---
color = 'tab:blue'
ax1.set_xlabel("日期")
ax1.set_ylabel("收盘价", color=color)
ax1.plot(df_merged['date'], df_merged['close'], color=color, label='收盘价', linewidth=2)
ax1.tick_params(axis='y', labelcolor=color)

# --- 副轴:特征曲线 ---
ax2 = ax1.twinx()
ax2.set_ylabel("特征值")

# 颜色循环(可以自定义更漂亮的)
colors = plt.cm.tab10.colors  # 10个颜色

# 画10个特征线
for i, col in enumerate(feature_cols):
    ax2.plot(df_merged['date'], df_merged[col], label=col, linestyle='--', color=colors[i % 10])

ax2.tick_params(axis='y')
fig.tight_layout()

# 图例合并收盘价和特征
lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax1.legend(lines1 + lines2, labels1 + labels2, loc='lower right')

plt.title("收盘价 + 10个特征走势" ,fontsize=16, pad=20)
plt.grid(True)
fig.tight_layout()
plt.subplots_adjust(top=0.92)  # 0.92 表示顶部保留 8% 空间(1 是满)
plt.show()

image.png


下一步预告:这些因子到底能不能赚钱?

别急,下一篇文章我们就来做个简单回测——看看这些因子能不能当上“预言家”。 记住一句话:

因子不是万能的,但没有因子是万万不能的。

要是你觉得写得还行,就留个在看 收藏 点赞 呗~ 我不是为了流量,我是怕你以后找不到(认真脸)。

—— 花姐 ❤️