# 代幣的每日 DEX OHLC 與 VWAP，使用精確分數

> Source: https://docs.blockvectra.com/zh-hant/guides/dex-daily-prices/

## 什麼是 DEX 每日價格資料集

BlockVectra 的 DEX 資料集會索引去中心化交易所交易活動，並計算每日彙總定價指標。DEX 每日價格端點（`getDexPrices`）提供指定代幣在指定日期範圍內的每日成交量加權平均價格（VWAP）、價格指標（欄位 `first_price`、`last_price`、`min_price` 與 `max_price`），以及成交量指標。

此資料集的可用性因網路而異；提供此資料集的鏈以[支援的鏈](https://docs.blockvectra.com/zh-hant/chains/)頁面為準。

此端點不分頁：要求日期範圍內的所有相符每日資料列都會直接傳回於 `data` 中，且絕不會包含 `next_cursor`。如果你的應用程式需要的是逐筆交換交易，而非每日彙總，請使用 `GET /{chain}/dex/swaps`（請參閱 [Data API 參考](https://docs.blockvectra.com/en/api/data/)）。

## 請求參數與限制

端點路徑為 `GET https://api.blockvectra.com/v1/data/{chain}/dex/prices`。所有請求都必須在 `x-api-key` 標頭中提供 API key 進行驗證。

此端點接受下列查詢參數：

| 參數      | 位置    | 類型          | 必填 | 說明                                                      |
| ------- | ----- | ----------- | -- | ------------------------------------------------------- |
| `chain` | path  | string      | 是  | 鏈識別碼，例如 `robinhood_mainnet`                             |
| `token` | query | string      | 是  | 20 位元組的基礎代幣地址，`0x` 可省略，大小寫皆可                            |
| `quote` | query | string      | 否  | 選用的 20 位元組計價代幣地址，用於限定單一基礎／計價交易對                         |
| `from`  | query | date string | 是  | UTC 開始日期（含當日），格式為 `YYYY-MM-DD`                          |
| `to`    | query | date string | 是  | UTC 結束日期（含當日），格式為 `YYYY-MM-DD`。`to - from` 必須 `<= 90` 天 |

### 限制與錯誤碼

當請求違反限制時，API 會傳回結構化錯誤主體 `{"error":{"code","message"}}`：

* <strong>HTTP 400</strong>（`bad_request`）：缺少必填查詢參數（`token`、`from` 或 `to`）、`token`／`quote` 地址語法無效、`YYYY-MM-DD` 日曆日期無效，或 `from` 晚於 `to`。
* <strong>HTTP 409</strong>（`span_exceeded`）：`to - from` 超過 90 天。
* <strong>HTTP 404</strong>（`unknown_chain`）：`{chain}` 不是 `GET /chains` 所列出的鏈。
* <strong>HTTP 422</strong>（`no_coverage`）：該鏈不支援 `dex_prices` 資料集能力。
* <strong>HTTP 503</strong>（`unavailable`）：服務暫時無法使用；請依照 `Retry-After` 標頭重試。

## 請求範例

下列範例查詢某個基礎代幣在 2026 年 9 月的每日 DEX 價格：

**cURL**

```bash
curl -s "https://api.blockvectra.com/v1/data/robinhood_mainnet/dex/prices?token=0x1Cdad396DB64BDa184d5182A97Dd9B3C62100b7D&from=2026-09-01&to=2026-09-30" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY"
```


  **TypeScript**

```ts
const url = new URL("https://api.blockvectra.com/v1/data/robinhood_mainnet/dex/prices");
url.searchParams.set("token", "0x1Cdad396DB64BDa184d5182A97Dd9B3C62100b7D");
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);
```


  **Python**

```python
import os
import requests

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


## 欄位詳細參考

`data` 中的每一項代表該代幣對在該 UTC 日期的每日 DEX 彙總指標：

### 代幣與計價資產

* `day`（string）：UTC 日期，格式為 `YYYY-MM-DD`。
* `token`（string）：20 位元組的基礎代幣地址，為小寫且帶 `0x` 前置詞的十六進位。
* `token_symbol`（string 或 `null`）：基礎代幣符號。
* `token_name`（string 或 `null`）：基礎代幣顯示名稱。
* `quote_token`（string）：計價資產地址。全零地址（`0x0000000000000000000000000000000000000000`）代表以原生 ETH 作為計價資產。
* `quote_symbol`（string 或 `null`）：計價資產符號（當 `quote_token` 為全零地址時為 `"ETH"`）。
* `quote_name`（string 或 `null`）：計價資產顯示名稱（當 `quote_token` 為全零地址時為 `"Ether"`）。
* `base_decimals`（integer 或 `null`）：基礎代幣小數位數（0–255）。
* `quote_decimals`（integer 或 `null`）：計價資產小數位數（當 `quote_token` 為全零地址時為 `18`）。

### 成交量與交易筆數

* `swap_count`（integer）：此資料列的交換筆數。
* `base_volume_raw`（string）：以最小單位表示的基礎代幣成交量，為無正負號的十進位整數字串（`UInt256String`）。
* `quote_volume_raw`（string）：以最小單位表示的計價代幣成交量，為無正負號的十進位整數字串（`UInt256String`）。
* `base_volume`（string 或 `null`）：依 `base_decimals` 縮放後的人類可讀基礎代幣成交量，為 `DecimalString`；當 `base_decimals` 未知時為 `null`。
* `quote_volume`（string 或 `null`）：依計價代幣小數位數縮放的計價成交量，為 `DecimalString`，或 `null`。

### 價格指標與 VWAP

* `vwap`（string 或 `null`）：成交量加權平均價格，為 `DecimalString`，或 `null`。
* `first_price`（string 或 `null`）：首個價格指標，為 `DecimalString`，或 `null`。
* `last_price`（string 或 `null`）：最後價格指標，為 `DecimalString`，或 `null`。
* `min_price`（string 或 `null`）：最低價格指標，為 `DecimalString`，或 `null`。
* `max_price`（string 或 `null`）：最高價格指標，為 `DecimalString`，或 `null`。

### 精確分數欄位

* `first_price_numerator` / `first_price_denominator`（string）：`first_price` 的精確整數分子與分母（`UInt256String`）。
* `last_price_numerator` / `last_price_denominator`（string）：`last_price` 的精確整數分子與分母（`UInt256String`）。
* `min_price_numerator` / `min_price_denominator`（string）：`min_price` 的精確整數分子與分母（`UInt256String`）。
* `max_price_numerator` / `max_price_denominator`（string）：`max_price` 的精確整數分子與分母（`UInt256String`）。
* `refreshed_at`（string）：此資料列的重新整理時間戳（ISO-8601 UTC 時間戳）。

### 回應結構中繼資料（`meta`）

* `chain`：鏈識別碼。
* `chain_slug`：標準的大寫鏈 slug。
* `chain_external_id`：CAIP-2 格式的鏈識別碼。
* `as_of_block`：該鏈最新完整寫入的區塊（由此資料集回報，不會與請求參數核對）。
* `coverage`：涵蓋範圍分類（此端點回報 `"full"`）。
* `refreshed_at`：中繼資料的重新整理時間戳。可能為 `null`：`null` 表示此資料的更新時間未知，應視為過期；以區塊為基礎的端點一律會傳回值。

## 為什麼價格使用精確分子與分母

標準 JSON 數字依賴 IEEE-754 雙精度浮點數，因而有精確度限制：

1. **浮點截斷與漂移**：Float64 值僅提供 53 位元精確度，而相除代幣數量會產生四捨五入漂移，並在後續計算中累積放大。
2. **傳輸安全**：將值格式化為十進位字串（`UInt256String`）可確保數字在 HTTP 傳輸過程中，不會在 JSON 解析器中失去精確度。

藉由為價格指標提供精確的整數分子與分母，BlockVectra 能進行精確的數學計算，無需轉換為浮點數。

### 在 TypeScript 中處理精確分數（BigInt）

在 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 提供專為有理數與小數計算打造的標準程式庫模組：`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 進行任意精確度的小數運算
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;
}

/**
 * 將大型日期範圍切分為最多 maxDays（預設：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;
}

/**
 * 跨多個 90 天分段回填代幣每日價格
 */
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://api.blockvectra.com/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 用量計算

每個 Data API 端點都會以計算單位（CU）計量消耗。`data.dex_prices` 的每次呼叫 CU 權重，以及代幣回填的預估消耗，計算如下：

- **方法計費權重：** `data.dex_prices` = 15 CU / 次。
- **回填 200 個代幣一年日線：** 單次請求最大跨度為 90 天，一年 365 天需分 5 段請求，總計 1,000 次呼叫，消耗 15,000 CU（約佔單用量週期免費額度的 <0.1%），按標價換算約 <$0.01。
- **日常維護（每天重新整理 200 個代幣的最新日線）：** 每天呼叫 200 次（每天消耗 3,000 CU），每個 30 天週期約呼叫 6,000 次，消耗 90,000 CU（約佔免費額度的 0.3%），按標價約每月 <$0.01。

當你需要擴充回填量或需要更高的請求並行量時，請在控制台[帳單頁面](https://console.blockvectra.com/billing/)進行鏈上儲值，以升級為付費帳戶。關於目前的費率與單位換算，請參閱[定價頁面](https://blockvectra.com/zh-hant/pricing/)。

## 下一步

* [瀏覽資料集目錄](https://blockvectra.com/zh-hant/data/)，查看 BlockVectra 索引的全部資料集。
* [查看免費方案與定價](https://blockvectra.com/zh-hant/pricing/#free)，確認你的帳戶包含的內容。
* [登入控制台](https://console.blockvectra.com/login/?next=%2Fkeys%2F)建立 API key。
