Un gráfico del precio del oro en vivo se construye con dos conjuntos de datos relacionados: una cotización spot actual para la cifra principal y barras OHLC históricas para la serie dibujada. El cliente carga una ventana de barras, las ordena por tiempo, representa sus valores y después actualiza solo la observación más reciente. También debe indicar si la última barra sigue en formación.
La distinción importa. La cotización spot puede cambiar entre dos barras, mientras que el cierre de una barra abierta es provisional. Si ambas se tratan como el mismo dato, aparecen puntos duplicados, horas engañosas o una línea que parece más reciente que sus datos.
Las cuatro piezas de un gráfico en vivo
La mayoría de gráficos necesita:
- un símbolo y un intervalo, como
XAU-USD-SPOT y 1d
- un rango inicial de barras OHLC
- una cotización spot para la cifra actual
- un bucle que sustituya la última barra en vez de añadir cada respuesta
El dibujo es el último paso. Antes hay que validar números, ordenar observaciones, conservar horas y decidir cómo mostrar una barra aún abierta.
Spot y OHLC responden preguntas distintas
El endpoint spot responde «¿cuál es el precio derivado actual?». Una barra diaria contiene apertura, máximo, mínimo y cierre para ese día 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
La respuesta spot incluye price, computed_at e is_stale. Úsalos para el precio grande y su estado de frescura.
Cada barra incluye bar_start, open, high, low, close, volume e is_closed. En spot, volume puede ser null, por lo que no debe ser obligatorio para dibujar. is_closed: false señala una barra en formación cuyos valores todavía pueden cambiar.
Las barras llegan de más reciente a más antigua. Ordénalas en sentido ascendente antes de dibujar el tiempo de izquierda a derecha.
Carga y normaliza los datos
Conserva los precios decimales como cadenas en el modelo y conviértelos a números solo cuando el cálculo de coordenadas lo exija.
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));
}
Una página basta para un gráfico diario reciente. Para un periodo mayor, continúa con next_cursor hasta reunir suficientes observaciones o recibir null.
Dibuja la línea con SVG
Este cálculo asigna el tiempo al eje horizontal y el cierre al vertical. El resultado se usa como atributo d de un <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(" ");
}
Añade role="img" y un aria-label claro al SVG. Como los puntos no dependen del renderer, el mismo modelo funciona con Canvas, una vista móvil o una librería de gráficos.
<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>
Sustituye la última barra por su hora
Las barras OHLC se consultan por REST. Haz polling de /v1/bars/latest para obtener la barra más reciente, cerrada o en formación. Si bar_start ya existe, reemplaza esa entrada. Añade una nueva solo cuando empiece otro intervalo.
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;
}
Usa un temporizador controlado, páralo cuando la página esté oculta y evita solicitudes simultáneas. Consultar con más frecuencia no hace más reciente una observación de origen.
Separa el precio principal de la línea
El precio grande y la línea pueden tener ritmos distintos. Consulta /v1/spot/XAU-USD-SPOT por separado, muestra su computed_at y etiqueta la cotización si is_stale es true. La hora de llegada al navegador no sustituye la hora de observación.
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>;
}
Cuando el mercado está cerrado
Un gráfico correcto puede quedarse quieto. Conserva la última cotización válida con su computed_at original y la última barra con su bar_start e is_closed reales. Si la interfaz necesita mostrar periodos de calendario vacíos, usa espacio o huecos; no copies el último precio como si fuera una observación nueva.
Tras un fallo de red, muestra el último dato válido con su hora o un error si nunca hubo una carga correcta. No guardes un error HTTP como precio cero.
Comprobaciones antes de publicar
- usa el símbolo canónico
XAU-USD-SPOT
- ordena las barras de antigua a reciente
- reemplaza cuando coincida
bar_start y añade solo un intervalo nuevo
- identifica
is_closed: false como provisional
- acepta
volume: null
- conserva
computed_at e is_stale del spot
- evita actualizaciones superpuestas y espera tras errores HTTP
- mantén las claves API en el servidor para llamadas autenticadas
Un gráfico es fiable cuando cada punto se puede relacionar con su hora y estado. Consulta la guía de barras históricas para parámetros y paginación, y la guía de WebSocket para distinguir ticks en vivo de barras OHLC.