Case Study/本番環境のケーススタディ

goldprice.dev はどのように現地通貨建てで金価格を算出しているか

goldprice.dev は、USD建ての金属価格とexchangerate.devの為替レートを組み合わせることで、鮮度の境界を隠すことなく現地通貨建ての金価格を提供している。その仕組みを解説する。

MMexchangerate.dev·Jul 16, 2026·7分で読了

goldprice.dev とexchangerate.devは、同じ会社が運営する姉妹プロダクトである。本番環境では、goldprice.devが金属価格の契約を担い、exchangerate.devが対応するフロート制通貨の為替レッグを供給する。この境界線は重要である。現地通貨建ての金価格には2つの市場観測値が含まれており、一方が新鮮であっても、もう一方の鮮度を保証することはできない。

Key points
goldprice.devが金価格と公開の換算レスポンスを担い、exchangerate.devが為替レッグを供給する。
公開されているgoldprice.devの /v1/convert エンドポイントは、1回の呼び出しで完結する本番向けの経路を提供する。
監査可能なデータ来歴が必要な場合、開発者は両方のAPIを個別に取得できる。
金の computed_at と、為替の data_updated_attimestampsourcemarket_session は、それぞれ独立して保存すること。
換算後の価格はインディケーティブな市場価値であり、約定可能なディーラー価格や決済価格ではない。

本番環境における境界線

goldprice.dev はUSD建ての金属観測値を起点とする。exchangerate.devは、対応するフロート制通貨についてUSDから現地通貨への為替観測値を提供する。そのうえでgoldprice.devが、単位、対応シンボル、バリデーション、最終レスポンスを含む、金属固有の換算契約を担う。

これは開示済みの姉妹プロダクト間の統合であり、独立した顧客による推奨ではない。ケーススタディとしての価値は運用面にある。為替APIが実際の本番データレッグを供給し、利用側のプロダクトが自らのドメインに対する責任を保持し続ける、という点である。

換算式

金は通常、1トロイオンスあたりのUSDで気配表示される。現地向けの表示では、通常1グラムあたりの通貨額が必要となる。まず重量を換算し、その後にUSDの為替レートを適用する。

formula · USD/oz to local currency/gramcopy
const GRAMS_PER_TROY_OZ = 31.1035;

function localGoldPerGram(usdPerTroyOz, usdToLocalRate) {
  return (usdPerTroyOz / GRAMS_PER_TROY_OZ) * usdToLocalRate;
}

重量の定数は固定である。しかし2つの市場入力は固定ではない。金の気配値と為替レートはそれぞれ、独自の観測時刻と鮮度ルールを持っている。

1回の呼び出しで完結する本番向け経路

呼び出し側にとっての本番環境の境界線は、goldprice.dev の `/v1/convert` である。これは金属レッグと為替レッグを、金属を意識した1つのエンドポイントの背後で組み合わせており、APIキーを必要としない。次の例は、金1グラムのインドネシアルピア建て価値をリクエストするものである。

javascript · one-request conversioncopy
const params = new URLSearchParams({
  from: "XAU",
  to: "IDR",
  amount: "1",
  unit: "gram",
});

const response = await fetch(
  `https://api.goldprice.dev/v1/convert?${params}`,
);

if (!response.ok) {
  throw new Error(`goldprice.dev returned ${response.status}`);
}

const conversion = await response.json();
console.log(conversion.result, conversion.to, conversion.timestamp);

この経路は、呼び出し側の契約を小さく保つ。1回のリクエスト、1つの数量、1つの金属単位、1つの対象通貨である。単一の timestamp は2つの市場レッグの両方を独立に証明するものではないため、為替の鮮度を監査する必要がある利用者は、以下の2レッグ方式を使用すべきである。

監査可能な2レッグ統合

各ソースのタイムスタンプを保持したい場合、入力を個別にキャッシュしたい場合、あるいはどちらのレッグが古いかを説明する必要がある場合は、両方のAPIを取得する。goldprice.dev のスポットは1トロイオンスあたりのUSD建て金価格を供給し、exchangerate.devはUSDから現地通貨へのレートを供給する。

javascript · explicit gold and FX legscopy
const GRAMS_PER_TROY_OZ = 31.1035;

async function getGoldInCurrencies(currencies) {
  const symbols = currencies.join(",");
  const [spotResponse, fxResponse] = await Promise.all([
    fetch("https://api.goldprice.dev/v1/spot/XAU-USD-SPOT"),
    fetch(`https://api.exchangerate.dev/v1/latest/USD?symbols=${symbols}`),
  ]);

  if (!spotResponse.ok || !fxResponse.ok) {
    throw new Error("A market-data request failed");
  }

  const spot = await spotResponse.json();
  const fx = await fxResponse.json();
  const usdPerGram = Number(spot.price) / GRAMS_PER_TROY_OZ;

  return {
    goldComputedAt: spot.computed_at,
    fxDataUpdatedAt: fx.data_updated_at,
    fxResponseTimestamp: fx.timestamp,
    fxSource: fx.source,
    fxMarketSession: fx.market_session,
    prices: Object.fromEntries(
      currencies.map((currency) => [
        currency,
        usdPerGram * Number(fx.rates[currency]),
      ]),
    ),
  };
}

const result = await getGoldInCurrencies(["EUR", "IDR", "JPY"]);
レスポンスのタイムスタンプは市場のタイムスタンプではない
金の観測値については spot.computed_at を、寄与した為替観測値のうち最も古いものについては fx.data_updated_at を、それぞれ保持すること。為替の timestamp はレスポンスが生成された時刻であり、source と market_session はデータの区分とセッションの文脈を補足する。これらのフィールドを Date.now() で置き換えてはならない。

サービスが分離されたままである理由

この統合によって、exchangerate.devが金価格算出エンジン全体になるわけではない。それはあくまで為替レイヤーであり続ける。この境界線を明示的に保つことで、通貨の鮮度、金属の鮮度、プロダクト固有の換算ルールが、1つの曖昧な値に埋没してしまうことを防いでいる。

  • goldprice.devは、呼び出し側に共通経路を自ら組み立てさせることなく、小さく金属に特化したインターフェースを公開できる。
  • exchangerate.devは、金属ドメインの外でも有用な、汎用的な為替契約を維持する。
  • 監査に敏感な利用者は、両方の観測値を個別に取得・保存できる。
  • 各サービスは、それぞれの市場データの語彙を用いて、障害と鮮度を報告できる。

古い/欠落したレッグを明示的に処理する

いずれかの入力が欠落している、ゼロである、数値でない、あるいはプロダクトが許容する鮮度ウィンドウの外にある場合、組み合わされた価格を生かしたままにすべきではない。最後の有効な値を保持する場合は、インターフェースがその値に元の観測時刻を明示している場合に限る。

  • 演算を行う前に、両方のHTTPレスポンスを確認する。
  • 有限でない、または正でない価格・レートは拒否する。
  • 金と為替それぞれに、個別の鮮度しきい値を設定する。
  • market_session: weekend は文脈情報として扱い、持ち越されたレートが最新であることの証明とはみなさない。
  • 換算エラーとともに、通貨ペアと両方の元の観測時刻をログに記録する。

丸めは計算中ではなく表示時に行う

乗算を通じて完全な精度を保持し、最終的な小数点以下の桁数は表示ロケールに決定させる。Intl.NumberFormat は、基となる値を変更することなく、通貨記号と桁区切りを処理する。

javascript · locale-aware displaycopy
function formatPrice(amount, currency, locale) {
  return new Intl.NumberFormat(locale, {
    style: "currency",
    currency,
  }).format(amount);
}

console.log(formatPrice(result.prices.IDR, "IDR", "id-ID"));
console.log(formatPrice(result.prices.JPY, "JPY", "ja-JP"));

結果はインディケーティブな値として扱う

算出された市場価値は、コインやバーの小売価格ではない。ディーラーのプレミアム、税金、加工費、決済手数料、約定可能な為替スプレッドは、この計算式の範囲外である。アプリケーションが実際のディーラー価格とその商業条件を付加しない限り、出力はインディケーティブなものとして表示すること。

このケーススタディが示すこと

goldprice.devは、exchangerate.devが本番の価格算出ワークフローにおいて為替レッグを供給できることを示す、当事者による証拠である。独立した第三者による証明ではない。有用な証拠はより限定的で、かつ再現可能である。開発者は同じ公開プロダクトを呼び出し、両方の観測時刻を保持し、演算そのものを自ら検証できる。

MM
exchangerate.dev
開発者向けの本番統合ノート。

Keep reading

Referencesource と market_session の読み方Read TutorialJavaScriptとNode.jsによる通貨換算Read Guideインディケーティブレートと約定可能な為替レートの違いRead