前提:店頭価格と国際価格は別物
金の値段を見るとき、数字は2種類ある。国際指標の XAU/USD(1トロイオンスあたり米ドル)と、日本の店頭価格(1グラムあたり円・税込)。同じ日でも 8〜12% ずれる。ずれの内訳を目で追うのは無理なので、コードで分解する。
差額を3つに分ける式
分解は単純で、次の3項に落ちる。
- 換算項:
usdPerOz × usdJpy ÷ 31.1035 - 税項:換算項 × 消費税率
- プレミアム項:店頭価格 − 換算項 − 税項
プレミアム項に残るのが、精製・輸送・保管・在庫リスク・店の利益。ここが何%かを見れば、その日の店頭価格が「相場で動いたのか、店が値付けを変えたのか」を切り分けられる。
実装
const TROY_OZ_G = 31.1035;
const toGramJpy = (usdPerOz, usdJpy) => (usdPerOz * usdJpy) / TROY_OZ_G;
function breakdown({ usdPerOz, usdJpy, retailPerGram, taxRate = 0.1 }) {
const intl = toGramJpy(usdPerOz, usdJpy);
const gap = retailPerGram - intl;
const tax = intl * taxRate;
const premium = gap - tax;
return {
intlPerGram: Math.round(intl),
gapPerGram: Math.round(gap),
taxPart: Math.round(tax),
premiumPart: Math.round(premium),
premiumPct: +((premium / intl) * 100).toFixed(1),
};
}
呼び出しはこう。
breakdown({ usdPerOz: 2650, usdJpy: 157, retailPerGram: 15500 });
// { intlPerGram: 13377, gapPerGram: 2123, taxPart: 1338, premiumPart: 786, premiumPct: 5.9 }
この数字の読み方
premiumPct が 5〜7% に収まっている日は、店頭価格はほぼ相場と税だけで決まっている。10% を超えたら、店側が値付けを上げている(あるいは在庫が薄い)。逆に 3% を切る日は、店頭価格が相場より安い=売り手にとって不利、買い手にとって有利な日になる。
注意点が1つある。品位補正を忘れると桁がずれる。K24 は 0.999、K18 は 0.750、K14 は 0.585 で、店頭価格はこの純度で按分されている。retailPerGram に K18 の数字を入れたら、intl 側も 0.750 を掛けて比較しないと意味がない。
実装上の落とし穴
toGramJpy の 31.1035 を 31 や 31.1 に丸めると、1グラムあたり数十円ずれる。1キロ買うと数万円の差になるので、定数は必ず 31.1035 で持つ。
もう1つ、USD/JPY の取得タイミング。為替は24時間動くので、店頭価格の更新時刻とレート取得時刻を揃えないと、差額に為替のノイズが乗る。表示用なら誤差として許容できるが、判断に使うなら時刻を揃える。
数字を並べて確認する
換算値と店頭価格を同じ画面で並べると、差が税なのか手数料なのか為替なのかが即座に分かる。K24・K18・K14 の1グラムあたりの数字と計算機はここに置いてある:金 1グラム 円 換算
自分で実装する場合、まず breakdown の出力を3日分並べてみるといい。プレミアム項が動いているのか、税項だけが動いているのかで、見えている「値動き」の正体が変わる。