4. River核心数据结构

在River中做在线学习,第一件事不是选模型,而是把数据组织成River能持续消费的结构。River的设计哲学是“单条样本驱动”:模型每次只看到一条样本,立即预测、立即学习、立即丢弃或保留统计量。因此,理解Stream、Dataset、TimeSeries以及它们背后的数据流迭代器,是金融流式机器学习能否落地的基础。本节围绕这四个对象,讲清它们的职责边界、协作方式,以及金融数据如何接入。

一、核心原理:四种结构的分工

River中与数据相关的核心抽象可以分成两层:数据容器层和迭代消费层。容器层负责“数据长什么样、从哪里来”,迭代层负责“如何一条条喂给模型”。

  • Stream(流):最基础的迭代器协议。任何实现了 __iter__ 并逐条产出 (x, y) 的对象都可以视为Stream。它不关心数据来源,只关心“下一条样本是什么”。
  • Dataset(数据集):对静态数据集的封装,通常支持按索引访问、划分训练/测试、批量迭代。它更像“可重复读取的数据表”,适合离线回测和基准测试。
  • TimeSeries(时间序列):面向时间戳有序数据的结构,强调时间索引、频率、缺失值处理。金融场景中,价格、成交量、订单簿快照天然是时间序列。
  • 数据流迭代器:把上述容器转换为统一的“逐条产出”接口,是River模型 learn_one / predict_one 的直接输入。
核心记忆点:Dataset 是“可回放的静态表”,TimeSeries 是“带时间轴的有序序列”,Stream 是“一次性或可重置的样本流”,迭代器是“把前三者统一成模型能吃的格式”。

二、方法流程:从数据源到模型的一条样本

无论数据来自CSV、数据库还是实时行情,接入River的流程都可以归纳为四步:

  1. 定义样本结构:确定 x 是字典(特征名到值)还是向量,y 是分类标签还是回归目标。
  2. 选择容器:离线回测用 Dataset 或 TimeSeries;实时接入用自定义生成器或消息队列消费者。
  3. 构造迭代器:通过 iter() 或 stream.iter_* 系列函数,把容器转成逐条样本。
  4. 驱动在线学习:在循环中调用 predict_one 与 learn_one,并记录指标。
from river import stream, metrics

# 假设 data 是 TimeSeries 或 Dataset
metric = metrics.Accuracy()

for x, y in stream.iter_csv("trades.csv", target="label"):
    y_pred = model.predict_one(x)      # 先预测
    if y_pred is not None:
        metric.update(y, y_pred)
    model.learn_one(x, y)              # 再学习

注意顺序:先预测、后学习。这是在线学习与批量学习最本质的区别,也是避免数据泄漏的关键。

三、金融数据接入方式与场景

金融数据接入River时,常见有三类来源,处理方式差异明显:

数据来源 推荐容器 接入方式 注意事项
历史CSV/Parquet Dataset / TimeSeries stream.iter_csv、iter_pandas 需按时间排序,避免未来函数
数据库快照 Dataset 分页查询 + 生成器 控制内存,逐批转逐条
实时行情/消息队列 自定义Stream Kafka/WebSocket消费者 处理乱序、重复、断线重连

以Tick级行情为例,一条样本通常包含时间戳、最新价、成交量、买卖盘口等字段。接入时建议把时间戳单独保留,特征字典只放模型可用的数值,标签由未来窗口收益生成。这样既符合River的 (x, y) 协议,也便于后续做特征工程。

金融数据源 → River容器 → 迭代器 → 在线模型 历史CSV/Parquet 离线回测 数据库快照 分页读取 实时行情/消息队列 Kafka / WebSocket Dataset 静态可回放 TimeSeries 时间有序 自定义 Stream 逐条产出 (x, y) 数据流迭代器 iter_csv / iter_pandas 统一为逐条样本 在线模型 predict_one / learn_one

四、误区与复盘:金融接入中最容易踩的坑

误区一:把Dataset当Stream反复遍历。 很多Dataset迭代器是一次性的,第二次循环会得到空结果。若需多轮训练,应显式重置或重新构造迭代器。
误区二:时间序列未排序就接入。 TimeSeries的价值在于时间有序。若CSV未按时间排序,模型会“看到未来”,回测指标虚高,实盘必然失效。
复盘建议: 接入任何金融数据前,先做三件事——检查时间戳单调性、确认标签生成不依赖未来数据、用小样本跑通 predict_one → learn_one 循环。这三步能过滤掉大部分流式学习的低级错误。

最后强调一点:River的Stream、Dataset、TimeSeries不是互斥的,而是可以组合的。你可以用TimeSeries承载原始行情,用Dataset做特征快照,再用自定义Stream把两者拼成模型输入。理解它们的职责边界,比记住API更重要。金融流式机器学习的稳定性,往往就藏在这些数据结构的细节里。