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 Backtest Fx Macro Strategies With Backtesting Py image
Share headline card X LinkedIn Email
Download

Implementation

How-To Guides

How To Backtest Fx Macro Strategies With Backtesting Py

A step-by-step guide to using FXMacroData central-bank announcement data to build a synthetic carry-spread index and backtest a GBP/USD carry strategy with backtesting.py — no external price provider required.

Auch verfügbar auf English
Share article X LinkedIn Email

Am Ende dieses Leitfadens haben Sie einen funktionierenden Python-Backtest, der nur FXMacroData verwendet keine externen Preisanbieter erforderlich. Backtesting.py Die Kommission hat die Kommission aufgefordert, die Ergebnisse der Untersuchung zu ermitteln.

Voraussetzungen

  • Python 3.10 oder neuer
  • Ein FXMacroData API-Schlüssel (anmelden Sie sich unter /abonnieren; USD-Endpunkte sind kostenlos)
  • Grundlegende Kenntnisse mit Pandas
  • pip Zugriff auf Installation backtesting- Ich weiß . requests- Ich weiß . pandasUnd ... numpy

Der Ansatz: Rückprüfung des Carry-Index

Backtesting.py ist eine leichte, ereignisgesteuerte Simulationsbibliothek, die interaktive Bokeh-Plots, wichtige Leistungsmetriken (Sharpe-Verhältnis, maximale Auslastung, Gewinnrate) und Parameteroptimierung aus einem einzigen Methodenanruf erzeugt.

Anstatt sich auf eine externe Preiszufuhr zu verlassen, erstellt dieser Leitfaden eine synthetischer Spread-Index Die Idee ist eine treue Darstellung eines Carry-Tradings: Die GBP/USD-Zinsdifferenz (BoE-Zinsen minus Fed-Zinserhöhungen) erwirtschaftet tägliche Zinsrenditen. Wir verwenden den kumulativen Wert dieser Erträge als unsere Backtest-Preisreihe und schalten dann Eingangssignale aus, wenn die Bank of England ihren Leitzins ändert.

So werden institutionelle Carry-Strategien tatsächlich modelliert der Preis, den Sie simulieren, ist der theoretische Gewinn und Verlust der Renditedifferenz, nicht eine Spot-Devisenquote.


Schritt 1 Installation von Abhängigkeiten

pip install backtesting requests pandas numpy

Schritt 2 Holen Sie die Zinsverlaufshistorien von FXMacroData ab

Beide . Endpunkt für den Leitzins des GBP Und die ... Endpunkt für den Kurs der USD-Policy Die Daten werden in der Liste der Zentralbanken auf der Grundlage der Datenbank der Zentrale Bank der Europäischen Union (Zentralbank) erfasst. announcement_datetime USD-Daten sind ohne API-Schlüssel verfügbar.

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

Schritt 3 Erstellen Sie einen täglichen Carry-Spread-Index

Zwischen den Ankündigungsdaten ist jeder Kurs konstant, so dass wir beide Reihen vorwärts füllen können, um einen Tageskurs für jeden Kalendertag zu erzeugen. Der Spread ist der GBP-Rate minus der USD-Rat. Der Carry-Index setzt sich zusammen, der täglich von einer Basis von 100 anläuft und genau den wirtschaftlichen Gewinn und Verlust einer langen GBP-Carry-Position wiedergibt.

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

Wenn die GBP-Erträge die USD-Ertragswerte übersteigen, zieht der Index nach oben; wenn die Fed schneller zusammengeht als die BoE, ziehet er nach unten. Dies ist genau der P&L-Pfad, den ein Carry-Trader auf dem GBP/USD-Paar erlebt.


Schritt 4 Erstellen der Eingangssignalkolonne

Ich füge ein Signal Spalte zum Preis DataFrame: +1 Auf der Bar, wenn die Bank von England steigt, −1 Auf einem Schnitt, 0 Wartezeit oder keine Daten.

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

Schritt 5 Schreiben Sie die Backtesting.py-Strategie

Backtesting.py erfordert eine Unterklasse Strategy und umsetzen . init() Und ... next()- Die ... Signal Die Spalte ist als Indicator Also erscheint es als sein eigenes Panel in der Bokeh-Ausgabe.

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
Anmerkung: Dies ist eine bewusst einfache illustrative Strategie. Real-World-Carry-Strategien Schicht in Vol Filter, Position Größe und Transaktionskosten-Modellierung. Behandeln Sie die Ergebnisse hier als Ausgangspunkt für Ihre eigene Forschung, nicht eine Live-Trading-Empfehlung.

Schritt 6 Backtest durchführen

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
Aktienkurve
Portfolio equity — GBP/USD carry-signal strategy (2005–2024) Illustrative Ausgabe · Backtesting.py · Nur FXMacroData-Daten
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.


Schritt 7 Erstellen Sie das interaktive Bokeh-Grafiken

Backtesting.py hat eine eingebaute .plot() Die Methode, die einen interaktiven HTML-Bericht erstellt. open_browser=False wenn Sie in einem Notizbuch oder einer kopflosen Umgebung laufen.

# 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")

Der erstellte Bericht enthält vier Paneele: das Carry-Index-Preiskart mit Ein- und Ausstiegsmarkern, die Eigenkapitalkurve, die Drawdown-Trace und den BoE-Zinssignalindikator.

Handelsverteilungstabelle
Handelsrückzahlung 24 abgeschlossene Geschäfte Illustrative Ausgabe · Backtesting.py · Nur FXMacroData-Daten
Bar chart showing individual trade returns for the GBP/USD carry strategy (14 wins, 10 losses)

Schritt 8 Optimierung der Parameter

Das ist Backtesting.py. bt.optimize() Durchsuchen Sie die Wartezeit, um die Konfiguration zu finden, die das Sharpe-Verhältnis maximiert:

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

Ein 7-Bar-Hold gibt dem Carry-Akkrual mehr Zeit, sich nach jeder Ankündigung zu vergrößern, wodurch Sharpe auf 0,68 verbessert wird und gleichzeitig der maximale Drawdown leicht reduziert wird.

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)

Schritt 9 Erweiterung mit zusätzlichen FXMacroData-Signalen

Die Kurspolitik ist nur eines von vielen Makrosignalen, die Sie von FXMacroData ziehen können. fetch_… Ruf an:

Inflationsausfälle

Ein höherer Preisindex als der vorhergesagte Druck verstärkt oft eine Verschärfung. Endpunkt der GBP-Inflation Mit dem ... Endpunkt der USD-Inflation ein Bestätigungssignal vor der Einnahme von Carry-Positionen hinzuzufügen.

Entwicklung des Arbeitsmarktes

Die Zentralbanken reagieren auf die Beschäftigungsdaten. GBP Arbeitslosigkeit Die Bank wird die Anlage als Regimefilter verwenden, um die Anlagen zu übernehmen.

Mehrpaar-Trägerkorb

Die Rate-Differenz wird bei der Ankündigung nur mit FXMacroData angepasst. announcement_datetime Zeitstempel.

# 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

Vollständig ausführbares Skript

Mit einer einzigen Datei können Sie direkt laufen kein externer Preisgeber erforderlich:

"""
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()

Zusammenfassung

Sie haben nun einen vollständigen makro-gesteuerten FX-Carry-Backtest mit nur FXMacroData und dem Backtesting.py-Framework ohne externe Preisanbieter erstellt.

  1. Holen Sie sich die Kurshistorien von GBP und USD aus dem GBP Und ... USD Ankündigungsendpunkte.
  2. Die Tageskurse für die Auszahlung von Zinszuschüssen werden in einem Synthetic Carry Index zusammengefasst.
  3. Ableiten Sie tägliche Einstiegssignale aus den Wechselkurseffekten der BoE (announcement_datetime Zeitstempel).
  4. Einführung backtesting.py Strategie-Klasse, die auf der Grundlage dieser Signale ein- und ausgeht.
  5. Führen Sie den Backtest durch, überprüfen Sie die wichtigsten Kennzahlen und erstellen Sie das interaktive Bokeh-Grafiken.
  6. Optimieren Sie den Parameter "Haltungszeit" mit bt.optimize()- Ich weiß .

Der nächste Artikel dieser Reihe erweitert diesen Ansatz auf eine Mehrwährungs-Behandelskorb Rangfolge von GBP, EUR, AUD und CAD gegenüber USD nach Kursdifferenz und dynamische Neuausgleichung bei jedem Ankündigungsereignis unter Verwendung nur von FXMacroData-Daten.

Sie können den vollständigen Indikatorkatalog für jede Währung im FXMacroData Dokumentationsindex- Und überprüfen Sie die ... Die Zinssätze für die Politik der GBP Und ... USD-Leistungszinsdokumente für Felddefinitionen und historische Berichtsdaten.

Blogroll

AI Answer-Ready

Key Facts

Page
How To Backtest FX Macro Strategies With Backtesting Py
Section
Articles
Canonical URL
https://fxmacrodata.com/de/artikel/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.