# 정확한 분수를 활용한 토큰의 일별 DEX OHLC 및 VWAP

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

## DEX 일별 가격 데이터셋이란

BlockVectra의 DEX 데이터셋은 탈중앙화 거래소(DEX) 거래 활동을 인덱싱하고 집계된 일일 가격 지표를 계산합니다. DEX 일별 가격 엔드포인트(`getDexPrices`)는 지정된 날짜 범위 동안 특정 토큰의 일일 거래량 가중 평균 가격(VWAP), 가격 지표(`first_price`, `last_price`, `min_price`, `max_price` 필드) 및 거래량 지표를 제공합니다.

이 데이터셋의 지원 여부는 네트워크마다 다릅니다. 이 데이터셋을 제공하는 체인은 [지원 체인](https://docs.blockvectra.com/en/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"}}`를 반환합니다:

* **HTTP 400 (`bad_request`)**: 필수 쿼리 파라미터(`token`, `from` 또는 `to`) 누락, 잘못된 `token`/`quote` 주소 구문, 잘못된 `YYYY-MM-DD` 달력 날짜, 또는 `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월 한 달간 기준 토큰의 일별 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): `YYYY-MM-DD` 형식의 UTC 날짜.
* `token` (string): 소문자 `0x` 접두사가 붙은 16진수 형태의 20바이트 기준 토큰 주소.
* `token_symbol` (string 또는 `null`): 기준 토큰 심볼.
* `token_name` (string 또는 `null`): 기준 토큰 표시 이름.
* `quote_token` (string): 호가 자산 주소. 전체가 0인 주소(`0x0000000000000000000000000000000000000000`)는 네이티브 ETH가 호가 자산임을 나타냅니다.
* `quote_symbol` (string 또는 `null`): 호가 자산 심볼 (`quote_token`이 전체 0 주소인 경우 `"ETH"`).
* `quote_name` (string 또는 `null`): 호가 자산 표시 이름 (`quote_token`이 전체 0 주소인 경우 `"Ether"`).
* `base_decimals` (integer 또는 `null`): 기준 토큰 소수점 자리수 (0–255).
* `quote_decimals` (integer 또는 `null`): 호가 자산 소수점 자리수 (`quote_token`이 전체 0 주소인 경우 `18`).

### 거래량 및 거래 횟수

* `swap_count` (integer): 이 행의 스왑 횟수.
* `base_volume_raw` (string): 부호 없는 정수 10진수 문자열(`UInt256String`) 형태의 원자 단위 기준 거래량.
* `quote_volume_raw` (string): 부호 없는 정수 10진수 문자열(`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`: 정규화된 대문자 체인 슬러그.
* `chain_external_id`: CAIP-2 형식의 체인 식별자.
* `as_of_block`: 체인의 완전히 기록된 최신 블록 (이 데이터셋에서 보고하며, 요청 파라미터에 대해 검사되지 않음).
* `coverage`: 커버리지 분류 (이 엔드포인트의 경우 `"full"`로 보고됨).
* `refreshed_at`: 메타데이터 갱신 타임스탬프. `null`일 수 있음: `null`은 이 데이터의 업데이트 시간을 알 수 없으며 오래된 것으로 간주해야 함을 의미합니다(블록 기반 엔드포인트는 항상 값을 반환함).

## 가격에 정확한 분자와 분모를 사용하는 이유

표준 JSON 숫자는 IEEE-754 배정밀도 부동 소수점에 의존하므로 다음과 같은 정밀도 한계가 있습니다:

1. **부동 소수점 잘림 및 드리프트**: Float64 값은 53비트의 정밀도만 제공하며, 토큰 수량을 나눌 때 계산 전반에 걸쳐 반올림 오차가 복합적으로 누적됩니다.
2. **전송 안전성**: 값을 10진수 문자열(`UInt256String`)로 포맷팅하면 숫자가 JSON 파서에서 정밀도를 잃지 않고 HTTP를 통해 전달됩니다.

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. 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}`;
}
```

### Python에서 정확한 분수 처리하기

Python은 유리수 및 10진수 계산을 위해 특별히 제작된 표준 라이브러리 모듈인 `fractions.Fraction`과 `decimal.Decimal`을 제공합니다.

```python
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}")
```

## 1년치 일별 가격 백필하기

90일 범위 제한 내에서 1년치 데이터(365일)를 백필하려면 전체 날짜 범위를 최대 90일의 연속된 윈도우로 나누고 청크 단위 요청을 실행합니다:

```ts
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;
}
```

## 용량 및 CU 사용량 계산

모든 Data API 엔드포인트는 Compute Unit(CU) 단위로 사용량을 측정합니다. `data.dex_prices`의 호출당 CU 가중치와 토큰 백필에 대한 예상 소비량은 아래와 같이 계산됩니다:

- **메서드 가중치:** `data.dex_prices` = 15 CU / 호출.
- **200개 토큰의 1년 치 일별 가격 백필:** 요청당 최대 90일 범위로 365일을 처리하려면 토큰당 5개 청크가 필요하며, 총 1,000회 호출됩니다. 총 소비량은 15,000 CU (무료 플랜 주기 할당량의 약 <0.1%), 정가 기준 약 <$0.01입니다.
- **일일 유지 관리(매일 200개 토큰 1회 갱신):** 일일 200회 호출(3,000 CU/일), 30일 주기당 총 약 6,000회 호출(90,000 CU, 무료 할당량의 약 0.3%), 정가 기준 월 약 <$0.01입니다.

백필 볼륨을 확장하거나 더 높은 요청 동시성이 필요한 경우 콘솔 [결제 페이지](https://console.blockvectra.com/billing/)에서 온체인 충전을 통해 유료 계정으로 업그레이드하세요. 유효 요율 및 단위 변환은 [요금 페이지](https://blockvectra.com/en/pricing/)를 참조하세요.

## 다음 단계

* [데이터셋 디렉터리 둘러보기](https://blockvectra.com/en/data/): BlockVectra가 인덱싱하는 모든 데이터셋 확인.
* [무료 플랜 및 요금 확인](https://blockvectra.com/en/pricing/#free): 내 계정에 포함된 혜택 확인.
* [콘솔 로그인](https://console.blockvectra.com/login/?next=%2Fkeys%2F): API key 생성.
