المدونة/Developer

اختبار الذهب بالعملة المحلية رجعياً دون تحيز استباقي في أسعار الصرف

دمج أشرطة XAU/USD اليومية المستقرة مع أرصاد أسعار الصرف التاريخية لاختبار استراتيجية ذهب بعملة محلية رجعياً، دون استخدام أسعار لم تكن متاحة فعلياً في ذلك الوقت عن طريق الخطأ.

Developer
اللغة:
العربية

يمكن أن تبدو استراتيجية الذهب مختلفة بـ EUR أو JPY أو IDR أو INR أو TRY حتى عندما تكون الصفقة الأساسية نفسها. سلسلة الذهب بالدولار الأمريكي تجيب عن سؤال: "ماذا فعل الذهب بالدولار؟" أما سلسلة العملة المحلية فتجيب عن سؤال: "كم كانت قيمة هذا المركز بالنسبة لهذا المستثمر؟"

هذا الفرق حساب بسيط، لكن البيانات التاريخية قد تجعل من السهل الوقوع في خطأ. يجمع هذا الدليل بين أشرطة XAU/USD اليومية المستقرة من goldprice.dev وأرصاد أسعار صرف الدولار الأمريكي المؤرخة من exchangerate.dev. القاعدة المهمة هي أن كل قيمة صرف يجب أن تكون القيمة المنشورة للتاريخ المحاكى، وليست سعراً جُلب اليوم ووُضع في صف تاريخي.

سلسلة أسعار العملة المحلية

بالنسبة لعملة مُسعّرة كعملة محلية لكل دولار أمريكي، يُحسب إغلاق الذهب بتلك العملة كما يلي:

local_gold_close = gold_usd_close × usd_to_local_fx

على سبيل المثال، XAU/USD × USD/IDR ينتج قيمة أونصة تروي واحدة من الذهب بالروبية. ينطبق النهج نفسه على أي عملة محلية مدعومة.

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

جلب أشرطة الذهب المستقرة

استخدم نقطة نهاية الأشرطة اليومية، لا طلب السعر الفوري الحالي. تُعاد الأشرطة الأحدث أولاً، والأسعار سلاسل نصية عشرية، والشريط الجاري تكوينه حالياً قد يحمل is_closed: false. يجب أن يستخدم الاختبار الرجعي الصفوف المستقرة فقط.

curl -G "https://api.goldprice.dev/v1/bars" \
  -H "Authorization: Bearer $GOLDPRICE_API_KEY" \
  --data-urlencode "symbol=XAU-USD-SPOT" \
  --data-urlencode "interval=1d" \
  --data-urlencode "from=2025-07-10T00:00:00Z" \
  --data-urlencode "to=2026-07-10T00:00:00Z" \
  --data-urlencode "limit=400"

تستخدم الاستجابة bar_start لبداية كل شريط يومي بتوقيت UTC. احتفظ بـ close وis_closed. لا تزال نافذة السجل اليومي الخاصة بخطتك سارية، وقد يتطلب نطاق أطول التنقل بين الصفحات عبر next_cursor.

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

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

فحص سنوي واحد تم التحقق منه

هذه الطريقة ليست نظرية فقط. في تاريخي التداول المشتركين والمستقرين 2025-07-10 و2026-07-10، ارتفع إغلاق XAU/USD اليومي من 3323.81 إلى 4119.172: أي عائد 23.93% للذهب بالدولار الأمريكي. حقق المركز نفسه عوائد مختلفة بالعملة المحلية بمجرد إدراج حركة صرف الدولار الأمريكي مقابل العملة المحلية:

عملة إعداد التقاريرعائد الذهب بالعملة المحلية
EUR26.95%
JPY37.10%
IDR37.97%
INR37.76%
TRY45.50%

كانت كل استجابة من نقطة نهاية أسعار الصرف رصداً دقيق التاريخ من نوع ecb_daily غير معبأ استباقياً. الحساب تسوية تاريخية، وليس تنبؤاً أو ادعاءً سببياً أو توصية استثمارية. للاطلاع على التفكيك الدقيق لمساهمة الذهب وسعر الصرف والتفاعل بينهما وراء هذه الأرقام، راجع هل ارتفع الذهب، أم انخفضت عملتك؟.

جلب سلسلة أسعار الصرف كما كانت معروفة في كل تاريخ

للعمل متعدد الأيام، تعيد نقطة نهاية النطاق (range) في exchangerate.dev الأرصاد اليومية في طلب واحد. هنا العملة الأساس هي USD والعملة المطلوبة هي IDR، لذا كل سعر هو الروبية مقابل الدولار.

curl -G "https://api.exchangerate.dev/v1/range" \
  -H "Authorization: Bearer $EXCHANGERATE_API_KEY" \
  --data-urlencode "base=USD" \
  --data-urlencode "symbols=IDR" \
  --data-urlencode "start_date=2025-07-10" \
  --data-urlencode "end_date=2026-07-10"

تحتوي استجابة النطاق على صفوف أيام العمل المنشورة فعلياً، بدلاً من توليد صف تقويمي لكل عطلة نهاية أسبوع أو عطلة رسمية. لا تملأ تلك التواريخ التقويمية المفقودة برصد لاحق عند بناء السلسلة التاريخية.

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

بناء سلسلة عملة محلية متوافقة بلغة Python

يطلب هذا المثال كلتا السلسلتين، ويحتفظ بأشرطة الذهب المستقرة وصفوف أسعار الصرف غير المعبأة استباقياً، ثم يجري ربطاً داخلياً (inner join) حسب التاريخ. الربط الداخلي مقصود: أي تاريخ مفقود من أي من المصدرين لا يُختلق بصمت.

import os
from decimal import Decimal

import pandas as pd
import requests

GOLD_API = "https://api.goldprice.dev/v1"
FX_API = "https://api.exchangerate.dev/v1"

START = "2025-07-10"
END = "2026-07-10"
LOCAL_CURRENCY = "IDR"

# This one-year example uses Pro history. Free keys can request 30 days.


def fetch_gold_daily(start: str, end: str) -> pd.DataFrame:
    response = requests.get(
        f"{GOLD_API}/bars",
        headers={"Authorization": f"Bearer {os.environ['GOLDPRICE_API_KEY']}"},
        params={
            "symbol": "XAU-USD-SPOT",
            "interval": "1d",
            "from": f"{start}T00:00:00Z",
            "to": f"{end}T00:00:00Z",
            "limit": 400,
        },
        timeout=15,
    )
    response.raise_for_status()
    rows = response.json()["bars"]

    gold = pd.DataFrame(rows)
    gold = gold[gold["is_closed"] & gold["close"].notna()].copy()
    gold["date"] = pd.to_datetime(gold["bar_start"], utc=True).dt.date
    gold["gold_usd_close"] = gold["close"].map(Decimal)
    return gold.set_index("date")[["gold_usd_close"]].sort_index()


def fetch_fx_daily(start: str, end: str, currency: str) -> pd.DataFrame:
    response = requests.get(
        f"{FX_API}/range",
        headers={"Authorization": f"Bearer {os.environ['EXCHANGERATE_API_KEY']}"},
        params={
            "base": "USD",
            "symbols": currency,
            "start_date": start,
            "end_date": end,
        },
        timeout=15,
    )
    response.raise_for_status()
    # The range endpoint contains published business-day rows only. Keep its
    # missing weekends and holidays missing rather than carrying a later rate.
    fx = pd.DataFrame(response.json()["data"])
    fx["date"] = pd.to_datetime(fx["date"]).dt.date
    fx["usd_to_local"] = fx["rates"].map(lambda rates: Decimal(str(rates[currency])))
    return fx.set_index("date")[["usd_to_local"]].sort_index()


gold = fetch_gold_daily(START, END)
fx = fetch_fx_daily(START, END, LOCAL_CURRENCY)

# Only use dates with an actual settled gold close and a fresh FX observation.
series = gold.join(fx, how="inner")
series["local_gold_close"] = series["gold_usd_close"] * series["usd_to_local"]
series["local_return"] = series["local_gold_close"].pct_change()

print(series.tail())

استخدام Decimal يحافظ على قيم السلاسل النصية العشرية التي تعيدها نقطة نهاية أشرطة الذهب. حوّلها فقط عند حافة العرض إذا احتاجت مكتبة الرسوم البيانية إلى أعداد عشرية float.

تجنب ثلاثة أخطاء شائعة

1. استخدام سعر اليوم في صف تاريخي

/v1/latest مخصصة للقيمة الحالية. لا يمكنها إعادة بناء سعر كان معروفاً في يوم سابق. استخدم نقاط نهاية أسعار الصرف المؤرخة أو النطاق للعمل التاريخي.

2. معاملة سعر مرحّل كرصد جديد

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

3. التصرف بناءً على الإغلاق نفسه الذي أنتج الإشارة

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

series["fast_ma"] = series["local_gold_close"].rolling(50).mean()
series["slow_ma"] = series["local_gold_close"].rolling(200).mean()
series["signal"] = (series["fast_ma"] > series["slow_ma"]).astype(int)

# A signal formed from today's close may only affect the next row's return.
series["strategy_return"] = series["local_return"] * series["signal"].shift(1)
series["strategy_value"] = (1 + series["strategy_return"].fillna(0)).cumprod()

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

سجّل المدخلات مع النتيجة

لكل تشغيل، احفظ نافذة التاريخ، والعملة، والطوابع الزمنية لاستجابات API، وحالة is_closed، وحالة is_forward_filled لسعر الصرف، والمراجعة الدقيقة للكود التي أنتجت السلسلة. هذه الحقول هي ما يتيح لقارئ لاحق التمييز بين حساب تاريخي قابل لإعادة الإنتاج وتحويل حالي لُصق في جدول بيانات قديم.

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

مقالات ذات صلة

Developer

Gold price WebSocket on Reddit: the streaming questions, answered

The real-time gold streaming questions that come up on Reddit, answered: whether a gold WebSocket exists, how the goldprice.dev XAU and XAG tick stream works end to end, and which other APIs actually offer one (most metals APIs just poll).

← اقرأ
Developer

Gold price API on Stack Overflow: developer troubleshooting, answered

The gold-price-API errors developers hit on Stack Overflow, fixed: 401 auth, 400 invalid_symbol, 429 rate limits, CORS from the browser, reading the response, and pulling historical data.

← اقرأ
Developer

Gold price API on Reddit: the developer questions, answered

The gold-price-API questions that come up on Reddit, answered straight: free tier, update frequency, rate limits, auth, where the number comes from, commercial use, and whether it's good enough to build on.

← اقرأ

goldprice.dev

أسعار الذهب اللحظية، بيانات OHLC التاريخية، وتجميع متعدد المصادر — متاحة عبر REST و SSE.