इस गाइड के अंत तक आपके पास एक कामकाजी पायथन बैकटेस्ट होगा जो केवल FXMacroData कोई बाहरी मूल्य प्रदाताओं की आवश्यकता नहीं है। आप GBP और USD नीति दर इतिहास को प्राप्त करेंगे, सीधे दर अंतर से एक सिंथेटिक कैरी-स्प्रेड सूचकांक बनाएंगे, और इसे चलाएंगे। backtesting.py इक्विटी-वक्र ग्राफ और प्रति-व्यापार सांख्यिकी का उत्पादन करना।
पूर्व शर्तें
- पायथन 3.10 या नया
- एक FXMacroData एपीआई कुंजी (साइन अप करें /अपना नाम लिखें; अमरीकी डालर के अंत बिंदु मुक्त हैं)
- पांडा के साथ बुनियादी परिचितता डेटाफ्रेम
pipस्थापित करने के लिए पहुँचbacktesting,requests,pandas, औरnumpy
दृष्टिकोणः ले जाने-सूचकांक बैकटेस्टिंग
backtesting.py एक हल्का घटना-संचालित सिमुलेशन पुस्तकालय है जो एक ही विधि कॉल से इंटरैक्टिव बोके ग्राफ, प्रमुख प्रदर्शन मीट्रिक (शार्प अनुपात, अधिकतम ड्रॉडाउन, जीत दर) और पैरामीटर अनुकूलन का उत्पादन करता है।
बाहरी मूल्य फ़ीड पर निर्भर होने के बजाय, यह गाइड एक सिंथेटिक लेयर-स्प्रेड इंडेक्स यह विचार एक कैरी ट्रेड का एक वफादार प्रतिनिधित्व हैः GBP/USD दर अंतर (BoE दर घटाकर फेड दर) दैनिक ब्याज-पर-कल्पनात्मक रिटर्न जमा करता है। हम उस संचयी मूल्य का उपयोग हमारे बैकटेस्ट "मूल्य" श्रृंखला के रूप में करते हैं, फिर जब भी बैंक ऑफ इंग्लैंड अपनी नीति दर बदलता है तो आग प्रवेश संकेत।
यह है कि कैसे संस्थागत ले जाने रणनीतियों वास्तव में मॉडलिंग कर रहे हैं कीमत आप अनुकरण कर रहे है सैद्धांतिक P & L पैदावार अंतर रखने के लिए, एक स्पॉट FX उद्धरण नहीं।
चरण 1 निर्भरताएँ स्थापित करें
pip install backtesting requests pandas numpy
चरण 2 FXMacroData से नीतिगत दरों के इतिहास को प्राप्त करें
दोनों GBP नीतिगत दर का अंतिम बिंदु और अमरीकी डालर नीतिगत दर का अंतिम बिंदु सीरीज की शुरुआत के बाद से प्रत्येक केंद्रीय बैंक के निर्णयों को वापस करें, प्रत्येक के साथ एक सटीक announcement_datetime यूनिक्स टाइमस्टैम्प. यूएसडी डेटा एपीआई कुंजी के बिना उपलब्ध है.
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 दैनिक ले जाने के लिए एक सूचकांक बनाएं
घोषणा की तारीखों के बीच प्रत्येक नीति दर स्थिर है, इसलिए हम प्रत्येक कैलेंडर दिन के लिए दैनिक दर उत्पन्न करने के लिए दोनों श्रृंखलाओं को आगे भर सकते हैं। स्प्रेड GBP दर शून्य USD दर है। ले जाने वाले सूचकांक यौगिक जो दैनिक आधार 100 से संचित होते हैं, जो कि एक लंबी 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
जब GBP की उपज USD की उपजा से अधिक होती है, तो सूचकांक ऊपर की ओर बढ़ता है; जब Fed BoE की तुलना में तेजी से तंग होता है, यह नीचे की ओर ढल जाता है। यह बिल्कुल P&L पथ है जो GBP/USD जोड़ी पर ले जाने वाले व्यापारी का अनुभव करता है।
चरण 4 प्रवेश संकेत स्तंभ का निर्माण
संलग्न करें Signal डेटा फ्रेम की कीमत के लिए स्तंभः +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 आपको उपवर्ग की आवश्यकता है 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 के दौरान BoE कटौती एक फेड के साथ मेल खाती है जिसने GBP के अपेक्षित ले जाने के लाभ को कम करने के लिए और भी तेजी से कटौती की।
चरण 7 इंटरैक्टिव बोके ग्राफ बनाएं
backtesting.py एक निर्मित जहाजों .plot() विधि जो एक इंटरैक्टिव एचटीएमएल रिपोर्ट प्रस्तुत करता है. पास 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 पैरामीटर अनुकूलित करें
backtesting.py की 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_… कॉलः
पूर्वानुमान से अधिक सीपीआई प्रिंट अक्सर एक सख्त पूर्वाग्रह को मजबूत करता है। GBP मुद्रास्फीति का अंत के साथ अमरीकी डालर मुद्रास्फीति का अंतिम बिंदु ले जाने की स्थिति लेने से पहले एक पुष्टिकरण संकेत जोड़ने के लिए।
केंद्रीय बैंक रोजगार के आंकड़ों पर प्रतिक्रिया देते हैं। GBP बेरोजगारी केवल तब ही ले जाने के लिए ट्रेडों में प्रवेश करें जब BoE की श्रम पृष्ठभूमि उसके निर्धारित दर पथ का समर्थन करती है।
एक रैंक किए गए कैरी बास्केट बनाने के लिए GBP और USD के साथ EUR, AUD और CAD के लिए नीतिगत दरों को प्राप्त करें। केवल 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 फ्रेमवर्क का उपयोग करके एक पूर्ण मैक्रो-संचालित FX ले जाने वाले बैकटेस्ट का निर्माण किया है कोई बाहरी मूल्य प्रदाताओं की आवश्यकता नहीं है। प्रमुख चरण थे:
- GBP और USD नीतिगत दरों का इतिहास GBP और अमरीकी डालर घोषणा अंत बिंदुओं.
- फोरवर्ड-फिल दैनिक दरें और GBPUSD स्प्रेड को सिंथेटिक कैरी इंडेक्स में संयुग्मित करें।
- बैंक की दर में बदलाव के घटनाक्रमों से दैनिक प्रवेश संकेत प्राप्त करें (
announcement_datetimeसमय-स्टैम्प) । - लागू करें
backtesting.pyरणनीति वर्ग जो उन संकेतों के आधार पर प्रवेश करता है और बाहर निकलता है। - बैकटेस्ट चलाएं, प्रमुख मीट्रिक की जांच करें, और इंटरैक्टिव बोके ग्राफ उत्पन्न करें।
- के साथ रखरखाव अवधि पैरामीटर अनुकूलित करें
bt.optimize().
इस श्रृंखला के अगले लेख में इस दृष्टिकोण को विस्तार से बताया गया है। बहु मुद्राओं की ले जाने वाली टोकरी GBP, EUR, AUD और CAD को USD के मुकाबले दर अंतर द्वारा रैंकिंग और केवल FXMacroData डेटा का उपयोग करके प्रत्येक घोषणा कार्यक्रम में गतिशील रूप से पुनर्व्यवस्थित करना।
पर प्रत्येक मुद्रा के लिए पूर्ण सूचक सूची का अन्वेषण करें FXMacroData प्रलेखन सूचकांक, और जाँच GBP नीतिगत दर डॉक्स और अमरीकी डालर नीतिगत दर डॉक्स क्षेत्र की परिभाषाओं और ऐतिहासिक कवरेज तिथियों के लिए।