在不引入外汇前视偏差的情况下回测本币计价的黄金
将结算后的XAU/USD日线K线与历史外汇观测值相结合,以本币回测黄金策略,避免无意中使用了在当时尚不可得的汇率。
阅读 →了解实时黄金价格图表如何组合现货报价、OHLC K线、UTC时间戳和受控轮询,并用TypeScript与SVG构建可运行示例。
实时黄金价格图表由两组相关数据组成:当前现货报价负责顶部数字,历史OHLC K线负责绘制价格序列。客户端先加载一段K线,按时间排序并绘制,然后只更新最新观测值。图表还应明确最新K线是否仍在形成。
这个区别很关键。两个K线周期之间,现货报价仍可能变化;而正在形成的K线,其收盘价只是暂定值。若把两者当成同一种数据,容易产生重复点、误导性的时间戳,或让图表看起来比底层数据更新得更快。
大多数黄金图表需要四项内容:
XAU-USD-SPOT 与 1d绘图只是最后一步。真正需要谨慎处理的是数字校验、时间排序、时间戳保留,以及对形成中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
现货响应包含 price、computed_at 和 is_stale。它们应驱动顶部价格与新鲜度状态。
每根K线包含 bar_start、open、high、low、close、volume 和 is_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。
下面的函数把时间映射到横轴,把收盘价映射到纵轴,并返回可直接用于 <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>
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_start 和 is_closed。若设计需要展示空白日历区间,应画成留白或缺口,而不是伪装成新观测值的复制价格。
网络请求失败时也一样:显示最后一份有效数据及其时间;若从未成功加载,则显示错误。不要把HTTP错误缓存成零价格。
XAU-USD-SPOTbar_start 相同时替换,只有新周期才追加is_closed: false 代表暂定值volume 为 nullcomputed_at 与 is_stale每个点都能追溯到观测时间和状态,图表才值得信任。端点参数与分页请参阅历史K线指南;实时价格流与K线的区别请参阅WebSocket指南。
相关指南
goldprice.dev
实时黄金价格、历史 OHLC 数据与多源聚合 — 通过 REST 和 SSE 接口提供。