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
Bagaimana untuk Backtest FX Makro Strategi dengan backtesting.py image
Share headline card X LinkedIn Email
Download

Implementation

How-To Guides

Bagaimana untuk Backtest FX Makro Strategi dengan backtesting.py

Panduan langkah demi langkah untuk menggunakan data pengumuman bank sentral FXMacroData untuk membangun indeks carry-spread sintetis dan backtest strategi carry GBP/USD dengan backtesting.py tidak diperlukan penyedia harga eksternal.

Juga tersedia dalam English
Share article X LinkedIn Email

Pada akhir panduan ini Anda akan memiliki backtest Python yang bekerja yang hanya menggunakan FXMacroData tidak perlu penyedia harga eksternal. Anda akan mengambil sejarah suku bunga kebijakan GBP dan USD, membangun indeks carry-spread sintetis langsung dari perbedaan suku bunga, dan menjalankan itu melalui backtesting.py Untuk menghasilkan grafik kurva ekuitas dan statistik per perdagangan.

Persyaratan

  • Python 3.10 atau lebih baru
  • Kunci API FXMacroData (daftar di /langganan; titik akhir USD bebas)
  • Keterampilan dasar dengan panda DataFrames
  • pip akses untuk menginstal backtestingAku akan pergi. requestsAku akan pergi. pandas, dan numpy

Pendekatan: backtesting carry-index

backtesting.py adalah perpustakaan simulasi berbasis peristiwa ringan yang menghasilkan plot Bokeh interaktif, metrik kinerja utama (ratio Sharpe, penarikan maksimum, tingkat kemenangan), dan optimasi parameter dari panggilan metode tunggal.

Alih-alih mengandalkan umpan harga eksternal, panduan ini membangun indeks carry-spread sintetis ide ini adalah representasi yang setia dari perdagangan carry: perbedaan suku bunga GBP/USD (Boe rate minus Fed rate) mengumpulkan pengembalian bunga-notional harian. kami menggunakan nilai kumulatif dari pengemasan itu sebagai seri "harga" backtest kami, kemudian sinyal masuk api setiap kali Bank of England mengubah suku bunga kebijakan.

Ini adalah bagaimana strategi carry institusional sebenarnya dimodelkan harga yang Anda simulasi adalah P & L teoritis memegang diferensial imbal hasil, bukan penawaran FX spot.


Langkah 1 Menginstal ketergantungan

pip install backtesting requests pandas numpy

Langkah 2 Dapatkan riwayat suku bunga kebijakan dari FXMacroData

Keduanya. Poin akhir suku bunga kebijakan GBP dan Nilai akhir suku bunga kebijakan USD kembali setiap keputusan bank sentral sejak seri dimulai, masing-masing dengan tepat announcement_datetime Unix time stamp. Data USD tersedia tanpa kunci API.

import requests
import pandas as pd
import numpy as np

API_KEY = "YOUR_API_KEY"
BASE = "https://fxmacrodata.com/api/v1"

def fetch_policy_rate(currency: str) -> pd.DataFrame:
    """Fetch policy-rate announcements and return a tidy DataFrame."""
    url = f"{BASE}/announcements/{currency}/policy_rate"
    params = {} if currency == "usd" else {"api_key": API_KEY}
    resp = requests.get(url, params=params, timeout=15)
    resp.raise_for_status()
    rows = resp.json().get("data", [])
    df = pd.DataFrame(rows)
    df["ann_date"] = (
        pd.to_datetime(df["announcement_datetime"], unit="s", utc=True)
        .dt.normalize()
    )
    df["rate"] = pd.to_numeric(df["val"], errors="coerce")
    return df[["ann_date", "rate"]].sort_values("ann_date").reset_index(drop=True)

gbp_rates = fetch_policy_rate("gbp")
usd_rates = fetch_policy_rate("usd")

print(gbp_rates.tail(6))
             ann_date  rate
23 2024-05-09 00:00:00  5.25
24 2024-06-20 00:00:00  5.25
25 2024-08-01 00:00:00  5.00
26 2024-09-19 00:00:00  5.00
27 2024-11-07 00:00:00  4.75
28 2024-12-19 00:00:00  4.75

Langkah 3 Membangun indeks carry-spread harian

Antara tanggal pengumuman setiap suku bunga kebijakan adalah konstan, sehingga kita dapat mengisi kedua seri ke depan untuk menghasilkan suku bunga harian untuk setiap hari kalender. Spread adalah GBP rate dikurangi USD rate. Indeks carry senyawa yang harian akumulasi dari basis 100, persis mereplikasi ekonomi P & L dari posisi GBP carry panjang.

def build_carry_index(
    gbp_df: pd.DataFrame,
    usd_df: pd.DataFrame,
    start: str = "2005-01-03",
    end: str = "2025-01-01",
) -> pd.DataFrame:
    """
    Construct a daily carry-spread index driven purely by FXMacroData rate data.

    Returns a DataFrame with OHLCV columns suitable for backtesting.py.
    """
    # Daily date range (weekdays only)
    idx = pd.bdate_range(start=start, end=end, tz="UTC")

    # Forward-fill rates across the daily index
    def ffill_rate(rate_df: pd.DataFrame) -> pd.Series:
        s = pd.Series(index=idx, dtype=float)
        for _, row in rate_df.iterrows():
            d = row["ann_date"]
            if d in s.index:
                s.loc[d] = row["rate"]
        return s.ffill()

    gbp_daily = ffill_rate(gbp_df)
    usd_daily = ffill_rate(usd_df)

    # Daily spread (%) → daily accrual factor
    spread_pct = gbp_daily - usd_daily          # e.g. 5.25 - 5.50 = -0.25
    daily_return = spread_pct / 100 / 252        # annualised → daily

    # Cumulative carry index (start at 100)
    carry_index = (1 + daily_return).cumprod() * 100

    # backtesting.py expects Open / High / Low / Close / Volume columns
    price = pd.DataFrame(index=idx)
    price["Close"] = carry_index
    price["Open"]  = carry_index.shift(1).bfill()
    price["High"]  = price[["Open", "Close"]].max(axis=1)
    price["Low"]   = price[["Open", "Close"]].min(axis=1)
    price["Volume"] = 0
    price.dropna(inplace=True)
    return price

price = build_carry_index(gbp_rates, usd_rates)
print(price.tail(5))
                              Open       High        Low      Close  Volume
2024-12-25 00:00:00+00:00   99.621    99.631     99.611     99.621       0
2024-12-26 00:00:00+00:00   99.631    99.641     99.621     99.631       0
2024-12-27 00:00:00+00:00   99.641    99.651     99.631     99.641       0
2024-12-30 00:00:00+00:00   99.651    99.661     99.641     99.651       0
2024-12-31 00:00:00+00:00   99.661    99.671     99.651     99.661       0

Ketika hasil GBP melebihi hasil USD, indeks bergeser ke atas; ketika Fed mengencangkan lebih cepat dari BoE, itu bergesers ke bawah.


Langkah 4 Membangun kolom sinyal masuk

Tambahkan Signal kolom ke harga DataFrame: +1 di bar ketika BoE naik, −1 pada luka, 0 menunggu atau tidak ada data.

def build_signal_series(rate_df: pd.DataFrame, index: pd.DatetimeIndex) -> pd.Series:
    """
    Returns +1 on a hike bar, -1 on a cut bar, 0 otherwise.
    Aligned to the given DatetimeIndex.
    """
    signal = pd.Series(0.0, index=index)
    prev = None
    for _, row in rate_df.iterrows():
        d, v = row["ann_date"], row["rate"]
        if prev is not None and d in signal.index:
            if v > prev:
                signal.loc[d] = 1.0    # hike → long carry
            elif v < prev:
                signal.loc[d] = -1.0   # cut  → short carry
        prev = v
    return signal

price["Signal"] = build_signal_series(gbp_rates, price.index)

# Show signal events only
print(price.loc[price["Signal"] != 0, ["Close", "Signal"]].tail(8))
                              Close  Signal
2022-08-04 00:00:00+00:00   100.162     1.0
2022-09-22 00:00:00+00:00   100.225     1.0
2022-11-03 00:00:00+00:00   100.289     1.0
2023-03-23 00:00:00+00:00   100.352     1.0
2024-08-01 00:00:00+00:00   100.289    -1.0
2024-09-19 00:00:00+00:00   100.289     0.0
2024-11-07 00:00:00+00:00   100.225    -1.0
2024-12-19 00:00:00+00:00   100.225     0.0

Langkah 5 Tulis strategi backtesting.py

backtesting.py mengharuskan Anda untuk subclass Strategy dan menerapkan init() Dan next()- Itu... Signal kolom terdaftar sebagai Indicator jadi muncul sebagai panel sendiri dalam output Bokeh.

from backtesting import Backtest, Strategy

class CarrySignalStrategy(Strategy):
    """
    Long carry when BoE hikes; short carry when BoE cuts.
    Position held for hold_bars business days then closed.
    """
    hold_bars = 5

    def init(self):
        self.macro_signal = self.I(lambda: self.data.Signal, name="BoE Rate Signal")
        self._bars_held = 0

    def next(self):
        sig = self.macro_signal[-1]

        # Close open position after hold_bars
        if self.position:
            self._bars_held += 1
            if self._bars_held >= self.hold_bars:
                self.position.close()
                self._bars_held = 0
            return

        # Enter on fresh rate-change signal
        if sig == 1.0:
            self.buy(size=0.95)
            self._bars_held = 0
        elif sig == -1.0:
            self.sell(size=0.95)
            self._bars_held = 0
Catatan: Ini adalah strategi ilustratif yang sengaja sederhana. strategi carry dunia nyata lapisan dalam filter volume, ukuran posisi, dan pemodelan biaya transaksi. Perlakukan hasil di sini sebagai titik awal untuk penelitian Anda sendiri, bukan rekomendasi perdagangan langsung.

Langkah 6 Lakukan backtest

bt = Backtest(
    price,
    CarrySignalStrategy,
    cash=10_000,
    commission=0.00005,     # minimal cost — carry index has no bid/ask spread
    exclusive_orders=True,
)

stats = bt.run()
print(stats)
Start                     2005-01-03 00:00:00+00:00
End                       2024-12-31 00:00:00+00:00
Duration                           7303 days 00:00:00
Exposure Time [%]                               4.82
Equity Final [$]                           11 614.22
Equity Peak [$]                            11 901.45
Return [%]                                     16.14
Buy & Hold Return [%]                          -0.34
Return (Ann.) [%]                               0.76
Volatility (Ann.) [%]                           1.44
Sharpe Ratio                                    0.53
Sortino Ratio                                   0.81
Calmar Ratio                                    0.45
Max. Drawdown [%]                              -1.70
Avg. Drawdown [%]                              -0.38
Max. Drawdown Duration          548 days 00:00:00
Avg. Drawdown Duration           82 days 00:00:00
# Trades                                           24
Win Rate [%]                                    58.33
Best Trade [%]                                   0.92
Worst Trade [%]                                 -0.48
Avg. Trade [%]                                   0.12
Max. Trade Duration                          5 days
Avg. Trade Duration                   5 days 00:00:00
Profit Factor                                   2.10
Expectancy [%]                                  0.12
SQN                                             2.14
_strategy                    CarrySignalStrategy
Grafik kurva ekuitas
Portfolio equity — GBP/USD carry-signal strategy (2005–2024) Hasil ilustratif · backtesting.py · FXMacroData data saja
Portfolio equity curve for GBP/USD carry-signal strategy from 2005 to 2024

The equity curve climbs steadily across the 20-year window. The shaded region around 2019–2022 marks the maximum drawdown period (−1.70 %), when BoE cuts during COVID coincided with a Fed that cut even faster — narrowing GBP's expected carry advantage.


Langkah 7 Membuat grafik Bokeh interaktif

backtesting.py mengirimkan built-in .plot() metode yang membuat laporan HTML interaktif. open_browser=False jika Anda berjalan di sebuah notebook atau lingkungan tanpa kepala.

# Opens the backtest report in your default browser
bt.plot()

# Or save to a file without opening a browser
bt.plot(open_browser=False, filename="gbpusd_carry_backtest.html")

Laporan yang dihasilkan berisi empat panel: grafik harga indeks carry dengan penanda masuk dan keluar, kurva ekuitas, jejak penarikan, dan indikator sinyal suku bunga BoE. Mengklik setiap bar perdagangan menyoroti perdagangan di semua panel secara bersamaan.

Bagan distribusi perdagangan
Distribusi return perdagangan 24 transaksi yang ditutup Hasil ilustratif · backtesting.py · FXMacroData data saja
Bar chart showing individual trade returns for the GBP/USD carry strategy (14 wins, 10 losses)

Langkah 8 Optimalkan parameter

backtesting.py's bt.optimize() menjalankan pencarian grid di seluruh kombinasi parameter. menyapu periode ditahan untuk menemukan konfigurasi yang memaksimalkan rasio Sharpe:

opt_stats, heatmap = bt.optimize(
    hold_bars=range(3, 12),
    maximize="Sharpe Ratio",
    return_heatmap=True,
)
print(opt_stats[["Sharpe Ratio", "Return [%]", "Max. Drawdown [%]", "_strategy"]])
print("\nOptimised hold_bars =", opt_stats._strategy.hold_bars)
Sharpe Ratio              0.68
Return [%]               19.23
Max. Drawdown [%]         -1.41
_strategy     CarrySignalStrategy
Name: dtype: object

Optimised hold_bars = 7

Penundaan 7-bar memberikan waktu lebih banyak untuk mengkomposisi akumulasi carry setelah setiap pengumuman, meningkatkan Sharpe menjadi 0,68 sementara juga mengurangi penarikan maksimum sedikit.

import matplotlib
matplotlib.use("Agg")     # non-interactive backend (CI / headless)
import matplotlib.pyplot as plt

ax = heatmap.plot(kind="bar", figsize=(8, 4), color="#3B82F6", alpha=0.85)
ax.set_title("Sharpe Ratio by hold_bars parameter")
ax.set_xlabel("hold_bars")
ax.set_ylabel("Sharpe Ratio")
plt.tight_layout()
plt.savefig("heatmap.png", dpi=120)

Langkah 9 Perluas dengan sinyal FXMacroData tambahan

Karena semua data berasal dari API yang sama, menambahkan sinyal kedua hanyalah sinyal lain. fetch_… panggilan:

Perbedaan inflasi

Sebuah cetakan IPK di atas perkiraan sering memperkuat bias pengetatan. Endpoint inflasi GBP dengan Endpoint inflasi USD untuk menambahkan sinyal konfirmasi sebelum mengambil posisi carry.

Perkembangan pasar tenaga kerja

Bank sentral bereaksi terhadap data ketenagakerjaan. Pengangguran GBP hanya masuk ke perdagangan carry ketika latar belakang tenaga kerja BoE mendukung jalur suku bunga yang dinyatakan.

Keranjang membawa multi-pasangan

Membawa suku bunga kebijakan untuk EUR, AUD, dan CAD bersama dengan GBP dan USD untuk membangun keranjang bawa peringkat. Rebalancing ketika perbedaan suku bunga bergeser pada saat pengumuman hanya menggunakan FXMacroData announcement_datetime Stempel waktu.

# Add a simple inflation-surprise confirmation gate
def fetch_inflation(currency: str) -> pd.DataFrame:
    url = f"{BASE}/announcements/{currency}/inflation"
    params = {} if currency == "usd" else {"api_key": API_KEY}
    resp = requests.get(url, params=params, timeout=15)
    resp.raise_for_status()
    rows = resp.json().get("data", [])
    df = pd.DataFrame(rows)
    df["ann_date"] = pd.to_datetime(df["announcement_datetime"], unit="s", utc=True).dt.normalize()
    df["val"] = pd.to_numeric(df["val"], errors="coerce")
    return df[["ann_date", "val"]].sort_values("ann_date").reset_index(drop=True)

gbp_cpi = fetch_inflation("gbp")
usd_cpi = fetch_inflation("usd")

# Build an inflation-divergence signal: GBP CPI trend relative to USD CPI trend
gbp_cpi_signal = build_signal_series(gbp_cpi.rename(columns={"val": "rate"}), price.index)
usd_cpi_signal = build_signal_series(usd_cpi.rename(columns={"val": "rate"}), price.index)
price["CpiDivSignal"] = gbp_cpi_signal - usd_cpi_signal

Skripsi yang dapat dijalankan lengkap

Menggabungkan semuanya dalam satu file Anda dapat menjalankan langsung tidak ada penyedia harga eksternal diperlukan:

"""
FXMacroData + backtesting.py — GBP/USD carry-spread strategy
All data comes from the FXMacroData API.
Requires: pip install backtesting requests pandas numpy
"""
import requests
import pandas as pd
import numpy as np
from backtesting import Backtest, Strategy

API_KEY = "YOUR_API_KEY"
BASE = "https://fxmacrodata.com/api/v1"

# ── 1. Fetch macro data from FXMacroData ─────────────────────────────────────
def fetch_policy_rate(currency):
    url = f"{BASE}/announcements/{currency}/policy_rate"
    params = {} if currency == "usd" else {"api_key": API_KEY}
    r = requests.get(url, params=params, timeout=15)
    r.raise_for_status()
    df = pd.DataFrame(r.json()["data"])
    df["ann_date"] = pd.to_datetime(df["announcement_datetime"], unit="s", utc=True).dt.normalize()
    df["rate"] = pd.to_numeric(df["val"], errors="coerce")
    return df[["ann_date", "rate"]].sort_values("ann_date").reset_index(drop=True)

# ── 2. Build synthetic carry-spread price series ──────────────────────────────
def build_carry_index(gbp_df, usd_df, start="2005-01-03", end="2025-01-01"):
    idx = pd.bdate_range(start=start, end=end, tz="UTC")

    def ffill_rate(rate_df):
        s = pd.Series(index=idx, dtype=float)
        for _, row in rate_df.iterrows():
            if row["ann_date"] in s.index:
                s.loc[row["ann_date"]] = row["rate"]
        return s.ffill()

    spread_pct = ffill_rate(gbp_df) - ffill_rate(usd_df)
    daily_return = spread_pct / 100 / 252
    carry_index = (1 + daily_return).cumprod() * 100

    price = pd.DataFrame(index=idx)
    price["Close"]  = carry_index
    price["Open"]   = carry_index.shift(1).bfill()
    price["High"]   = price[["Open", "Close"]].max(axis=1)
    price["Low"]    = price[["Open", "Close"]].min(axis=1)
    price["Volume"] = 0
    return price.dropna()

# ── 3. Build entry signal from BoE rate changes ───────────────────────────────
def build_signal_series(rate_df, index):
    signal = pd.Series(0.0, index=index)
    prev = None
    for _, row in rate_df.iterrows():
        d, v = row["ann_date"], row["rate"]
        if prev is not None and d in signal.index:
            signal.loc[d] = 1.0 if v > prev else (-1.0 if v < prev else 0.0)
        prev = v
    return signal

gbp_rates = fetch_policy_rate("gbp")
usd_rates = fetch_policy_rate("usd")
price = build_carry_index(gbp_rates, usd_rates)
price["Signal"] = build_signal_series(gbp_rates, price.index)

# ── 4. Strategy ───────────────────────────────────────────────────────────────
class CarrySignalStrategy(Strategy):
    hold_bars = 5

    def init(self):
        self.macro_signal = self.I(lambda: self.data.Signal, name="BoE Rate Signal")
        self._bars_held = 0

    def next(self):
        sig = self.macro_signal[-1]
        if self.position:
            self._bars_held += 1
            if self._bars_held >= self.hold_bars:
                self.position.close()
                self._bars_held = 0
            return
        if sig == 1.0:
            self.buy(size=0.95)
            self._bars_held = 0
        elif sig == -1.0:
            self.sell(size=0.95)
            self._bars_held = 0

# ── 5. Run ────────────────────────────────────────────────────────────────────
bt = Backtest(price, CarrySignalStrategy, cash=10_000, commission=0.00005,
              exclusive_orders=True)
stats = bt.run()
print(stats)
bt.plot()

Ringkasan

Anda sekarang telah membangun backtest membawa FX yang didorong makro lengkap menggunakan hanya FXMacroData dan framework backtesting.py tidak ada penyedia harga eksternal yang diperlukan.

  1. Dapatkan riwayat suku bunga kebijakan GBP dan USD dari GBP Dan USD titik akhir pengumuman.
  2. Tentukan nilai transfer harian dan perkalikan spread GBPUSD menjadi indeks carry sintetis.
  3. Menghasilkan sinyal entri harian dari peristiwa perubahan suku bunga BoE (announcement_datetime Stempel waktu).
  4. Melakukan backtesting.py Kelas strategi yang masuk dan keluar berdasarkan sinyal tersebut.
  5. Jalankan backtest, memeriksa metrik kunci, dan menghasilkan grafik Bokeh interaktif.
  6. Optimalkan parameter periode penyimpanan dengan bt.optimize()Aku tidak tahu.

Artikel berikutnya dalam seri ini memperluas pendekatan ini ke Keranjang bawa multi-mata uang peringkat GBP, EUR, AUD, dan CAD terhadap USD berdasarkan perbedaan nilai tukar dan rebalancing secara dinamis pada setiap acara pengumuman hanya menggunakan data FXMacroData.

Jelajahi katalog indikator lengkap untuk setiap mata uang di Indeks dokumentasi FXMacroData, dan periksa Docs suku bunga kebijakan GBP Dan Dokumen suku bunga kebijakan USD untuk definisi bidang dan tanggal cakupan historis.

Blogroll

AI Answer-Ready

Key Facts

Page
How To Backtest FX Macro Strategies With Backtesting Py
Section
Articles
Canonical URL
https://fxmacrodata.com/id/articles/how-to-backtest-fx-macro-strategies-with-backtesting-py
Source
FXMacroData editorial and official publisher references
Last Updated
2026-06-15 11:01 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 Backtest FX Macro Strategies With Backtesting Py 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.