Alex Owens
2024年4月12日
之前的博文讨论了存储在ArcticDB中数据的不可变性,将其作为优化性能、提高横向扩展能力和防止数据损坏的一种手段。这种不可变性的另一个结果是能够“时间旅行”回滚到符号的早期版本。在存储的符号具有日期时间索引的使用场景中,这也被称为“双时间性”,因为你不仅可以获取最新版本符号中特定日期范围内的数据,还可以获取历史版本在特定时间点的数据。
那么为什么这很有用呢?让我们通过一个真实的玩具示例来展示这个功能为何如此强大。
假设你正在从一个数据供应商那里接收数据,他们每天提供 CSV 文件,其中包含该交易日内所有证券每分钟实时交易的开盘价、收盘价、最高价、最低价、交易量和交易数据
with open("AAPL_2024-01-01.csv", "r") as f:
daily_df = pandas.read_csv(f, index_col="datetime")
lib.append("AAPL", daily_df)
print(lib.read("AAPL").data) open close low high volume trades
datetime
2024-01-01 08:00:00 0.758089 0.339878 0.781040 0.098037 0.134402 346
2024-01-01 16:29:00 0.221275 0.773076 0.321803 0.586879 0.250758 201
这里我只包含了当天的第一分钟和最后一分钟,以减少数据量,并且数值是随机生成的。对于每个证券,每天的一般流程如上所示。然而,数据供应商有时也会重新公布早期时间段的数据,以纠正原始数据中的错误。假设我们有 2024 年前 3 天的标准每日数据
open close low high volume trades
datetime
2024-01-01 08:00:00 0.758089 0.339878 0.781040 0.098037 0.134402 346
2024-01-01 16:29:00 0.221275 0.773076 0.321803 0.586879 0.250758 201
2024-01-02 08:00:00 0.543514 0.121713 0.908510 0.859530 0.673515 810
2024-01-02 16:29:00 0.363511 0.506118 0.445974 0.031269 0.743444 665
2024-01-03 08:00:00 0.054882 0.319868 0.520630 0.986617 0.812310 847
2024-01-03 16:29:00 0.237748 0.932168 0.990305 0.026718 0.109527 527
然后,在第四天,供应商提供通常的每日数据,并重新公布 2024–01–02 的数据。将数据写入ArcticDB现在将包含如下代码
with open("AAPL_2024-01-02_restatement.csv", "r") as f:
restated_df = pandas.read_csv(f, index_col="datetime")
lib.update("AAPL", restated_df)
print(lib.read("AAPL").data) open close low high volume trades
datetime
2024-01-01 08:00:00 0.758089 0.339878 0.781040 0.098037 0.134402 346
2024-01-01 16:29:00 0.221275 0.773076 0.321803 0.586879 0.250758 201
2024-01-02 08:00:00 0.726245 0.209358 0.382218 0.652552 0.060542 452
2024-01-02 16:29:00 0.912180 0.317210 0.460713 0.581922 0.330218 642
2024-01-03 08:00:00 0.054882 0.319868 0.520630 0.986617 0.812310 847
2024-01-03 16:29:00 0.237748 0.932168 0.990305 0.026718 0.109527 527
2024-01-04 08:00:00 0.813585 0.571894 0.813374 0.598003 0.542102 184
2024-01-04 16:29:00 0.808495 0.375533 0.665888 0.847430 0.885961 245
其中第三行和第四行现在与之前不同。我们现在遇到了一个问题。假设我们决定在 2024–01–03 进行交易,依据的是当时我们可获得的信息。如果我们只能读取符号的最新版本
AAPL
,那么 2024-01-02 的价格数据将与我们做出决定时的数据不符。这时,“时间旅行”就派上了用场。这个as_of
参数可用于read
用于指定我们要读取的是早期版本,而不仅仅是最新版本。在此示例中print(lib.read("AAPL", as_of=2).data)
open close low high volume trades
datetime
2024-01-01 08:00:00 0.758089 0.339878 0.781040 0.098037 0.134402 346
2024-01-01 16:29:00 0.221275 0.773076 0.321803 0.586879 0.250758 201
2024-01-02 08:00:00 0.543514 0.121713 0.908510 0.859530 0.673515 810
2024-01-02 16:29:00 0.363511 0.506118 0.445974 0.031269 0.743444 665
2024-01-03 08:00:00 0.054882 0.319868 0.520630 0.986617 0.812310 847
2024-01-03 16:29:00 0.237748 0.932168 0.990305 0.026718 0.109527 527
将准确显示 2024–01–03 追加操作完成后数据看起来的样子。这引出了一个问题,我们如何知道应该读取哪个版本?
有三种选择。所有修改操作
write/append/update
返回一个 VersionedItem
对象。除其他属性外,此对象还有一个version
属性,告知你刚刚创建的符号的版本号。这些信息可以存储在一个单独的符号中,也可以存储在完全不同的数据库中,具体取决于实际需求。另外,可以向
as_of
提供一个时间戳(UTC 时间),它将检索在指定时间点是最新的版本。例如,如果供应商每天下午 5 点提供数据,并且我们总是在下午 6 点之前将其写入 ArcticDB,那么lib.read("AAPL", as_of=pandas.Timestamp("2024-01-03T18:00:00"))
将检索与请求版本 2 相同的数据。
最后,我们可以使用快照来定义在特定时间点精确的符号-版本对集合。然后可以将快照名称提供给该
as_of
参数,以检索该快照引用的符号版本lib.read("AAPL", as_of="snapshot-2024-01-03")
在这里给出的这个简单示例中,使用快照并没有比使用时间戳带来任何好处。然而,在符号持续修改且交易决策基于多个符号而非单个符号的情况下,快照提供了一种精确引用符号-版本对集合的方式,无需担心时钟偏差等细节问题。
但是,如果你不需要这个功能,并且只关心最新版本怎么办?在这种情况下,保留永远不会再次读取的旧数据会浪费磁盘空间。在这种情况下,所有修改操作都有一个参数
prune_previous_versions
在写入新版本时删除符号的旧版本,但快照引用的任何版本除外。