3、数据源与特征工程(下):特征构建与降维

好,咱们接着往下聊。上一节我们把原始数据清洗干净了,现在手里攥着一堆交易记录、公告、还有舆情文本。但光有这些还不够——模型看不懂「今天成交量很大」这种话,它只认数字。所以,我们要把这些原始信息,翻译成模型能理解的「特征」。

说白了,特征构建就是给数据「化妆」。化得好,模型一眼就能看出谁是内幕交易;化得不好,再牛的算法也白搭。我这些年踩过的坑,十有八九都出在这一步。

3.1 价格动量特征

价格动量,听起来玄乎,其实就是看股价在某个时间段里「跑得有多猛」。内幕交易者往往会在消息公布前悄悄建仓,股价会提前异动。

我个人习惯用这几个指标:

  • 累计收益率:过去N天的总涨幅。比如5日累计收益率 = (今日收盘价 / 5天前收盘价) - 1。
  • 相对强弱指数(RSI):衡量近期涨跌力度。超过70算超买,低于30算超卖。内幕交易前,RSI经常在60-80之间徘徊。
  • 移动平均线偏离度:当前价格偏离20日均线的百分比。偏离太大,说明有人在「抢跑」。

核心逻辑:内幕交易者不会等到公告当天才动手。他们会在消息公布前3-10天开始布局。所以,短周期动量特征(3天、5天、10天)比长周期(60天、120天)更敏感。

代码实现其实不复杂:

# 计算5日累计收益率
df['ret_5d'] = df['close'].pct_change(5)

# 计算RSI
def calc_rsi(data, window=14):
    delta = data.diff()
    gain = delta.where(delta > 0, 0)
    loss = -delta.where(delta < 0, 0)
    avg_gain = gain.rolling(window).mean()
    avg_loss = loss.rolling(window).mean()
    rs = avg_gain / avg_loss
    return 100 - (100 / (1 + rs))

df['rsi_14'] = calc_rsi(df['close'])

避坑指南:我曾经在计算RSI时直接用收盘价,结果发现停牌日的数据把指标搞崩了。后来我改用复权价格,并且把停牌日的收益率设为0。记住,停牌日不是「没涨没跌」,而是「没有交易」——这两个概念不一样。

3.2 波动率特征

波动率,说白了就是股价「上蹿下跳」的程度。内幕交易有个特点:交易量还没放大,股价已经开始「抖」了。为什么?因为内幕资金在偷偷吸筹,买卖盘口被扰动。

我常用的波动率特征:

  • 历史波动率:过去N天收益率的标准差。这是最基础的。
  • 振幅:(最高价 - 最低价) / 收盘价。日内波动越大,越可疑。
  • 波动率突变:当前波动率 / 过去60天平均波动率。这个比值突然飙升,往往意味着有「大事」要发生。
特征名称 计算方式 内幕交易场景下的表现
历史波动率(10日) std(ret_1d, 10) 公告前5-10天开始缓慢上升
日内振幅 (high - low) / close 公告前1-3天突然放大
波动率突变比 vol_10d / vol_60d 比值超过1.5时需高度警惕

嗯,这里要注意:波动率特征和动量特征经常一起用。动量告诉你「涨了多少」,波动率告诉你「涨得稳不稳」。内幕交易往往是「偷偷涨、慢慢抖」,而不是「突然拉涨停」——那种太显眼了,容易被查。

3.3 异常成交量特征

成交量是内幕交易最直接的「指纹」。你想想看,一个平时每天只成交100万股的股票,突然连续三天成交500万股,而且股价还在涨——这正常吗?

我常用的成交量特征:

  • 成交量倍率:当日成交量 / 过去20日均量。超过2倍就算异常。
  • 成交量变异系数:成交量标准差 / 成交量均值。这个指标能捕捉「忽大忽小」的异常模式。
  • 量价背离:股价上涨但成交量萎缩,或者股价下跌但成交量放大。内幕交易者经常在拉升时「对倒」制造假量。

重要提醒:别只看绝对成交量。大盘股和小盘股的成交量基数差几十倍。一定要用「相对指标」——比如成交量倍率、换手率。我见过有人直接用原始成交量做特征,结果模型只学会了「大盘股成交量本来就大」,完全没抓到内幕信号。

# 计算成交量倍率
df['volume_ratio'] = df['volume'] / df['volume'].rolling(20).mean()

# 计算量价背离
df['price_up'] = (df['close'] > df['close'].shift(1)).astype(int)
df['volume_down'] = (df['volume'] < df['volume'].shift(1)).astype(int)
df['divergence'] = df['price_up'] & df['volume_down']  # 量价背离信号

3.4 网络舆情情绪得分

这部分是我觉得最有意思的。内幕交易者虽然偷偷摸摸,但市场上总会有「风声」。股吧、微博、雪球上的讨论,往往能提前反映内幕信息。

怎么做?我一般分三步:

  1. 文本采集:抓取目标股票相关的帖子、评论。注意过滤掉广告和水军。
  2. 情感分析:用预训练的BERT模型或者简单的词典法,给每条文本打一个「情绪分」:-1(极度负面)到+1(极度正面)。
  3. 聚合统计:按天计算情绪均值、情绪波动率、极端情绪占比。

举个例子:某股票平时情绪分在0.1左右(中性偏正面),突然连续三天情绪分飙到0.6以上,而且发帖量也翻倍了——这大概率是有人在「吹风」。

实战经验:我曾经用LDA主题模型把舆情文本分成「利好」「利空」「中性」三类,然后统计每类占比。结果发现,内幕交易前「利好」类帖子占比会提前3天开始上升,而「中性」类占比下降。这个信号比单纯的情绪得分更稳定。

# 伪代码:舆情情绪聚合
def aggregate_sentiment(df_sentiment):
    daily_sentiment = df_sentiment.groupby('date').agg({
        'score': ['mean', 'std', lambda x: (x > 0.5).sum()]
    })
    daily_sentiment.columns = ['sentiment_mean', 'sentiment_std', 'extreme_positive_cnt']
    return daily_sentiment

3.5 特征标准化与降维

特征造了一大堆,但问题来了:动量特征可能是0.xx的量级,成交量倍率可能是2.xx,情绪得分是-1到1。这些特征「尺度」不一样,直接扔进模型,大数值的特征会主导训练过程。

标准化,就是把所有特征拉到同一个尺度上。我常用两种方法:

  • Z-score标准化:(x - mean) / std。适合数据近似正态分布的情况。
  • Min-Max归一化:(x - min) / (max - min)。把数据压缩到[0,1]区间。适合有明确上下界的特征,比如情绪得分。

标准化之后,特征数量可能还是太多。你想想看,光动量特征我就造了十几个,再加上波动率、成交量、舆情……总共可能上百个特征。这么多特征,模型容易过拟合,训练也慢。

降维,就是「删繁就简」。我常用的方法:

  • PCA(主成分分析):把原始特征线性组合成几个「主成分」。我一般保留能解释90%方差的主成分数量。
  • 特征选择:用随机森林的特征重要性排序,只保留Top 20的特征。这个方法更直观,可解释性强。

我的习惯:先做特征选择,再做PCA。为什么?因为PCA会破坏特征的可解释性——你没法跟老板解释「主成分1」是什么意思。先用特征选择砍掉不重要的特征,保留20-30个核心特征,然后再用PCA压缩到10-15维。这样既保留了可解释性,又控制了维度。

from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

# 标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# PCA降维
pca = PCA(n_components=0.90)  # 保留90%方差
X_pca = pca.fit_transform(X_scaled)

好了,特征工程这部分就讲到这里。你可能会问:「这么多特征,到底哪些最重要?」嗯,这个问题没有标准答案。不同的股票、不同的市场环境,重要特征都不一样。我建议你先把所有特征都造出来,然后用随机森林跑一遍特征重要性排序——让数据自己告诉你答案。


专注资料整理