第三章 特征工程基础:什么是特征?常见技术指标的计算与实现

各位同学,欢迎来到第三章。

上一章我们聊了数据获取和清洗。数据到手了,干干净净的,然后呢?

直接扔进模型?

别急。你想想看,原始的价格序列,比如“收盘价 100.5”,模型能看懂什么?它只能看到一个数字。但咱们做交易的人,看的是趋势、是强弱、是背离。这些“高维”的信息,就是特征。

3.1 到底什么是特征?

特征,说白了就是数据的“代言人”

原始数据是“原材料”,特征是“提炼出来的精华”。

举个例子:

  • 原始数据:过去10天的收盘价 [100, 101, 102, 103, 104, 105, 106, 107, 108, 109]
  • 特征1:过去10天的涨幅 = (109 - 100) / 100 = 9%
  • 特征2:过去5日均线 > 过去10日均线(金叉?死叉?)
  • 特征3:RSI是否超过70(超买?)

模型不关心价格是100还是1000,它关心的是这些“衍生信号”

核心观点:特征的质量,决定了模型的上限。数据决定了模型能飞多高,特征决定了它能不能起飞。

我个人习惯把特征分为三类:

  1. 基础统计特征:均值、方差、最大值、最小值、涨跌幅。
  2. 技术指标特征:移动平均线、RSI、MACD、布林带等。
  3. 高阶衍生特征:波动率、相关性、量价配合、时间序列分解等。

这一章,我们重点攻克第二类——技术指标特征。这是量化交易里最经典、最实用的特征家族。

3.2 移动平均线(MA)—— 趋势的“平滑器”

移动平均线,可能是你接触的第一个技术指标。

它的思想很简单:把过去N天的价格平均一下,消除噪音,看清趋势。

3.2.1 计算公式

简单移动平均线(SMA):

SMA(N) = (P1 + P2 + ... + PN) / N

其中P1到PN是过去N天的收盘价。

3.2.2 代码实现

用Python实现,其实就一行核心逻辑。但我建议你手写一遍,别直接用pandas的rolling,理解底层原理很重要。

import pandas as pd
import numpy as np

def calculate_sma(data, window=20):
    """
    计算简单移动平均线
    :param data: 包含'close'列的DataFrame
    :param window: 窗口大小,默认20天
    :return: 添加了'SMA'列的DataFrame
    """
    # 我习惯用rolling,但为了教学,我写个循环版本
    sma_values = []
    for i in range(len(data)):
        if i < window - 1:
            sma_values.append(np.nan)  # 前window-1天没有值
        else:
            # 取过去window天的收盘价,求平均
            window_data = data['close'].iloc[i - window + 1 : i + 1]
            sma_values.append(window_data.mean())
    data['SMA_' + str(window)] = sma_values
    return data

# 示例用法
# df = pd.read_csv('stock_data.csv')
# df = calculate_sma(df, window=5)
# df = calculate_sma(df, window=20)
# print(df[['close', 'SMA_5', 'SMA_20']].tail())

避坑指南:我曾经在实盘策略里直接用pandas的rolling().mean(),结果发现数据对齐出了问题——未来数据泄露了。记住:计算特征时,永远只用历史数据,不能用未来数据。我建议你每次计算完,都检查一下索引是否对齐。

3.2.3 实战中的用法

  • 金叉/死叉:短期均线上穿长期均线 = 买入信号;下穿 = 卖出信号。
  • 支撑/压力:价格回踩均线不破,可能是支撑;反弹不过均线,可能是压力。
  • 趋势判断:均线向上 = 上升趋势;均线向下 = 下降趋势。

嗯,这里要注意:均线是滞后指标。它反映的是过去,不是未来。别指望它预测拐点,它只是帮你确认趋势。

3.3 相对强弱指数(RSI)—— 动量的“体温计”

RSI,全称Relative Strength Index。它衡量的是价格变动的速度和幅度。

说白了,就是看“涨得猛不猛,跌得狠不狠”。

3.3.1 计算公式

RSI的计算稍微复杂一点,但核心思想很直观:

RSI = 100 - (100 / (1 + RS))
其中 RS = 平均上涨幅度 / 平均下跌幅度

通常取14天作为周期。

3.3.2 代码实现

def calculate_rsi(data, window=14):
    """
    计算相对强弱指数
    :param data: 包含'close'列的DataFrame
    :param window: 周期,默认14
    :return: 添加了'RSI'列的DataFrame
    """
    # 计算每日涨跌幅
    delta = data['close'].diff()
    
    # 分离上涨和下跌
    gain = delta.where(delta > 0, 0)
    loss = -delta.where(delta < 0, 0)
    
    # 计算平均上涨和平均下跌(使用指数移动平均,更平滑)
    avg_gain = gain.ewm(span=window, adjust=False).mean()
    avg_loss = loss.ewm(span=window, adjust=False).mean()
    
    # 计算RS和RSI
    rs = avg_gain / avg_loss
    rsi = 100 - (100 / (1 + rs))
    
    data['RSI_' + str(window)] = rsi
    return data

# 示例
# df = calculate_rsi(df, window=14)
# print(df[['close', 'RSI_14']].tail())

注意:当avg_loss为0时,RS会变成无穷大,RSI会变成100。这在连续上涨的行情中会发生。我建议你在代码里加一个保护:if avg_loss == 0: rsi = 100。否则模型可能会学到奇怪的东西。

3.3.3 实战中的用法

  • 超买/超卖:RSI > 70 为超买,可能回调;RSI < 30 为超卖,可能反弹。
  • 背离:价格创新高,但RSI没创新高 = 顶背离,看跌;价格创新低,但RSI没创新低 = 底背离,看涨。
  • 中心线穿越:RSI上穿50 = 多头占优;下穿50 = 空头占优。

我记得有一次做回测,发现RSI在震荡行情里特别好用,但在单边趋势里经常给出错误信号。为什么会这样?因为RSI本质上是一个均值回归指标,趋势行情里它会被“打脸”。

3.4 指数平滑异同移动平均线(MACD)—— 趋势的“加速器”

MACD,全称Moving Average Convergence Divergence。它由三部分组成:

  • DIF线:快线(12日EMA) - 慢线(26日EMA)
  • DEA线:DIF线的9日EMA(信号线)
  • MACD柱:2 × (DIF - DEA)

MACD的核心思想是:通过两条指数移动平均线的“聚合与分离”,来判断趋势的强度和转折。

3.4.1 计算公式

EMA(12) = 前一日EMA(12) × 11/13 + 今日收盘价 × 2/13
EMA(26) = 前一日EMA(26) × 25/27 + 今日收盘价 × 2/27
DIF = EMA(12) - EMA(26)
DEA = 前一日DEA × 8/10 + 今日DIF × 2/10
MACD柱 = 2 × (DIF - DEA)

3.4.2 代码实现

def calculate_macd(data, fast=12, slow=26, signal=9):
    """
    计算MACD指标
    :param data: 包含'close'列的DataFrame
    :param fast: 快线周期,默认12
    :param slow: 慢线周期,默认26
    :param signal: 信号线周期,默认9
    :return: 添加了'DIF', 'DEA', 'MACD'列的DataFrame
    """
    # 计算快线和慢线的EMA
    ema_fast = data['close'].ewm(span=fast, adjust=False).mean()
    ema_slow = data['close'].ewm(span=slow, adjust=False).mean()
    
    # 计算DIF
    dif = ema_fast - ema_slow
    
    # 计算DEA(信号线)
    dea = dif.ewm(span=signal, adjust=False).mean()
    
    # 计算MACD柱
    macd = 2 * (dif - dea)
    
    data['DIF'] = dif
    data['DEA'] = dea
    data['MACD'] = macd
    
    return data

# 示例
# df = calculate_macd(df)
# print(df[['close', 'DIF', 'DEA', 'MACD']].tail())

个人经验:我建议你把MACD的三个分量都作为特征输入模型,而不是只用MACD柱。因为DIF和DEA的交叉、背离,包含了更丰富的信息。我曾经只用了MACD柱,结果模型在震荡市里表现很差,加上DIF和DEA后,效果提升明显。

3.4.3 实战中的用法

  • 金叉/死叉:DIF上穿DEA = 买入信号;下穿DEA = 卖出信号。
  • 零轴穿越:DIF上穿零轴 = 多头市场;下穿零轴 = 空头市场。
  • 背离:价格与DIF的背离,比RSI背离更可靠。

3.5 本章知识体系总览

下面这张图,是我为你梳理的本章知识结构。你可以把它当作一个“特征工程地图”。

特征工程基础:技术指标知识体系 技术指标特征 移动平均线 (MA) SMA: 简单移动平均 EMA: 指数移动平均 金叉/死叉信号 相对强弱指数 (RSI) 超买(>70) / 超卖(<30) 顶背离 / 底背离 中心线穿越 (50) MACD DIF: 快线-慢线 DEA: 信号线 MACD柱: 动量强度 核心思想:将原始价格序列转化为有意义的交易信号 趋势跟踪 + 动量判断 + 超买超卖识别 注意:所有指标都是滞后指标,需要结合使用,避免单一指标陷阱

3.6 本章小结

这一章我们聊了三个最基础、最常用的技术指标:

  • 移动平均线:趋势的平滑器,帮你识别方向。
  • RSI:动量的体温计,帮你判断强弱。
  • MACD:趋势的加速器,帮你捕捉转折。

你可能会问:这些指标这么老,还有用吗?

有用。非常有用。

关键在于你怎么用。不是简单地“金叉买入,死叉卖出”,而是把它们作为特征,让模型自己去学习它们之间的非线性关系。模型会发现:当RSI超买且MACD出现顶背离时,下跌的概率是80%;当均线多头排列且RSI在50以上时,上涨的概率是70%。

这就是特征工程的魅力。

最后送你一句话:特征工程不是堆砌指标,而是理解市场。每一个指标背后,都是一种市场逻辑。理解了逻辑,你才能做出好的特征。


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