블로그/개발자

실시간 금 가격 차트는 어떻게 작동할까?

실시간 금 가격 차트가 현물 호가, OHLC 바, UTC 타임스탬프, 제어된 폴링을 결합하는 방식을 TypeScript와 SVG 예제로 알아봅니다.

개발자

실시간 금 가격 차트는 서로 관련된 두 데이터로 구성된다. 큰 현재 가격에는 현물 호가를 쓰고, 선 그래프에는 과거 OHLC 바를 쓴다. 클라이언트는 일정 기간의 바를 불러와 시간순으로 정렬하고 그린 뒤, 가장 최근 관측값만 갱신한다. 마지막 바가 아직 형성 중인지도 표시해야 한다.

현물 호가는 두 바 사이에서도 바뀔 수 있지만 형성 중인 바의 종가는 잠정값이다. 둘을 같은 값처럼 처리하면 중복 점, 잘못된 시각, 실제 데이터보다 더 최신처럼 보이는 선이 생길 수 있다.

실시간 차트의 네 요소

대부분의 금 차트에는 다음이 필요하다.

  • XAU-USD-SPOT, 1d 같은 심볼과 간격
  • 처음 그릴 OHLC 바 구간
  • 큰 현재 가격에 사용할 현물 호가
  • 응답을 무조건 추가하지 않고 마지막 바를 교체하는 갱신 루프

렌더링은 마지막 단계다. 그 전에 숫자를 검증하고, 관측값을 정렬하고, 타임스탬프를 보존하고, 형성 중인 바를 어떻게 보여 줄지 정해야 한다.

현물 호가와 OHLC 바의 역할

현물 엔드포인트는 “현재 산출 가격은 얼마인가?”에 답한다. 일별 바는 해당 UTC 날짜의 시가, 고가, 저가, 종가를 제공한다.

GET https://api.goldprice.dev/v1/spot/XAU-USD-SPOT
GET https://api.goldprice.dev/v1/bars?symbol=XAU-USD-SPOT&interval=1d&from=2026-07-25&to=2026-08-23

현물 응답의 price, computed_at, is_stale은 큰 가격과 데이터 상태를 표시하는 데 쓴다.

각 바에는 bar_start, open, high, low, close, volume, is_closed가 있다. 금 현물 바의 volume은 null일 수 있으므로 가격 차트의 필수값으로 두면 안 된다. is_closed: false는 아직 형성 중인 바이며 다음 갱신에서 OHLC 값이 바뀔 수 있다는 뜻이다.

바는 최신순으로 반환된다. 시간축을 왼쪽에서 오른쪽으로 그리려면 먼저 오래된 순서로 정렬한다.

초기 데이터를 불러오고 정규화하기

데이터 모델에서는 십진 가격을 문자열로 유지하고, SVG 좌표 계산 시점에만 JavaScript 숫자로 변환한다.

type GoldBar = {
  bar_start: string;
  open: string | null;
  high: string | null;
  low: string | null;
  close: string | null;
  volume: string | null;
  is_closed: boolean;
};

type BarsResponse = {
  bars: GoldBar[];
  next_cursor: string | null;
};

type ChartPoint = {
  time: string;
  close: number;
  isClosed: boolean;
};

async function loadPoints(from: string, to: string): Promise<ChartPoint[]> {
  const url = new URL("https://api.goldprice.dev/v1/bars");
  url.search = new URLSearchParams({
    symbol: "XAU-USD-SPOT", interval: "1d", from, to,
  }).toString();

  const response = await fetch(url);
  if (!response.ok) throw new Error(`Gold bars failed: ${response.status}`);
  const page = (await response.json()) as BarsResponse;

  return page.bars
    .filter((bar) => bar.close !== null && Number.isFinite(Number(bar.close)))
    .map((bar) => ({ time: bar.bar_start, close: Number(bar.close), isClosed: bar.is_closed }))
    .sort((a, b) => Date.parse(a.time) - Date.parse(b.time));
}

최근 일별 차트는 한 페이지로 충분하다. 더 긴 구간은 next_cursor가 null이 되거나 필요한 관측값을 모을 때까지 페이지를 이어서 가져온다.

SVG 선 경로 만들기

아래 함수는 시간을 가로축에, 종가를 세로축에 대응시키고 <path>d에 넣을 문자열을 반환한다.

function linePath(points: ChartPoint[], width: number, height: number): string {
  if (points.length === 0) return "";
  const values = points.map((point) => point.close);
  const min = Math.min(...values);
  const span = Math.max(...values) - min || 1;

  return points.map((point, index) => {
    const x = points.length === 1 ? width / 2 : (index / (points.length - 1)) * width;
    const y = height - ((point.close - min) / span) * height;
    return `${index === 0 ? "M" : "L"} ${x.toFixed(2)} ${y.toFixed(2)}`;
  }).join(" ");
}

SVG에는 role="img"와 명확한 aria-label도 추가한다. 데이터 배열이 렌더러와 분리되어 있으므로 이후 Canvas, 모바일 차트, 다른 차트 패키지로 바꾸기 쉽다.

<svg viewBox="0 0 720 280" role="img" aria-label="Gold price in US dollars">
  <path
    d={linePath(points, 720, 280)}
    fill="none"
    stroke="currentColor"
    strokeWidth="2"
  />
</svg>

타임스탬프로 마지막 바 교체하기

OHLC 바는 REST로 제공된다. /v1/bars/latest를 폴링하면 가장 최근의 확정 또는 형성 중인 바를 얻을 수 있다. 같은 bar_start가 있으면 해당 항목을 교체하고, 새 간격이 시작됐을 때만 추가한다.

async function refreshLatest(current: ChartPoint[]): Promise<ChartPoint[]> {
  const response = await fetch(
    "https://api.goldprice.dev/v1/bars/latest?symbol=XAU-USD-SPOT&interval=1d",
    { cache: "no-store" },
  );
  if (!response.ok) throw new Error(`Latest bar failed: ${response.status}`);

  const { bar } = await response.json() as { bar: GoldBar | null };
  if (!bar || bar.close === null || !Number.isFinite(Number(bar.close))) return current;

  const next = { time: bar.bar_start, close: Number(bar.close), isClosed: bar.is_closed };
  const index = current.findIndex((point) => point.time === next.time);
  if (index === -1) return [...current, next].sort((a, b) => Date.parse(a.time) - Date.parse(b.time));

  const copy = current.slice();
  copy[index] = next;
  return copy;
}

페이지가 숨겨지면 타이머를 멈추고, 요청이 겹치지 않게 한다. 폴링을 빠르게 해도 원본 관측값 자체가 더 최신이 되는 것은 아니다.

큰 현재 가격은 별도로 갱신하기

현재 가격과 차트 선은 서로 다른 주기로 갱신할 수 있다. /v1/spot/XAU-USD-SPOT을 따로 요청하고 해당 호가의 computed_at을 보여 준다. is_stale이 true라면 오래된 값임을 표시한다. 브라우저가 응답을 받은 시각으로 관측 시각을 덮어쓰면 안 된다.

type SpotQuote = {
  symbol: string;
  price: string;
  computed_at: string;
  is_stale: boolean;
};

async function loadSpot(): Promise<SpotQuote> {
  const response = await fetch(
    "https://api.goldprice.dev/v1/spot/XAU-USD-SPOT",
    { cache: "no-store" },
  );
  if (!response.ok) throw new Error(`Gold spot failed: ${response.status}`);
  return response.json() as Promise<SpotQuote>;
}

시장이 닫혔을 때

정확한 차트는 그대로 멈춰 있을 수 있다. 마지막 유효 현물가와 원래 computed_at, 마지막 바의 실제 bar_startis_closed를 유지한다. 달력의 빈 기간이 필요하다면 공백이나 간격으로 보여 주고, 이전 가격을 새 관측값처럼 복제하지 않는다.

네트워크 실패 때도 마지막 정상 데이터와 시각을 표시한다. 정상 데이터가 한 번도 없었다면 오류를 보여 준다. HTTP 오류를 가격 0으로 캐시하면 안 된다.

출시 전 확인 항목

  • 표준 심볼 XAU-USD-SPOT 사용
  • 그리기 전에 오래된 바부터 정렬
  • bar_start가 같으면 교체하고 새 간격만 추가
  • is_closed: false가 잠정값임을 표시
  • volume: null 허용
  • 현물의 computed_atis_stale 보존
  • 중복 요청을 막고 HTTP 오류 뒤에는 대기
  • 인증 요청의 API 키는 서버에 보관

각 점의 관측 시각과 상태를 추적할 수 있어야 신뢰할 수 있는 차트다. 요청 매개변수와 페이지 처리는 과거 바 가이드, 실시간 틱과 OHLC 바의 차이는 WebSocket 가이드를 참고하라.

관련 가이드

개발자

선견 편향(look-ahead bias) 없이 현지 통화로 금 백테스트하기

확정된 XAU/USD 일별 바(bar)와 과거 환율 관측치를 결합해, 당시에는 알 수 없었던 환율을 실수로 사용하지 않고 현지 통화로 금 전략을 테스트한다.

읽기 →
개발자

WordPress에 실시간 금 시세 위젯 추가하기

iframe 하나로 WordPress에 무료로 설정 가능한 실시간 금 시세 위젯을 추가한다. Gutenberg와 Elementor에서 모두 작동하며, API 키도 플러그인도 필요 없다.

읽기 →
개발자

JavaScript로 금 주얼리 가격 계산기 만들기

실시간 캐럿별 그램당 가격으로 금 주얼리 가격 계산기를 만든다. 정확한 소수 연산, 캐싱, 마진, 그리고 명확한 평가 한계까지 다룬다.

읽기 →

goldprice.dev

실시간 금 시세, 과거 OHLC 데이터, 다중 소스 집계 — REST 및 SSE로 제공됩니다.