原始数据解析:JSON/CSV解析、时间戳标准化、价格与数量精度处理、缺失值识别

做量化交易的朋友都知道,订单簿数据是咱们吃饭的家伙。但说实话,交易所给过来的原始数据,那叫一个「原生态」——各种格式混着来,时间戳五花八门,精度问题能让你算错一个亿。今天我就把这几年的血泪经验掰开揉碎,跟你聊聊怎么把这堆「毛坯房」数据,装修成能直接用的「精装房」。

一、JSON与CSV解析:两种格式的「脾气」你得摸透

交易所的数据格式,说白了就两派:JSON派和CSV派。我个人习惯是,高频场景用CSV,灵活场景用JSON。为什么?你想想看,CSV解析起来快,但字段顺序一换就崩;JSON灵活,但解析开销大。

先看JSON解析。交易所返回的订单簿快照,通常是这样的:

{
  "bids": [
    ["100.50", "2.0"],
    ["100.45", "5.0"]
  ],
  "asks": [
    ["100.55", "3.0"],
    ["100.60", "1.5"]
  ],
  "timestamp": 1623456789.123
}

这里有个坑——价格和数量都是字符串!我在项目中遇到过,直接当浮点数解析,结果精度丢了,算出来的订单簿深度差了十万八千里。正确的做法是:

import json
from decimal import Decimal

def parse_orderbook(json_str):
    data = json.loads(json_str)
    bids = [(Decimal(price), Decimal(qty)) for price, qty in data['bids']]
    asks = [(Decimal(price), Decimal(qty)) for price, qty in data['asks']]
    return bids, asks, data['timestamp']

CSV解析呢?交易所给的CSV文件,字段顺序你得先确认。我吃过一次亏,以为第一列是时间戳,结果是对价。后来我学乖了,先读表头:

import csv
from decimal import Decimal

def parse_csv(file_path):
    with open(file_path, 'r') as f:
        reader = csv.DictReader(f)
        for row in reader:
            price = Decimal(row['price'])
            qty = Decimal(row['quantity'])
            timestamp = float(row['timestamp'])
            # 处理逻辑...
我的小技巧:不管JSON还是CSV,我都先用一个「数据预览函数」打印前5行,看看字段名、数据类型、有没有乱码。这一步花不了30秒,但能省下后面排查问题的半天时间。

二、时间戳标准化:把「北京时间」和「Unix时间」统一起来

时间戳这玩意儿,真是让人头大。有的交易所给Unix毫秒,有的给秒,还有的给带时区的字符串。我记得有一次,把毫秒当秒用了,结果回测出来的策略收益翻了三倍——嗯,那是假的。

标准化的思路其实很简单:统一转成Unix毫秒整数。为什么用毫秒?因为订单簿的更新频率通常是毫秒级的,用秒的话,同一秒内的多个更新就撞车了。

import pandas as pd
from datetime import datetime, timezone

def normalize_timestamp(ts, source_format='unix_ms'):
    if source_format == 'unix_ms':
        return int(ts)
    elif source_format == 'unix_s':
        return int(ts * 1000)
    elif source_format == 'iso':
        dt = datetime.fromisoformat(ts.replace('Z', '+00:00'))
        return int(dt.timestamp() * 1000)
    else:
        raise ValueError(f"未知的时间戳格式: {source_format}")

这里要注意时区问题。我曾经遇到一个交易所,给的ISO时间戳是UTC+8,但文档里写的是UTC。后来我统一用UTC+0做内部存储,展示的时候再转成本地时间。你想想看,如果策略里混着两个时区的时间,回测结果能准吗?

避坑指南:千万不要在原始数据上直接修改时间戳!我建议你新建一列叫「ts_normalized」,保留原始时间戳。这样万一标准化逻辑有bug,还能追溯回去。

三、价格与数量精度处理:小数点后几位,决定了你的盈亏

做订单簿清洗,精度问题是绕不开的坎。比特币价格动辄几万美金,小数点后两位就够用?不对,有些交易所支持到小数点后八位。而数量呢,有的币种最小交易单位是0.0001个。

我的处理原则是:用Decimal,不用float。float的二进制浮点表示,在累加和比较时会有精度误差。你想想看,如果订单簿里几万个订单,每个都差一点点,最后算出来的买卖盘口能准吗?

from decimal import Decimal, ROUND_HALF_UP

def round_price(price, tick_size='0.01'):
    """按最小价格变动单位四舍五入"""
    tick = Decimal(tick_size)
    return (Decimal(price) / tick).quantize(Decimal('1'), rounding=ROUND_HALF_UP) * tick

def round_qty(qty, lot_size='0.001'):
    """按最小交易量单位四舍五入"""
    lot = Decimal(lot_size)
    return (Decimal(qty) / lot).quantize(Decimal('1'), rounding=ROUND_HALF_UP) * lot

这里有个细节:交易所的tick_size和lot_size,你得从交易所的「交易对信息」接口获取。我见过有人硬编码0.01,结果遇到一个0.001 tick_size的币种,所有价格都算错了。

交易所 BTC/USDT tick_size BTC/USDT lot_size
Binance 0.01 0.00001
OKX 0.1 0.0001
Coinbase 0.01 0.00001
核心原则:精度处理要「向上兼容」。也就是说,如果原始数据有8位小数,你处理成6位,那丢失的2位就是误差。我一般保留原始精度,只在计算时做临时四舍五入。

四、缺失值识别:那些「消失」的订单去哪了?

订单簿数据里,缺失值是个大问题。为什么会有缺失?可能是网络抖动丢包了,可能是交易所的撮合引擎没来得及推送,也可能是数据采集程序自己挂了。

我总结了几种常见的缺失模式:

  • 整行缺失:某个时间点的快照完全没收到。这时候我会用前一个快照填充,但标记为「插值数据」。
  • 部分字段缺失:比如价格有,但数量是空。这种情况我直接丢弃该行,因为数量缺失的订单没法用。
  • 价格/数量为0:有些交易所用0表示「已成交」或「已撤单」。这不算缺失,但需要特殊处理。
import pandas as pd
import numpy as np

def detect_missing(df):
    """识别缺失值并分类"""
    report = {}
    
    # 整行缺失:检查时间戳是否连续
    expected_ts = pd.date_range(start=df['ts_normalized'].min(), 
                                end=df['ts_normalized'].max(), freq='100ms')
    missing_ts = expected_ts.difference(df['ts_normalized'])
    report['missing_timestamps'] = len(missing_ts)
    
    # 字段缺失
    for col in ['price', 'quantity']:
        null_count = df[col].isnull().sum()
        zero_count = (df[col] == 0).sum()
        report[f'{col}_null'] = null_count
        report[f'{col}_zero'] = zero_count
    
    return report

我曾经遇到过一个奇葩情况:某个交易所的订单簿,在价格剧烈波动时,会「漏掉」中间几个价格档位的订单。后来发现是他们的推送机制有bug,只推送变化的部分,但变化太快时丢包了。解决方案是:用前后两个快照做差值,把缺失的档位补上。

我的经验:缺失值处理没有银弹。你得先搞清楚「为什么缺失」,再决定「怎么处理」。如果是网络问题,用前值填充;如果是交易所的撮合逻辑,那得按规则来。我一般会在数据清洗阶段保留一个「缺失标记列」,方便后续分析。

知识体系总览

说了这么多,咱们用一张图把整个流程串起来。这张图是我做数据清洗时的「导航地图」,每次拿到新数据,先按这个流程走一遍:

订单簿数据清洗与特征工程 · 原始数据解析流程 原始数据输入 格式解析:JSON / CSV 时间戳标准化 → Unix毫秒 精度处理:Decimal + tick_size 缺失值识别与处理 ✅ 清洗后的订单簿数据 JSON解析 CSV解析 格式校验 Unix秒→毫秒 ISO→毫秒 时区统一 Decimal替代float tick_size四舍五入 整行缺失 字段缺失 零值处理

这张图把整个流程分成了五个步骤:数据输入、格式解析、时间戳标准化、精度处理、缺失值识别。每一步都有对应的技术方案和注意事项。你下次拿到新数据,可以照着这个流程走一遍,基本不会漏掉关键环节。

好了,关于原始数据解析的核心内容就这些。记住一句话:数据清洗花的时间,会在后续建模和回测中十倍百倍地还回来。别嫌麻烦,每一步都做到位,后面才能睡得安稳。