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.

متوفر أيضًا في English
Share article X LinkedIn Email

بحلول نهاية هذا الدليل سيكون لديك اختبار خلفي يعمل في Python يستخدم فقط FXMacroData لا حاجة لمقدمي الأسعار الخارجيين. ستحصل على تاريخ أسعار أسعار GBP و USD ، وتبني مؤشر نقل انتشار اصطناعي مباشرة من فارق السعر ، وتشغيل ذلك من خلال backtesting.py لإنتاج مخططات منحنى الأسهم والإحصاءات لكل صفقة.

الشروط المسبقة

  • بايثون 3.10 أو أحدث
  • مفتاح FXMacroData API (ارجع إلى / اشترك؛ نقاط النهاية بالدولار الأمريكي مجانية)
  • معرفة أساسية بالباندا DataFrames
  • pip الوصول إلى تثبيت backtesting- لا requests- لا pandasو numpy

النهج: اختبار الخلفي لمؤشر الحمل

backtesting.py هي مكتبة محاكاة خفيفة الوزن التي تعمل على تحريك الأحداث تنتج مخططات بوكيه تفاعلية ومقاييس أداء رئيسية (نسبة شارب، الحد الأقصى للاستقطاب، معدل الفوز) ، وتحسين المعلمات من دعوة طريقة واحدة.

بدلا من الاعتماد على تغذية الأسعار الخارجية، هذا الدليل يبني مؤشر انتشار الحمل الاصطناعي المعلومات التي تم الحصول عليها بالكامل من FXMacroData. الفكرة هي تمثيل مخلص للتجارة: فرق سعر GBP / USD (سعر بنك إنجلترا ناقص سعر الفيدرالية) يتراكم يومياً على العائدات الفائدة المفاهيمية. نستخدم القيمة التراكمية لهذا التراجع كسلسلة "السعر" للاختبار الخلفي، ثم إشارات دخول النار كلما قام بنك انجلترا بتغيير سعر السياسة.

هذه هي الطريقة التي يتم فيها نمذجة استراتيجيات الحمل المؤسسية في الواقع السعر الذي تقوم بمحاكيته هو الربح النظري و الربح من الاحتفاظ بتفاوت العائد، وليس عرضا فوريا للعملات الأجنبية.


الخطوة 1 تثبيت التبعيات

pip install backtesting requests pandas numpy

الخطوة 2 احضر تاريخ أسعار الفائدة من FXMacroData

كلاهما النقطة النهائية لسعر سعر العملة البريطانية و النقطة النهائية لسعر سعر العملة الأمريكي يعيد كل قرار للبنك المركزي منذ بداية السلسلة، كل منهما مع رقم دقيق announcement_datetime طابع وقت يونيكس. بيانات الدولار الأمريكي متوفرة بدون مفتاح 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

الخطوة 3 بناء مؤشر يومي للفروق

بين تواريخ الإعلان كل سعر سعر ثابت، لذلك يمكننا إعادة تعبئة كل من السلسلة لإنتاج سعر يومي لكل يوم تقويمي. ينتج الفارق من سعر الجنيه الإسترليني ناقص سعر الدولار الأمريكي. يجمع مؤشر الحمل الذي يتراكم يوميًا من قاعدة 100، مما يعكس بالضبط الربح والفائدة الاقتصادية لموقف الحمل الطويل في الجنيه إسترلينى.

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

عندما تتجاوز عائدات الجنيه الإسترليني عائدات الدولار الأمريكي، يتحرك المؤشر صعوداً؛ عندما يشدّ الاحتياطي الفيدرالي أسرع من بنك إنجلترا، يتحرّك إلى الأسفل. هذا هو بالضبط مسار الربح والفائدة الذي يختبره المتداول على زوج الجنيه الأسترلينى/الدولار.


الخطوة 4 بناء عمود إشارة الدخول

إرفق Signal العمود إلى السعر DataFrame: +1 على البار عندما يرتفع بنك الاوروبيا −1 على جرح 0 في انتظار أو لا توجد بيانات

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

الخطوة 5 اكتب استراتيجية backtesting.py

backtesting.py يتطلب منك أن تدرس Strategy و تنفيذ init() و next(). Signal يتم تسجيل العمود كـ Indicator لذا فإنه يظهر كلوحة خاصة بها في مخرج بوكيه.

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
ملاحظة: هذه استراتيجية توضيحية بسيطة عمدا. استراتيجيات تحمل العالم الحقيقي طبقة في فلاتر حجم، وتحديد حجم الموقف، ونمذجة تكلفة المعاملات. تعامل النتائج هنا كنقطة انطلاق للبحث الخاص بك، وليس توصية التداول الحي.

الخطوة 6 اجري الاختبار الخلفي

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
الرسم البياني للوجهة الملتوية للأسهم
رأس المال في المحفظة استراتيجية إشارة نقل GBP/USD (20052024) مخرجات توضيحية · backtesting.py · بيانات FXMacroData فقط
Portfolio equity curve for GBP/USD carry-signal strategy from 2005 to 2024

يرتفع منحنى الأسهم بشكل مطرد عبر نافذة 20 عامًا. تشير المنطقة المظلمة حول 20192022 إلى فترة الانسحاب القصوى (-1.70٪) ، عندما تزامنت خفضات بنك الاحتياطي البريطاني خلال COVID مع الاحتفاظ بالاحتياط الفيدرالي الذي خفض بشكل أسرع مما يقلص ميزة حمل الجنيه الإسترليني المتوقعة.


الخطوة 7 إنشاء مخطط بوكيه تفاعلي

backtesting.py يقدم جهازاً مدمجاً .plot() طريقة تعطي تقرير HTML تفاعلي. open_browser=False إذا كنت تعمل في دفتر ملاحظات أو بيئة بدون رأس.

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

يحتوي التقرير المنتج على أربع لوحات: مخطط أسعار مؤشر الحمل مع علامات الدخول والخروج ، منحنى الأسهم ، مسار السحب ، ومؤشر إشارة سعر الفائدة في بنك إنجلترا. ينصب النقر على أي شريط تجاري يبرز التداول عبر جميع اللوحات في وقت واحد.

مخطط توزيع التجارة
توزيع عوائد الصفقات 24 صفقة مغلقة مخرجات توضيحية · backtesting.py · بيانات FXMacroData فقط
Bar chart showing individual trade returns for the GBP/USD carry strategy (14 wins, 10 losses)

الخطوة 8 تحسين المعلمات

الاختبار الخلفي. bt.optimize() يقوم بتشغيل بحث شبكة عبر مجموعات المعايير. مسح فترة الاحتفاظ للعثور على التكوين الذي يزيد من نسبة شارب:

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

يمنح الاحتفاظ بـ 7 أشرطة المزيد من الوقت لتراكم الحمل بعد كل إعلان ، مما يحسن شارب إلى 0.68 مع تقليل الحد الأقصى للسحب قليلاً. تصدير خريطة الحرارة لمسح المعيارات المرئية:

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)

الخطوة 9 تمديد مع إشارات FXMacroData إضافية

أسعار السياسة هي واحدة فقط من العديد من الإشارات الكلية التي يمكنك استخراجها من FXMacroData. لأن جميع البيانات تأتي من نفس واجهة برمجة التطبيقات، إضافة إشارة ثانية هو ببساطة آخر fetch_… اتصل:

تباين التضخم

غالبًا ما يعزز نمط مؤشر أسعار المستهلك أعلى من المتوقع تحيزًا صارمًا. نقطة نهاية التضخم بالجنيه الإسترليني مع نقطة نهاية التضخم بالدولار لإضافة إشارة تأكيد قبل اتخاذ مواقف تحمل.

اتجاهات سوق العمل

تستخدم البنوك المركزية بيانات التوظيف البطالة في الجنيه الإسترليني إطلاق كمرشح نظام فقط دخول صفقات نقل عندما يدعم الخلفية العمل البنك المركزي بطريق سعر المعدل المعلن.

سلة حمل متعددة الأزواج

احضر أسعار السياسة لـ EUR و AUD و CAD جنبا إلى جنب مع GBP و USD لبناء سلة نقل مرتبة. إعادة التوازن عندما تتغير فروق أسعار الفائدة في وقت الإعلان باستخدام FXMacroData فقط announcement_datetime طوابع زمنية

# 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

النص المكتمل القابل للتشغيل

وضع كل شيء معا في ملف واحد يمكنك تشغيل مباشرة لا حاجة مزود الأسعار الخارجي:

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

ملخص

لقد بنيت الآن اختبار خلفي كامل لتحمل العملات الأجنبية مدفوعاً بالماكرو باستخدام فقط FXMacroData و إطار backtesting.py لا حاجة إلى مزودي أسعار خارجيين. كانت الخطوات الرئيسية:

  1. احصل على تاريخ أسعار الفائدة السياسية للجنيه الإسترليني والدولار الأمريكي من جنيه استرليني و الدولار الأمريكي نقاط الإعلان النهائية.
  2. سعر الفائدة اليومي المنتظر وربط فروق GBPUSD إلى مؤشر نقل اصطناعي.
  3. استنتاج إشارات الدخول اليومية من أحداث تغير أسعار البنك المركزي (announcement_datetime طوابع زمنية)
  4. تنفيذ backtesting.py فئة استراتيجية تدخل وتخرج بناء على تلك الإشارات.
  5. قم بإجراء الاختبار الخلفي، وفحص المقاييس الرئيسية، وتوليد مخطط بوكيه تفاعلي.
  6. تحسين معيار فترة الاحتفاظ مع bt.optimize(). .

المقال التالي في هذه السلسلة يمتد هذا النهج إلى سلة نقل متعددة العملات ترتيب الجنيه الإسترليني، اليورو، الدولار الأسترالي، والكاد مقابل الدولار الأمريكي حسب الفارق في سعر الصرف وإعادة التوازن بشكل ديناميكي في كل حدث إعلان باستخدام بيانات FXMacroData فقط.

استكشف كتالوج المؤشرات الكامل لكل عملة في مؤشر وثائق FXMacroData، وتحقق من أسعار العملة البريطانية و أسعار سعر العملة الأمريكية لمعرفة تعريفات الحقول وتواريخ التغطية التاريخية.

Blogroll

AI Answer-Ready

Key Facts

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