Bertukar Kesenjangan Antara Hasil, Bukan Hasil Sendiri
Perbedaan imbal hasil obligasi pemerintah antara dua ekonomi adalah salah satu kekuatan struktural yang paling andal di pasar valuta asing. Ketika imbal 10 tahun AS bergerak secara material di atas setara Bund Jerman, modal cenderung mengalir ke aset USD dan EUR/USD menghadapi tekanan penjualan yang berkelanjutan. Ketika spread tersebut menyusut, pasangan pulih. Hubungan tidak mekanis, tetapi cukup gigih untuk membangun strategi perdagangan berbasis aturan di sekitarnya.
Panduan ini memandu Anda melalui membangun strategi perdagangan pasangan laba laba yang lengkap menggunakan FXMacroData API. Pada akhirnya Anda akan memiliki sistem Python yang berfungsi yang:
- Mengambil obligasi pemerintah hasil seri waktu untuk dua mata uang melalui Nilai akhir hasil obligasi FXMacroData
- Menghitung spread hasil dan rata-rata bergulir dan standar deviasi
- Menghasilkan sinyal FX panjang/pendek yang didorong secara statistik menggunakan reversi rata-rata Z-score
- Melacak posisi terbuka, menerapkan pengendalian risiko dasar dan munculkan peringatan masuk/keluar
Tesis Inti
Jangkauan spread rata-rata-reversed di sekitar keseimbangan yang stabil secara struktural. Ketika spread memperluas secara tajam di atas rata-ratanya baru-baru ini, pasangan FX yang sesuai secara statistik terlalu diperpanjang dan cenderung untuk kembali.
Persyaratan
Sebelum memulai, pastikan Anda siapkan:
- Python 3.9+ semua cuplikan menggunakan anotasi tipe standar
- Kunci API FXMacroData daftar di /langganan dan menyalin kunci Anda dari dasbor akun
- Paket PythonAku tidak tahu.
requestsAku akan pergi.pandasAku akan pergi.numpy
pip install requests pandas numpy
Simpan kunci API Anda sebagai variabel lingkungan jangan pernah hard-code di file sumber:
export FXMACRO_API_KEY="YOUR_API_KEY"
Langkah 1: Dapatkan Data Hasil Obligasi Pemerintah
The foundation of this strategy is reliable bond yield time series. FXMacroData provides 10-year government bond yields for all major currency blocs via the gov_bond_10y Setiap pengamatan membawa dateAku akan pergi. val (pengembalian dalam persen), dan announcement_datetime untuk timestamp rilis tingkat dua.
Di sini kita menarik USD dan EUR 10-tahun hasil, kemudian mengumpulkan mereka ke dalam DataFrame tunggal selaras dengan tanggal:
import os
import requests
import pandas as pd
from datetime import datetime, timedelta
BASE_URL = "https://fxmacrodata.com/api/v1"
API_KEY = os.environ["FXMACRO_API_KEY"]
def fetch_yield(currency: str, tenor: str = "gov_bond_10y", start: str = "2022-01-01") -> pd.Series:
"""Fetch a bond yield series from FXMacroData and return as a dated Series."""
resp = requests.get(
f"{BASE_URL}/announcements/{currency}/{tenor}",
params={"api_key": API_KEY, "start": start},
timeout=15,
)
resp.raise_for_status()
records = resp.json()["data"]
if not records:
raise ValueError(f"No data returned for {currency}/{tenor}")
series = pd.Series(
{r["date"]: r["val"] for r in records},
name=f"{currency.upper()}_10Y",
dtype=float,
)
series.index = pd.to_datetime(series.index)
return series.sort_index()
# Fetch USD and EUR 10-year yields
usd_10y = fetch_yield("usd")
eur_10y = fetch_yield("eur")
# Align on a shared date index (inner join drops days missing in either series)
yields = pd.DataFrame({"USD_10Y": usd_10y, "EUR_10Y": eur_10y}).dropna()
print(yields.tail())
# Output:
# USD_10Y EUR_10Y
# 2025-04-08 4.41 2.71
# 2025-04-09 4.39 2.69
# 2025-04-10 4.49 2.70
# 2025-04-11 4.52 2.73
# 2025-04-14 4.47 2.70
Anda dapat menggunakan pasangan mata uang yang didukung oleh gov_bond_10y titik akhir Kombinasi yang populer termasuk spread USD/JPY, AUD/USD, dan GBP/USD.
US vs EUR 10-Year Yield Ilustratif
USD yields rose faster than EUR equivalents through 2022–2024, creating a persistently wide spread that weighed on EUR/USD throughout the cycle.
Langkah 2: Hitung Spread Yield dan Z-Score
Spread mentah (USD dikurangi hasil EUR) memberi tahu Anda arah mana modal yang bias secara struktural. Z-score menormalkan spread terhadap sejarah baru-baru ini, memberi Anda sinyal tanpa dimensi yang dapat dibandingkan secara langsung di berbagai periode waktu dan pasangan mata uang.
A Z-score above +1.5 indicates the spread has widened unusually far — a potential short EUR/USD (long USD) setup. A Z-score below −1.5 indicates abnormal compression — a potential long EUR/USD setup.
def compute_spread_signal(
yields_df: pd.DataFrame,
base_col: str,
quote_col: str,
lookback: int = 60,
entry_z: float = 1.5,
exit_z: float = 0.3,
) -> pd.DataFrame:
"""
Compute yield spread, rolling Z-score, and directional signals.
A positive spread means base currency yields are higher → base currency
is structurally favoured → signal to be long base / short quote FX pair.
Mean reversion: enter when Z-score is extreme, exit when it normalises.
"""
df = yields_df.copy()
df["spread"] = df[base_col] - df[quote_col]
# Rolling statistics over the lookback window
roll = df["spread"].rolling(lookback, min_periods=lookback // 2)
df["spread_mean"] = roll.mean()
df["spread_std"] = roll.std()
# Z-score: how many standard deviations from the rolling mean
df["zscore"] = (df["spread"] - df["spread_mean"]) / df["spread_std"].replace(0, float("nan"))
# Signal: +1 = long base/short quote, -1 = short base/long quote, 0 = flat
df["signal"] = 0
df.loc[df["zscore"] > entry_z, "signal"] = -1 # spread too wide → expect compression → short base pair
df.loc[df["zscore"] < -entry_z, "signal"] = +1 # spread too tight → expect widening → long base pair
# Exit (override) when Z-score returns toward zero
df.loc[df["zscore"].abs() < exit_z, "signal"] = 0
return df.dropna(subset=["zscore"])
analysis = compute_spread_signal(yields, base_col="USD_10Y", quote_col="EUR_10Y")
print(analysis[["spread", "spread_mean", "zscore", "signal"]].tail(10))
USD–EUR 10Y Spread and Z-Score — Illustrative
Z-scores di luar ±1,5 episode yang secara historis ditandai di mana penyebaran telah bergerak terlalu jauh terlalu cepat dan cenderung rata-rata-reversi selama minggu-minggu berikutnya.
Langkah 3: Luaskan ke beberapa pasangan
Strategi spread tunggal pada EUR/USD berguna, tetapi kekuatan nyata muncul ketika Anda menjalankan logika yang sama di beberapa pasangan secara bersamaan.
Anda juga bisa menggunakan yield-tenor pendek the gov_bond_2y Endpoint sangat sensitif terhadap ekspektasi suku bunga kebijakan, menjadikannya indikator terkemuka dibandingkan dengan seri 10 tahun:
PAIRS = [
# (base_currency, quote_currency, fx_pair_label)
("usd", "eur", "EUR/USD"),
("usd", "jpy", "USD/JPY"),
("aud", "usd", "AUD/USD"),
("gbp", "usd", "GBP/USD"),
]
TENOR = "gov_bond_10y" # swap for gov_bond_2y for rate-expectation signals
results = {}
for base_ccy, quote_ccy, fx_label in PAIRS:
try:
base_series = fetch_yield(base_ccy, tenor=TENOR)
quote_series = fetch_yield(quote_ccy, tenor=TENOR)
df = pd.DataFrame({
f"{base_ccy.upper()}_10Y": base_series,
f"{quote_ccy.upper()}_10Y": quote_series,
}).dropna()
signal_df = compute_spread_signal(
df,
base_col=f"{base_ccy.upper()}_10Y",
quote_col=f"{quote_ccy.upper()}_10Y",
)
latest = signal_df.iloc[-1]
results[fx_label] = {
"spread_pct": round(latest["spread"], 3),
"zscore": round(latest["zscore"], 2),
"signal": int(latest["signal"]),
}
print(f"{fx_label}: spread={latest['spread']:.3f}%, z={latest['zscore']:+.2f}, signal={int(latest['signal']):+d}")
except Exception as exc:
print(f"{fx_label}: skipped — {exc}")
# Example output:
# EUR/USD: spread=1.770%, z=+1.21, signal=0
# USD/JPY: spread=4.050%, z=+2.18, signal=-1
# AUD/USD: spread=0.340%, z=-0.55, signal=0
# GBP/USD: spread=0.890%, z=-1.62, signal=+1
Referensi sinyal
- +1: spread terlalu terkompresi mengharapkan perpindahan kaki panjang mata uang dasar (misalnya, panjang GBP/USD)
- - 1: spread terlalu luas mengharapkan kompresi kaki pendek mata uang dasar (misalnya, USD/JPY pendek adalah JPY panjang)
- 0: menyebar dalam kisaran normal tidak ada tepi arah tetap datar
Langkah 4: Tambahkan 2 Tahun vs 10 Tahun Slope Overlay
Bentuk kurva imbal hasil menambah dimensi kedua untuk strategi. kurva curvature (imbal hasil ujung panjang naik lebih cepat daripada ujung pendek) biasanya sinyal peningkatan harapan pertumbuhan dan mendukung mata uang. kurva terbalik atau rata sering mendahului perlambatan dan pivot bank sentral.
Tarik kedua 2 tahun Dan 10 tahun menghasilkan untuk menghitung kemiringan, dan menggunakannya sebagai filter regime: hanya mengambil sinyal spread di arah sejajar dengan kemirungan kurva mata uang rumah.
def fetch_curve_slope(currency: str, start: str = "2022-01-01") -> pd.Series:
"""Return the 10Y–2Y slope for a currency (positive = normal/steep, negative = inverted)."""
y10 = fetch_yield(currency, tenor="gov_bond_10y", start=start)
y2 = fetch_yield(currency, tenor="gov_bond_2y", start=start)
slope = (y10 - y2).dropna()
slope.name = f"{currency.upper()}_slope"
return slope
def apply_curve_filter(
signal_df: pd.DataFrame,
base_slope: pd.Series,
quote_slope: pd.Series,
) -> pd.DataFrame:
"""
Suppress signals that contradict the curve-slope regime.
Long base / short quote (signal=+1) is only taken when:
- base curve is steep (positive slope) AND
- quote curve is flat or inverted (slope < base_slope)
Short base / long quote (signal=-1) is only taken when:
- quote curve is steep relative to base
"""
df = signal_df.copy()
df = df.join(base_slope.rename("base_slope"), how="left")
df = df.join(quote_slope.rename("quote_slope"), how="left")
df[["base_slope", "quote_slope"]] = df[["base_slope", "quote_slope"]].ffill()
# Slope differential: positive → base is steeper → supportive for base
df["slope_diff"] = df["base_slope"] - df["quote_slope"]
# Filter: suppress longs when slope_diff is negative (quote steeper)
df.loc[(df["signal"] == +1) & (df["slope_diff"] < 0), "signal"] = 0
# Filter: suppress shorts when slope_diff is positive (base steeper)
df.loc[(df["signal"] == -1) & (df["slope_diff"] > 0), "signal"] = 0
return df
# Example for EUR/USD
usd_slope = fetch_curve_slope("usd")
eur_slope = fetch_curve_slope("eur")
analysis_filtered = apply_curve_filter(analysis, base_slope=usd_slope, quote_slope=eur_slope)
print(analysis_filtered[["zscore", "signal", "slope_diff"]].tail(8))
Kemiringan kurva USD dan EUR (10Y2Y) Ilustratif
Both curves inverted in 2022–2023; the relative slope differential still provided a tradeable signal even when absolute slopes were negative.
Langkah 5: Membuat peringatan dan membangun monitor langsung
Langkah terakhir mengumpulkan logika sinyal ke dalam monitor yang dapat Anda jalankan pada jadwal (penutupan harian, setiap jam, atau dipicu segera setelah pengumuman imbal hasil obligasi melalui Kalender rilis FXMacroDataKetika sinyal baru menyala, monitor mencetak peringatan terstruktur yang dapat Anda jalankan ke Slack, email, atau webhook perdagangan.
from dataclasses import dataclass
from typing import Literal
SignalType = Literal["LONG", "SHORT", "EXIT", "HOLD"]
@dataclass
class SpreadAlert:
fx_pair: str
signal: SignalType
spread_pct: float
zscore: float
slope_diff: float
timestamp: str
def latest_signal(
base_ccy: str,
quote_ccy: str,
fx_pair: str,
tenor: str = "gov_bond_10y",
lookback: int = 60,
) -> SpreadAlert:
base_yields = fetch_yield(base_ccy, tenor=tenor)
quote_yields = fetch_yield(quote_ccy, tenor=tenor)
df = pd.DataFrame({
"base": base_yields,
"quote": quote_yields,
}).dropna()
base_col, quote_col = "base", "quote"
df = compute_spread_signal(
df.rename(columns={"base": base_col, "quote": quote_col}),
base_col=base_col,
quote_col=quote_col,
lookback=lookback,
)
# Curve filter
base_slope = fetch_curve_slope(base_ccy)
quote_slope = fetch_curve_slope(quote_ccy)
df = apply_curve_filter(df, base_slope, quote_slope)
row = df.iloc[-1]
sig_map = {1: "LONG", -1: "SHORT", 0: "HOLD"}
return SpreadAlert(
fx_pair=fx_pair,
signal=sig_map[int(row["signal"])],
spread_pct=round(float(row["spread"]), 3),
zscore=round(float(row["zscore"]), 2),
slope_diff=round(float(row.get("slope_diff", float("nan"))), 3),
timestamp=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
)
# Run for all pairs
for base_ccy, quote_ccy, fx_label in PAIRS:
try:
alert = latest_signal(base_ccy, quote_ccy, fx_label)
print(
f"[{alert.timestamp}] {alert.fx_pair:8s} | {alert.signal:5s} | "
f"spread={alert.spread_pct:+.3f}% z={alert.zscore:+.2f} slope_diff={alert.slope_diff:+.3f}"
)
except Exception as exc:
print(f"{fx_label}: error — {exc}")
# Example output:
# [2025-04-14T08:32:11Z] EUR/USD | HOLD | spread=+1.770% z=+1.21 slope_diff=+0.210
# [2025-04-14T08:32:14Z] USD/JPY | SHORT | spread=+4.050% z=+2.18 slope_diff=+0.580
# [2025-04-14T08:32:17Z] AUD/USD | HOLD | spread=+0.340% z=-0.55 slope_diff=-0.120
# [2025-04-14T08:32:20Z] GBP/USD | LONG | spread=+0.890% z=-1.62 slope_diff=-0.340
Tip Jadwal
Data hasil obligasi diperbarui ketika pemerintah menerbitkan hasil emisi baru dan ketika bank sentral merilis keputusan kebijakan. Kalender rilis FXMacroData untuk menemukan lelang obligasi atau pengumuman kebijakan yang dijadwalkan berikutnya, dan memicu pembaruan sinyal Anda segera setelah peristiwa itu terjadi.
Distribusi sinyal di antara pasangan Ilustratif
Dengan ambang Z-score ±1,5 pada jendela bergulir 60 hari, sebagian besar hari berada di zona datar, memusatkan penempatan modal ke pengaturan keyakinan tinggi.
Ringkasan dan Langkah Selanjutnya
Anda sekarang memiliki kerangka kerja perdagangan pasangan laba laba yang lengkap.
- Pengambilan hasil obligasi
/announcements/{currency}/gov_bond_10yDan/announcements/{currency}/gov_bond_2yPasang bahan baku dengan timestamp pengumuman tingkat dua - Spread dan Z-score rata-rata kemunduran sekitar jendela bergulir 60 hari menghasilkan tingkat masuk dan keluar yang objektif tanpa penyesuaian kurva pada ambang batas tunggal
- Filter kemiringan kurva diferensial 10Y2Y bertindak sebagai gerbang rezim, menekan sinyal yang berjalan melawan bias struktural dari kurva imbal hasil masing-masing mata uang
- Tanda langsung terstruktur
SpreadAlertoutput mudah diarahkan ke setiap pemberitahuan, logging, atau pipa eksekusi
Ekstensi alami termasuk menggabungkan spread hasil dengan perbedaan suku bunga kebijakan Dan Perbedaan IPC dalam skor makro komposit, atau menggunakan tingkat inflasi titik impas untuk beralih dari nominal ke real yield spread untuk posisi yang disesuaikan dengan inflasi.
Dokumentasi lengkap untuk semua hasil dan tingkat titik akhir yang tersedia di /api-referensiUntuk mendapatkan kunci API Anda, kunjungi /langgananAku tidak tahu.