Metadata-Version: 2.5
Name: vikron-nautilus
Version: 0.3.0
Summary: Read Vikron Catalog data with NautilusTrader Python
Project-URL: Repository, https://gitee.com/vikron-data/vikron-nautilus
License-Expression: MIT
License-File: LICENSE
Requires-Python: >=3.12
Requires-Dist: numpy<3,>=1.26
Requires-Dist: packaging>=24
Requires-Dist: pyarrow<27,>=21
Provides-Extra: v1
Requires-Dist: nautilus-trader<2.0.0,>=1.226.0; extra == 'v1'
Provides-Extra: v2
Requires-Dist: nautilus-trader>=2.0.0rc6; extra == 'v2'
Description-Content-Type: text/markdown

# vikron-nautilus

将 Vikron Downloader 生成的 Catalog 数据接入 NautilusTrader，支持量化投研与策略回测。

支持平台：Windows amd64、Linux amd64/arm64、macOS arm64。

## Catalog 格式

| Downloader 输出格式 | 数据目录 | Python：`nautilus-trader` 依赖范围 | Rust：Nautilus crate 依赖范围 | Rust 宏参数 |
| --- | --- | --- | --- | --- |
| Catalog v1 | `vikron/catalog-v1` | `>=1.226.0,<2.0.0` | `>=0.55.0,<=0.64.0` | `v1` |
| Catalog v2 | `vikron/catalog-v2` | `>=2.0.0rc6` | `>=0.65.0` | `v2` |

根据数据目录选择对应版本。

## 支持的数据

| 数据 | 类型归属 | 类型名称（Python / Rust） | 数据来源 | 主要源字段 |
| --- | --- | --- | --- | --- |
| 报价 | Nautilus 原生 | `QuoteTick` | `book_ticker` | `bid_price`、`ask_price`、`bid_amount`、`ask_amount` |
| 成交 | Nautilus 原生 | `TradeTick` | `trades` | `id`、`side`、`price`、`amount` |
| 订单簿增量 | Nautilus 原生 | `OrderBookDelta` | `incremental_book_L2` | `is_snapshot`、`side`、`price`、`amount` |
| 标记价格 | Nautilus 原生 | `MarkPriceUpdate` | `derivative_ticker` | `mark_price` |
| 指数价格 | Nautilus 原生 | `IndexPriceUpdate` | `derivative_ticker` | `index_price` |
| 资金费率 | Nautilus 原生 | `FundingRateUpdate` | `derivative_ticker` | `funding_rate`、`funding_timestamp` |
| 强平 | Vikron 自定义 | `Liquidation` | `liquidations` | `id`、`side`、`price`、`amount` |
| 持仓量 | Vikron 自定义 | `OpenInterestUpdate` | `derivative_ticker` | `open_interest` |
| 最新价格 | Vikron 自定义 | `LastPriceUpdate` | `derivative_ticker` | `last_price` |
| 预测资金费率 | Vikron 自定义 | `PredictedFundingRateUpdate` | `derivative_ticker` | `predicted_funding_rate`、`funding_timestamp` |

## 自定义数据接入

| 官方接口 | Python v1 | Python v2 | Rust v1 | Rust v2 |
| --- | --- | --- | --- | --- |
| ParquetDataCatalog 查询 | 支持 | 支持 | 支持 | 支持 |
| BacktestNode 自动加载 | 支持 | 当前不支持 | 当前不支持 | 当前不支持 |
| BacktestEngine 手动加入数据 | 支持 | 支持 | 支持 | 支持 |

## 安装

### Python

需要 Python 3.12+。v1、v2 分别安装在独立虚拟环境中。

```sh
python -m pip install --upgrade pip

# Catalog v1
python -m pip install "vikron-nautilus[v1]==0.3.0"

# Catalog v2（另一个虚拟环境）
python -m pip install "vikron-nautilus[v2]==0.3.0"
```

### Rust

需要 Rust 1.98.1+；使用 Nautilus `0.66.0` 时需要 Rust 1.99.0+。

#### Arrow 版本配置

| Nautilus Rust 版本 | `arrow-array` / `arrow-schema` 配置 | Catalog 宏参数 |
| --- | --- | --- |
| `0.55.0–0.62.0` | `"58"` | `v1` |
| `0.63.0–0.64.0` | `"59"` | `v1` |
| `0.65.0–0.66.0` | `"59"` | `v2` |

根据上表选择 Nautilus 和 Arrow 版本，再将对应配置加入项目的 `Cargo.toml`。两个 Arrow 依赖应与 Nautilus 解析到同一版本。

#### Catalog v2

```toml
[dependencies]
vikron-nautilus = "0.3.0"
arrow-array = "59"
arrow-schema = "59"

nautilus-core = "=0.65.0"
nautilus-model = { version = "=0.65.0", features = ["high-precision"] }
nautilus-serialization = { version = "=0.65.0", features = ["arrow"] }
nautilus-persistence = { version = "=0.65.0", features = ["high-precision"] }
```

#### Catalog v1

使用以下依赖配置：

```toml
[dependencies]
vikron-nautilus = "0.3.0"
arrow-array = "59"
arrow-schema = "59"

nautilus-core = "=0.64.0"
nautilus-model = { version = "=0.64.0", features = ["high-precision"] }
nautilus-serialization = { version = "=0.64.0", features = ["arrow"] }
nautilus-persistence = { version = "=0.64.0", features = ["high-precision"] }
```

使用 Nautilus `0.55.0–0.62.0` 时，将上例中的 Nautilus 版本统一替换为所需版本，并将两个 Arrow 依赖改为 `"58"`。

#### 生成扩展类型

在自己项目的一个公共模块中调用一次，其他模块通过导入复用：

```rust
vikron_nautilus::define_catalog_types! {
    version: v2,
    arrow_array: ::arrow_array,
    arrow_schema: ::arrow_schema,
    pub mod vikron_data;
}
```

使用 Catalog v1 时，将 `version: v2` 改为 `version: v1`。

生成模块提供四种自定义数据类型，以及 `prepare_catalog` 和 `register_custom_types`。

请保留 `nautilus-core`、`nautilus-model` 和 `nautilus-serialization` 的依赖名，并让项目中的 Nautilus crates 使用同一版本。Arrow 依赖由用户配置，其他宏辅助依赖由 vikron 提供。

多个模块或 crate 应导入同一份生成类型。Arrow 依赖重命名后，在宏中使用对应的新路径。

## Catalog

每个进程使用前调用 `prepare_catalog(path)`，传入本地 Catalog 根目录，完成类型注册并返回绝对路径。将示例中的路径和品种替换为自己的数据。

Rust 从生成模块导入该函数，并处理返回的错误。可重复调用。

下载新增数据类型后，再次初始化目录。

### Windows

- **目录与网络盘**：支持本地目录和 UNC 路径（如 `\\server\share\vikron\catalog-v1`）。目录别名和网络盘映射由 `prepare_catalog` 自动处理；共享需要凭据时，先手动连接网络盘。
- **Python v1 精度**：Windows 上的 NautilusTrader 1.x 使用 8 字节定点数，读取 Downloader 的 16 字节数据前需转换。转换会将小数精度从 16 位舍入到 9 位，超出范围时返回错误：

  ```sh
  # 一次性全量转换（默认写到 <src>-int64，原 catalog 不动）
  python -m vikron_nautilus.downcast ./data/vikron/catalog-v1 ./data/vikron/catalog-v1-int64

  # 每天下载后只转新增文件（已存在的跳过）
  python -m vikron_nautilus.downcast ./data/vikron/catalog-v1 ./data/vikron/catalog-v1-int64 --incremental
  ```

  之后按 `price_precision=9`、`size_precision=9` 构造合约（tick 仍是 `0.1` / `0.001`）。

  也可以让 `prepare_catalog` 自动回退（第一次调用时转换，之后复用；只转新增文件）：

  ```python
  path = prepare_catalog("./data/vikron/catalog-v1", fallback="downcast")
  # 或全局生效：set VIKRON_WIDE_PRECISION_FALLBACK=downcast
  ```

  默认 `fallback="error"`。自动转换时会发出警告，副本写在原目录旁；原目录只读时写入 `%LOCALAPPDATA%\vikron\catalog-int64`。

- **Rust**：`high-precision` 在 Windows 上可正常编译，不受上面的精度限制；UNC 路径同样会自动映射盘符。

### Python · Catalog v1

```python
from vikron_nautilus import prepare_catalog, Liquidation
from nautilus_trader.persistence.catalog import ParquetDataCatalog

path = prepare_catalog("./data/vikron/catalog-v1")
catalog = ParquetDataCatalog(path)
symbol = "ETHUSDT-PERP.BINANCE"
events = catalog.query(Liquidation, identifiers=[symbol])
for event in events:
    print(event.data.instrument_id, event.data.price, event.data.size)
```

### Python · Catalog v2

```python
from vikron_nautilus import prepare_catalog, Liquidation
from nautilus_trader.model import NautilusDataType
from nautilus_trader.persistence import ParquetDataCatalog

path = prepare_catalog("./data/vikron/catalog-v2")
catalog = ParquetDataCatalog(path)
symbol = "ETHUSDT-PERP.BINANCE"
events = catalog.query_custom_data(
    NautilusDataType.Custom(Liquidation.type_name_static()),
    identifiers=[symbol],
)
for event in events:
    value = event.data
    print(value.instrument_id, value.price, value.size, value.ts_init)
```

Python v1 / v2 均可通过 `identifiers` 筛选品种（完整的 Nautilus `InstrumentId`），通过 `start`、`end` 指定时间范围（Unix 纳秒，按 `ts_init` 筛选，包含起止边界）。

### Rust · Catalog v1 / v2

以下是 v2 查询示例。示例在二进制入口生成模块；如果已经在库入口生成，请改为导入公共模块，删除重复的宏调用。

```rust
use nautilus_model::data::Data;
use nautilus_persistence::backend::parquet::catalog::ParquetDataCatalog;
vikron_nautilus::define_catalog_types! {
    version: v2,
    arrow_array: ::arrow_array,
    arrow_schema: ::arrow_schema,
    pub mod vikron_data;
}

use vikron_data::{Liquidation, prepare_catalog};

fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let path = prepare_catalog("./data/vikron/catalog-v2")?;
    let mut catalog = ParquetDataCatalog::new(&path, None, None, None, None);
    let identifiers = vec!["ETHUSDT-PERP.BINANCE".to_string()];
    let events = catalog.query_custom_data_dynamic(
        "VikronLiquidation", Some(&identifiers), None, None, None, None, false,
    )?;

    for event in events {
        if let Data::Custom(custom) = event {
            if let Some(value) = custom.data.as_any().downcast_ref::<Liquidation>() {
                println!("{} {} {}", value.instrument_id, value.price, value.size);
            }
        }
    }
    Ok(())
}
```

使用 v1 时，将宏参数改为 `version: v1`、目录改为 `catalog-v1`，并将 `ParquetDataCatalog` 的导入改为：

```rust
use nautilus_persistence::backend::catalog::ParquetDataCatalog;
```

自定义数据的查询名称为类型名前加 `Vikron`，例如 `VikronOpenInterestUpdate`。

## BacktestNode

### Python · v1

沿用 Python Catalog v1 示例中的 `catalog` 和 `path`，通过 Binance 官方适配器在线获取 Instrument，再写入同一 Catalog：

```python
import asyncio
from decimal import Decimal

from nautilus_trader.adapters.binance.futures.providers import BinanceFuturesInstrumentProvider
from nautilus_trader.adapters.binance.http.client import BinanceHttpClient
from nautilus_trader.common.component import LiveClock
from nautilus_trader.model.identifiers import InstrumentId
from nautilus_trader.model.objects import FIXED_PRECISION


async def fetch_instrument(symbol):
    clock = LiveClock()
    client = BinanceHttpClient(
        clock=clock, api_key=None, api_secret=None,
        base_url="https://fapi.binance.com",
    )
    provider = BinanceFuturesInstrumentProvider(client=client, clock=clock)
    instrument_id = InstrumentId.from_str(symbol)
    await provider.load_ids_async([instrument_id])
    instrument = provider.find(instrument_id)
    if instrument is None:
        raise ValueError(f"交易所未返回品种信息：{symbol}")

    # 匹配运行时精度：通常为 16 位，Windows v1 转换后为 9 位。
    # 保留交易所的价格与数量步长。
    values = instrument.to_dict(instrument)
    values.update(price_precision=FIXED_PRECISION, size_precision=FIXED_PRECISION)
    for field in ("price_increment", "size_increment"):
        values[field] = format(Decimal(values[field]), f".{FIXED_PRECISION}f")
    return type(instrument).from_dict(values)


symbol = "ETHUSDT-PERP.BINANCE"
instrument = asyncio.run(fetch_instrument(symbol))
catalog.write_data([instrument])
```

在线接口返回当前品种参数；历史回测需核对当时的步长和交易规则，已下架品种需另行提供 Instrument。未配置 API 凭证时，适配器使用默认手续费档位。

接着配置回测：

```python
from nautilus_trader.backtest.config import (
    BacktestDataConfig, BacktestEngineConfig, BacktestRunConfig, BacktestVenueConfig,
)
from nautilus_trader.backtest.node import BacktestNode

config = BacktestRunConfig(
    venues=[BacktestVenueConfig(
        name="BINANCE", oms_type="NETTING", account_type="MARGIN",
        starting_balances=["100000 USDT"],
    )],
    data=[
        BacktestDataConfig(
            catalog_path=path, data_cls="nautilus_trader.model.data:QuoteTick",
            instrument_ids=[symbol],
        ),
        BacktestDataConfig(
            catalog_path=path, data_cls="vikron_nautilus:Liquidation",
            instrument_ids=[symbol], client_id="VIKRON",
            metadata={"instrument_id": symbol},
        ),
    ],
    engine=BacktestEngineConfig(),  # 在此配置自己的策略。
    chunk_size=10000,              # 设置为 None 时一次性加载。
    raise_exception=True,
)
node = BacktestNode(configs=[config])
try:
    results = node.run()
finally:
    node.dispose()
```

策略订阅方式见下方 BacktestEngine 的 Python 示例；metadata 和 client ID 需与上述配置一致。

### Python · v2、Rust · v1 / v2

自定义数据请使用下方 BacktestEngine 方式。

## BacktestEngine

以下为数据接入片段：`engine` 已配置交易场所、手续费和初始资金，`strategy` 是自己的策略，`instrument` 是在线获取的品种信息，价格和数量精度需匹配 Catalog。分批加载和时间顺序由使用者管理。

### Python · v1

沿用 Catalog v1 示例中的 `events` 和 `symbol`，补上与订阅一致的 metadata，再加入 Engine：

```python
from nautilus_trader.model.data import CustomData, DataType
from nautilus_trader.model.identifiers import ClientId

kind = DataType(Liquidation, {"instrument_id": symbol})
events = [CustomData(kind, event.data) for event in events]
engine.add_instrument(instrument)
engine.add_strategy(strategy)
engine.add_data(events, client_id=ClientId("VIKRON"), sort=True)
try:
    engine.run()
finally:
    engine.dispose()
```

在策略的 `on_start` 中订阅，metadata 应与数据一致；在 `on_data` 中处理事件：

```python
from nautilus_trader.model.data import DataType
from nautilus_trader.model.identifiers import ClientId

# 放在策略的 on_start 中；symbol 为 Catalog 示例中的品种。
kind = DataType(Liquidation, {"instrument_id": symbol})
self.subscribe_data(kind, client_id=ClientId("VIKRON"))
```

### Python · v2

沿用 Catalog v2 示例中的 `events` 和 `symbol`，将数据加入 Engine：

```python
from nautilus_trader.model import ClientId

engine.add_instrument(instrument)
engine.add_strategy(strategy)
engine.add_data(events, client_id=ClientId("VIKRON"), sort=True)
try:
    engine.run()
finally:
    engine.dispose()
```

在策略的 `on_start` 中订阅，在 `on_data` 中处理事件：

```python
from nautilus_trader.model import ClientId, DataType

# 放在策略的 on_start 中；symbol 为 Catalog 示例中的品种。
kind = DataType(Liquidation.type_name_static(), {"instrument_id": symbol}, symbol)
self.subscribe_data(kind, client_id=ClientId("VIKRON"))
```

### Rust · v1 / v2

添加 `nautilus-backtest`、`nautilus-common` 和 `nautilus-trading` 依赖。使用对应的 Catalog 初始化方式后：

```rust
use nautilus_model::identifiers::ClientId;

let events = catalog.query_custom_data_dynamic(
    "VikronLiquidation", Some(&identifiers), None, None, None, None, false,
)?;
engine.add_instrument(&instrument)?;
engine.add_strategy(strategy)?;
engine.add_data(events, Some(ClientId::from("VIKRON")), true, true)?;
engine.run(None, None, None, false)?;
engine.dispose();
```

从查询结果中的 `Data::Custom(custom)` 取出 `custom.data_type.clone()`，传给自己的策略；在 `on_start` 中订阅：

```rust
use nautilus_common::actor::DataActor;

self.subscribe_data(kind, Some(ClientId::from("VIKRON")), None);
```

在策略的 `on_data` 回调中处理 `CustomData`。

## Rust 自定义类型

四种类型均包含以下公共字段：

| 字段 | Rust 类型 | 含义 |
| --- | --- | --- |
| `instrument_id` | `InstrumentId` | 完整 Nautilus 品种标识 |
| `ts_event` | `UnixNanos` | 事件时间 |
| `ts_init` | `UnixNanos` | 初始化时间 |

| Rust 类型 | 注册名称 | 额外字段 |
| --- | --- | --- |
| `Liquidation` | `VikronLiquidation` | `source_id: Option<String>`、`side: OrderSide`、`price: Price`、`size: Quantity` |
| `OpenInterestUpdate` | `VikronOpenInterestUpdate` | `open_interest: Decimal` |
| `LastPriceUpdate` | `VikronLastPriceUpdate` | `price: Price` |
| `PredictedFundingRateUpdate` | `VikronPredictedFundingRateUpdate` | `rate: Decimal`、`funding_timestamp: Option<UnixNanos>` |

`Decimal` 字段可通过字符串的 `.parse()` 构造。类型提供 `data_type(instrument_id)`，用于创建订阅的数据类型。

这些是观测数据，不会自动触发订单撮合或资金费结算。持仓量单位沿用数据源；预测资金费率与实际结算资金费率分别保留。

## 常见问题

### 查询提示 custom data type is not registered？

确认已在查询前成功调用生成模块的 `prepare_catalog` 或 `register_custom_types`，并处理返回的错误。检查查询名称是否使用 `Vikron` 前缀，以及数据目录和宏参数是否对应。每个进程都需要独立注册。

### 为什么查询成功，但 downcast_ref 返回 None？

确认注册时使用的类型与向下转型的类型来自同一个生成模块。重复展开宏或多个共享适配 crate 的副本，都可能产生不同的类型身份。

## 许可证

本库采用 [MIT 许可证](./LICENSE)。Vikron Downloader、NautilusTrader 和行情数据分别适用各自的许可或授权条款。
