수익률 이 아닌 수익률 간 격차 를 교환
두 경제 사이의 정부 채권 수익률 차이는 외환 시장에서 가장 신뢰할 수있는 구조적 힘 중 하나입니다. 미국 10 년 수익률이 독일 펀드 동등보다 크게 이동하면 자본은 USD 자산으로 흐르는 경향이 있으며 EUR/USD는 지속적인 판매 압력에 직면합니다. 그 스프레드가 압축되면 부자가 회복됩니다. 관계는 기계적이지 않지만 그 주위에 규칙 기반 거래 전략을 구축 할 수있는만큼 지속적입니다.
이 가이드는 FXMacroData API를 사용하여 완전한 수익률 스프레드 쌍 거래 전략을 구축하는 과정을 안내합니다.
- 를 통해 두 화폐에 대한 정부 채권 수익률 시간 시리즈를 가져옵니다. FXMacroData 채권 수익률 최종 지점
- 수익률 스프레드와 그 롤링 평균과 표준편차를 계산합니다.
- Z-점수 평균 회귀를 사용하여 통계적으로 구동 된 긴 / 짧은 FX 신호를 생성합니다.
- 오픈 포지션을 추적하고 기본적인 위험 통제를 적용하고 입력/출출 경고를 표시합니다.
핵심 논문
이윤 스프레드는 구조적으로 안정된 평형의 중심에서 평균 반전된다. 스프레이드가 최근 평균을 크게 초과할 때 해당 FX 쌍은 통계적으로 과도하게 확장되어 다시 올 가능성이 있다. 스플레드가 정상화 될 때 정의된 출구로 평균 반전의 방향으로 진입하는 것이 이 접근법의 기초이다.
필수 조건
시작 하기 전 다음 과 같은 것 들 을 준비 해 두십시오.
- 파이썬 3.9+ 모든 단편들은 표준 타입 주석을 사용합니다
- FXMacroData API 키 등록하세요 / 가입 그리고 계정 대시보드에서 키를 복사
- 파이썬 패키지
requestspandasnumpy
pip install requests pandas numpy
환경 변수로 API 키를 저장합니다.
export FXMACRO_API_KEY="YOUR_API_KEY"
단계 1: 정부 채권 수익률 데이터를 가져오기
이 전략의 기초는 신뢰할 수 있는 채권 수익 시간 시리즈입니다. FXMacroData는 주요 통화 블록에 대한 10년 정부 채권 이산율을 gov_bond_10y 각 관찰은 date, a val (수분율로 표시된 수익률) announcement_datetime 두 번째 레벨의 릴리스 타임 스탬프에 대한
여기서는 10년 USD와 EUR의 수익을 추출하고, 날짜에 맞춰진 하나의 데이터프레임으로 집계합니다.
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
지탱되는 모든 통화 을 사용할 수 있습니다. gov_bond_10y 엔드포인트 유행한 조합은 USD/JPY, AUD/USD, GBP/USD 스프레드입니다.
US vs EUR 10-Year Yield — Illustrative
2022~2024년까지 USD 수익률은 EUR 대금보다 더 빠르게 상승했고, 주기에 걸쳐 EUR/USD에 영향을 미치는 지속적인 폭의 스프레드를 만들었습니다.
단계 2: 수익률 스프레드와 Z 스코어를 계산합니다.
원시 스프레드 (USD 미소 EUR 수익률) 는 자본이 구조적으로 편향된 방향을 알려줍니다. Z 점수는 최근 역사에 대한 스프레이드를 정상화하여 시간 기간과 통화 쌍에 직접 비교 가능한 비차원 신호를 제공합니다.
+1.5 이상의 Z 점수는 스프레드가 비정상적으로 크게 잠재적인 EUR/USD (장 USD) 짧은 설정으로 확대되었다는 것을 나타냅니다. -1.5 이하의 Z 점치는 비정상적인 압축을 나타냅니다 잠재적 인 EUR/ USD 긴 설정.
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 점수는 ±1.5 이상으로 확산이 너무 빨리 진행되어 다음 주 동안 평균 역행 경향이있는 역사적으로 표시 된 에피소드입니다.
단계 3: 여러 쌍으로 확장
EUR/USD의 단일 스프레드 전략은 유용하지만, 여러 쌍을 동시에 동일한 논리를 실행할 때 실제 힘은 나타납니다. USD/JPY, AUD/USD, GBP/USD 스프레이드 신호를 결합하면 각 포지션이 독립적으로 크기가있는 다양화된 거시적으로 구동되는 포트폴리오를 제공합니다.
또한 짧은 텐어 을 사용할 수 있습니다. gov_bond_2y 이 종점은 정책금리 기대에 특히 민감하여 10년 연대와 비교하면 선도적 지표입니다.
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
신호 참조
- + 1: 스프레드는 너무 압축되어 있습니다. 확대될 것으로 예상됩니다.
- -1: 스프레드는 너무 넓다 압축을 예상한다 기본 통화 단축 (예를 들어, USD/JPY 단축은 JPY 길다)
- 0: 정상 범위 내에서 퍼져있어요 방향 가장자리가 없네요 평평하게 유지하세요
단계 4: 2년 대 10년 경사 부피를 추가합니다.
이산화 곡선의 형태는 전략에 두 번째 차원을 추가합니다. 절단 곡선 (장단기 수익률이 단기보다 더 빨리 상승) 은 일반적으로 성장 기대를 개선하고 통화를 지원하는 신호입니다. 역전 또는 평평화 곡선은 종종 둔화와 중앙 은행 회전보다 앞서 있습니다.
두쪽 다 2년 그리고 10년 기울기를 계산하고, 그 기울기를 기전 필터로 사용한다: 오직 주식 통화 곡선 기울기와 정렬된 방향으로 스프레드 신호를 가져야 한다.
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))
USD and EUR Curve Slope (10Y–2Y) — Illustrative
Both curves inverted in 2022–2023; the relative slope differential still provided a tradeable signal even when absolute slopes were negative.
단계 5: 알림을 생성하고 실시간 모니터를 구축
마지막 단계는 신호 논리를 모니터로 집어넣고 일정 (일일 폐쇄, 시간, 또는 을 통해 채권 수익 발표 직후 발동) 에 실행할 수 있습니다. FXMacroData 발매 달력새로운 신호가 발사되면 모니터에서 구조화된 알림을 인쇄하여 Slack, 이메일 또는 거래 웹 으로 이동할 수 있습니다.
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
스케줄링 팁
채권 수익률 데이터는 정부가 새로운 발행 결과를 발표하고 중앙 은행이 정책 결정을 발표 할 때 업데이트됩니다. 고정 타임러에 대한 설문 조사 대신 FXMacroData 발매 달력 다음 예정된 채권 경매나 정책 발표를 찾아서 그 사건이 발사된 직후 신호 업데이트를 시작하세요.
쌍들 사이 신호 분포 예시적
60일 분기 창에서 Z 점수 임계값이 ±1.5인 경우, 상당수의 날이 평평한 영역에 떨어지며, 자본 투입이 높은 확신을 가진 세트업에 집중됩니다.
요약 및 다음 단계
이제 당신은 완전한 수익률 스프레드 쌍 거래 프레임워크를 가지고 있습니다. 주요 구성 블록은:
- 채권 수익률
/announcements/{currency}/gov_bond_10y그리고/announcements/{currency}/gov_bond_2y원자재에 두 번째 레벨의 발표 시간표를 공급합니다. - 스프레드와 Z-스코어 60일 분기 창 주위의 평균 역전으로 하나의 임계값에 곡선을 맞추지 않고 객관적인 입출입수준을 생성합니다.
- 곡선 기울기 필터 10Y2Y 이차는 각 통화 자체 수익률 곡선의 구조적 편향에 반대하는 신호를 억제하는 레지엄 게이트 역할을 합니다.
- 실시간 경고 구조화
SpreadAlert출력은 어떤 알림, 로깅 또는 실행 파이프라인으로 쉽게 이동할 수 있습니다.
자연적인 확장은 수익률 스프레드를 정책금리차기 그리고 CPI 차이는 복합 매크로 점수를 얻거나 부진율 인플레이션 조정된 포지셔닝을 위한 명목 수익률 스프레드에서 실제 수익률으로 전환하기 위해서입니다.
모든 사용 가능한 수익률 및 비율 최종 지점에 대한 완전한 문서는 /api- 참조API 키를 받으려면 / 가입- 그래요