الانتقال إلى المحتوى
جميع مستندات المكتبة

احتساب تمويل العقود الآجلة الدائمة في الاختبارات التاريخية

الكود Machine Learning for Trading

الملخص

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

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

الأفكار الرئيسية

  • يُحسب التدفق النقدي للتمويل من حجم المركز الموقّع والقيمة المسعّرة والمضاعف ومعدل التمويل.
  • تُطبّق التسوية بعد تحديث الوسيط للأسعار المرجعية عند الطابع الزمني.
  • يساعد توحيد UTC والحماية من التكرار على منع التسويات الفائتة أو المتكررة.
  • يتطلب فحص المقاييس ظهور جميع الطوابع الزمنية للتسوية المقدمة في الخط الزمني للمحرك.
  • يؤدي غياب سعر مرجعي حالي لمركز مفتوح إلى فشل التسوية.

الوسوم

النص الكامل
# funding_backtest.py


```py
"""Funding-settlement accounting for perpetual-futures engine backtests."""

from __future__ import annotations

import math
from datetime import UTC, datetime
from functools import wraps
from typing import Any

import polars as pl


def _as_utc(value: datetime) -> datetime:
    return value.replace(tzinfo=UTC) if value.tzinfo is None else value.astimezone(UTC)


class FundingSettlementLedger:
    """Apply position-signed funding during the engine's bar-time update."""

    def __init__(self, funding_rates: pl.DataFrame) -> None:
        required = {"symbol", "timestamp", "funding_rate"}
        missing = required - set(funding_rates.columns)
        if missing:
            raise ValueError(f"funding rates are missing columns: {sorted(missing)}")
        selected = funding_rates.select("symbol", "timestamp", "funding_rate")
        if selected.null_count().row(0) != (0, 0, 0):
            raise ValueError("funding settlements cannot contain null keys or rates")
        if selected.n_unique(["symbol", "timestamp"]) != selected.height:
            raise ValueError("funding settlement keys must be unique")

        self._rates: dict[datetime, dict[str, float]] = {}
        for row in selected.sort("timestamp", "symbol").iter_rows(named=True):
            rate = float(row["funding_rate"])
            if not math.isfinite(rate):
                raise ValueError("funding rates must be finite")
            self._rates.setdefault(_as_utc(row["timestamp"]), {})[str(row["symbol"])] = rate
        self._rate_count = selected.height
        self._settled_timestamps: set[datetime] = set()
        self._funding_pnl = 0.0
        self._funding_events = 0
        self._funding_settlements = 0
        self._installed = False

    def settle(self, timestamp: datetime, broker: Any) -> float:
        """Settle one timestamp exactly once against positions marked on that bar."""
        normalized = _as_utc(timestamp)
        if normalized in self._settled_timestamps:
            return 0.0
        rates = self._rates.get(normalized)
        if rates is None:
            return 0.0
        self._settled_timestamps.add(normalized)
        self._funding_settlements += len(rates)

        event_cash = 0.0
        for symbol, rate in rates.items():
            position = broker.positions.get(symbol)
            if position is None or float(position.quantity) == 0.0:
                continue
            mark = broker.get_mark_price(symbol, quantity=position.quantity)
            if mark is None:
                raise RuntimeError(f"funding settlement has no current mark for {symbol!r}")
            event_cash -= (
                float(position.quantity)
                * float(mark)
                * float(getattr(position, "multiplier", 1.0))
                * rate
            )
        if event_cash:
            broker.cash = float(broker.cash) + event_cash
            self._funding_pnl += event_cash
            self._funding_events += 1
        return event_cash

    def install(self, broker: Any) -> None:
        """Install settlement immediately after each engine mark update."""
        if self._installed:
            raise RuntimeError("funding settlement ledger is already installed")
        original_update_time = broker._update_time

        @wraps(original_update_time)
        def update_time_with_funding(timestamp, *args, **kwargs):
            result = original_update_time(timestamp, *args, **kwargs)
            self.settle(timestamp, broker)
            return result

        broker._update_time = update_time_with_funding
        self._installed = True

    def metrics(self) -> dict[str, float]:
        """Return cashflows actually presented to the engine timeline."""
        if self._funding_settlements != self._rate_count:
            raise RuntimeError(
                "funding settlement coverage is incomplete: "
                f"{self._funding_settlements}/{self._rate_count} keys reached the engine timeline"
            )
        return {
            "funding_pnl": self._funding_pnl,
            "funding_events": float(self._funding_events),
            "funding_settlements": float(self._funding_settlements),
        }

```

يُعرض النص كاملًا مع نسبه إلى مصدره وفقًا لترخيصه. الترخيص: MIT

أعدّ وكيل الأبحاث في Stratmill هذا الملخص استنادًا إلى المصدر الأصلي؛ وهو ليس نسخة منه.