Compute the gold-to-silver ratio with the API
Pull gold and silver spot prices in a single async call, divide them, and display the ratio with a working JavaScript example.
Read →Pull daily OHLC history from the goldprice.dev API and run a simple moving-average crossover backtest in Python to evaluate a gold trading strategy.
Before you act on any price signal, you want to know whether it would have worked historically. The /v1/prices/history endpoint gives you daily OHLC going back years, which is enough to test most strategies without any paid data subscription.
This post walks through fetching the history, computing a 50/200-day moving-average crossover, and evaluating the result: the classic "golden cross / death cross" signal applied to the metal itself. For a primer on what drives gold's price at a macro level, see What actually drives the gold price.
The history endpoint takes a symbol, a date range, and an interval:
GET /v1/prices/history?symbol=XAU-USD-SPOT&from=2023-01-01&to=2026-06-01&interval=1d
The response is a series array of daily bars:
{
"series": [
{ "date": "2023-01-02", "open": 1824.30, "high": 1831.50, "low": 1820.10, "close": 1829.40 },
...
]
}
Here is the fetch code:
import os
import requests
import pandas as pd
API_KEY = os.environ["GOLDPRICE_API_KEY"]
BASE = "https://api.goldprice.dev"
def fetch_ohlc(symbol: str, from_date: str, to_date: str) -> pd.DataFrame:
resp = requests.get(
f"{BASE}/v1/prices/history",
params={"symbol": symbol, "from": from_date, "to": to_date, "interval": "1d"},
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=15,
)
resp.raise_for_status()
data = resp.json()
df = pd.DataFrame(data["series"])
df["date"] = pd.to_datetime(df["date"])
df = df.set_index("date").sort_index()
return df
df = fetch_ohlc("XAU-USD-SPOT", "2021-01-01", "2026-06-01")
print(df.tail())
You need requests and pandas. pip install requests pandas if you do not have them.
A moving-average crossover strategy generates a buy signal when a short-window average crosses above a long-window average, and a sell signal when it crosses back below. We use 50 and 200 days, which are widely watched levels.
df["ma50"] = df["close"].rolling(50).mean()
df["ma200"] = df["close"].rolling(200).mean()
# Signal: 1 when ma50 > ma200, 0 otherwise
df["signal"] = (df["ma50"] > df["ma200"]).astype(int)
# Position: difference of signal tells us when we cross
df["position"] = df["signal"].diff()
# +1.0 = buy (golden cross), -1.0 = sell (death cross)
crosses = df[df["position"] != 0][["close", "position"]].dropna()
print(crosses.head(10))
We compare the strategy's return against simply holding gold for the same period.
# Daily log returns
df["log_return"] = (df["close"] / df["close"].shift(1)).apply(lambda x: x if pd.notna(x) else 1)
import numpy as np
df["log_return"] = np.log(df["close"] / df["close"].shift(1))
# Strategy return: only in market when signal == 1
# Shift signal by 1 day to avoid look-ahead bias (we act on the next open)
df["strategy_return"] = df["log_return"] * df["signal"].shift(1)
# Cumulative returns
df["cum_market"] = df["log_return"].cumsum().apply(np.exp)
df["cum_strategy"] = df["strategy_return"].cumsum().apply(np.exp)
final = df[["cum_market", "cum_strategy"]].dropna().iloc[-1]
print(f"Buy-and-hold: {final['cum_market']:.2%}")
print(f"MA crossover: {final['cum_strategy']:.2%}")
This gives you cumulative return multipliers. A value of 1.45 means 45% total return over the period.
Raw return does not tell you much without knowing how volatile the path was. Annualized Sharpe ratio is a standard check:
trading_days = 252
strat_returns = df["strategy_return"].dropna()
sharpe = (strat_returns.mean() / strat_returns.std()) * np.sqrt(trading_days)
print(f"Annualized Sharpe: {sharpe:.2f}")
# Max drawdown
cum = df["cum_strategy"].dropna()
rolling_max = cum.cummax()
drawdown = (cum - rolling_max) / rolling_max
max_dd = drawdown.min()
print(f"Max drawdown: {max_dd:.2%}")
A Sharpe above 1.0 is generally considered acceptable. Max drawdown tells you the worst peak-to-trough loss the strategy experienced. That is the number that tests whether you would have held.
A single backtest proves nothing. The 50/200 crossover is studied by enough people that any edge it had historically has likely been priced in. What this code gives you is a framework.
Swap in different window sizes (say, 20/50 or 10/100) and re-run. Add a filter: only take the long signal when the 200-day average is itself rising. Test on different sub-periods: how did the strategy behave during rate-hike cycles versus rate-cut cycles? Each question is a few lines of pandas.
The history endpoint supports any date range your plan covers. To get fresh data for a daily cron job:
from datetime import date, timedelta
today = date.today().isoformat()
two_years_ago = (date.today() - timedelta(days=730)).isoformat()
df = fetch_ohlc("XAU-USD-SPOT", two_years_ago, today)
Run this before your strategy logic and you always have an up-to-date dataset.
On data quality: the /v1/prices/history endpoint returns the same aggregated price used in live spot queries, computed from multiple sources. Because that aggregation can change over time as sources are added or adjusted, very old historical data and recently computed data may have subtle differences in methodology. For most backtesting purposes this does not matter, but if you are running a high-frequency strategy sensitive to exact price levels, verify that your historical assumptions match how the current aggregation works before drawing hard conclusions.
Get started at goldprice.dev/docs/quickstart. The free tier includes history access, and you can view current plan limits before upgrading.
// related guides
Pull gold and silver spot prices in a single async call, divide them, and display the ratio with a working JavaScript example.
Read →How to convert a USD per troy ounce gold price into other currencies and per-gram units, with a working JavaScript example.
Read →Read per-source prices from the API response, compute the spread in basis points, and flag when sources disagree beyond your tolerance threshold.
Read →// goldprice.dev
Live gold prices, historical OHLC, and multi-source aggregation — available via REST and SSE.