第三章:特征工程基础——技术指标计算与统计特征

大家好,我是你们的量化交易工程师朋友。今天咱们聊聊特征工程里最基础、也最核心的一块——技术指标计算和统计特征提取。

说实话,我刚开始做量化那会儿,觉得技术指标就是金叉死叉那一套。后来踩了不少坑才明白:指标本身不赚钱,但指标背后的数学逻辑,才是我们和市场的对话语言

3.1 技术指标计算:从均线说起

均线(MA)是最简单的,但也是最容易被低估的。我个人习惯把均线分成三组:

  • 短期均线(5日、10日):捕捉快速波动
  • 中期均线(20日、60日):识别趋势方向
  • 长期均线(120日、250日):判断牛熊分界

代码实现其实很简单,但有个细节要注意——初始值的处理。我在项目中遇到过一个问题:直接用pandas的rolling函数,前N天会全是NaN。后来我改用min_periods参数,让数据量不足时也能计算。

import pandas as pd
import numpy as np

def calculate_ma(df, windows=[5, 10, 20, 60]):
    """计算多周期均线,处理初始缺失值"""
    for w in windows:
        df[f'ma_{w}'] = df['close'].rolling(window=w, min_periods=1).mean()
    return df
我的小技巧: 均线交叉策略里,别只看金叉死叉。我习惯把均线斜率也作为一个特征——斜率向上且价格在均线上方,才是真正的强势。

3.2 RSI:超买超卖的真相

RSI(相对强弱指标)很多人用14天周期,但我觉得这太死板了。你想想看,不同品种的波动节奏完全不一样。

我曾经在期货市场里试过:把RSI周期从14改成9,信号提前了2-3天,但假信号也多了。后来我总结了一个经验:波动大的品种用短周期,趋势强的品种用长周期

def calculate_rsi(df, period=14):
    """计算RSI,注意处理除零错误"""
    delta = df['close'].diff()
    gain = delta.where(delta > 0, 0)
    loss = -delta.where(delta < 0, 0)
    
    avg_gain = gain.rolling(window=period, min_periods=1).mean()
    avg_loss = loss.rolling(window=period, min_periods=1).mean()
    
    rs = avg_gain / avg_loss.replace(0, np.nan)  # 避免除零
    df['rsi'] = 100 - (100 / (1 + rs))
    df['rsi'] = df['rsi'].fillna(50)  # 初始值设为中性
    return df
避坑指南: 我曾经在回测里发现RSI在震荡市表现很好,但在单边行情里频繁发出反向信号。后来我加了一个条件:只有价格在20日均线上方时,才考虑RSI的超买信号。效果好了不少。

3.3 MACD:趋势与动量的结合

MACD说白了就是两条均线的差值,再加一条信号线。但它的魅力在于:把趋势和动量融合在一个指标里

我记得有一次做股指期货的策略,发现MACD柱状线的斜率比金叉死叉更敏感。柱状线由负转正,往往比DIF上穿DEA早1-2根K线。

def calculate_macd(df, fast=12, slow=26, signal=9):
    """计算MACD,返回DIF、DEA和柱状线"""
    ema_fast = df['close'].ewm(span=fast, adjust=False).mean()
    ema_slow = df['close'].ewm(span=slow, adjust=False).mean()
    
    df['macd_dif'] = ema_fast - ema_slow
    df['macd_dea'] = df['macd_dif'].ewm(span=signal, adjust=False).mean()
    df['macd_bar'] = 2 * (df['macd_dif'] - df['macd_dea'])
    return df
核心要点: MACD的背离信号比金叉死叉更可靠。价格创新低但MACD没创新低,这叫底背离——我靠这个信号抓到过好几次反弹行情。

3.4 滚动窗口统计特征

技术指标之外,我还喜欢用滚动窗口统计特征。说白了就是:在过去N天里,价格是怎么分布的

常用的统计量包括:

特征名称 计算方式 我的用法
滚动均值 过去N天收盘价的平均值 判断当前价格相对于历史均值的位置
滚动标准差 过去N天收盘价的标准差 衡量波动剧烈程度
滚动偏度 过去N天收益率的偏度 判断涨跌不对称性
滚动峰度 过去N天收益率的峰度 识别极端行情出现的概率
滚动分位数 过去N天的20%、50%、80%分位 构建价格通道
def rolling_stats(df, window=20):
    """计算滚动窗口统计特征"""
    close = df['close']
    returns = close.pct_change()
    
    df['roll_mean'] = close.rolling(window).mean()
    df['roll_std'] = close.rolling(window).std()
    df['roll_skew'] = returns.rolling(window).skew()
    df['roll_kurt'] = returns.rolling(window).kurt()
    df['roll_q20'] = close.rolling(window).quantile(0.2)
    df['roll_q80'] = close.rolling(window).quantile(0.8)
    return df
经验之谈: 滚动窗口的大小很关键。我一般用20天(约一个月)作为基准,然后根据品种调整。比如比特币波动大,我用10天;国债波动小,我用40天。

3.5 波动率特征:市场的情绪温度计

波动率是我最看重的特征之一。为什么?因为波动率会聚集——大涨大跌之后,往往还有大涨大跌。

我常用的波动率特征有三种:

  1. 历史波动率:过去N天收益率的标准差,年化处理
  2. 已实现波动率:日内高频数据的波动,用5分钟K线计算
  3. 波动率锥:不同周期波动率的对比,判断当前波动率处于历史什么位置
def volatility_features(df, windows=[5, 20, 60]):
    """计算多周期波动率特征"""
    returns = df['close'].pct_change()
    
    for w in windows:
        # 年化波动率(假设一年252个交易日)
        vol = returns.rolling(w).std() * np.sqrt(252)
        df[f'vol_{w}d'] = vol
        
        # 波动率变化率
        df[f'vol_change_{w}d'] = vol.pct_change()
        
        # 波动率相对于历史均值的位置
        df[f'vol_zscore_{w}d'] = (vol - vol.rolling(252).mean()) / vol.rolling(252).std()
    
    return df
注意: 波动率特征在低波动时期容易产生假信号。我曾经在2017年A股的低波动行情里,用波动率突破做策略,结果连续止损。后来我加了一个条件:波动率必须处于历史30%分位以上才开仓

3.6 特征组合的艺术

单个指标就像单个乐器,组合起来才能奏出交响乐。我常用的组合方式:

  • 均线 + RSI:均线定方向,RSI定入场时机
  • MACD + 波动率:MACD给信号,波动率给仓位大小
  • 滚动统计 + 波动率锥:判断当前市场状态是正常还是异常

举个例子,我做过一个策略:当价格在20日均线上方(趋势向上),RSI低于30(超卖),同时波动率处于历史低位(即将爆发),这时候开多单。这个组合在震荡市里表现一般,但在趋势行情里效果很好。

核心思想: 特征工程不是堆砌指标,而是用数学语言描述市场状态。每个特征都应该回答一个问题:现在市场处于什么状态?

3.7 本章知识体系

下面这张图是我自己画的,把本章的核心逻辑串起来了:

特征工程知识体系 原始价格数据 技术指标 滚动统计特征 波动率特征 技术指标子类 • 均线(MA) • RSI • MACD 滚动统计子类 • 均值、标准差 • 偏度、峰度 • 分位数 波动率子类 • 历史波动率 • 已实现波动率 • 波动率锥 特征矩阵

嗯,这张图把本章的内容串起来了。从原始价格数据出发,经过三大类特征提取,最终形成特征矩阵。这就是我们后续做聚类和自适应交易的基础。


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