第三章:数据仓库与特征工程:数据存储方案、特征提取、选择与降维、质量监控

大家好,我是老张。今天咱们聊聊投研流程里最“脏”最“累”的活——数据仓库与特征工程。

说实话,很多做AI投研的朋友,一上来就喜欢调模型、调参数。但我个人经验是,模型再牛,也救不了垃圾数据。你想想看,如果喂进去的数据都是错的,或者特征之间高度冗余,那模型学出来的东西,说白了就是“精致的胡说八道”。

所以,这一章我带你走一遍数据从存储到特征落地的完整链路。我会结合我踩过的坑,把SQL/NoSQL怎么选、特征怎么提、怎么降维、怎么监控质量,一次性讲透。

3.1 数据存储方案:SQL vs NoSQL,别选错了

先聊存储。投研数据有个特点:既有高度结构化的行情数据,又有非结构化的新闻、研报、舆情文本。

我个人习惯是:结构化数据用SQL,非结构化或半结构化数据用NoSQL。但这不是绝对的,得看场景。

核心原则:数据一致性要求高、关系复杂 → SQL;数据量大、模式灵活、需要快速写入 → NoSQL。

3.1.1 SQL 方案:PostgreSQL + TimescaleDB

我在项目中遇到过,早期用MySQL存分钟级K线数据,结果表一大了,查询就慢得像蜗牛。后来换成了PostgreSQL + TimescaleDB,才算是解决了。

TimescaleDB是PostgreSQL的一个扩展,专门处理时序数据。它自动按时间分区,查询性能提升明显。

-- 创建超表(Hypertable)
CREATE TABLE stock_ohlc (
    time        TIMESTAMPTZ NOT NULL,
    symbol      TEXT NOT NULL,
    open        DOUBLE PRECISION,
    high        DOUBLE PRECISION,
    low         DOUBLE PRECISION,
    close       DOUBLE PRECISION,
    volume      BIGINT
);

-- 转换为超表,按时间和股票代码分区
SELECT create_hypertable('stock_ohlc', 'time', chunk_time_interval => INTERVAL '1 day');

-- 查询某只股票最近一周的收盘价
SELECT time, close
FROM stock_ohlc
WHERE symbol = '000001.SZ'
  AND time >= NOW() - INTERVAL '7 days'
ORDER BY time;

嗯,这里要注意:chunk_time_interval 要根据你的数据量来调。我一般设成1天到7天,太短了分区太多,太长了查询性能下降。

3.1.2 NoSQL 方案:MongoDB 与 Elasticsearch

对于新闻、研报这种文本数据,我推荐MongoDB。它的文档模型很灵活,字段可以随时加,不用改表结构。

举个例子,一条新闻可能有标题、正文、发布时间、情感标签、关联股票列表。用MongoDB存,就是一个JSON文档,非常自然。

// MongoDB 文档示例
{
  "_id": ObjectId("..."),
  "title": "某公司发布2024年业绩预告",
  "content": "公司预计净利润同比增长50%...",
  "publish_time": ISODate("2024-01-15T09:30:00Z"),
  "sentiment": 0.85,
  "related_stocks": ["000001.SZ", "600000.SH"]
}

另外,Elasticsearch 我主要用来做全文搜索。投研人员经常要搜“某公司 业绩 暴雷”这种关键词,ES的倒排索引比SQL的LIKE快几个数量级。

我的经验:不要试图用一个数据库解决所有问题。我见过有人硬用MySQL存JSON字段,结果查询和写入都慢得要命。该用NoSQL就用NoSQL,该用ES就用ES,各司其职。

3.2 特征提取方法:从原始数据到有效特征

数据存好了,下一步就是提取特征。特征提取,说白了就是把原始数据变成模型能理解的数值。

我把它分成三类:时序特征、文本特征、另类数据特征。

3.2.1 时序特征提取

股票价格、成交量这些时序数据,最常用的特征包括:

  • 统计特征:过去N天的均值、标准差、最大值、最小值、分位数
  • 技术指标:RSI、MACD、布林带、移动平均线
  • 波动率特征:已实现波动率、隐含波动率
  • 时序交叉特征:不同时间窗口的均值之差(如5日均线-20日均线)

我记得有一次做因子回测,发现一个简单的“过去5天涨幅”特征,居然比一堆复杂的技术指标效果还好。有时候,简单就是美。

import pandas as pd
import numpy as np

def extract_time_series_features(df, window=5):
    """
    提取时序统计特征
    df: 包含 'close' 列的 DataFrame
    """
    df = df.copy()
    # 过去5天的均值
    df['ma_5'] = df['close'].rolling(window=window).mean()
    # 过去5天的标准差
    df['std_5'] = df['close'].rolling(window=window).std()
    # 过去5天的涨跌幅
    df['return_5'] = df['close'].pct_change(periods=window)
    # 过去5天的最大值与最小值之差
    df['range_5'] = df['close'].rolling(window=window).max() - df['close'].rolling(window=window).min()
    return df

3.2.2 文本特征提取

新闻、研报这些文本数据,怎么变成特征?我常用的方法:

  • TF-IDF:简单有效,适合关键词提取
  • Word2Vec / GloVe:词向量,能捕捉语义相似性
  • BERT 等预训练模型:效果最好,但计算成本高
  • 情感分析:用预训练模型或词典,输出情感得分

避坑指南:我曾经用BERT对全量新闻做特征提取,结果跑了三天三夜还没跑完。后来改成先用TF-IDF做粗筛,只对关键新闻用BERT,效率提升了10倍。算力不是无限的,该省就省。

3.3 特征选择与降维:别让特征“打架”

特征多了,不一定好。你想想看,如果两个特征高度相关(比如“开盘价”和“收盘价”),模型就会学偏,还容易过拟合。

特征选择,就是去掉冗余的、无关的特征。降维,则是把高维特征压缩到低维空间。

3.3.1 特征选择方法

方法 原理 适用场景
方差过滤 去掉方差接近0的特征 特征值几乎不变的情况
相关系数法 计算特征与目标变量的相关性 线性关系明显的场景
卡方检验 检验特征与目标变量的独立性 分类问题,特征为离散值
基于模型的选择 用随机森林/Lasso等模型筛选重要特征 非线性关系,特征数量较多

我个人最常用的是基于模型的选择。用Lasso回归,它会自动把不重要的特征系数压缩到0,非常直观。

from sklearn.linear_model import Lasso
from sklearn.feature_selection import SelectFromModel

# 假设 X 是特征矩阵,y 是目标变量
lasso = Lasso(alpha=0.01)
lasso.fit(X, y)

# 选择系数不为0的特征
selector = SelectFromModel(lasso, prefit=True)
X_selected = selector.transform(X)

# 查看哪些特征被选中
selected_features = X.columns[selector.get_support()]
print(f"选中的特征: {selected_features.tolist()}")

3.3.2 降维:PCA 与 t-SNE

降维最经典的是PCA(主成分分析)。它把原始特征投影到几个“主成分”上,保留最大方差。

但PCA有个缺点:解释性差。你很难说清楚“主成分1”到底代表什么。

所以,如果是为了可视化,我更喜欢用t-SNE。它能很好地保持数据的局部结构,画出来的图很漂亮。

我的建议:如果是为了建模,优先用特征选择,而不是降维。因为特征选择保留了原始特征的含义,方便你向业务方解释。降维后的特征,解释起来很麻烦。

3.4 数据质量监控:别让坏数据毁了模型

数据质量,是投研的命门。我见过太多案例,因为数据质量没监控,模型上线后突然崩了。

数据质量监控,我总结为“四看”:

  • 看完整性:有没有缺失值?缺失率是否超过阈值?
  • 看准确性:数值是否在合理范围内?比如股价不可能为负数。
  • 看一致性:不同数据源之间的数据是否矛盾?比如A股代码在Wind和Tushare里是否一致?
  • 看时效性:数据是否及时更新?延迟超过多久需要告警?

3.4.1 自动化监控脚本

我习惯写一个简单的监控脚本,每天跑一次。一旦发现异常,就发邮件或钉钉告警。

import pandas as pd
import numpy as np
from datetime import datetime, timedelta

def check_data_quality(df, symbol_col='symbol', time_col='time', price_col='close'):
    """
    数据质量检查
    """
    issues = []
    
    # 1. 检查缺失值
    missing_rate = df.isnull().mean()
    for col, rate in missing_rate.items():
        if rate > 0.05:  # 缺失率超过5%就告警
            issues.append(f"列 {col} 缺失率 {rate:.2%}")
    
    # 2. 检查价格是否合理
    if price_col in df.columns:
        invalid_prices = df[df[price_col] <= 0]
        if len(invalid_prices) > 0:
            issues.append(f"发现 {len(invalid_prices)} 条价格为0或负数的记录")
    
    # 3. 检查数据时效性
    latest_time = df[time_col].max()
    if latest_time < datetime.now() - timedelta(hours=1):
        issues.append(f"数据最新时间 {latest_time},已超过1小时未更新")
    
    return issues

# 使用示例
issues = check_data_quality(df)
if issues:
    for issue in issues:
        print(f"[告警] {issue}")
    # 发送告警邮件或钉钉消息
else:
    print("数据质量检查通过")

避坑指南:我曾经因为没监控数据时效性,模型用了一周前的旧数据做预测,结果回测曲线漂亮,实盘一塌糊涂。从那以后,我每个数据管道都加了时间戳监控,延迟超过30分钟就自动暂停模型服务。

3.5 本章知识体系图

下面这张图,帮你梳理本章的核心逻辑。从数据存储,到特征提取,再到特征选择与降维,最后是质量监控,环环相扣。

数据仓库与特征工程全流程 数据存储方案 SQL (PostgreSQL + TimescaleDB) NoSQL (MongoDB) 搜索引擎 (Elasticsearch) 特征提取方法 时序特征 (统计/技术指标) 文本特征 (TF-IDF/BERT) 另类数据特征 特征选择与降维 方差过滤 / 相关系数法 基于模型的选择 (Lasso) PCA / t-SNE 降维 数据质量监控(完整性 / 准确性 / 一致性 / 时效性) 自动化监控脚本 → 告警 → 暂停模型服务

好了,这一章的内容就到这里。数据存储和特征工程,是投研的“地基”。地基打不牢,楼盖得再高也得塌。希望你能把这些方法用起来,少踩一些我当年踩过的坑。


公众号:蓝海数据掘金营,微信deep3321