ブログ/開発者向け

リアルタイム金価格チャートはどう動くのか?

現物価格、OHLCバー、UTCタイムスタンプ、制御されたポーリングを組み合わせる仕組みを、TypeScriptとSVGの実例で解説します。

開発者向け

リアルタイム金価格チャートは、関連する2種類のデータから作られる。大きく表示する現在値には現物価格を使い、折れ線には過去のOHLCバーを使う。クライアントは一定期間のバーを読み込み、時系列に並べて描画し、その後は最新の観測値だけを更新する。最新バーが形成中かどうかも示す必要がある。

この区別は重要だ。現物価格は2本のバーの間でも動く一方、形成中バーの終値は暫定値である。同じ値として扱うと、点の重複、誤解を招く時刻、元データより新しく見える線が生じる。

リアルタイムチャートを構成する4要素

多くの金価格チャートには次の要素が必要だ。

  • XAU-USD-SPOT1d のようなシンボルと間隔
  • 最初に描く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

現物レスポンスには pricecomputed_atis_stale が含まれる。大きな価格表示と鮮度表示にはこの値を使う。

各バーには bar_startopenhighlowclosevolumeis_closed が含まれる。金現物バーの volume は null の場合があるため、価格チャート描画の必須条件にしてはいけない。is_closed: false は形成中のバーを示し、次回更新でOHLC値が変わる可能性がある。

バーは新しい順に返る。時間を左から右へ描く前に、古い順へ並べ替える。

初期データを取得して整形する

データモデルでは価格を10進文字列のまま保持し、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));
}

最近の日次チャートなら通常は1ページで足りる。長い期間では、必要な観測数を得るか 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エラーを価格ゼロとしてキャッシュしてはいけない。

公開前の確認項目

  • 正規シンボル XAU-USD-SPOT を使う
  • 描画前にバーを古い順へ並べる
  • bar_start が同じなら置換し、新しい期間だけ追加する
  • is_closed: false が暫定値であると示す
  • volume: null を許容する
  • 現物価格の computed_atis_stale を保持する
  • 更新要求の重複を防ぎ、HTTPエラー後は待機する
  • 認証要求のAPIキーはサーバー側に置く

各点の観測時刻と状態を追跡できるチャートなら信頼できる。パラメータとページングは履歴バーガイド、リアルタイムtickとOHLCバーの違いはWebSocketガイドを参照してほしい。

関連ガイド

開発者向け

為替のルックアヘッド・バイアスを避けて現地通貨建てゴールドをバックテストする

確定済みのXAU/USD日次バーと過去のFX観測値を組み合わせ、当時は入手できなかったレートを誤って使うことなく、現地通貨建てのゴールド戦略をテストする方法。

読む →
開発者向け

WordPressにライブ・ゴールド価格ウィジェットを追加する

iframeひとつで、無料の設定可能なライブ・ゴールド価格ウィジェットをWordPressに追加できる。GutenbergとElementorの両方で動作し、APIキーもプラグインも不要。

読む →
開発者向け

JavaScriptでゴールド・ジュエリー価格計算機を作る

ライブのカラット別グラム単価から、正確な10進数演算・キャッシュ・マージン・明確な評価上の限界を備えたゴールド・ジュエリー価格計算機を作る。

読む →

goldprice.dev

リアルタイムの金価格、ヒストリカルOHLC、マルチソース集計 — REST・SSE経由で提供。