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.

Também disponível em English
Share article X LinkedIn Email

No final deste guia, você terá um backtest Python que usa apenas FXMacroData sem fornecedores de preços externos necessários. backtesting.py A Comissão Europeia e o Conselho de Ministros da União Europeia (CE)

Requisitos prévios

  • Python 3.10 ou mais recente
  • Uma chave de API FXMacroData (inscreva-se em / subscrever; os pontos finais em USD são gratuitos)
  • Familiarização básica com pandas DataFrames
  • pip acesso à instalação backtesting- Não . requests- Não . pandasE ... numpy

A abordagem: backtesting do índice de carregamento

backtesting.py é uma biblioteca de simulação leve orientada a eventos que produz gráficos interativos de Bokeh, métricas de desempenho chave (ratio Sharpe, taxa de retirada máxima, taxa da vitória) e otimização de parâmetros a partir de uma única chamada de método.

Em vez de depender de um fornecimento externo de preços, este guia constrói um índice de spread de transferência sintético A ideia é uma representação fiel de um carry trade: o diferencial de taxa GBP/USD (taxa da BoE menos taxa da Fed) acumula retornos de juros nocionais diários. Usamos o valor acumulado desse acúmulo como nossa série de "preço" de backtest, em seguida, sinais de entrada de fogo sempre que o Banco da Inglaterra muda sua taxa de política.

É assim que as estratégias de carregamento institucionais são realmente modeladas o preço que você está simulado é o P&L teórico de manter o diferencial de rendimento, não uma cotação de câmbio spot.


Passo 1 Instalar dependências

pip install backtesting requests pandas numpy

Passo 2 Obter históricos de taxas de juro da FXMacroData

Ambos os ... Ponto de partida da taxa de juro da GBP E o ... Ponto de partida da taxa de juro de política monetária em USD Retorna todas as decisões do banco central desde o início da série, cada uma com um número preciso announcement_datetime Data de USD está disponível sem uma chave 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

Passo 3 Construir um índice de spread de carregamento diário

Entre as datas de anúncio, cada taxa de política é constante, então podemos preencher ambas as séries para produzir uma taxa diária para cada dia civil. O spread é a taxa GBP menos a taxa USD. O índice de carregamento compõe que acumulam diariamente a partir de uma base de 100, replicando exatamente o P&L econômico de uma posição de carrega longa em GBP.

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

Quando os rendimentos do GBP excedem os rendimento do USD, o índice se desloca para cima; quando o Fed se aperta mais rápido que o BoE, ele desce.


Passo 4 Construir a coluna de sinal de entrada

Anexe um Signal coluna para o preço DataFrame: +1 no bar quando o BoE subir, −1 num corte, 0 em espera ou sem dados.

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

Passo 5 Escrever a estratégia de backtesting.py

backtesting.py requer que você subclasse Strategy e implementar init() E ... next()- O ... Signal A coluna é registada como um Indicator então aparece como seu próprio painel na saída 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
Nota: Esta é uma estratégia ilustrativa deliberadamente simples. estratégias de transporte do mundo real camada em filtros de volume, dimensionamento de posição e modelagem de custo de transação. Trate os resultados aqui como um ponto de partida para sua própria pesquisa, não uma recomendação de negociação ao vivo.

Passo 6 Faça o 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
Gráfico da curva de participação
Capital próprio da carteira Estratégia de sinalização de transferência GBP/USD (20052024) Resultado ilustrativo · backtesting.py · Somente dados FXMacroData
Portfolio equity curve for GBP/USD carry-signal strategy from 2005 to 2024

A curva de ações sobe de forma constante ao longo da janela de 20 anos. A região sombreada em torno de 20192022 marca o período máximo de redução (−1,70%) quando os cortes do BoE durante o COVID coincidiram com um Fed que cortou ainda mais rápido reduzindo a vantagem de transporte esperada do GBP.


Passo 7 Gerencie o gráfico interativo Bokeh

O backtesting.py tem um sistema integrado . .plot() método que rende um relatório HTML interativo. open_browser=False se estiver a correr num caderno ou num ambiente sem cabeça.

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

O relatório gerado contém quatro painéis: o gráfico de preços do índice de carregamento com marcadores de entrada e saída, a curva de ações, o rastreamento de retirada e o indicador de sinal de taxa do BoE. Clicar em qualquer barra de negociação destaca que a negociação em todos os painéis simultaneamente.

Gráfico de distribuição do comércio
Distribuição de rendimentos de transacções 24 transacões concluídas Resultado ilustrativo · backtesting.py · Somente dados FXMacroData
Bar chart showing individual trade returns for the GBP/USD carry strategy (14 wins, 10 losses)

Passo 8 Optimização dos parâmetros

- O que é? bt.optimize() A função de busca de rede em combinações de parâmetros.

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

Um suporte de 7 bares dá ao carregamento mais tempo para se compor após cada anúncio, melhorando o Sharpe para 0,68 enquanto também reduz o drawdown máximo ligeiramente.

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)

Passo 9 Extender com sinais FXMacroData adicionais

As taxas de política são apenas um dos muitos sinais macro que você pode extrair do FXMacroData. fetch_… ligação:

Divergência da inflação

Uma impressão do IPC acima da previsão reforça frequentemente um viés de aperto. Ponto de inflação GBP com o ... Ponto de inflação em USD Adicionar um sinal de confirmação antes de tomar posições de carregamento.

Tendências do mercado de trabalho

Os bancos centrais reagem aos dados sobre o emprego. Desemprego em GBP A taxa de juro é a taxa de câmbio mais elevada do que a taxa nominal.

Cesto de transporte de vários pares

Reequilibrar quando os diferenciais de taxa mudarem no momento do anúncio utilizando apenas FXMacroData announcement_datetime - Marcas de tempo.

# 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

O script executável completo

Colocando tudo em um único arquivo você pode executar diretamente nenhum fornecedor de preços externo necessário:

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

Resumo

Você construiu agora um backtest de transferência de FX completo baseado em macro usando apenas FXMacroData e o framework backtesting.py sem fornecedores de preços externos necessários.

  1. Obter históricos das taxas de juro de GBP e USD do GBP E ... USD Pontos finais de anúncio.
  2. Preencher as taxas diárias a prazo e compor o spread GBPUSD num índice de carregamento sintético.
  3. Derivar sinais diários de entrada de eventos de variação de taxas do BoE (announcement_datetime marcas de data e hora).
  4. Implementar um backtesting.py classe de estratégia que entra e sai com base nesses sinais.
  5. Execute o backtest, inspecione métricas-chave e gere o gráfico interativo de Bokeh.
  6. Optimize o parâmetro de período de retenção com bt.optimize()- Não .

O próximo artigo desta série estende esta abordagem a um Cesto de transporte de várias moedas classificação do GBP, EUR, AUD e CAD em relação ao USD por diferencial de taxa e reequilíbrio dinâmico em cada evento de anúncio utilizando apenas dados FXMacroData.

Consulte o catálogo completo dos indicadores para cada moeda no site da Índice de documentação FXMacroData- E verifique o ... Docs da taxa de juro de referência do GBP E ... Docs sobre a taxa de juro de política do dólar para as definições de campos e datas históricas de cobertura.

Blogroll

AI Answer-Ready

Key Facts

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