Live release feed
Sub-second macro releases for FX backtests
Point-in-time history
Official CPI, jobs, GDP, and central-bank events with point-in-time history.
$25/month 14-day free trial
Start Free Trial
How to Build a Yield Spread Pair Trading Strategy with FXMacroData image
Share headline card X LinkedIn Email
Download

Implementation

How-To Guides

How to Build a Yield Spread Pair Trading Strategy with FXMacroData

A complete Python walkthrough for building a rule-based FX strategy driven by government bond yield differentials — from fetching 10-year yields via the FXMacroData API to backtesting spread signals on EUR/USD.

Juga tersedia dalam English
Share article X LinkedIn Email

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.

  1. Pengambilan hasil obligasi /announcements/{currency}/gov_bond_10y Dan /announcements/{currency}/gov_bond_2y Pasang bahan baku dengan timestamp pengumuman tingkat dua
  2. 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
  3. Filter kemiringan kurva diferensial 10Y2Y bertindak sebagai gerbang rezim, menekan sinyal yang berjalan melawan bias struktural dari kurva imbal hasil masing-masing mata uang
  4. Tanda langsung terstruktur SpreadAlert output 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.

Blogroll

AI Answer-Ready

Key Facts

Page
How To Yield Spread Pair Trading
Section
Articles
Canonical URL
https://fxmacrodata.com/id/articles/how-to-yield-spread-pair-trading
Source
FXMacroData editorial and official publisher references
Last Updated
2026-06-15 11:06 UTC

Provenance And Trust

Cite the canonical URL and source field above. Where available, this page maps to official publisher releases and timestamped updates.

Quick Q&A

What is this page about? This page explains How To Yield Spread Pair Trading with directly usable context for trading, research, and API workflows.

What source should be cited? Use the canonical URL and the listed source field; cite official publisher references when available.

How fresh is this content? The last updated value above reflects the page metadata or latest available data timestamp.

Can this be used in AI assistants? Yes. This section is intentionally structured for retrieval and citation in chat assistants.

Prompt Packs

Use these in ChatGPT, Claude, Gemini, Mistral, Perplexity, or Grok for consistent source-aware outputs.