OHLC và VWAP DEX hàng ngày cho token với phân số chính xác

Truy vấn giá OHLC và VWAP DEX hàng ngày từ Data API, xử lý phân số hữu tỉ chính xác trong TypeScript và Python, đồng thời tải bù dữ liệu lịch sử hiệu quả.

Bộ dữ liệu giá DEX hàng ngày là gì

Bộ dữ liệu DEX của BlockVectra lập chỉ mục hoạt động giao dịch trên các sàn giao dịch phi tập trung và tính toán các chỉ số giá tổng hợp hàng ngày. Endpoint giá DEX hàng ngày (getDexPrices) cung cấp giá trung bình có trọng số theo khối lượng (VWAP) hàng ngày, các chỉ số giá (trường first_price, last_price, min_price và max_price) cùng các chỉ số khối lượng cho token được chỉ định trong một khoảng ngày nhất định.

Tính khả dụng của bộ dữ liệu này khác nhau giữa các mạng; các chuỗi cung cấp bộ dữ liệu này được liệt kê trên trang Các chuỗi được hỗ trợ.

Endpoint này không phân trang: tất cả các hàng dữ liệu hàng ngày khớp với khoảng ngày yêu cầu được trả về trực tiếp trong data, và không bao giờ có next_cursor. Nếu ứng dụng của bạn cần từng giao dịch swap thay vì dữ liệu tổng hợp hàng ngày, hãy dùng GET /{chain}/dex/swaps (xem Tài liệu tham khảo Data API).

Tham số yêu cầu và giới hạn

Đường dẫn endpoint là GET https://api.blockvectra.com/v1/data/{chain}/dex/prices. Mọi yêu cầu đều cần xác thực bằng API key trong header x-api-key.

Endpoint chấp nhận các tham số truy vấn sau:

Tham sốVị tríKiểuBắt buộcMô tả
chainpathstringCóĐịnh danh chuỗi, ví dụ robinhood_mainnet
tokenquerystringCóĐịa chỉ token cơ sở 20 byte, 0x không bắt buộc, chấp nhận cả chữ hoa và chữ thường
quotequerystringKhôngĐịa chỉ token định giá 20 byte tùy chọn để giới hạn ở một cặp token cơ sở/token định giá
fromquerydate stringCóNgày bắt đầu UTC, bao gồm ngày này, YYYY-MM-DD
toquerydate stringCóNgày kết thúc UTC, bao gồm ngày này, YYYY-MM-DD. to - from phải là <= 90 ngày

Ràng buộc và mã lỗi

Khi yêu cầu vi phạm ràng buộc, API trả về nội dung lỗi có cấu trúc {"error":{"code","message"}}:

  • HTTP 400 (bad_request): Thiếu tham số truy vấn bắt buộc (token, from hoặc to), cú pháp địa chỉ token/quote không hợp lệ, ngày lịch YYYY-MM-DD không hợp lệ hoặc from sau to.
  • HTTP 409 (span_exceeded): to - from vượt quá 90 ngày.
  • HTTP 404 (unknown_chain): {chain} không nằm trong các chuỗi được liệt kê bởi GET /chains.
  • HTTP 422 (no_coverage): Chuỗi không hỗ trợ bộ dữ liệu dex_prices.
  • HTTP 503 (unavailable): Dịch vụ tạm thời không khả dụng; thử lại theo header Retry-After.

Ví dụ yêu cầu

Các ví dụ sau truy vấn giá DEX hàng ngày cho một token cơ sở trong tháng 9 năm 2026:

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"

Tham khảo chi tiết các trường

Mỗi mục trong data biểu thị các chỉ số DEX tổng hợp hàng ngày cho cặp token vào ngày UTC đó:

Token và tài sản định giá

  • day (string): Ngày UTC ở định dạng YYYY-MM-DD.
  • token (string): Địa chỉ token cơ sở 20 byte ở dạng thập lục phân chữ thường với tiền tố 0x.
  • token_symbol (string hoặc null): Ký hiệu token cơ sở.
  • token_name (string hoặc null): Tên hiển thị của token cơ sở.
  • quote_token (string): Địa chỉ tài sản định giá. Địa chỉ toàn số không (0x0000000000000000000000000000000000000000) biểu thị ETH gốc làm tài sản định giá.
  • quote_symbol (string hoặc null): Ký hiệu tài sản định giá ("ETH" khi quote_token là địa chỉ toàn số không).
  • quote_name (string hoặc null): Tên hiển thị của tài sản định giá ("Ether" khi quote_token là địa chỉ toàn số không).
  • base_decimals (integer hoặc null): Số chữ số thập phân của token cơ sở (0–255).
  • quote_decimals (integer hoặc null): Số chữ số thập phân của tài sản định giá (18 khi quote_token là địa chỉ toàn số không).

Khối lượng và số giao dịch

  • swap_count (integer): Số giao dịch swap trong hàng này.
  • base_volume_raw (string): Khối lượng token cơ sở theo đơn vị nhỏ nhất, dưới dạng chuỗi số nguyên thập phân không dấu (UInt256String).
  • quote_volume_raw (string): Khối lượng tài sản định giá theo đơn vị nhỏ nhất, dưới dạng chuỗi số nguyên thập phân không dấu (UInt256String).
  • base_volume (string hoặc null): Khối lượng token cơ sở ở dạng dễ đọc, được quy đổi theo base_decimals dưới dạng DecimalString; là null khi không biết base_decimals.
  • quote_volume (string hoặc null): Khối lượng tài sản định giá được quy đổi theo số chữ số thập phân của tài sản định giá dưới dạng DecimalString, hoặc null.

Các chỉ số giá và VWAP

  • vwap (string hoặc null): Giá trung bình có trọng số theo khối lượng dưới dạng DecimalString, hoặc null.
  • first_price (string hoặc null): Chỉ số giá đầu tiên dưới dạng DecimalString, hoặc null.
  • last_price (string hoặc null): Chỉ số giá cuối cùng dưới dạng DecimalString, hoặc null.
  • min_price (string hoặc null): Chỉ số giá thấp nhất dưới dạng DecimalString, hoặc null.
  • max_price (string hoặc null): Chỉ số giá cao nhất dưới dạng DecimalString, hoặc null.

Các trường phân số chính xác

  • first_price_numerator / first_price_denominator (string): Tử số và mẫu số nguyên chính xác cho first_price (UInt256String).
  • last_price_numerator / last_price_denominator (string): Tử số và mẫu số nguyên chính xác cho last_price (UInt256String).
  • min_price_numerator / min_price_denominator (string): Tử số và mẫu số nguyên chính xác cho min_price (UInt256String).
  • max_price_numerator / max_price_denominator (string): Tử số và mẫu số nguyên chính xác cho max_price (UInt256String).
  • refreshed_at (string): Thời điểm cập nhật hàng này (dấu thời gian UTC ISO-8601).

Siêu dữ liệu cấu trúc phản hồi (meta)

  • chain: Định danh chuỗi.
  • chain_slug: Slug chuỗi chuẩn dạng chữ hoa.
  • chain_external_id: Định danh chuỗi ở định dạng CAIP-2.
  • as_of_block: Khối mới nhất của chuỗi đã được ghi đầy đủ (do bộ dữ liệu này báo cáo, không đối chiếu với tham số yêu cầu).
  • coverage: Phân loại phạm vi bao phủ (trả về "full" cho endpoint này).
  • refreshed_at: Thời điểm cập nhật siêu dữ liệu. Có thể là null: null nghĩa là không biết thời điểm cập nhật dữ liệu này và nên coi dữ liệu là cũ; các endpoint dựa trên khối luôn trả về một giá trị.

Vì sao giá dùng tử số và mẫu số chính xác

Số JSON tiêu chuẩn dựa trên số dấu phẩy động độ chính xác kép IEEE-754, có các giới hạn về độ chính xác:

  1. Cắt bỏ và sai lệch số dấu phẩy động: Các giá trị Float64 chỉ có 53 bit độ chính xác, và phép chia lượng token tạo ra sai lệch do làm tròn tích lũy qua các phép tính.
  2. An toàn khi truyền dữ liệu: Định dạng giá trị thành chuỗi số thập phân (UInt256String) đảm bảo truyền số qua HTTP mà không mất độ chính xác trong bộ phân tích JSON.

Bằng cách cung cấp tử số và mẫu số nguyên chính xác cho các chỉ số giá, BlockVectra cho phép tính toán toán học chính xác mà không cần chuyển đổi sang số dấu phẩy động.

Xử lý phân số chính xác trong TypeScript (BigInt)

Trong TypeScript, bạn có thể dùng BigInt tích hợp để so sánh bằng phép nhân chéo và chuyển đổi sang số điểm cố định mà không cần chuyển đổi sang số dấu phẩy động:

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

// 1. Ratio comparison without floating-point conversion: check if close price is higher than open price
// a / b > c / d  is equivalent to  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. Convert fraction to a fixed-point decimal string with arbitrary scale (without floating-point loss)
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}`;
}

Xử lý phân số chính xác trong Python

Python cung cấp các module thư viện tiêu chuẩn dành riêng cho tính toán số hữu tỉ và số thập phân: fractions.Fraction và decimal.Decimal.

from decimal import Decimal, getcontext
from fractions import Fraction

# 1. Exact rational calculations with 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"])
)

# Exact price delta without floating-point rounding error
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. Arbitrary-precision decimal arithmetic with 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}")

Tải bù một năm giá hàng ngày

Để tải bù dữ liệu một năm (365 ngày) trong giới hạn khoảng ngày 90 ngày, chia toàn bộ khoảng ngày thành các cửa sổ liên tiếp tối đa 90 ngày và gửi yêu cầu theo từng đoạn:

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

/**
 * Split a large date range into consecutive spans of at most maxDays (default: 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;
}

/**
 * Backfill token daily prices across multiple 90-day chunks
 */
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;
}

Tính toán dung lượng và mức sử dụng CU

Mọi endpoint Data API đều đo mức sử dụng bằng Compute Units (CU). Trọng số CU mỗi lệnh gọi cho data.dex_prices và mức sử dụng ước tính khi tải bù dữ liệu token được tính bên dưới:

Trọng số phương thứcdata.dex_prices: 15 CU / call
  • Nạp bù dữ liệu 1 năm giá hàng ngày cho 200 token: với phạm vi tối đa 90 ngày mỗi yêu cầu, việc bao phủ 365 ngày cần 5 phần cho mỗi token, tổng cộng 1,000 lệnh gọi. Tổng mức tiêu thụ là 15,000 CU (khoảng <0.1% hạn mức chu kỳ gói miễn phí), khoảng <$0.01 theo giá niêm yết.
  • Duy trì hàng ngày (làm mới 200 token một lần mỗi ngày): 200 lệnh gọi/ngày (3,000 CU/ngày), tổng cộng khoảng 6,000 lệnh gọi mỗi chu kỳ 30 ngày (90,000 CU, khoảng 0.3% hạn mức miễn phí), khoảng <$0.01/tháng theo giá niêm yết.

Khi tăng khối lượng tải bù hoặc cần mức đồng thời yêu cầu cao hơn, hãy nạp tiền on-chain trên trang Thanh toán của console để nâng cấp lên tài khoản trả phí. Để biết mức giá và quy đổi đơn vị hiện tại, xem Trang bảng giá.

Các bước tiếp theo

Cập nhật lần cuối:

Trên trang này