Blog/Developer

Backtest emas mata uang lokal tanpa bias look-ahead FX

Gabungkan bar harian XAU/USD yang telah settle dengan observasi FX historis untuk menguji strategi emas dalam mata uang lokal tanpa secara tidak sengaja menggunakan kurs yang belum tersedia pada saat itu.

Developer
Bahasa:
Bahasa Indonesia

Strategi emas bisa terlihat berbeda dalam EUR, JPY, IDR, INR, atau TRY meskipun transaksi yang mendasarinya sama. Seri emas USD menjawab, "apa yang dilakukan emas dalam dolar?" Seri mata uang lokal menjawab, "berapa nilai posisi ini bagi investor tersebut?"

Perbedaan itu adalah aritmetika sederhana, tetapi data historis bisa membuatnya mudah keliru. Panduan ini menggabungkan bar harian XAU/USD yang telah settle dari goldprice.dev dengan observasi FX USD bertanggal dari exchangerate.dev. Aturan pentingnya adalah setiap nilai FX harus merupakan nilai yang dipublikasikan untuk tanggal yang disimulasikan, bukan kurs yang diambil hari ini lalu ditempatkan pada baris historis.

Seri harga mata uang lokal

Untuk mata uang yang dikuotasi sebagai mata uang lokal per USD, hitung close emas dalam mata uang tersebut sebagai:

local_gold_close = gold_usd_close × usd_to_local_fx

Sebagai contoh, XAU/USD × USD/IDR menghasilkan nilai rupiah dari satu troy ounce emas. Pendekatan yang sama berlaku untuk mata uang lokal lain yang didukung.

Ini adalah identitas konversi, bukan penjelasan mengapa salah satu pasar bergerak. Simpan kedua seri sumber beserta tanggal observasinya di samping harga lokal turunannya agar hasilnya dapat direproduksi.

Ambil bar emas yang telah settle

Gunakan endpoint bar harian, bukan permintaan spot saat ini. Bar dikembalikan dari yang terbaru lebih dulu, harga berupa string desimal, dan bar yang sedang terbentuk dapat memiliki is_closed: false. Backtest hanya boleh menggunakan baris yang telah settle.

curl -G "https://api.goldprice.dev/v1/bars" \
  -H "Authorization: Bearer $GOLDPRICE_API_KEY" \
  --data-urlencode "symbol=XAU-USD-SPOT" \
  --data-urlencode "interval=1d" \
  --data-urlencode "from=2025-07-10T00:00:00Z" \
  --data-urlencode "to=2026-07-10T00:00:00Z" \
  --data-urlencode "limit=400"

Respons menggunakan bar_start untuk waktu mulai UTC setiap bar harian. Pertahankan close dan is_closed. Jendela riwayat harian pada paket Anda tetap berlaku, dan rentang yang lebih panjang mungkin memerlukan paging melalui next_cursor.

Permintaan satu tahun persis ini menggunakan riwayat Pro. Kunci Free dapat meminta 30 hari riwayat harian; gunakan jendela yang lebih pendek atau paket dengan kedalaman riwayat yang diperlukan sebelum menjalankannya.

Untuk strategi khusus USD, panduan backtesting OHLC yang sudah ada adalah titik awal yang lebih sederhana. Sisa panduan ini menambahkan seri waktu kedua karena mata uang pelaporannya sendiri bergerak.

Pemeriksaan satu tahun yang tervalidasi

Metode ini bukan sekadar teori. Pada tanggal bisnis yang telah settle dan sama, 2025-07-10 dan 2026-07-10, close harian XAU/USD naik dari 3323.81 menjadi 4119.172: return emas-USD sebesar 23.93%. Posisi yang sama memiliki return mata uang lokal yang berbeda setelah pergerakan FX USD/lokal disertakan:

Mata uang pelaporanReturn emas mata uang lokal
EUR26.95%
JPY37.10%
IDR37.97%
INR37.76%
TRY45.50%

Setiap respons endpoint FX adalah observasi ecb_daily bertanggal tepat dan non-forward-filled. Perhitungan ini adalah rekonsiliasi historis, bukan prakiraan, klaim kausal, atau rekomendasi investasi. Untuk dekomposisi persis emas, FX, dan interaksi di balik angka-angka ini, lihat Was gold up, or was your currency down?.

Ambil seri FX sebagaimana diketahui pada setiap tanggal

Untuk pekerjaan multi-hari, endpoint range milik exchangerate.dev mengembalikan observasi harian dalam satu permintaan. Di sini base-nya adalah USD dan mata uang yang diminta adalah IDR, sehingga setiap kurs adalah rupiah per dolar.

curl -G "https://api.exchangerate.dev/v1/range" \
  -H "Authorization: Bearer $EXCHANGERATE_API_KEY" \
  --data-urlencode "base=USD" \
  --data-urlencode "symbols=IDR" \
  --data-urlencode "start_date=2025-07-10" \
  --data-urlencode "end_date=2026-07-10"

Respons range berisi baris hari-bisnis yang dipublikasikan, bukan membuat-buat baris kalender untuk setiap akhir pekan atau hari libur. Jangan mengisi tanggal kalender yang hilang tersebut dengan observasi berikutnya saat membangun seri historis.

Baca seri waktu FX historis dalam satu panggilan untuk respons range dan cara melakukan backfill FX tanpa bias look-ahead untuk aturan observasi bertanggal.

Membangun seri mata uang lokal yang selaras dalam Python

Contoh ini meminta kedua seri, mempertahankan bar emas yang telah settle dan baris FX yang non-forward-filled, lalu melakukan inner join berdasarkan tanggal. Inner join ini disengaja: tanggal yang hilang dari salah satu sumber tidak diciptakan secara diam-diam.

import os
from decimal import Decimal

import pandas as pd
import requests

GOLD_API = "https://api.goldprice.dev/v1"
FX_API = "https://api.exchangerate.dev/v1"

START = "2025-07-10"
END = "2026-07-10"
LOCAL_CURRENCY = "IDR"

# This one-year example uses Pro history. Free keys can request 30 days.


def fetch_gold_daily(start: str, end: str) -> pd.DataFrame:
    response = requests.get(
        f"{GOLD_API}/bars",
        headers={"Authorization": f"Bearer {os.environ['GOLDPRICE_API_KEY']}"},
        params={
            "symbol": "XAU-USD-SPOT",
            "interval": "1d",
            "from": f"{start}T00:00:00Z",
            "to": f"{end}T00:00:00Z",
            "limit": 400,
        },
        timeout=15,
    )
    response.raise_for_status()
    rows = response.json()["bars"]

    gold = pd.DataFrame(rows)
    gold = gold[gold["is_closed"] & gold["close"].notna()].copy()
    gold["date"] = pd.to_datetime(gold["bar_start"], utc=True).dt.date
    gold["gold_usd_close"] = gold["close"].map(Decimal)
    return gold.set_index("date")[["gold_usd_close"]].sort_index()


def fetch_fx_daily(start: str, end: str, currency: str) -> pd.DataFrame:
    response = requests.get(
        f"{FX_API}/range",
        headers={"Authorization": f"Bearer {os.environ['EXCHANGERATE_API_KEY']}"},
        params={
            "base": "USD",
            "symbols": currency,
            "start_date": start,
            "end_date": end,
        },
        timeout=15,
    )
    response.raise_for_status()
    # The range endpoint contains published business-day rows only. Keep its
    # missing weekends and holidays missing rather than carrying a later rate.
    fx = pd.DataFrame(response.json()["data"])
    fx["date"] = pd.to_datetime(fx["date"]).dt.date
    fx["usd_to_local"] = fx["rates"].map(lambda rates: Decimal(str(rates[currency])))
    return fx.set_index("date")[["usd_to_local"]].sort_index()


gold = fetch_gold_daily(START, END)
fx = fetch_fx_daily(START, END, LOCAL_CURRENCY)

# Only use dates with an actual settled gold close and a fresh FX observation.
series = gold.join(fx, how="inner")
series["local_gold_close"] = series["gold_usd_close"] * series["usd_to_local"]
series["local_return"] = series["local_gold_close"].pct_change()

print(series.tail())

Penggunaan Decimal mempertahankan nilai string desimal yang dikembalikan oleh endpoint bar emas. Konversikan hanya di titik presentasi jika pustaka charting memerlukan float.

Hindari tiga kesalahan umum

1. Menggunakan kurs hari ini pada baris historis

/v1/latest ditujukan untuk nilai saat ini. Endpoint ini tidak dapat merekonstruksi kurs yang seharusnya dapat diketahui pada hari yang telah lewat. Gunakan endpoint FX bertanggal atau range untuk pekerjaan historis.

2. Memperlakukan kurs yang dibawa (carried) sebagai observasi baru

Nilai FX bisa valid untuk ditampilkan pada hari libur meski tidak baru dipublikasikan pada hari itu. Field is_forward_filled membuat perbedaan itu terlihat. Pilih dan dokumentasikan apakah model Anda membawa nilai-nilai tersebut atau membatasi diri pada tanggal yang dipublikasikan bersama. Contoh ini memilih opsi kedua.

3. Bertindak berdasarkan close yang sama dengan yang menghasilkan sinyal

Jika moving average menggunakan close suatu hari, close itu belum tersedia sebelum hari tersebut berakhir. Geser (shift) sinyal trading sebanyak satu baris sebelum menerapkannya pada return.

series["fast_ma"] = series["local_gold_close"].rolling(50).mean()
series["slow_ma"] = series["local_gold_close"].rolling(200).mean()
series["signal"] = (series["fast_ma"] > series["slow_ma"]).astype(int)

# A signal formed from today's close may only affect the next row's return.
series["strategy_return"] = series["local_return"] * series["signal"].shift(1)
series["strategy_value"] = (1 + series["strategy_return"].fillna(0)).cumprod()

Pergeseran itu menghilangkan sumber bias look-ahead yang terpisah. Ini tidak membuat strategi menjadi menguntungkan, dan tidak memperhitungkan spread, pajak, biaya penyimpanan, eksekusi, atau premi pasar lokal apa pun.

Catat input beserta hasilnya

Untuk setiap run, simpan jendela tanggal, mata uang, timestamp respons API, status is_closed, status is_forward_filled FX, dan revisi kode persis yang menghasilkan seri tersebut. Field-field itulah yang memungkinkan pembaca di kemudian hari membedakan perhitungan historis yang dapat direproduksi dari konversi saat ini yang ditempel ke dalam spreadsheet lama.

Untuk konversi live atau harga mata uang lokal yang menghadap konsumen, gunakan endpoint konversi emas sebagai gantinya. Konversi historis secara desain adalah perhitungan dua seri: gunakan bar historis yang telah settle dan observasi FX bertanggal yang menjadi pasangannya.

panduan terkait

Developer

Gold price WebSocket on Reddit: the streaming questions, answered

The real-time gold streaming questions that come up on Reddit, answered: whether a gold WebSocket exists, how the goldprice.dev XAU and XAG tick stream works end to end, and which other APIs actually offer one (most metals APIs just poll).

Baca →
Developer

Gold price API on Stack Overflow: developer troubleshooting, answered

The gold-price-API errors developers hit on Stack Overflow, fixed: 401 auth, 400 invalid_symbol, 429 rate limits, CORS from the browser, reading the response, and pulling historical data.

Baca →
Developer

Gold price API on Reddit: the developer questions, answered

The gold-price-API questions that come up on Reddit, answered straight: free tier, update frequency, rate limits, auth, where the number comes from, commercial use, and whether it's good enough to build on.

Baca →

goldprice.dev

Harga emas real-time, OHLC historis, dan agregasi multi-sumber — tersedia via REST dan SSE.