# 获取代币的 DEX 日线（开高低收与 VWAP）与精确分数

> 原文地址: https://docs.blockvectra.com/zh/guides/dex-daily-prices/

## 什么是 DEX 日线数据集 [#什么是-dex-日线数据集]

BlockVectra 的 DEX 数据集涵盖链上去中心化交易所（DEX）的成交记录与日聚合价格。其中，DEX 日线接口（`getDexPrices`）提供指定代币在指定日期范围内的每日成交量加权平均价（VWAP）、价格指标（字段名 `first_price`、`last_price`、`min_price`、`max_price`）以及成交量指标。

各链开放该数据集的情况有所不同，提供该数据集的链以[支持的链](/zh/chains/)页面为准。

该端点返回指定时间窗口内的数据列表，不进行分页，响应体中永不包含 `next_cursor`。如果业务需要逐笔具体的成交流水而非按天聚合的日线，请参阅 `GET /{chain}/dex/swaps` 端点（详见 [Data API 参考](/zh/api/data/)）。

## 请求参数与规格限制 [#请求参数与规格限制]

DEX 日线请求地址为 `GET https://dev-api.blockvectra.network/v1/data/{chain}/dex/prices`。所有请求必须在 `x-api-key` 请求头中传入 API key 进行鉴权。

端点支持以下查询参数：

| 参数名     | 位置    | 类型    | 是否必填 | 规格说明                                                     |
| ------- | ----- | ----- | ---- | -------------------------------------------------------- |
| `chain` | path  | 字符串   | 是    | 目标链标识，例如 `robinhood_mainnet`                             |
| `token` | query | 字符串   | 是    | 20 字节基础代币地址（base token address），`0x` 前缀可选，大小写均可          |
| `quote` | query | 字符串   | 否    | 可选的 20 字节计价代币地址，用于限定为单个基础/计价代币交易对                        |
| `from`  | query | 日期字符串 | 是    | UTC 起始日期（包含），格式为 `YYYY-MM-DD`                            |
| `to`    | query | 日期字符串 | 是    | UTC 截止日期（包含），格式为 `YYYY-MM-DD`，且 `to - from` 必须 `<= 90` 天 |

### 规格约束与错误码 [#规格约束与错误码]

接口在参数不符合规格时返回标准错误结构 `{"error":{"code","message"}}`：

* **HTTP 400 (`bad_request`)**：缺少必需参数（`token`、`from` 或 `to`）、`token` 或 `quote` 代币地址格式无效、日期不是合法日历日期，或 `from` 晚于 `to`。
* **HTTP 409 (`span_exceeded`)**：请求跨度 `to - from` 超过 90 天。
* **HTTP 404 (`unknown_chain`)**：`{chain}` 不在 `GET /chains` 返回的链列表中。
* **HTTP 422 (`no_coverage`)**：请求的链未提供 `dex_prices` 数据集能力。
* **HTTP 503 (`unavailable`)**：服务暂时不可用，可依照响应的 `Retry-After` 头重试。

## 请求示例 [#请求示例]

下方示例查询特定基础代币在 2026 年 9 月份的日线数据：

<Tabs groupId="code-lang" items="['cURL', 'TypeScript', 'Python']">
  <Tab value="cURL">
    ```bash
    curl -s "https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/dex/prices?token=0x2260fac5e5542a773aa44fbcfedf7c193bc2c599&from=2026-09-01&to=2026-09-30" \
      -H "x-api-key: $BLOCKVECTRA_API_KEY"
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    const url = new URL("https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/dex/prices");
    url.searchParams.set("token", "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599");
    url.searchParams.set("from", "2026-09-01");
    url.searchParams.set("to", "2026-09-30");

    const res = await fetch(url, {
      headers: {
        "x-api-key": process.env.BLOCKVECTRA_API_KEY!,
      },
    });

    if (!res.ok) {
      throw new Error(`Request failed with status ${res.status}`);
    }

    const body = await res.json();
    console.log(body);
    ```
  </Tab>

  <Tab value="Python">
    ```python
    import os
    import requests

    res = requests.get(
        "https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/dex/prices",
        params={
            "token": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599",
            "from": "2026-09-01",
            "to": "2026-09-30",
        },
        headers={
            "x-api-key": os.environ["BLOCKVECTRA_API_KEY"],
        },
    )
    res.raise_for_status()
    print(res.json())
    ```
  </Tab>
</Tabs>

## 返回字段详解 [#返回字段详解]

`data` 数组中的每项表示该代币在某一 UTC 日期的交易汇总：

### 代币与计价信息 [#代币与计价信息]

* `day`（字符串）：UTC 日期，格式为 `YYYY-MM-DD`。
* `token`（字符串）：20 字节基础代币地址（`0x` 前缀的小写十六进制）。
* `token_symbol`（字符串或 `null`）：基础代币符号。
* `token_name`（字符串或 `null`）：基础代币名称。
* `quote_token`（字符串）：计价资产地址。全零地址（`0x0000000000000000000000000000000000000000`）代表原生 ETH 作为计价资产。
* `quote_symbol`（字符串或 `null`）：计价资产符号，当 `quote_token` 为全零地址时固定为 `"ETH"`。
* `quote_name`（字符串或 `null`）：计价资产名称，当 `quote_token` 为全零地址时固定为 `"Ether"`。
* `base_decimals`（整数或 `null`）：基础代币小数位数（0–255）。
* `quote_decimals`（整数或 `null`）：计价资产小数位数，当 `quote_token` 为全零地址时固定为 `18`。

### 交易量与成交统计 [#交易量与成交统计]

* `swap_count`（整数）：该行对应的成交笔数。
* `base_volume_raw`（字符串）：基础代币原始成交量，以最小单位表示的非负十进制整数字符串（`UInt256String`）。
* `quote_volume_raw`（字符串）：计价资产原始成交量，以最小单位表示的非负十进制整数字符串（`UInt256String`）。
* `base_volume`（字符串或 `null`）：经 `base_decimals` 换算后的基础代币人类可读成交量（`DecimalString`）；若 `base_decimals` 未知则为 `null`。
* `quote_volume`（字符串或 `null`）：经计价代币小数位换算后的计价成交量（`DecimalString`），或 `null`。

### 价格指标与 VWAP [#价格指标与-vwap]

* `vwap`（字符串或 `null`）：成交量加权平均价（以小数定点数字符串表示），或 `null`。
* `first_price`（字符串或 `null`）：首个价格指标（以定点数字符串表示），或 `null`。
* `last_price`（字符串或 `null`）：末个价格指标（以定点数字符串表示），或 `null`。
* `min_price`（字符串或 `null`）：最低价格指标（以定点数字符串表示），或 `null`。
* `max_price`（字符串或 `null`）：最高价格指标（以定点数字符串表示），或 `null`。

### 精确分数表示 [#精确分数表示]

* `first_price_numerator` / `first_price_denominator`（字符串）：`first_price` 的精确整数分子与分母（`UInt256String`）。
* `last_price_numerator` / `last_price_denominator`（字符串）：`last_price` 的精确整数分子与分母（`UInt256String`）。
* `min_price_numerator` / `min_price_denominator`（字符串）：`min_price` 的精确整数分子与分母（`UInt256String`）。
* `max_price_numerator` / `max_price_denominator`（字符串）：`max_price` 的精确整数分子与分母（`UInt256String`）。
* `refreshed_at`（字符串）：该行的刷新时间（ISO-8601 UTC 时间戳）。

### 元数据字段 (`meta`) [#元数据字段-meta]

* `chain`：链标识名称。
* `chain_slug`：全大写规范链标识。
* `chain_external_id`：CAIP-2 标准外部链 ID。
* `as_of_block`：该响应 finality 水位线对应的索引头高度（本数据集仅上报，不参与请求校验）。
* `finalized_block`：重组安全水位线区块高度（并非共识最终性）。
* `coverage`：覆盖度状态（本端点固定为 `"full"`）。
* `refreshed_at`：元数据时间戳。

## 为什么价格使用分子与分母表示 [#为什么价格使用分子与分母表示]

链上去中心化交易所的价格源自自动做市商（AMM）流动性池的代币储备比值或兑换公式。

直接使用标准 JSON 数字（IEEE-754 双精度浮点数）存在精度局限：

1. **浮点截断与舍入偏差**：双精度浮点数仅有 53 位有效精度，在储备比值除法运算中会产生舍入误差并在后续计算中级联放大。
2. **传输安全**：以十进制整数字符串（`UInt256String`）表示分子与分母，确保数值在 HTTP 传输与 JSON 解析过程中不丢失精度。

通过返回精确的整数分子与分母，用户可以在不经过浮点数转换的情况下进行计算，在量化策略、套利监测与精确会计核算中避免浮点舍入误差。

### 在 TypeScript（BigInt）中不经过浮点数处理 [#在-typescriptbigint中不经过浮点数处理]

在 TypeScript 环境下，可使用原生 `BigInt` 实现不经过浮点数转换的比率比较与定点数转换：

```ts
interface DexDailyPrice {
  first_price_numerator: string;
  first_price_denominator: string;
  last_price_numerator: string;
  last_price_denominator: string;
}

// 1. 比率比较（不经过浮点数）：判断收盘价是否高于开盘价
// a / b > c / d  等价于  a * d > c * b
export function isPriceUp(row: DexDailyPrice): boolean {
  const openNum = BigInt(row.first_price_numerator);
  const openDen = BigInt(row.first_price_denominator);
  const closeNum = BigInt(row.last_price_numerator);
  const closeDen = BigInt(row.last_price_denominator);

  return closeNum * openDen > openNum * closeDen;
}

// 2. 将分子分母精确转换为指定小数位数的定点数字符串（不经过浮点数）
export function fractionToFixedString(
  numeratorStr: string,
  denominatorStr: string,
  decimals = 18
): string {
  const num = BigInt(numeratorStr);
  const den = BigInt(denominatorStr);
  if (decimals === 0) {
    return (num / den).toString();
  }
  const scaleFactor = 10n ** BigInt(decimals);

  const scaled = (num * scaleFactor) / den;
  const intPart = scaled / scaleFactor;
  const remainder = scaled % scaleFactor;
  const fracPart = remainder.toString().padStart(decimals, "0");

  return `${intPart}.${fracPart}`;
}
```

### 在 Python 中不经过浮点数处理 [#在-python-中不经过浮点数处理]

Python 提供了高精度数学工具标准库：`fractions.Fraction` 用于有理数运算，`decimal.Decimal` 用于指定精度的高精度十进制换算。

```python
from decimal import Decimal, getcontext
from fractions import Fraction

# 1. 使用 fractions.Fraction 进行精确有理数运算
open_price = Fraction(
    int(row["first_price_numerator"]),
    int(row["first_price_denominator"])
)
close_price = Fraction(
    int(row["last_price_numerator"]),
    int(row["last_price_denominator"])
)

# 精确价格变动（不经过浮点运算）
price_delta = close_price - open_price
print(f"Price delta (fraction): {price_delta}")

if open_price != 0:
    percentage_change = (price_delta / open_price) * 100
    print(f"Percentage change: {float(percentage_change):.4f}%")

# 2. 使用 decimal.Decimal 设置自定义高精度（如 50 位有效数字）
getcontext().prec = 50

if int(row["first_price_denominator"]) != 0:
    open_decimal = Decimal(row["first_price_numerator"]) / Decimal(row["first_price_denominator"])
    print(f"High precision open: {open_decimal}")
```

## 回填一年历史日线的分段请求写法 [#回填一年历史日线的分段请求写法]

在 90 天单次跨度限制下回填一整年（365 天）历史日线时，需将目标时间段按不超过 90 天的窗口切分成多个子区间，依次发起请求并合并结果：

```ts
interface DateSpan {
  from: string;
  to: string;
}

/**
 * 将任意较长日期区间按最大天数（上限 90 天）切分为连续子区间
 */
export function splitDateRange(startDateStr: string, endDateStr: string, maxDays = 90): DateSpan[] {
  const spans: DateSpan[] = [];
  let currentStart = new Date(startDateStr);
  const end = new Date(endDateStr);

  while (currentStart <= end) {
    const chunkEnd = new Date(currentStart);
    chunkEnd.setUTCDate(chunkEnd.getUTCDate() + (maxDays - 1));
    const effectiveEnd = chunkEnd < end ? chunkEnd : end;

    spans.push({
      from: currentStart.toISOString().slice(0, 10),
      to: effectiveEnd.toISOString().slice(0, 10),
    });

    const nextStart = new Date(effectiveEnd);
    nextStart.setUTCDate(nextStart.getUTCDate() + 1);
    currentStart = nextStart;
  }

  return spans;
}

/**
 * 分段回填特定代币的历史日线
 */
export async function backfillTokenDailyPrices(
  chain: string,
  token: string,
  startDate: string,
  endDate: string,
  apiKey: string
) {
  const chunks = splitDateRange(startDate, endDate, 90);
  const allDailyPrices = [];

  for (const chunk of chunks) {
    const url = new URL(`https://dev-api.blockvectra.network/v1/data/${chain}/dex/prices`);
    url.searchParams.set("token", token);
    url.searchParams.set("from", chunk.from);
    url.searchParams.set("to", chunk.to);

    const res = await fetch(url, {
      headers: { "x-api-key": apiKey },
    });

    if (!res.ok) {
      throw new Error(`Failed to fetch span ${chunk.from}..${chunk.to}: HTTP ${res.status}`);
    }

    const json = await res.json();
    allDailyPrices.push(...json.data);
  }

  return allDailyPrices;
}
```

## 用量与 CU 消耗测算 [#用量与-cu-消耗测算]

调用 Data API 的每个方法均按计算单元（CU）计量。单次调用 `data.dex_prices` 消耗的 CU 以及批量回填任务的测算如下所示。所有数值均在构建时从平台计划与方法权重数据实时计算得出：

<DexPricesEstimate lang="zh" />

当你的回填数据量较大或并发请求较高时，可以在[控制台](https://console.blockvectra.com/zh/login/)充值以获得更高的速率上限与充裕的计算单元。关于当前的计量单元与定价细则，请参阅[定价页](https://blockvectra.com/zh/pricing/)。

## 下一步 [#下一步]

* [浏览数据集目录](https://blockvectra.com/zh/data/)，查看 BlockVectra 索引的全部数据集。
* [查看免费额度与定价](https://blockvectra.com/zh/pricing/#free)，确认账户可用的方案。
* [登录控制台](https://console.blockvectra.com/zh/login/?next=%2Fzh%2Fkeys%2F)创建 API key。
