Avi-L's picture
FIX slippage: k*sigma_daily*sqrt(Q/ADV), realized vol from data
70bf7eb verified
Raw History Blame Contribute Delete
2.29 kB
"""Fee, slippage and funding models for institutional-grade crypto backtests.
Conventions (Binance USDT-M VIP0, verified 2026-10):
- taker fee 5 bps, maker fee 2 bps
- funding: charged at 8h ticks, cost = funding_rate * notional, sign
follows the position side (longs pay when rate > 0)
- slippage: square-root impact model k * sqrt(notional / ADV), floor 2 bps
Everything here is a COST function: positive output = money lost.
"""
from __future__ import annotations
import numpy as np
import pandas as pd
TAKER_FEE = 0.0005
MAKER_FEE = 0.0002
SLIPPAGE_FLOOR_BPS = 2.0
IMPACT_K = 0.5 # square-root impact constant
DAILY_VOL_BPS_FALLBACK = 200.0 # used only if the caller cannot supply realized vol
def fee(notional: float, side: str = "taker") -> float:
return abs(notional) * (TAKER_FEE if side == "taker" else MAKER_FEE)
def slippage_cost(notional: float, adv_notional: float,
daily_vol_bps: float = DAILY_VOL_BPS_FALLBACK) -> float:
"""Square-root impact cost in quote currency.
impact_bps = k * sigma_daily_bps * sqrt(Q / ADV)
sigma_daily_bps is REALIZED daily volatility (the engine passes the
value computed from the data); the floor is 2 bps.
"""
if adv_notional <= 0:
raise ValueError("adv_notional must be positive")
bps = max(
SLIPPAGE_FLOOR_BPS,
IMPACT_K * daily_vol_bps * np.sqrt(abs(notional) / adv_notional),
)
return abs(notional) * bps / 1e4
def funding_cost(
positions: pd.Series,
funding: pd.Series,
prices: pd.Series,
) -> pd.Series:
"""Per-bar funding cost series, accrued only at funding timestamps.
positions: position in units of base asset, indexed by bar start time.
funding: funding_rate at each 8h tick.
prices: mark/close price at each bar, for notional valuation.
A bar starting at t covers [t, t+1h); a funding tick at t charges that
tick to the position held entering the tick, i.e. the position at t.
"""
tick_times = funding.index
pos_at_tick = positions.reindex(tick_times, method="ffill")
price_at_tick = prices.reindex(tick_times, method="ffill")
cost = pos_at_tick * price_at_tick * funding # longs pay positive funding
return cost.reindex(positions.index, fill_value=0.0).fillna(0.0)