博客/开发者

实时黄金价格图表是如何工作的?

了解实时黄金价格图表如何组合现货报价、OHLC K线、UTC时间戳和受控轮询,并用TypeScript与SVG构建可运行示例。

开发者

实时黄金价格图表由两组相关数据组成:当前现货报价负责顶部数字,历史OHLC K线负责绘制价格序列。客户端先加载一段K线,按时间排序并绘制,然后只更新最新观测值。图表还应明确最新K线是否仍在形成。

这个区别很关键。两个K线周期之间,现货报价仍可能变化;而正在形成的K线,其收盘价只是暂定值。若把两者当成同一种数据,容易产生重复点、误导性的时间戳,或让图表看起来比底层数据更新得更快。

实时图表的四个部分

大多数黄金图表需要四项内容:

  • 一个交易符号和时间周期,例如 XAU-USD-SPOT1d
  • 一段初始OHLC K线
  • 用于顶部数字的当前现货报价
  • 一个替换最新K线的刷新循环,而不是每次都追加

绘图只是最后一步。真正需要谨慎处理的是数字校验、时间排序、时间戳保留,以及对形成中K线的处理方式。

现货报价与OHLC K线回答不同问题

现货端点回答“当前推导价格是多少”。日K线回答“这个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。它们应驱动顶部价格与新鲜度状态。

每根K线包含 bar_startopenhighlowclosevolumeis_closed。黄金现货K线的 volume 可以是 null,因此绘制价格图时不能依赖成交量。is_closed: false 表示K线仍在形成,下次刷新时OHLC值可能变化。

API按最新优先返回K线,而绘图通常需要时间从左向右推进,所以生成图表点前要按时间升序排列。

获取并整理初始数据

在数据模型中保留十进制字符串,只在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> 的路径字符串。

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中 <path>d 属性,并为图表添加 role="img" 与清晰的 aria-label。数据数组与SVG保持分离,之后换成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>

按时间戳替换最新K线

OHLC K线通过REST提供。轮询 /v1/bars/latest 可得到最新的已完成或形成中K线。不要盲目追加:连续响应往往拥有相同的 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,同时保留最后一根K线真实的 bar_startis_closed。若设计需要展示空白日历区间,应画成留白或缺口,而不是伪装成新观测值的复制价格。

网络请求失败时也一样:显示最后一份有效数据及其时间;若从未成功加载,则显示错误。不要把HTTP错误缓存成零价格。

上线前检查

  • 使用标准符号 XAU-USD-SPOT
  • 绘制前将K线从旧到新排序
  • bar_start 相同时替换,只有新周期才追加
  • 让用户知道 is_closed: false 代表暂定值
  • 允许 volume 为 null
  • 保留现货报价的 computed_atis_stale
  • 避免刷新请求重叠,并在HTTP错误后退避
  • 使用认证请求时,把API密钥保留在服务器端

每个点都能追溯到观测时间和状态,图表才值得信任。端点参数与分页请参阅历史K线指南;实时价格流与K线的区别请参阅WebSocket指南

相关指南

goldprice.dev

实时黄金价格、历史 OHLC 数据与多源聚合 — 通过 REST 和 SSE 接口提供。