数据采集与清洗:从原始数据到可用资产

做市场预测模型,有个残酷的现实我得先告诉你——80%的时间都花在数据准备上。模型跑起来可能就几分钟,但前面那些脏活累活,才是真正的重头戏。

我个人习惯把数据采集和清洗看作「炼油」的过程。原油不能直接烧,原始数据也不能直接喂给模型。今天咱们就聊聊,怎么把那些乱七八糟的原始数据,变成干净可用的分析素材。

数据来源:你的数据从哪来?

做量化分析,数据来源无非三条路:API、爬虫、数据库。每条路我都踩过坑,一个一个说。

1. API接口——最优雅的方式

API是官方提供的数据通道。说白了,就是数据方给你开了一扇门,你按规矩敲门就能拿到数据。

我在项目中常用的是交易所的REST API。比如获取比特币的实时价格:

import requests
import pandas as pd

# 以币安API为例
url = "https://api.binance.com/api/v3/klines"
params = {
    "symbol": "BTCUSDT",
    "interval": "1h",
    "limit": 100
}

response = requests.get(url, params=params)
data = response.json()

# 转换成DataFrame
columns = ['timestamp', 'open', 'high', 'low', 'close', 'volume',
           'close_time', 'quote_volume', 'count', 'taker_buy_vol',
           'taker_buy_quote_vol', 'ignore']
df = pd.DataFrame(data, columns=columns)
print(df[['timestamp', 'open', 'high', 'low', 'close']].head())
小技巧:API调用通常有频率限制。我习惯在请求之间加个time.sleep(0.5),别把人家服务器打崩了。

2. 爬虫——不得已的选择

有些数据没有API,或者API收费太贵。这时候就得爬虫上场了。

但我要提醒你——爬虫有法律风险。我曾经因为爬取某财经网站的数据,被对方发了律师函。从那以后,我定了个规矩:先看robots.txt,再看是否有付费API,最后才考虑爬虫。

如果确实需要爬虫,用BeautifulSoup或Scrapy都行。但记住:控制频率,尊重数据源。

3. 数据库——老本行最稳

如果你在公司做量化,大概率数据已经存在数据库里了。MySQL、PostgreSQL、MongoDB都常见。

连接数据库的代码很简单:

import pymysql
import pandas as pd

conn = pymysql.connect(
    host='localhost',
    user='root',
    password='your_password',
    database='market_data'
)

query = "SELECT * FROM stock_daily WHERE trade_date >= '2024-01-01'"
df = pd.read_sql(query, conn)
conn.close()
注意:数据库密码千万别硬编码在代码里。用环境变量或者配置文件管理,这是基本素养。

数据清洗流程:从脏数据到干净数据

数据拿到手了,但别高兴太早。真实世界的数据,比你想象的脏得多。

我总结了一套清洗流程,你照着做基本不会出大问题:

  1. 初步探查——用df.info()和df.describe()快速了解数据概况
  2. 格式统一——日期格式、数值类型、列名规范
  3. 缺失值处理——删还是补?看情况
  4. 异常值检测——那些离谱的数据点,得揪出来
  5. 重复值去重——同一个数据出现两次,肯定有问题
  6. 一致性检查——比如开盘价不能高于最高价,这是常识

嗯,这六步走完,数据基本就能用了。

缺失值处理:补还是不补?

缺失值是个老生常谈的问题。但我想说,没有万能的方法,全看场景。

常见的处理方式:

方法 适用场景 缺点
直接删除 缺失比例很小(<5%) 可能丢失有用信息
均值/中位数填充 数值型数据,分布较均匀 会降低数据方差
前向填充(ffill) 时间序列数据 假设数据变化不大
插值法 有趋势的数据 计算量稍大
模型预测 缺失值较多且重要 可能引入模型偏差

举个例子,处理股票数据的缺失值:

# 前向填充——股票数据常用
df['close'].fillna(method='ffill', inplace=True)

# 线性插值——适合有趋势的数据
df['close'].interpolate(method='linear', inplace=True)

# 用前一天同一时间的数据填充
df['close'].fillna(df['close'].shift(1), inplace=True)
我的经验:金融时间序列数据,我首选前向填充。因为市场数据通常是连续的,前一个价格比均值更靠谱。但如果是节假日导致的缺失,千万别填充——那会引入虚假数据。

异常值处理:揪出那些「离谱」的数据

异常值,说白了就是那些「看着就不对」的数据点。比如某股票突然涨了1000%,或者成交量突然少了几个零。

为什么会这样?可能是数据录入错误,可能是系统故障,也可能是真实的市场异动。你得学会区分。

检测异常值的方法:

  • 3σ原则——超过均值±3个标准差的数据,视为异常
  • 箱线图法——超出1.5倍IQR的数据点
  • Z-score法——标准化后绝对值大于3的
  • 领域知识——比如股票涨跌幅超过10%,在A股就是异常

代码实现:

import numpy as np

# Z-score方法
def detect_outliers_zscore(data, threshold=3):
    z_scores = np.abs((data - data.mean()) / data.std())
    return z_scores > threshold

# 箱线图方法
def detect_outliers_iqr(data):
    Q1 = data.quantile(0.25)
    Q3 = data.quantile(0.75)
    IQR = Q3 - Q1
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR
    return (data < lower_bound) | (data > upper_bound)

# 应用
outliers = detect_outliers_zscore(df['close'])
print(f"发现 {outliers.sum()} 个异常值")
避坑指南:我曾经在回测时没处理异常值,结果模型根据一个「乌龙指」数据做出了交易决策,回测收益高得离谱。但实盘一跑,亏得亲妈都不认识。记住——异常值不处理,回测就是自欺欺人。

知识体系总览

说了这么多,咱们用一张图把整个流程串起来。你想想看,数据从采集到清洗,其实就这几步:

数据采集与清洗知识体系 数据来源 API接口 网络爬虫 数据库查询 清洗流程 初步探查 格式统一 缺失值处理 异常值检测 去重与一致性 干净数据 可用于建模 可用于回测 可用于分析 缺失值处理方法 删除 | 均值填充 | 前向填充 | 插值 异常值检测方法 3σ原则 | 箱线图 | Z-score | 领域知识 核心原则:数据质量决定模型上限 花80%时间做数据清洗,是值得的

这张图把整个流程串起来了。你从左边选数据来源,中间走清洗流程,右边得到干净数据。下面两个子模块是清洗中的重点——缺失值和异常值处理。

说实话,数据清洗这活儿不性感,但它是所有预测模型的基石。我见过太多人急着调模型参数,结果数据里一堆坑,最后模型跑出来的结果根本没法用。

记住一句话:垃圾进,垃圾出。数据质量不过关,再牛的算法也白搭。

我的习惯:每次拿到新数据,我都会先画个分布图看看。肉眼扫一遍,很多问题就暴露了。别一上来就写代码,先理解数据长什么样。

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