התחשבנות במימון חוזים עתידיים תמידיים בבקטסטים
סיכום
מסמך זה מתאר ספר חשבונות להחלת תזרימי מזומנים של מימון על פוזיציות בחוזים עתידיים תמידיים במהלך בקטסט. בכל חותמת זמן של מימון הוא משתמש בכמות החתומה של הפוזיציה, בשער הסימון הנוכחי, במכפיל חוזה אם ישנו ובשיעור המימון כדי לחשב את התאמת המזומן. לכן שיעור מימון חיובי משפיע בכיוונים מנוגדים על פוזיציות לונג ושורט.
ספר החשבונות מנרמל חותמות זמן ל־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 על סמך המקור; הוא אינו העתק של המקור.