# 数据存储与数据库 (/zh/features/data/data-storage-and-databases)

下载后的市场数据应放在哪里，又如何只读取需要的部分？VBT 可将任意数据对象保存到 CSV、HDF5、Feather、Parquet、DuckDB、SQL 数据库或
ArcticDB，再读回可用于研究的数据对象。在 Python 中存储市场数据，并为下一次回测选择所需日期和标的。

```python title="Store four weeks of minute bars in DuckDB and query one week"
>>> data = vbt.GBMOHLCData.pull(  # (1)
...     ["AAA", "BBB", "CCC"],
...     start="2024-01-01",
...     end="2024-01-29",
...     timeframe="1min",
...     std=0.0005,
...     seed=42,
... )
>>> data.loc[:"2024-01-21 23:59"].to_duckdb(connection="market.duckdb")
>>> data.loc["2024-01-22":].to_duckdb(  # (2)
...     connection="market.duckdb",
...     if_exists="append",
... )
>>> vbt.DuckDBData.list_tables(connection="market.duckdb")
['AAA', 'BBB', 'CCC']

>>> week = vbt.DuckDBData.pull(
...     ["AAA", "BBB", "CCC"],
...     start="2024-01-15",
...     end="2024-01-22",
...     tz="UTC",  # (3)
...     connection="market.duckdb",
... )
>>> week.close.shape
(10080, 3)
>>> week.close.head(3).round(2)
symbol                       AAA     BBB    CCC
2024-01-15 00:00:00+00:00  67.01  176.99  60.39
2024-01-15 00:01:00+00:00  66.99  176.51  60.20
2024-01-15 00:02:00+00:00  67.18  175.62  60.15
```

1.  三个标的、四周的固定随机种子合成分钟 K 线，让示例可离线运行。
2.  将之后的 K 线追加到相同表中，如同每次下载后的定时任务。
3.  以 UTC 而非机器本地时区读取日期。

每个标的成为一张表，追加操作延长历史，查询则仅返回请求的一周数据，作为普通数据对象，可直接用于指标和回测。

## 选择存储格式 \[#choosing-a-storage-format]

根据采集和复用数据的方式选择：

| 您的需求                                 | 从哪里开始                          |
| ------------------------------------ | ------------------------------ |
| 可检查并与其他工具交换的文件                       | CSV                            |
| 在一个文件中存储多个标的数据集，并在之后追加               | HDF5                           |
| 保存 DataFrame，用 Python 和 Arrow 工具重新加载 | Feather                        |
| 一次写入、反复查询的压缩归档                       | Parquet，适合时按日期分区               |
| 对本地表或已有 Parquet 文件执行 SQL 分析          | DuckDB                         |
| 数据已存于 PostgreSQL、MySQL 或 SQLite      | 通过 `vbt.SQLData` 使用 SQLAlchemy |
| 在本地或对象存储中保存有版本的 DataFrame            | ArcticDB                       |

可以同时使用多种格式。保留 Parquet 归档，用 DuckDB 查询子集，再将结果传入回测。下面示例展示存储和读取。

## 文件 \[#files]

每个数据对象都有 `to_csv`、`to_hdf`、`to_feather` 和 `to_parquet`，对应的
`vbt.CSVData`、`vbt.HDFData`、`vbt.FeatherData` 和 `vbt.ParquetData` 类负责读回文件。同样四周的分钟 K
线，根据格式不同，占用空间差别很大：

```python title="Compare file sizes for 40,320 bars of three symbols"
>>> import os

>>> data.to_csv("csv")
>>> data.to_hdf("data.h5")
>>> data.to_parquet("parquet")
>>> data.to_feather("feather")

>>> def size_mb(path):
...     if os.path.isfile(path):
...         return os.path.getsize(path) / 1e6
...     return sum(
...         os.path.getsize(os.path.join(d, f))
...         for d, _, files in os.walk(path)
...         for f in files
...     ) / 1e6

>>> paths = ["csv", "data.h5", "parquet", "feather", "market.duckdb"]
>>> pd.Series({path: round(size_mb(path), 1) for path in paths})
csv              12.0
data.h5           5.2
parquet           5.9
feather           4.8
market.duckdb     5.8
dtype: float64
```

CSV 几乎处处可读，但体积最大、解析最慢。HDF5 将多个标的保存在同一文件。Parquet
采用压缩列式存储，多数数据工具可读取，并能按标的或日期分区。读取文件夹会发现其中的文件，按文件名匹配标的。CSV 和 HDF 读取器可分块处理，仅保留需要的行。Parquet
筛选器可选择日期或其他值，如下方[分区数据集示例](#pyarrow-and-fastparquet)所示。

使用独立文件时，每个文件被视为一个标的。分区 Parquet 目录则可为同一标的保留多个日期分区。对于已有的每日文件集合，可用 Pandas 合并并使用
`vbt.Data.from_data`，也可通过 DuckDB 查询。

## 数据库 \[#databases]

`vbt.DuckDBData` 在 DuckDB 中存储和查询数据。DuckDB 是运行于进程内的嵌入式分析数据库，也能直接查询 Parquet 和 CSV 文件。`vbt.SQLData` 支持
SQLAlchemy 能连接的任意数据库，例如 PostgreSQL、MySQL、SQLite；`vbt.ArcticDBData` 则利用 ArcticDB 存储大型版本化时间序列。DuckDB 和
SQLData 接受自定义 SQL 查询，并能列出模式、表和视图。ArcticDBData 列出库与标的，并通过 ArcticDB 自身 API 读取。`vbt.DuckDBData.pull`
默认以只读方式打开数据库，因此多个研究进程可同时读取同一文件。连接可作为对象传入，也可根据 URL 创建。反复执行 SQL 读取和更新时，可复用 SQLAlchemy 引擎及其连接池。

ArcticDBData 可选择已存的 `version`、日期区间和列。因此即使写入新数据后，在比较研究运行时，仍能重新访问保存的数据集版本。

## 使用现有数据存储 \[#use-your-existing-data-store]

无需为每个标的单独建表。自定义查询可从共享表选择行、连接其他输入，并为 VBT 重命名列。结果会成为与提供商下载相同类型的数据对象。

这个小示例创建一个包含两个标的虚构价格的 Parquet 文件，再仅读取最后两天。在您自己的归档中，文件已经存在：

```python title="Query an existing multi-symbol Parquet file"
>>> archive_rows = pd.DataFrame(
...     {
...         "timestamp": pd.to_datetime(["2024-01-01", "2024-01-02", "2024-01-03"] * 2, utc=True),
...         "ticker": ["AAA"] * 3 + ["BBB"] * 3,
...         "price": [100.0, 102.0, 104.0, 50.0, 49.0, 51.0],
...     }
... )
>>> archive_rows.to_parquet("archive.parquet", index=False)
>>> archive_data = vbt.DuckDBData.pull(
...     ["AAA", "BBB"],
...     query=vbt.key_dict(
...         {
...             symbol: f"""
...                 SELECT timestamp, price AS Close
...                 FROM read_parquet('archive.parquet')
...                 WHERE ticker = '{symbol}' AND timestamp >= '2024-01-02'
...                 ORDER BY timestamp
...             """
...             for symbol in ["AAA", "BBB"]
...         }
...     ),
...     index_col="timestamp",
... )
>>> archive_data.close
symbol                       AAA   BBB
timestamp
2024-01-02 00:00:00+00:00  102.0  49.0
2024-01-03 00:00:00+00:00  104.0  51.0
```

SQL 查询在数据成为 Pandas 对象之前选择行和列。DuckDB
可以[在读取时筛选 Parquet 数据](https://duckdb.org/docs/current/data/parquet/overview)，让研究运行仅使用大型归档中的相关切片。SQLData
对 SQL 数据库提供同样的自定义查询入口。如果已有返回 DataFrame
的加载器，[市场数据页面](/zh/features/data/market-data-sources/#custom-providers)也展示了如何将其引入 VBT。

## 持续更新存储 \[#keeping-stores-current]

让研究数据自动保持最新。数据保存器结合 `update`
和保存：按计划拉取新增数据并追加到存储，可停止并恢复而不丢失进度，如下方[数据保存器](#data-saver)功能介绍所示。研究过程中，数据缓存将每次下载保存在本地，使重新运行 notebook
时读取缓存而非提供商，如[数据缓存](#data-caching)功能介绍所示。

CSV、HDF5、SQL、DuckDB 和 ArcticDB 都有保存器，并支持多个标的。使用与存储匹配的保存器，例如 `vbt.DuckDBDataSaver` 或
`vbt.SQLDataSaver`，并传入知道如何获取更新的提供商数据对象。[数据流水线页面](/zh/features/data/financial-data-pipelines/)介绍增量更新与调度。

## 保存研究会话 \[#save-a-research-session]

如需将数据与 VBT 设置和获取参数一同保留，可用 `data.save` 保存整个对象，再通过数据类的 `load` 方法恢复。这适合在另一会话中继续使用同一提供商对象。Parquet
等表格导出则适合与其他工具共享底层数据。[配置与持久化](/zh/features/tooling/configuration-and-persistence/)介绍 VBT 对象的保存和恢复。

## 数据缓存 \[#data-caching]

新增于 v2026.3.1

✅ 所有数据类现在都支持数据缓存，让您从（远程）数据源拉取时将数据缓存在磁盘上。缓存使用高效键值存储 LMDB。缓存键根据类、标的和其他获取参数的哈希生成，确保不同拉取分别缓存。

```python title="Pull AAPL minute data from TradingView or cache if available"
>>> data = vbt.TVData.pull(
...     "AAPL",
...     exchange="NASDAQ",
...     timeframe="1 minute",
...     tz="America/New_York",
...     cache=True  # (1)
... )
```

1.  下次使用相同参数拉取相同数据时，将从缓存加载。使用 `refresh_cache=True` 刷新缓存。

## ArcticDB \[#arcticdb]

新增于 v2026.3.1

✅ [ArcticDB](https://arcticdb.io/) 是为 Python 数据科学生态设计的无服务器 DataFrame 数据库引擎。它依托通用对象存储（兼容 S3 的存储及
Azure Blob Storage），大规模存储、读取和处理 Series 与 DataFrame。除运行中的 Python 环境和对象存储访问权限外，ArcticDB
无需额外基础设施，数秒即可安装。

```python title="Save minute data to a ArcticDB database, and read one day"
>>> data = vbt.TVData.pull(
...     "AAPL",
...     exchange="NASDAQ",
...     timeframe="1 minute",
...     tz="America/New_York"
... )

>>> data.to_arcticdb()

>>> day_data = vbt.ArcticDBData.pull(
...     "AAPL",
...     start="2026-02-27 09:30:00",
...     end="2026-02-27 16:00:00",
...     tz="America/New_York",
... )
>>> day_data.get()
                             Open    High     Low    Close    Volume
datetime
2026-02-27 09:30:00-05:00  272.90  272.90  269.80  269.960   49256.0
2026-02-27 09:31:00-05:00  269.97  270.12  269.19  269.230   43715.0
2026-02-27 09:32:00-05:00  269.18  270.05  269.18  269.890   33913.0
2026-02-27 09:33:00-05:00  269.85  270.01  269.60  269.770   27569.0
2026-02-27 09:34:00-05:00  269.74  269.98  269.40  269.500   22963.0
...                           ...     ...     ...      ...       ...
2026-02-27 15:55:00-05:00  264.41  264.51  263.15  263.490  162153.0
2026-02-27 15:56:00-05:00  263.51  263.78  263.23  263.670  114741.0
2026-02-27 15:57:00-05:00  263.67  263.78  262.89  263.655  113641.0
2026-02-27 15:58:00-05:00  263.61  263.92  263.25  263.620  158462.0
2026-02-27 15:59:00-05:00  263.68  264.28  263.65  264.200  107619.0

[390 rows x 5 columns]
```

## SQL 查询 \[#sql-queries]

新增于 v2023.10.22

✅ 借助 [DuckDB](https://duckdb.org/)，现在可以直接对数据实例运行 SQL 查询。

```python title="Run a rolling average of 14 days on minute data using SQL"
>>> data = vbt.TVData.pull(
...     "AAPL",
...     exchange="NASDAQ",
...     timeframe="1 minute",
...     tz="America/New_York"
... )

>>> data.sql("""
...     SELECT datetime, AVG(Close) OVER(
...         ORDER BY "datetime" ASC
...         RANGE BETWEEN INTERVAL 14 DAYS PRECEDING AND CURRENT ROW
...     ) AS "Moving Average"
...     FROM "AAPL";
... """)
datetime
2023-09-11 09:30:00-04:00    180.080000
2023-09-11 09:31:00-04:00    179.965000
2023-09-11 09:32:00-04:00    180.000000
2023-09-11 09:33:00-04:00    180.022500
2023-09-11 09:34:00-04:00    179.984000
                                    ...
2023-10-20 15:55:00-04:00    177.786669
2023-10-20 15:56:00-04:00    177.785492
2023-10-20 15:57:00-04:00    177.784322
2023-10-20 15:58:00-04:00    177.783166
2023-10-20 15:59:00-04:00    177.781986
Name: Moving Average, Length: 11700, dtype: float64
```

## DuckDB \[#duckdb]

新增于 v2023.10.22

✅ [DuckDB](https://duckdb.org/) 是高性能分析数据库系统，提供功能完善的 SQL
方言来操作各种数据存储。即使数据无法装入内存，也能对本地数据运行分析查询，无需分布式框架；还可以直接查询 CSV、Parquet 和 JSON 文件。

```python title="Save minute data to a DuckDB database, and read one day"
>>> data = vbt.TVData.pull(
...     "AAPL",
...     exchange="NASDAQ",
...     timeframe="1 minute",
...     tz="America/New_York"
... )

>>> URL = "database.duckdb"
>>> data.to_duckdb(connection=URL)

>>> day_data = vbt.DuckDBData.pull(
...     "AAPL",
...     start="2023-10-02 09:30:00",
...     end="2023-10-02 16:00:00",
...     tz="America/New_York",
...     connection=URL
... )
>>> day_data.get()
                              Open    High     Low   Close    Volume
datetime
2023-10-02 09:30:00-04:00  171.260  171.34  170.93  171.10   61654.0
2023-10-02 09:31:00-04:00  171.130  172.37  171.13  172.30   53481.0
2023-10-02 09:32:00-04:00  172.310  172.64  172.16  172.64   44750.0
2023-10-02 09:33:00-04:00  172.640  172.97  172.54  172.78   53195.0
2023-10-02 09:34:00-04:00  172.780  173.07  172.75  173.00   47416.0
...                            ...     ...     ...     ...       ...
2023-10-02 15:55:00-04:00  173.300  173.51  173.26  173.51   61619.0
2023-10-02 15:56:00-04:00  173.525  173.59  173.42  173.43   45066.0
2023-10-02 15:57:00-04:00  173.430  173.55  173.42  173.50   45220.0
2023-10-02 15:58:00-04:00  173.510  173.60  173.46  173.56   47371.0
2023-10-02 15:59:00-04:00  173.560  173.78  173.56  173.75  161253.0

[390 rows x 5 columns]
```

## SQLAlchemy \[#sqlalchemy]

新增于 v2023.10.22

✅ [SQLAlchemy](https://www.sqlalchemy.org/) 提供标准接口，让开发者编写不依赖具体数据库的代码，与众多 SQL
数据库引擎通信。借助它，您现在可以轻松将数据存入 SQL 数据库，也能从中读取。

```python title="Save minute data to a PostgreSQL database, and read one day"
>>> data = vbt.TVData.pull(
...     "AAPL",
...     exchange="NASDAQ",
...     timeframe="1 minute",
...     tz="America/New_York"
... )

>>> URL = "postgresql://postgres:postgres@localhost:5432"
>>> data.to_sql(engine=URL)

>>> day_data = vbt.SQLData.pull(
...     "AAPL",
...     start="2023-10-02 09:30:00",
...     end="2023-10-02 16:00:00",
...     tz="America/New_York",
...     engine=URL
... )
>>> day_data.get()
                              Open    High     Low   Close    Volume
datetime
2023-10-02 09:30:00-04:00  171.260  171.34  170.93  171.10   61654.0
2023-10-02 09:31:00-04:00  171.130  172.37  171.13  172.30   53481.0
2023-10-02 09:32:00-04:00  172.310  172.64  172.16  172.64   44750.0
2023-10-02 09:33:00-04:00  172.640  172.97  172.54  172.78   53195.0
2023-10-02 09:34:00-04:00  172.780  173.07  172.75  173.00   47416.0
...                            ...     ...     ...     ...       ...
2023-10-02 15:55:00-04:00  173.300  173.51  173.26  173.51   61619.0
2023-10-02 15:56:00-04:00  173.525  173.59  173.42  173.43   45066.0
2023-10-02 15:57:00-04:00  173.430  173.55  173.42  173.50   45220.0
2023-10-02 15:58:00-04:00  173.510  173.60  173.46  173.56   47371.0
2023-10-02 15:59:00-04:00  173.560  173.78  173.56  173.75  161253.0

[390 rows x 5 columns]
```

## PyArrow 与 FastParquet \[#pyarrow-and-fastparquet]

新增于 v2023.10.22

✅ 数据可以通过 PyArrow 写入 Feather，也可通过 PyArrow 或 FastParquet 写入 Parquet。Parquet
在一次写入、多次读取的情景下表现尤其出色，具有高效压缩和解压能力，非常适合时间序列存储。

```python title="Save minute data to a Parquet dataset partitioned by day, and read one day"
>>> data = vbt.TVData.pull(
...     "AAPL",
...     exchange="NASDAQ",
...     timeframe="1 minute",
...     tz="America/New_York"
... )

>>> data.to_parquet(partition_by="day")  # (1)

>>> day_data = vbt.ParquetData.pull("AAPL", filters=[("group", "==", "2023-10-02")])
>>> day_data.get()
                              Open    High     Low   Close    Volume
datetime
2023-10-02 09:30:00-04:00  171.260  171.34  170.93  171.10   61654.0
2023-10-02 09:31:00-04:00  171.130  172.37  171.13  172.30   53481.0
2023-10-02 09:32:00-04:00  172.310  172.64  172.16  172.64   44750.0
2023-10-02 09:33:00-04:00  172.640  172.97  172.54  172.78   53195.0
2023-10-02 09:34:00-04:00  172.780  173.07  172.75  173.00   47416.0
...                            ...     ...     ...     ...       ...
2023-10-02 15:55:00-04:00  173.300  173.51  173.26  173.51   61619.0
2023-10-02 15:56:00-04:00  173.525  173.59  173.42  173.43   45066.0
2023-10-02 15:57:00-04:00  173.430  173.55  173.42  173.50   45220.0
2023-10-02 15:58:00-04:00  173.510  173.60  173.46  173.56   47371.0
2023-10-02 15:59:00-04:00  173.560  173.78  173.56  173.75  161253.0

[390 rows x 5 columns]
```

1.  不设置 `partition_by` 时，数据保存到单个 Parquet 文件。在较新版本的 PyArrow 和 Pandas 中，仍可按任意列筛选行。

## 数据保存器 \[#data-saver]

新增于 1.1.1

✅ 想象一个脚本，可以定期从交易所拉取最新数据并保存到磁盘，全程无需干预。VBT 实现了两个这样的类：一个保存为 CSV，另一个保存为 HDF。

```python title="BTCUSDT_1m_saver.py"
from vectorbtpro import *

import logging
logging.basicConfig(level=logging.INFO)

if __name__ == "__main__":
    if vbt.CSVDataSaver.file_exists():
        csv_saver = vbt.CSVDataSaver.load()
        csv_saver.update()
        init_save = False
    else:
        data = vbt.BinanceData.pull(
            "BTCUSDT",
            start="10 minutes ago UTC",
            timeframe="1 minute"
        )
        csv_saver = vbt.CSVDataSaver(data)
        init_save = True
    csv_saver.update_every(1, "minute", init_save=init_save)
    csv_saver.save()  # (1)
```

1.  CSV 数据保存器只保留最新数据更新，作为下一次更新的起点。请务必保存它，并在下一次运行时复用。

```console title="Run in console and then interrupt"
$ python BTCUSDT_1m_saver.py
2023-02-01 12:26:36.744000+00:00 - 2023-02-01 12:36:00+00:00: : 1it [00:01,  1.22s/it]
INFO:vectorbtpro.data.saver:Saved initial 10 rows from 2023-02-01 12:27:00+00:00 to 2023-02-01 12:36:00+00:00
INFO:vectorbtpro.utils.schedule_:Starting schedule manager with jobs [Every 1 minute do update(save_kwargs=None) (last run: [never], next run: 2023-02-01 13:37:38)]
INFO:vectorbtpro.data.saver:Saved 2 rows from 2023-02-01 12:36:00+00:00 to 2023-02-01 12:37:00+00:00
INFO:vectorbtpro.data.saver:Saved 2 rows from 2023-02-01 12:37:00+00:00 to 2023-02-01 12:38:00+00:00
INFO:vectorbtpro.utils.schedule_:Stopping schedule manager
```

```console title="Run in console again to continue"
$ python BTCUSDT_1m_saver.py
INFO:vectorbtpro.utils.schedule_:Starting schedule manager with jobs [Every 1 minute do update(save_kwargs=None) (last run: [never], next run: 2023-02-01 13:42:08)]
INFO:vectorbtpro.data.saver:Saved 5 rows from 2023-02-01 12:38:00+00:00 to 2023-02-01 12:42:00+00:00
INFO:vectorbtpro.utils.schedule_:Stopping schedule manager
```

## 本地数据 \[#local-data]

新增于 1.0.0

✅ 获取远程数据后，您很可能希望将其保存到磁盘。现在有两种新选择：序列化整个数据类，或将实际数据保存为 CSV 或
HDF5。每个数据集可保存在一个扁平文件中，比数据库更易处理。保存后，可以通过反序列化，或使用专门加载 CSV、HDF5
文件的数据类，轻松重新读取。这些类支持按行和日期时间区间筛选、更新、分块，甚至智能数据集搜索：递归遍历子目录，返回匹配指定 glob 模式或正则表达式的数据集 🧲

```python title="Fetch and save symbols separately, then load them back jointly"
>>> btc_data = vbt.BinanceData.pull("BTCUSDT")
>>> eth_data = vbt.BinanceData.pull("ETHUSDT")

>>> btc_data.to_hdf()
>>> eth_data.to_hdf()

>>> data = vbt.BinanceData.from_hdf(start="2020", end="2021")
```

键 2/2: 100%

```python
>>> data.close
symbol                      BTCUSDT  ETHUSDT
Open time
2020-01-01 00:00:00+00:00   7200.85   130.77
2020-01-02 00:00:00+00:00   6965.71   127.19
2020-01-03 00:00:00+00:00   7344.96   134.35
2020-01-04 00:00:00+00:00   7354.11   134.20
2020-01-05 00:00:00+00:00   7358.75   135.37
...                             ...      ...
2020-12-27 00:00:00+00:00  26281.66   685.11
2020-12-28 00:00:00+00:00  27079.41   730.41
2020-12-29 00:00:00+00:00  27385.00   732.00
2020-12-30 00:00:00+00:00  28875.54   752.17
2020-12-31 00:00:00+00:00  28923.63   736.42

[366 rows x 2 columns]
```
