بحلول نهاية هذا الدليل سيكون لديك اختبار خلفي يعمل في 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
الرسم البياني للوجهة الملتوية للأسهم
يرتفع منحنى الأسهم بشكل مطرد عبر نافذة 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")
يحتوي التقرير المنتج على أربع لوحات: مخطط أسعار مؤشر الحمل مع علامات الدخول والخروج ، منحنى الأسهم ، مسار السحب ، ومؤشر إشارة سعر الفائدة في بنك إنجلترا. ينصب النقر على أي شريط تجاري يبرز التداول عبر جميع اللوحات في وقت واحد.
مخطط توزيع التجارة
الخطوة 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 لا حاجة إلى مزودي أسعار خارجيين. كانت الخطوات الرئيسية:
- احصل على تاريخ أسعار الفائدة السياسية للجنيه الإسترليني والدولار الأمريكي من جنيه استرليني و الدولار الأمريكي نقاط الإعلان النهائية.
- سعر الفائدة اليومي المنتظر وربط فروق GBPUSD إلى مؤشر نقل اصطناعي.
- استنتاج إشارات الدخول اليومية من أحداث تغير أسعار البنك المركزي (
announcement_datetimeطوابع زمنية) - تنفيذ
backtesting.pyفئة استراتيجية تدخل وتخرج بناء على تلك الإشارات. - قم بإجراء الاختبار الخلفي، وفحص المقاييس الرئيسية، وتوليد مخطط بوكيه تفاعلي.
- تحسين معيار فترة الاحتفاظ مع
bt.optimize(). .
المقال التالي في هذه السلسلة يمتد هذا النهج إلى سلة نقل متعددة العملات ترتيب الجنيه الإسترليني، اليورو، الدولار الأسترالي، والكاد مقابل الدولار الأمريكي حسب الفارق في سعر الصرف وإعادة التوازن بشكل ديناميكي في كل حدث إعلان باستخدام بيانات FXMacroData فقط.
استكشف كتالوج المؤشرات الكامل لكل عملة في مؤشر وثائق FXMacroData، وتحقق من أسعار العملة البريطانية و أسعار سعر العملة الأمريكية لمعرفة تعريفات الحقول وتواريخ التغطية التاريخية.