第三章 特征工程入门:技术指标计算与截面处理

各位同学,欢迎来到特征工程这一章。

说实话,在量化这个行当里摸爬滚打这些年,我越来越觉得:特征工程做得好,模型就成功了一半。深度学习再厉害,喂进去的是垃圾,出来的还是垃圾。今天我们就来聊聊,怎么把原始行情数据,加工成模型能吃的「好料」。

3.1 技术指标计算:均线、RSI、MACD

技术指标这东西,很多人觉得老掉牙。但我个人习惯是,永远不要轻视这些经典指标。它们背后是几代交易员用真金白银验证过的市场规律。

3.1.1 移动平均线(MA)

均线是最基础的特征。说白了,就是过去N天的平均收盘价。但你别小看它,不同周期的均线组合,能反映趋势的强弱。

import pandas as pd
import numpy as np

# 假设df是包含'close'列的DataFrame
df['ma5'] = df['close'].rolling(window=5).mean()
df['ma20'] = df['close'].rolling(window=20).mean()
df['ma60'] = df['close'].rolling(window=60).mean()

# 我个人喜欢加一个均线斜率特征
df['ma5_slope'] = (df['ma5'] - df['ma5'].shift(5)) / df['ma5'].shift(5)
我的经验: 别只用收盘价算均线。我在做期货策略时,发现用成交量的加权均价(VWAP)做均线,效果往往更好。你可以试试看。

3.1.2 相对强弱指标(RSI)

RSI衡量的是价格变动的速度和幅度。公式不复杂,但理解它的含义更重要:RSI超过70算超买,低于30算超卖。不过在实际中,这个阈值得根据品种调整。

def compute_rsi(series, period=14):
    delta = series.diff()
    gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
    loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
    rs = gain / loss
    rsi = 100 - (100 / (1 + rs))
    return rsi

df['rsi_14'] = compute_rsi(df['close'], 14)
避坑指南: 我曾经在股指期货上直接用默认的14天RSI,结果发现信号频繁失效。后来我改成按交易天数调整周期——比如对于日线数据,用14天没问题;但对于5分钟线,我建议用60-120周期。你想想看,不同时间尺度下,市场的「节奏」是不一样的。

3.1.3 指数平滑异同移动平均线(MACD)

MACD其实是均线的衍生品。它由三部分组成:DIF线(快线)、DEA线(慢线)、柱状图。核心逻辑是:当快线上穿慢线时,是买入信号;下穿时,是卖出信号

# 计算MACD
exp12 = df['close'].ewm(span=12, adjust=False).mean()
exp26 = df['close'].ewm(span=26, adjust=False).mean()
df['macd_dif'] = exp12 - exp26
df['macd_dea'] = df['macd_dif'].ewm(span=9, adjust=False).mean()
df['macd_bar'] = 2 * (df['macd_dif'] - df['macd_dea'])

嗯,这里要注意:MACD的滞后性比较强。我建议把它当作趋势确认工具,而不是入场触发器。

3.2 滚动统计特征

技术指标是「有名字」的特征。但很多时候,我们需要一些「无名」的统计特征。滚动统计就是这类特征的典型代表。

说白了,就是在一个滑动窗口内,计算各种统计量。比如:

  • 滚动均值:反映近期平均水平
  • 滚动标准差:反映波动率
  • 滚动偏度:反映分布不对称性
  • 滚动峰度:反映尾部风险
  • 滚动最大值/最小值:反映支撑阻力位
window = 20
df['roll_mean'] = df['close'].rolling(window).mean()
df['roll_std'] = df['close'].rolling(window).std()
df['roll_skew'] = df['close'].rolling(window).skew()
df['roll_kurt'] = df['close'].rolling(window).kurt()
df['roll_max'] = df['close'].rolling(window).max()
df['roll_min'] = df['close'].rolling(window).min()

# 我个人还喜欢加一个「位置特征」
df['position_in_range'] = (df['close'] - df['roll_min']) / (df['roll_max'] - df['roll_min'])
核心思路: 滚动统计特征的价值在于,它能捕捉到市场状态的「动态变化」。比如波动率突然放大,往往意味着变盘在即。我在2015年股灾期间,就是靠滚动波动率的异常飙升,提前减了仓。

3.3 时序滞后特征

金融市场是有记忆的。今天的价格,往往和昨天的价格有关。时序滞后特征,就是把这个「记忆」显式地表达出来。

最简单的做法:

# 生成滞后特征
for lag in [1, 2, 3, 5, 10, 20]:
    df[f'close_lag_{lag}'] = df['close'].shift(lag)
    df[f'volume_lag_{lag}'] = df['volume'].shift(lag)

但这里有个坑:滞后太多期,特征会变得非常稀疏。我建议最多滞后到20期,再多的话,信息增益就微乎其微了。

另外,我习惯把滞后特征和差分特征结合起来用:

# 差分特征:今天相对于N天前的涨跌幅
for lag in [1, 5, 10]:
    df[f'return_{lag}d'] = (df['close'] - df['close'].shift(lag)) / df['close'].shift(lag)
一个小技巧: 对于高频数据(比如分钟级),滞后1-5期就够了。对于日线数据,可以适当放宽到10-20期。你想想看,不同频率的数据,记忆长度是不一样的。

3.4 截面标准化与市值中性化

前面讲的特征,都是针对单只股票的「时序特征」。但深度学习模型往往需要处理多只股票。这时候,截面标准化就派上用场了。

3.4.1 截面标准化

截面标准化的目的,是消除不同股票之间的量纲差异。比如A股价格100元,B股价格5元,如果不做标准化,模型会天然地偏向高价股。

# 假设df是包含多只股票的面板数据,index是日期,columns是股票代码
# 截面标准化:在每个时间截面上,对特征进行Z-score标准化
def cross_sectional_normalize(df_feature):
    mean = df_feature.mean(axis=1)
    std = df_feature.std(axis=1)
    df_normalized = (df_feature.subtract(mean, axis=0)).divide(std, axis=0)
    return df_normalized

# 对收益率特征做截面标准化
df['return_normalized'] = cross_sectional_normalize(df['return_1d'])

3.4.2 市值中性化

市值中性化,说白了就是剔除市值因子对特征的影响。为什么要这么做?因为市值大的股票和市值小的股票,在很多特征上天然不同。如果不做中性化,模型学到的可能只是「大小盘风格」,而不是真正的Alpha信号。

具体做法:

  1. 对每个特征,用市值做回归
  2. 取回归的残差作为中性化后的特征
import statsmodels.api as sm

def market_cap_neutralize(feature, market_cap):
    """
    对特征做市值中性化
    feature: 某时间截面上所有股票的特征值
    market_cap: 对应时间截面上所有股票的市值
    """
    X = sm.add_constant(np.log(market_cap))  # 市值取对数,更符合线性关系
    y = feature.values
    model = sm.OLS(y, X).fit()
    residual = y - model.predict(X)
    return residual

# 应用:对每个时间截面做中性化
# 这里假设df_feature和df_market_cap都是面板数据
df_neutralized = df_feature.copy()
for date in df_feature.index:
    df_neutralized.loc[date] = market_cap_neutralize(
        df_feature.loc[date], 
        df_market_cap.loc[date]
    )
避坑指南: 我曾经犯过一个错误——先做截面标准化,再做市值中性化。结果发现两个步骤有重叠,导致特征信息丢失。正确的顺序是:先做市值中性化,再做截面标准化。或者干脆只做市值中性化,因为标准化可以在模型内部通过Batch Normalization实现。

3.5 本章知识体系

说了这么多,我们来梳理一下本章的核心逻辑。下面这张图,是我自己画的知识框架:

特征工程知识体系 原始行情数据 技术指标计算 滚动统计特征 时序滞后特征 截面标准化 市值中性化 MA RSI MACD 均值/标准差 偏度/峰度 滞后1-20期 差分特征 深度学习模型输入特征 特征工程的核心:从原始数据中提取有效信息,同时消除噪声和冗余

这张图把本章的内容串起来了。你看,从原始行情数据出发,我们做了四类特征:技术指标、滚动统计、时序滞后、截面处理。前三类属于「时序特征」,最后一类属于「截面特征」。两者结合,才能给模型提供全面的信息。

最后说一句: 特征工程没有标准答案。我见过有人用50个技术指标堆出一个模型,效果还不如只用5个精心设计的特征。所以,质量永远比数量重要。多思考每个特征背后的经济学含义,少做无脑的「特征海啸」。

公众号:蓝海资料掘金营,微信deep3321