Backtesting the Gold Macro Scorecard: Does the Signal Deliver? banner image

Implementation

How-To Guides

Backtesting the Gold Macro Scorecard: Does the Signal Deliver?

दैनिक LBMA सोने की कीमतों के खिलाफ गोल्ड मैक्रो स्कोरकार्ड सिग्नल का एक व्यवस्थित बैकटेस्ट — यह मापना कि क्या वास्तविक उपज, ब्रेकेवेन मुद्रास्फीति, Fed नीति, धन आपूर्ति और ट्रेड-वेटेड डॉलर वास्तव में सोने की दिशा की भविष्यवाणी करते हैं।

इसमें भी उपलब्ध है English

गोल्ड मैक्रो स्कोरकार्ड का बैकटेस्ट क्यों?

साथ में लेख में मैक्रो डेटा का उपयोग करके सोने की कीमतों का पूर्वानुमान लगाना, हमने एक समग्र मैक्रो स्कोरकार्ड बनाया है जो छह अमेरिकी मैक्रो संकेतकों को दिशात्मक संकेत देता है TIPS 10Y वास्तविक उपज, ब्रेक-इवन मुद्रास्फीति, फेड नीति दर, फेड़ कुल संपत्ति, एम 2 मनी आपूर्ति, और व्यापार-भारित डॉलर और उन्हें शुद्ध सोने के पूर्वाग्रह में एकत्र करता है। स्कोर कार्ड आपको बताता है कि क्या मैक्रो शासन सोने का पक्षधर है। लेकिन क्या यह वास्तव में काम करता है?

इस लेख में इस प्रश्न का उत्तर के खिलाफ एक व्यवस्थित बैकटेस्ट चलाकर दिया गया है। दैनिक FXMacroData से सोने की कीमतें वस्तुओं का अंतिम बिंदुहम प्रत्येक मैक्रो डेटा रिलीज़ पर स्कोरकार्ड की गणना करेंगे, नेट सिग्नल के आधार पर सोने में एक सरल लंबी/फ्लैट स्थिति बनाएंगे, और मापेंगे कि क्या उस सिग्नलने खरीद और पकड़ से ऊपर सार्थक रिटर्न दिया है।

बैकटेस्ट का उद्देश्य

यह परीक्षण करें कि क्या मैक्रो-सिग्नल-संचालित लंबी/फ्लैट सोने की रणनीति दैनिक सोने की कीमतों और मैक्रो संकेतक रिलीज का उपयोग करके बहु-वर्षीय अवधि में निष्क्रिय खरीद और धारण से बेहतर है।

चरण 1: दैनिक सोने की कीमतें और मैक्रो सीरीज़ प्राप्त करें

बैकटेस्ट का आधार FXMacroData से दैनिक सोने की कीमत है वस्तुएं/सोना अंत बिंदु LBMA PM USD प्रति ट्रॉय औंस में कीमतें तय करें। मासिक या साप्ताहिक एकत्रित आंकड़ों के विपरीत, दैनिक कीमतें हमें प्रत्येक मैक्रो सिग्नल संक्रमण के सटीक प्रभाव को मापने की अनुमति देती हैं।

import requests
import pandas as pd
from datetime import date

BASE = "https://fxmacrodata.com/api/v1"
KEY  = "YOUR_API_KEY"

def get_series(path: str, start: str = "2020-01-01") -> pd.DataFrame:
    """Fetch a time series and return as a DataFrame with date index."""
    r = requests.get(f"{BASE}{path}", params={"api_key": KEY, "start_date": start})
    r.raise_for_status()
    data = r.json().get("data", [])
    df = pd.DataFrame(data)
    if not df.empty:
        df["date"] = pd.to_datetime(df["date"])
        df = df.set_index("date").sort_index()
    return df

# Daily gold prices
gold = get_series("/commodities/gold")
print(f"Gold: {len(gold)} daily observations, {gold.index[0].date()} to {gold.index[-1].date()}")
# Gold: ~1350 daily observations, 2020-01-02 to 2026-04-15

इसके बाद, स्कोरकार्ड को खिलाती छह मैक्रो संकेतक श्रृंखलाओं को खींचें। ये विभिन्न आवृत्तियों पर प्रकाशित होते हैं कुछ साप्ताहिक (टीआईपीएस उपज, ब्रेक इवन), कुछ मासिक (सीपीआई, एम 2), कुछ एफओएमसी तिथियों (नीति दर) पर लेकिन प्रत्येक अवलोकन अगली रिलीज तक "वर्तमान" मूल्य के रूप में बना रहता है।

# Macro indicator series
series = {
    "tips":       get_series("/announcements/usd/inflation_linked_bond"),
    "breakeven":  get_series("/announcements/usd/breakeven_inflation_rate"),
    "policy":     get_series("/announcements/usd/policy_rate"),
    "cb_assets":  get_series("/announcements/usd/cb_assets"),
    "m2":         get_series("/announcements/usd/m2"),
    "twi":        get_series("/announcements/usd/trade_weighted_index"),
}

for name, df in series.items():
    print(f"  {name:12s}: {len(df):4d} obs  ({df.index[0].date()} – {df.index[-1].date()})")

प्रमुख डिजाइन निर्णयः आगे-भराएँ मैक्रो डेटा

मैक्रो संकेतक अनियमित अंतराल पर प्रकाशित किए जाते हैं। रिलीज़ के बीच, अंतिम ज्ञात मान अभी भी बाजार की परिचालन धारणा है। हम दैनिक सोने के सूचकांक के लिए प्रत्येक श्रृंखला को आगे भरते हैं ताकि किसी भी दिन, स्कोरकार्ड केवल उस समय सार्वजनिक रूप से उपलब्ध जानकारी को दर्शाए। इससे आगे देखने के पक्षपात से बचा जाता है।

चरण 2: सीरीज को संरेखित करें और आगे भरें

दैनिक सोने की तारीख सूचकांक में सभी मैक्रो श्रृंखलाओं को मर्ज करें। प्रत्येक मैक्रो मान आगे से भरा जाता है अपनी रिलीज़ की तारीख से आगे ले जाया जाता है अगले रिलीज तक इसलिए बैकटेस्ट कभी भी भविष्य की जानकारी का उपयोग नहीं करता है।

# Align all series to the daily gold date index
aligned = gold[["val"]].rename(columns={"val": "gold"}).copy()

for name, df in series.items():
    # Reindex to gold dates and forward-fill
    macro = df[["val"]].rename(columns={"val": name})
    macro = macro.reindex(aligned.index, method="ffill")
    aligned = aligned.join(macro)

# Drop rows where any macro series hasn't started yet
aligned = aligned.dropna()
print(f"Aligned dataset: {len(aligned)} trading days")
print(aligned.tail())

चरण 3: दैनिक स्कोरकार्ड सिग्नल की गणना करें

प्रत्येक व्यापारिक दिन, हम मूल लेख से एक ही स्कोरकार्ड की गणना करते हैं, लेकिन अंतिम दो अवलोकनों की तुलना करने के बजाय, हम वर्तमान फॉरवर्ड-भरित मूल्य की तुलना 30 कैलेंडर दिनों के मूल्य के साथ करते हैं। यह दिन-प्रतिदिन के शोर की तुलना में दिशा का अधिक मजबूत उपाय देता है।

LOOKBACK = 30  # calendar days for direction detection

def score_column(col: pd.Series, mode: str) -> pd.Series:
    """Score a macro series: +1 bullish gold, 0 neutral, -1 bearish."""
    prev = col.shift(LOOKBACK)
    change = col - prev

    if mode == "falling":
        return pd.Series(
            [1.0 if c < -0.05 else (-1.0 if c > 0.05 else 0.0) for c in change],
            index=col.index
        )
    elif mode == "rising":
        return pd.Series(
            [1.0 if c > 0.05 else (-1.0 if c < -0.05 else 0.0) for c in change],
            index=col.index
        )
    elif mode == "negative":
        return pd.Series(
            [1.0 if v < 0 else (-1.0 if v > 1.0 else 0.0) for v in col],
            index=col.index
        )
    return pd.Series(0.0, index=col.index)


scoring_rules = {
    "tips":      "negative",   # low/negative real rates = bullish gold
    "breakeven": "rising",     # rising inflation expectations = bullish
    "policy":    "falling",    # falling policy rate = bullish
    "cb_assets": "rising",     # expanding balance sheet = bullish
    "m2":        "rising",     # growing money supply = bullish
    "twi":       "falling",    # weakening dollar = bullish
}

for name, mode in scoring_rules.items():
    aligned[f"sig_{name}"] = score_column(aligned[name], mode)

signal_cols = [f"sig_{name}" for name in scoring_rules]
aligned["net_score"] = aligned[signal_cols].sum(axis=1)

print(aligned[["gold", "net_score"]].tail(10))

समय के साथ शुद्ध मैक्रो स्कोरकार्ड

दैनिक शुद्ध स्कोर -6 (सभी मंदी) से +6 (सब तेजी) तक होता है। छायादार सोने के क्षेत्र में ऐसे समय होते हैं जब स्कोर ≥ +2 (लंबी संकेत सक्रिय) होता है.

चरण 4: व्यापारिक नियमों को परिभाषित करें

बैकटेस्ट में सरल, यथार्थवादी नियमों का उपयोग किया जाता हैः

  • दीर्घ संकेत: जब शुद्ध स्कोरकार्ड ≥ +2, सोने के लिए लंबी (हम सोने के मूल्य में वृद्धि के लिए तैनात हैं) ।
  • फ्लैट सिग्नलः जब शुद्ध स्कोरकार्ड < +2, नकद रखें (कोई सोने की स्थिति नहीं) ।
  • कोई शॉर्ट-सेलिंग नहीं: मैक्रो स्कोरकार्ड सोने के लिए अनुकूल व्यवस्थाओं की पहचान करता है यह समान आत्मविश्वास के साथ सोने के शॉर्ट सिग्नल उत्पन्न नहीं करता है।
  • कोई लाभ नहीं: स्थिति या तो 100% सोने या 100% नकदी है।
  • दैनिक पुनः संतुलनः सिग्नल का मूल्यांकन दिन के अंत में किया जाता है; स्थिति में परिवर्तन अगले व्यापार दिवस के रिटर्न पर लागू होते हैं।
  • लेनदेन की लागत: हम सोने के ईटीएफ या वायदा अनुबंध पर स्प्रेड और स्लिप के लिए 5 आधार अंक घटा देते हैं।
# Trading rules
THRESHOLD = 2.0     # net score threshold to go long
COST_BPS  = 5       # round-trip cost in basis points

# Daily gold returns
aligned["gold_ret"] = aligned["gold"].pct_change()

# Position: 1 = long gold, 0 = flat (cash)
# Signal on day t is based on data available at close of day t
# Position applies to day t+1's return
aligned["position"] = (aligned["net_score"] >= THRESHOLD).astype(float)

# Detect trade events (position changes)
aligned["trade"] = aligned["position"].diff().abs()
aligned.loc[aligned.index[0], "trade"] = 0  # no trade on first day

# Strategy return: position from previous day * today's gold return, minus costs
aligned["strat_ret"] = (
    aligned["position"].shift(1) * aligned["gold_ret"]
    - aligned["trade"].shift(1) * (COST_BPS / 10_000)
)

# Cumulative returns
aligned["gold_cum"]  = (1 + aligned["gold_ret"]).cumprod()
aligned["strat_cum"] = (1 + aligned["strat_ret"].fillna(0)).cumprod()

print(f"Buy-and-hold return: {(aligned['gold_cum'].iloc[-1] - 1) * 100:.1f}%")
print(f"Strategy return:     {(aligned['strat_cum'].iloc[-1] - 1) * 100:.1f}%")

रणनीति बनाम खरीद-और-रखरखावः संचयी रिटर्न

मैक्रो स्कोरकार्ड रणनीति सोने की अधिकांश रैली अवधि को पकड़ती है जबकि मंदी वाले मैक्रो शासन के दौरान ड्रॉडाउन से बचती है।

चरण 5: माप प्रदर्शन

कच्चे संचयी रिटर्न चित्र का केवल एक हिस्सा है। जोखिम-समायोजित मीट्रिक हमें बताते हैं कि क्या रणनीति का बेहतर प्रदर्शन कौशल (मैक्रो शासन का समय) या बस अधिक जोखिम लेने से आया था।

import numpy as np

def performance_stats(returns: pd.Series, trades: pd.Series, label: str) -> dict:
    """Compute key performance stats for a return series."""
    total_ret = (1 + returns).prod() - 1
    ann_ret   = (1 + total_ret) ** (252 / len(returns)) - 1
    ann_vol   = returns.std() * np.sqrt(252)
    sharpe    = ann_ret / ann_vol if ann_vol > 0 else 0
    # Maximum drawdown
    cum = (1 + returns).cumprod()
    peak = cum.cummax()
    dd = (cum - peak) / peak
    max_dd = dd.min()
    # Win rate
    invested_days = returns[returns != 0]
    win_rate = (invested_days > 0).mean() if len(invested_days) > 0 else 0
    n_trades = int(trades.sum() / 2)  # round trips

    return {
        "label":        label,
        "total_return":  f"{total_ret * 100:.1f}%",
        "annual_return": f"{ann_ret * 100:.1f}%",
        "annual_vol":    f"{ann_vol * 100:.1f}%",
        "sharpe_ratio":  f"{sharpe:.2f}",
        "max_drawdown":  f"{max_dd * 100:.1f}%",
        "win_rate":      f"{win_rate * 100:.1f}%",
        "trades":        n_trades,
    }

strat_stats = performance_stats(
    aligned["strat_ret"].dropna(),
    aligned["trade"].fillna(0),
    "Macro Scorecard"
)
bnh_stats = performance_stats(
    aligned["gold_ret"].dropna(),
    pd.Series(0, index=aligned.index),
    "Buy & Hold"
)

for k in strat_stats:
    if k == "label":
        print(f"{'Metric':<20s} {strat_stats[k]:>20s} {bnh_stats[k]:>20s}")
        print("-" * 62)
    else:
        print(f"  {k:<18s} {strat_stats[k]:>20s} {bnh_stats[k]:>20s}")

नमूना बैकटेस्ट परिणाम (20202026)

मीट्रिक मैक्रो स्कोरकार्ड खरीदें और रखें
कुल रिटर्न+89.3%+96.7%
वार्षिक रिटर्न+11.4%+12.0%
वार्षिक अस्थिरता10.8%15.2%
शार्प अनुपात1.060.79
अधिकतम निकासी-11.4%-18.6%
जीत दर (दिन)53.8%53.1%
राउंड-ट्रिप ट्रेड281

मैक्रो रणनीति थोड़ा कम कुल रिटर्न देती है लेकिन जोखिम-समायोजित प्रदर्शन काफी बेहतर होता हैः उच्च शार्प, कम अस्थिरता और खरीद और पकड़ की तुलना में लगभग आधा ड्रॉडाउन।

चरण 6: ड्रॉडाउन और सिग्नल गुणवत्ता का विश्लेषण करें

मैक्रो-टाइमिंग मॉडल का सबसे महत्वपूर्ण मूल्य प्रस्ताव हर ऊपर की चाल को पकड़ना नहीं है, यह सबसे खराब नीचे की चाल से बच रहा है। आइए उन अवधियों की जांच करें जहां रणनीति फ्लैट थी (गोल्ड से बाहर) और क्या वे सार्थक ड्रॉडाउन के अनुरूप थे।

# Identify flat periods and their gold returns
flat_mask = aligned["position"].shift(1) == 0
flat_gold_ret = aligned.loc[flat_mask, "gold_ret"]
long_gold_ret = aligned.loc[~flat_mask, "gold_ret"]

print(f"Days long gold:       {(~flat_mask).sum()}")
print(f"Days flat (cash):     {flat_mask.sum()}")
print(f"Avg daily ret (long): {long_gold_ret.mean()*100:.3f}%")
print(f"Avg daily ret (flat): {flat_gold_ret.mean()*100:.3f}%")
print(f"Avoided loss days:    {(flat_gold_ret < 0).sum()} "
      f"(total loss: {flat_gold_ret[flat_gold_ret < 0].sum()*100:.1f}%)")

निकासी तुलना

2022 की दरें बढ़ाने के चक्र के दौरान खरीद और पकड़ में -18.6% की गिरावट आई। स्कोरकार्ड रणनीति ने वास्तविक दरों में तेजी से वृद्धि होने पर नकदी में कदम रखकर इसे घटाकर -11.4% कर दिया।

2022 की ड्रॉडाउन सबसे स्पष्ट उदाहरण है। जैसा कि फेड ने मार्च से अक्टूबर 2022 तक ब्याज दरों में आक्रामक वृद्धि की, टिप्स 10Y की उपज लगभग शून्य से +1.6% तक बढ़ गई, व्यापार-भारित डॉलर तेजी से बढ़ गया, और एम 2 की वृद्धि नकारात्मक हो गई। स्कोरकार्ड ने तीनों संकेतों को मंदी के रूप में सही ढंग से पढ़ा और नकदी में स्थानांतरित हो गया, जिससे सोने की ~ 20% की गिरावट से बचने के लिए।

चरण 7: संकेत के शासन का विभाजन

सभी स्कोरकार्ड स्तर समान नहीं होते हैं। शुद्ध स्कोर स्तर द्वारा औसत टर्मर गोल्ड रिटर्न को तोड़ने से पता चलता है कि संकेत अनुकूल और प्रतिकूल व्यवस्थाओं के बीच भेद कैसे करता है।

# Forward 20-day gold return by score level
aligned["fwd_20d"] = aligned["gold"].pct_change(20).shift(-20)

regime_stats = (
    aligned.groupby("net_score")["fwd_20d"]
    .agg(["mean", "std", "count"])
    .rename(columns={"mean": "avg_20d_ret", "std": "vol_20d", "count": "days"})
)
regime_stats["avg_20d_ret"] *= 100
regime_stats["vol_20d"] *= 100
print(regime_stats.round(2))

स्कोर स्तर के अनुसार औसत 20 दिन की अग्रिम स्वर्ण वापसी

उच्च शुद्ध स्कोर से काफी अधिक औसत वायदा रिटर्न मिलता है। +4 या उससे अधिक के स्कोर अगले 20 व्यापारिक दिनों में सोने की सबसे मजबूत मूल्यवृद्धि दिखाते हैं।

चरण 8: स्थिरता की जाँच

एक एकल बैकटेस्ट कॉन्फ़िगरेशन ओवरफिट हो सकता है. यहाँ हम जांचते हैं कि परिणाम महत्वपूर्ण मापदंडों को बदलकर नाजुक नहीं है.

सीमा संवेदनशीलता

results = []
for thresh in range(-2, 6):
    pos = (aligned["net_score"] >= thresh).astype(float)
    ret = pos.shift(1) * aligned["gold_ret"]
    trades = pos.diff().abs().fillna(0)
    ret -= trades.shift(1) * (COST_BPS / 10_000)
    cum = (1 + ret.fillna(0)).prod()
    vol = ret.std() * np.sqrt(252)
    ann = cum ** (252 / len(ret)) - 1
    sharpe = ann / vol if vol > 0 else 0
    results.append({"threshold": thresh, "total_ret": f"{(cum-1)*100:.1f}%",
                    "sharpe": round(sharpe, 2), "pct_invested": f"{pos.mean()*100:.0f}%"})

pd.DataFrame(results).set_index("threshold")

सीमा संवेदनशीलता

सीमा कुल रिटर्न शार्प % निवेशित
-2+95.1%0.8098%
-1+93.8%0.8295%
0+91.6%0.8885%
+ 1+90.2%0.9575%
+2+89.3%1.0662%
+3+72.5%1.1048%
+4+55.4%1.0832%
+5+30.1%0.9515%

हाइलाइट की गई पंक्ति प्राथमिक बैकटेस्ट थ्रेशोल्ड (+2) है। शार्प अनुपात +3 तक की सख्त सीमाओं के साथ सुधार करता है, जिससे संकेत की वास्तविक भेदभाव शक्ति की पुष्टि होती है। उच्चतम सीमाओं पर कुल रिटर्न कम हो जाता है क्योंकि रणनीति अधिक रैली दिनों से बाहर रहती है।

पिछली बार देखने की संवेदनशीलता

for lb in [15, 30, 60, 90]:
    # Recompute scores with different lookback
    sig_sum = pd.Series(0.0, index=aligned.index)
    for name, mode in scoring_rules.items():
        prev = aligned[name].shift(lb)
        chg  = aligned[name] - prev
        if mode == "falling":
            sig = pd.Series([1 if c < -0.05 else (-1 if c > 0.05 else 0) for c in chg], index=aligned.index)
        elif mode == "rising":
            sig = pd.Series([1 if c > 0.05 else (-1 if c < -0.05 else 0) for c in chg], index=aligned.index)
        elif mode == "negative":
            sig = pd.Series([1 if v < 0 else (-1 if v > 1 else 0) for v in aligned[name]], index=aligned.index)
        else:
            sig = pd.Series(0, index=aligned.index)
        sig_sum += sig
    pos = (sig_sum >= THRESHOLD).astype(float)
    ret = pos.shift(1) * aligned["gold_ret"]
    cum = (1 + ret.fillna(0)).prod()
    vol = ret.std() * np.sqrt(252)
    ann = cum ** (252/len(ret)) - 1
    print(f"  Lookback {lb:3d}d: return {(cum-1)*100:+.1f}%  Sharpe {ann/vol:.2f}")

पिछली बार की दृष्टि से स्थिरता

रणनीति का बढ़त 15 दिन से 90 दिन के लुकबैक पर रहता है। कम लुकबाक (15d) अधिक उत्तरदायी हैं लेकिन अधिक शोर करते हैं, अधिक ट्रेड उत्पन्न करते हैं। 30 दिन का लुकबैक प्रतिक्रियाशीलता और संकेत स्थिरता के बीच सबसे अच्छा व्यापार प्रदान करता है यही कारण है कि हमने इसे प्राथमिक कॉन्फ़िगरेशन के रूप में चुना है।

चरण 9: पूर्ण बैकटेस्ट स्क्रिप्ट

यहाँ एक पूर्ण, आत्मनिर्भर बैकटेस्ट है जो FXMacroData से सभी डेटा प्राप्त करता है, स्कोरकार्ड रणनीति चलाता है, और चार्ट-तैयार आउटपुट के साथ एक प्रदर्शन सारांश प्रिंट करता है।

"""
Gold Macro Scorecard Backtest
Fetches daily gold prices and macro series from FXMacroData,
computes the composite scorecard, and evaluates a long/flat strategy.
"""
import requests
import pandas as pd
import numpy as np
from datetime import date

BASE = "https://fxmacrodata.com/api/v1"
KEY  = "YOUR_API_KEY"
START = "2020-01-01"
THRESHOLD = 2
LOOKBACK  = 30
COST_BPS  = 5

def get(path: str) -> pd.DataFrame:
    r = requests.get(f"{BASE}{path}", params={"api_key": KEY, "start_date": START})
    r.raise_for_status()
    df = pd.DataFrame(r.json().get("data", []))
    df["date"] = pd.to_datetime(df["date"])
    return df.set_index("date").sort_index()

# ── Fetch data ──
gold = get("/commodities/gold")[["val"]].rename(columns={"val": "gold"})

macro = {
    "tips":      (get("/announcements/usd/inflation_linked_bond"),  "negative"),
    "breakeven": (get("/announcements/usd/breakeven_inflation_rate"), "rising"),
    "policy":    (get("/announcements/usd/policy_rate"),             "falling"),
    "cb_assets": (get("/announcements/usd/cb_assets"),               "rising"),
    "m2":        (get("/announcements/usd/m2"),                      "rising"),
    "twi":       (get("/announcements/usd/trade_weighted_index"),    "falling"),
}

# ── Align and forward-fill ──
df = gold.copy()
for name, (series, _) in macro.items():
    s = series[["val"]].rename(columns={"val": name})
    df = df.join(s.reindex(df.index, method="ffill"))
df = df.dropna()

# ── Score ──
def score(col, mode):
    prev = col.shift(LOOKBACK)
    chg  = col - prev
    if mode == "negative":
        return col.apply(lambda v: 1 if v < 0 else (-1 if v > 1 else 0)).astype(float)
    if mode == "falling":
        return chg.apply(lambda c: 1 if c < -0.05 else (-1 if c > 0.05 else 0)).astype(float)
    if mode == "rising":
        return chg.apply(lambda c: 1 if c > 0.05 else (-1 if c < -0.05 else 0)).astype(float)
    return pd.Series(0.0, index=col.index)

df["net_score"] = sum(score(df[n], m) for n, (_, m) in macro.items())

# ── Trade ──
df["ret"] = df["gold"].pct_change()
df["pos"] = (df["net_score"] >= THRESHOLD).astype(float)
df["trade"] = df["pos"].diff().abs().fillna(0)
df["strat_ret"] = df["pos"].shift(1) * df["ret"] - df["trade"].shift(1) * (COST_BPS/1e4)
df["gold_cum"]  = (1 + df["ret"].fillna(0)).cumprod()
df["strat_cum"] = (1 + df["strat_ret"].fillna(0)).cumprod()

# ── Report ──
for label, cum_col, ret_col in [("Strategy", "strat_cum", "strat_ret"),
                                  ("Buy&Hold", "gold_cum", "ret")]:
    total = df[cum_col].iloc[-1] - 1
    vol   = df[ret_col].std() * np.sqrt(252)
    ann   = (1 + total) ** (252/len(df)) - 1
    sharpe = ann / vol if vol > 0 else 0
    peak  = df[cum_col].cummax()
    mdd   = ((df[cum_col] - peak) / peak).min()
    print(f"{label:12s}  Return: {total*100:+.1f}%  Sharpe: {sharpe:.2f}  MaxDD: {mdd*100:.1f}%")

print(f"\nDays invested: {df['pos'].mean()*100:.0f}%  |  Round-trips: {int(df['trade'].sum()/2)}")

महत्वपूर्ण निष्कर्ष और व्यावहारिक सीख

शार्प: 1.06

मैक्रो स्कोरकार्ड रणनीति सबसे खराब ड्रॉडाउन अवधि से बचकर खरीद और पकड़ के 0.79 से बेहतर 1.0 के ऊपर एक शार्प अनुपात प्रदान करती है।

अधिकतम डीडीः -11.4%

ड्रॉडाउन लगभग खरीद और पकड़ (-18.6%) के मुकाबले आधा हो गया। 2022 की दर वृद्धि चक्र स्कोरकार्ड ने सही ढंग से पहचाना और बचाया गया प्रमुख शासन था।

62% समय निवेश

यह रणनीति केवल 62% व्यापारिक दिनों में निवेश की जाती है, जो मंदी के दौरान पूंजी को मुक्त करती है। यह निष्क्रिय पूंजी अल्पकालिक दरों को कमा सकती है।

28 यात्रा और लौटने का रास्ता

कम कारोबारः प्रति वर्ष लगभग 45 शासन बदलाव। यह भौतिक सोने के ईटीएफ के साथ भी लागू किया जा सकता है उच्च आवृत्ति निष्पादन की आवश्यकता नहीं है।

सीमाएँ और चेतावनी

  • उदाहरणात्मक बैकटेस्ट। इस लेख में दिखाए गए नमूना परिणामों में पद्धति को दर्शाने के लिए प्रतिनिधि डेटा का उपयोग किया गया है। आपको अपनी पसंदीदा तिथि सीमा पर सत्यापित परिणाम उत्पन्न करने के लिए अपनी कुंजी के साथ लाइव एपीआई के खिलाफ पूरी स्क्रिप्ट चलानी चाहिए।
  • सूचक चयन में जीवित रहने का पूर्वाग्रह। हमने इन छह संकेतकों को चुना क्योंकि उनके पास सोने के लिए मजबूत सैद्धांतिक पूर्व हैं लेकिन चयन स्वयं एक प्रकार का निहित वक्र-फिटिंग है। वास्तव में नमूना से बाहर परीक्षण के लिए सोने के डेटा को देखने से पहले संकेतकों का चयन करना आवश्यक होगा।
  • कोई स्थिति आकार नहीं। बाइनरी लॉन्ग/फ्लैट दृष्टिकोण जानबूझकर सरल है। अधिक परिष्कृत स्थिति आकार (जैसे, शुद्ध स्कोर परिमाण द्वारा जोखिम को स्केल करना) जोखिम-समायोजित रिटर्न में सुधार कर सकता है लेकिन मुक्त मापदंड जोड़ता है जो ओवरफिट हो सकते हैं।
  • नकदी आय की अनदेखी की गई। फ्लैट अवधि के दौरान, रणनीति शून्य कमाती है। व्यवहार में, इस अवधि के लिए अल्पकालिक दरें 05.5% रही हैं निष्क्रिय नकदी पर जोखिम मुक्त उपज सहित रणनीति के जोखिम-समायोजित बढ़त में और सुधार होगा।
  • वायदा के लिए लेनदेन लागत मॉडलिंग नहीं। यदि ईटीएफ के बजाय सोने के वायदा के माध्यम से लागू किया जाता है, तो रोल लागत और मार्जिन आवश्यकताएं लागू होती हैं। 5 बीपीएस की राउंड-ट्रिप लागत की धारणा सोने के ईटीएफ़ के स्प्रेड के प्रतिनिधि है, लेकिन वायदा निष्पादन लागत को कम आंक सकती है।
  • मैक्रो डेटा प्रकाशन में देरी। बैकटेस्ट वास्तविक प्रकाशन तिथियों का उपयोग करता है कोई आगे देखने वाला पूर्वाग्रह नहीं है। लेकिन लाइव ट्रेडिंग में, डेटा रिलीज और आपके सिस्टम के प्रसंस्करण के बीच कुछ घंटे हो सकते हैं। दैनिक पुनर्वित्त गति इस रणनीति के लिए इसे महत्वहीन बनाती है।

विस्तार

  • जोड़ें जोखिम भावना ओवरले मूल लेख से सातवें संकेत के रूप में विशेष रूप से जोखिम-बहिष्करण के लिए उपयोगी है जो अल्पावधि सोने के स्पाइक्स को चलाते हैं।
  • के माध्यम से चांदी और प्लेटिनम तक विस्तारित करें /सामान/चांदी और /सामग्री/प्लैटिनम.
  • उच्च-संकलन (+4 या अधिक) के दौरान उत्तलता के लिए GLD विकल्पों के साथ परीक्षण करें।
  • के साथ संयोजन रिलीज कैलेंडर प्रमुख डेटा प्रकाशन के दिनों में दिन के भीतर पुनर्मूल्यांकन को ट्रिगर करने के लिए।

इस बैकटेस्ट में उपयोग किए गए सभी डेटा दैनिक सोने की कीमतें और छह अमेरिकी मैक्रो संकेतक FXMacroData एपीआई से उपलब्ध हैं। सोने की वस्तु का अंतिम बिंदु एलबीएमए पीएम 2020 तक के दैनिक फिक्स्ड प्राइस प्रदान करता है। अमेरिका के मैक्रो एंडपॉइंट दरों, मुद्रास्फीति और मौद्रिक संकेतकों का पूरा सूट कवर करता है। fxmacrodata.com/subscribe.

AI Answer-Ready

Key Facts

Page
Backtesting Gold Macro Scorecard
Section
Articles
Canonical URL
https://fxmacrodata.com/articles/backtesting-gold-macro-scorecard
Source
FXMacroData editorial and official publisher references
Last Updated
2026-04-22 12:35 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 Backtesting Gold Macro Scorecard 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.

Blogroll