返回 Expert 笔记
Expert Day 97

资金费率套利(Cash and Carry)

Funding rate 机制、cash-and-carry、delta-neutral 对冲、不同所 funding 差异

2026-08-06
Phase 2 - 统计套利与Alpha Research (Day 89-102)
量化策略套利FundingRateCashAndCarryDeltaNeutral

日期: 2026-08-06 方向: 量化 / 统计套利 / Alpha 阶段: Phase 2 - 统计套利与Alpha Research (Day 89-102) 标签: #量化策略 #套利 #FundingRate #CashAndCarry #DeltaNeutral


今日目标

类型内容
学习Funding rate 机制、cash-and-carry、delta-neutral 对冲、不同所 funding 差异
实操实现自动对冲的 funding 套利系统,含 delta 漂移修正
产出funding_arb.py — Funding 套利引擎 + 风控

一、理论与模型

1.1 永续合约与资金费率

永续合约(perpetual)没有到期日,通过 funding rate 拉回到现货价。

Binance 公式: $$ F = \text{Premium Index} + \text{clamp}(\text{Interest Rate} - \text{Premium Index}, -0.05%, 0.05%) $$

  • Premium Index:8h TWAP 的 (perp - spot) / spot
  • Interest Rate:固定 0.01% / 8h
  • Funding 每 8h 结算一次(部分所每 1h 或 4h)

含义

  • F > 0:long 付 short(市场偏多头,long pay)
  • F < 0:short 付 long(市场偏空,short receive)
  • 极端 F = ±0.75%/8h,年化 ±820%

1.2 Cash and Carry 套利

核心:当 funding 持续为正(如 +0.05%/8h),做空 perp + 持有现货:

头寸PnL
现货Long BTC+ΔP
永续Short BTC-ΔP(对冲)+ funding收入
Delta neutral+funding

年化收益: $$ \text{APR} = F \times \frac{365 \times 24}{8} = F \times 1095 $$

F = 0.01%/8h → APR ≈ 11% F = 0.05%/8h → APR ≈ 55% F = 0.10%/8h → APR ≈ 110%

1.3 Reverse Carry(负 funding)

F < 0 时反向:做多 perp + 现货做空(或借入并卖出)

问题:现货做空机会有限:

  • CEX:通过借贷市场(Margin)借入现货
  • DEX:用 AAVE/Compound 借入抵押
  • 借入成本可能高于 funding 收入

1.4 Delta 漂移与再平衡

理论:现货 + 短永续 = delta neutral 实际

  • 价格剧烈变动后,现货数量与永续合约名义不匹配
  • BTC 从 $30K → $40K 后,1 BTC 现货 = $40K,但 1 BTC 永续合约名义 = $40K(如果是 USD-margined)
  • USDT 永续:合约名义随价变,无需再平衡(quanto effect)
  • COIN-margined(币本位):现货 1 BTC vs 永续 1 BTC contract size,但永续盈亏以 BTC 计 → 凸性影响

经验:每天或每 5% 价格变动后再平衡 delta。

1.5 Funding 历史分布

资产平均 F (8h)Std (8h)极端 F
BTC+0.01%0.012%+0.50%/-0.20%
ETH+0.012%0.014%+0.60%/-0.18%
SOL+0.015%0.020%+0.75%/-0.30%
Memes (PEPE)+0.025%0.040%+0.75%/-0.50%

经验法则

  • F > 0.03%/8h(年化 33%+):carry 显著
  • F < -0.01%/8h:reverse carry 机会
  • |F| > 0.10%/8h:tail event,可能反转

1.6 对手所 funding 套利

不同所 funding 不同(FTX 时代有显著差异):

F at the same time
Binance+0.02%
Bybit+0.04%
OKX+0.025%
dYdX+0.05%
GMX+0.08%

操作:在高 F 所做空、低 F 所做多 perp 双 perp 对冲(无现货占用)。

1.7 DeFi Funding 套利(独有机会)

GMX-V2 / dYdX

  • GMX funding 由 GLP/GLP holders 决定,机制不同
  • 可能持续高 funding(pool 偏移时)
  • DeFi 永续 funding 历史 spread 大于 CEX

Pendle PT/YT

  • PT (Principal Token) 锁定到期价,等价于零息债
  • YT (Yield Token) 持有未来 yield
  • 可用 YT 做 funding 等价 carry

二、直觉与陷阱

陷阱 1:Funding 不是无风险

许多新手觉得 cash-and-carry 是无风险套利。风险

  1. 基差风险:spot - perp 不会完美锁定
  2. 资金占用风险:现货占资本,资金成本高时净亏
  3. 强平风险:perp 仓位若杠杆高,spot 闪崩前 perp 已被强平
  4. funding 变化:F 可能变负,套利反转
  5. 对手方:FTX 倒闭客户钱锁住

陷阱 2:忽略借贷利率

如果用借入资金做现货:

  • AAVE 借 USDC 利率 5%,funding 4%(年化)→ 净负
  • 必须 funding > 借贷利率 + 操作成本

陷阱 3:交易成本累积

每 8h 调仓一次,年化 1095 次。每次 0.05% taker fee →累 55%/年成本。 解法

  1. 用 maker 单(rebate 或 0 fee)
  2. 减少调仓频率(只在 delta 偏 > 5% 时调)
  3. 选低 fee 所

陷阱 4:funding 反转

正 funding 持续 1 个月不代表永远。LUNA/FTX 期 funding 反转极快。 解法:实时监控 funding,反转后立即出场。

陷阱 5:cross-margin vs isolated

  • Cross-margin:账户内资金共用 → 强平联动
  • Isolated:每仓位独立保证金 → 但需更多资金

cash-and-carry 推荐 isolated,强平不会蔓延。

陷阱 6:闪崩 + perp 强平

2020-03-12 BTC 一日 -50%,许多 carry 仓位 perp 端先于 spot 强平。 解法

  • 杠杆 ≤ 2x(保守)
  • 准备额外保证金
  • 用 USD-margined 而非 COIN-margined(币本位强平时连锁)

陷阱 7:funding 计算误用

Bybit funding 公式与 Binance 略不同(区间不同)。一定要用各所自己的实时 API。


三、代码实现

3.1 完整 Funding 套利引擎

# funding_arb.py
"""
Funding Rate Arbitrage Engine
- Real-time funding monitor across exchanges
- Delta-neutral carry
- Auto rebalance
- Risk controls
"""

import numpy as np
import pandas as pd
import requests
import time
from typing import Dict, List, Optional
from dataclasses import dataclass, field


# ----------------------------- Funding API ---------------------------
class FundingClient:
    @staticmethod
    def binance_funding(symbol: str = 'BTCUSDT') -> Dict:
        """当前 funding rate + next funding time"""
        url = "https://fapi.binance.com/fapi/v1/premiumIndex"
        r = requests.get(url, params={'symbol': symbol})
        d = r.json()
        return {
            'symbol': symbol,
            'mark_price': float(d['markPrice']),
            'index_price': float(d['indexPrice']),
            'last_funding': float(d['lastFundingRate']),
            'next_funding_time': int(d['nextFundingTime']) / 1000,
            'exchange': 'Binance',
        }

    @staticmethod
    def binance_funding_history(symbol: str = 'BTCUSDT', limit: int = 100) -> pd.DataFrame:
        url = "https://fapi.binance.com/fapi/v1/fundingRate"
        r = requests.get(url, params={'symbol': symbol, 'limit': limit})
        df = pd.DataFrame(r.json())
        df['fundingRate'] = df['fundingRate'].astype(float)
        df['fundingTime'] = pd.to_datetime(df['fundingTime'], unit='ms')
        return df.set_index('fundingTime')[['fundingRate']]

    @staticmethod
    def bybit_funding(symbol: str = 'BTCUSDT') -> Dict:
        url = "https://api.bybit.com/v5/market/tickers"
        r = requests.get(url, params={'category': 'linear', 'symbol': symbol})
        d = r.json()['result']['list'][0]
        return {
            'symbol': symbol,
            'mark_price': float(d['markPrice']),
            'last_funding': float(d['fundingRate']),
            'next_funding_time': int(d['nextFundingTime']) / 1000,
            'exchange': 'Bybit',
        }

    @staticmethod
    def okx_funding(symbol: str = 'BTC-USDT-SWAP') -> Dict:
        url = "https://www.okx.com/api/v5/public/funding-rate"
        r = requests.get(url, params={'instId': symbol})
        d = r.json()['data'][0]
        return {
            'symbol': symbol,
            'last_funding': float(d['fundingRate']),
            'next_funding_time': int(d['nextFundingTime']) / 1000,
            'exchange': 'OKX',
        }


# ----------------------------- 多所 funding 比较 -----------------------------
def funding_screener(symbols: List[str] = ['BTC', 'ETH', 'SOL']) -> pd.DataFrame:
    rows = []
    for sym in symbols:
        try:
            b = FundingClient.binance_funding(f'{sym}USDT')
            rows.append({'symbol': sym, 'exchange': 'Binance',
                         'funding_8h': b['last_funding'],
                         'funding_apr': b['last_funding'] * 1095})
        except Exception as e:
            print(f"Binance {sym} err: {e}")
        try:
            by = FundingClient.bybit_funding(f'{sym}USDT')
            rows.append({'symbol': sym, 'exchange': 'Bybit',
                         'funding_8h': by['last_funding'],
                         'funding_apr': by['last_funding'] * 1095})
        except Exception as e:
            print(f"Bybit {sym} err: {e}")
        try:
            ok = FundingClient.okx_funding(f'{sym}-USDT-SWAP')
            rows.append({'symbol': sym, 'exchange': 'OKX',
                         'funding_8h': ok['last_funding'],
                         'funding_apr': ok['last_funding'] * 1095})
        except Exception as e:
            print(f"OKX {sym} err: {e}")
    return pd.DataFrame(rows).sort_values(['symbol', 'funding_apr'], ascending=[True, False])


# ----------------------------- 套利仓位管理 -----------------------------
@dataclass
class CarryPosition:
    symbol: str
    spot_qty: float          # 现货数量
    perp_short_qty: float    # 永续做空数量(正数)
    spot_entry_price: float
    perp_entry_price: float
    funding_collected: float = 0
    open_time: float = field(default_factory=time.time)

    def delta(self, spot_price: float) -> float:
        """组合 delta(应为 0)"""
        return self.spot_qty - self.perp_short_qty

    def pnl(self, spot_price: float, perp_price: float) -> Dict:
        spot_pnl = (spot_price - self.spot_entry_price) * self.spot_qty
        perp_pnl = (self.perp_entry_price - perp_price) * self.perp_short_qty
        return {
            'spot_pnl': spot_pnl,
            'perp_pnl': perp_pnl,
            'funding': self.funding_collected,
            'total': spot_pnl + perp_pnl + self.funding_collected,
        }

    def needs_rebalance(self, threshold: float = 0.02) -> bool:
        diff = abs(self.spot_qty - self.perp_short_qty) / self.spot_qty
        return diff > threshold


class CarryArbEngine:
    """资金费率套利引擎"""

    def __init__(self, min_apr: float = 0.10, max_position_size: float = 100_000,
                  max_leverage: float = 2.0, rebalance_threshold: float = 0.02):
        self.min_apr = min_apr
        self.max_position_size = max_position_size
        self.max_leverage = max_leverage
        self.rebalance_threshold = rebalance_threshold
        self.positions: Dict[str, CarryPosition] = {}

    def evaluate_opportunity(self, symbol: str) -> Dict:
        """评估是否开仓"""
        try:
            f = FundingClient.binance_funding(f'{symbol}USDT')
            apr = f['last_funding'] * 1095

            # 加上历史平均(防止 outlier)
            hist = FundingClient.binance_funding_history(f'{symbol}USDT', 21)
            hist_apr = hist['fundingRate'].mean() * 1095

            return {
                'symbol': symbol,
                'current_apr': apr,
                'avg_apr_7d': hist_apr,
                'should_enter': apr > self.min_apr and hist_apr > self.min_apr * 0.5,
                'mark_price': f['mark_price'],
            }
        except Exception as e:
            return {'error': str(e)}

    def open_position(self, symbol: str, notional_usd: float) -> Optional[CarryPosition]:
        opp = self.evaluate_opportunity(symbol)
        if not opp.get('should_enter'):
            print(f"  {symbol}: opportunity not strong enough (APR {opp.get('current_apr', 0):.2%})")
            return None

        price = opp['mark_price']
        qty = notional_usd / price

        # 模拟下单(实盘要走 CEX API)
        pos = CarryPosition(
            symbol=symbol,
            spot_qty=qty,
            perp_short_qty=qty,
            spot_entry_price=price,
            perp_entry_price=price,
        )
        self.positions[symbol] = pos
        print(f"  Opened {symbol}: spot {qty:.4f} @ {price}, perp short {qty:.4f}")
        return pos

    def collect_funding(self, symbol: str, funding_rate: float):
        """每 8h 调用一次"""
        if symbol not in self.positions:
            return
        pos = self.positions[symbol]
        # short 收 funding(funding > 0 时)
        funding_pnl = pos.perp_short_qty * pos.perp_entry_price * funding_rate
        pos.funding_collected += funding_pnl
        return funding_pnl

    def rebalance_delta(self, symbol: str, spot_price: float, perp_price: float):
        if symbol not in self.positions:
            return
        pos = self.positions[symbol]
        if not pos.needs_rebalance(self.rebalance_threshold):
            return

        diff = pos.spot_qty - pos.perp_short_qty
        # 调整 perp 端
        if diff > 0:
            # 现货多于 perp 空头,加空 diff
            pos.perp_short_qty += diff
            print(f"  Rebalance {symbol}: increase short by {diff:.6f}")
        else:
            pos.perp_short_qty += diff  # diff < 0 reduces short
            print(f"  Rebalance {symbol}: reduce short by {-diff:.6f}")

    def close_position(self, symbol: str, spot_price: float, perp_price: float) -> Dict:
        if symbol not in self.positions:
            return {}
        pos = self.positions[symbol]
        pnl = pos.pnl(spot_price, perp_price)
        held_days = (time.time() - pos.open_time) / 86400
        annualized_return = pnl['total'] / (pos.spot_qty * pos.spot_entry_price) / max(held_days, 1) * 365
        del self.positions[symbol]
        return {**pnl, 'annualized': annualized_return, 'held_days': held_days}


# ----------------------------- 历史回测 -----------------------------
def backtest_carry_strategy(symbol: str = 'BTCUSDT',
                              entry_apr_threshold: float = 0.20) -> pd.DataFrame:
    """回测:funding APR > threshold 入场,反转出场"""
    funding = FundingClient.binance_funding_history(symbol, 1000)
    funding['apr'] = funding['fundingRate'] * 1095
    funding['signal'] = (funding['apr'] > entry_apr_threshold).astype(int)
    # 假设无成本 carry
    funding['ret'] = funding['signal'].shift(1) * funding['fundingRate']
    funding['equity'] = (1 + funding['ret'].fillna(0)).cumprod()
    return funding


# ----------------------------- 主程序 -----------------------------
if __name__ == '__main__':
    print("Funding Rate Screener")
    print("=" * 60)
    df = funding_screener(['BTC', 'ETH', 'SOL'])
    print(df.to_string(index=False))

    print("\nHistorical Carry Backtest (BTC)")
    bt = backtest_carry_strategy('BTCUSDT', entry_apr_threshold=0.15)
    n_signals = bt['signal'].sum()
    total_ret = bt['equity'].iloc[-1] - 1
    print(f"  Signal hits: {n_signals} / {len(bt)} ({n_signals/len(bt):.1%})")
    print(f"  Total return: {total_ret:.2%}")
    print(f"  Days: {(bt.index[-1] - bt.index[0]).days}")
    print(f"  Annualized: {(1 + total_ret) ** (365 / (bt.index[-1] - bt.index[0]).days) - 1:.2%}")

    # 模拟仓位管理
    print("\nLive Carry Engine (simulated)")
    engine = CarryArbEngine(min_apr=0.10)
    engine.open_position('BTC', notional_usd=10_000)

四、真实数据/案例

案例 1:2021 牛市的 funding 黄金时代

时段BTC funding年化
2021-040.10%/8h110%
2021-100.08%/8h88%
2022-010.05%/8h55%

简单 cash-and-carry 即可年化 50%+。Three Arrows Capital、Alameda 都大量做。

案例 2:3AC 的 carry trade 灾难(2022)

3AC 部分仓位是 carry:

  • 现货 BTC + 短 perp BTC 看似安全
  • 杠杆 5x 放大收益
  • LUNA 崩盘导致连锁
  • 现货 BTC 暴跌使 spot 端 margin 不够,强平
  • 永续端反向获利但被 cross-margin 联动

教训:cross-margin + 高杠杆 = carry 也能爆仓。

案例 3:FTX 破产的 funding 信号

2022-11 FTX 危机前:

  • 11-06:BTC perp funding 突然降至 -0.05%(罕见)
  • 11-08:funding 持续负,提示市场恐慌
  • 反向 carry 机会浮现,但需要做空 spot(高难度)

教训:funding 是市场情绪 leading indicator。

案例 4:DeFi funding 套利成熟(2024-)

GMX V2 / dYdX V4:

  • 无 KYC、合约自托管
  • funding 历史平均略高于 CEX(liquidity premium)
  • 但 protocol risk + gas 成本高

实证(dYdX V4 2024):

  • BTC carry 年化 ≈ 12%(vs CEX 8%)
  • 但 gas + slippage 实际净 ≈ 8%

案例 5:跨所 funding 套利(机构)

某机构(公开 paper):

  • 在 Binance / Bybit / OKX 做反向 carry
  • F_Bybit > F_Binance + 1% 时:在 Bybit 短 + Binance 多 perp
  • 不需现货,纯 perp 双开
  • 风险:basis 风险(两所价格短暂偏离)

五、CEX vs DEX 策略差异

维度CEX FundingDEX Funding
可用所Binance/Bybit/OKX/BitgetdYdX/GMX/Hyperliquid/Synthetix
频率每 8h每 1h(dYdX)/ 持续(GMX)
gas0取决于链(L2 低)
滑点较大(依池)
funding 公式平台公开协议代码(透明但复杂)
对手方CEX 破产风险协议 / oracle 风险
资金效率margin call 频繁较保守
稳健性API 中断时断链上不可中断

DeFi funding 套利独有

  1. GMX v2 GLP:GLP 持有者收 perp 交易者亏损 + funding,复杂但高 yield
  2. Hyperliquid HLP:类似但更复杂
  3. Pendle:把未来 yield 流证券化,可锁定 funding income

六、风险管理

6.1 Carry 仓位风控

风险控制
强平杠杆 ≤ 2x;保证金 buffer ≥ 30%
funding 反转实时监控;F < 0 持续 24h 平仓
CEX 倒闭单所仓位 < 总资本 30%
资金成本借贷利率 < funding 70% 才入场
delta 漂移每 5% 价格动 / 每天再平衡
Tail event黑天鹅时 spot/perp 联动失败,预留现金

6.2 杠杆 sizing

def safe_carry_leverage(funding_apr, vol_annual=0.6, target_drawdown=0.15):
    """
    Kelly-style:funding 是 ER,vol 是风险
    """
    # 单倍杠杆下 worst-case drawdown ≈ vol × 1.65 (95%)
    if funding_apr <= 0:
        return 0
    raw = target_drawdown / (vol_annual * 0.5)
    # 保守 cap
    return min(raw, 2.0)

6.3 多所分散

30% Binance + 30% Bybit + 20% OKX + 10% dYdX + 10% Cash buffer

七、关键速查

Funding APR 转换

APR = funding_8h × 365 × 24 / 8 = funding_8h × 1095
APR = funding_1h × 365 × 24 = funding_1h × 8760  (for hourly funding venues)

入场门槛建议

自信度APR 门槛
激进8%
中性15%
保守25%
必须做50%+

Delta 监控

delta = spot_qty - perp_short_qty * (perp_price / spot_price)
if abs(delta) / spot_qty > 0.05:
    rebalance()

八、面试题

Q1:Cash-and-carry 套利真的"无风险"吗?

:不是。

  1. 强平风险:perp 端高杠杆时闪崩可能先于 spot 强平
  2. funding 反转:F 可能突然变负
  3. 基差风险:spot vs perp 价格短期偏离
  4. 对手方风险:CEX 倒闭(FTX)锁仓
  5. 流动性风险:极端行情下减仓滑点大
  6. 资金占用成本:融资利率高时净亏 正确表述:carry 是 risk-adjusted positive carry,不是 risk-free arb。

Q2:怎么决定开仓 funding 阈值?

  • 覆盖成本:funding > 借贷利率 + 操作成本(fee + slippage)+ 保险费
  • 历史分布:当前 funding > 历史 80th percentile 时入场(top quintile)
  • 动量:funding 上升趋势 + 高位 → 入场;下降趋势 → 等
  • 对比基准:> 国债 + risk premium(如 10% APR)
  • 加密推荐:F > 0.025%/8h(年化 27%)保守入场

Q3:你怎么管理 delta 漂移?

  • 频率:根据价格 move 动态:每 5% 价格变动 OR 每 24h 检查一次
  • 阈值:|delta| / spot_qty > 2% 触发再平衡
  • 方法:调整 perp 端而非 spot(spot 转账成本高)
  • 币本位 vs USD 本位:USD-margined perp 自动 delta neutral;coin-margined 有凸性需主动管理
  • 成本:每次再平衡按 taker fee 计,频繁再平衡吞噬 alpha

Q4:DeFi funding 套利相比 CEX 优劣?

  • 优势
    • 自托管,无 CEX 倒闭风险
    • 部分协议 funding 更高(流动性溢价)
    • 透明(合约代码可审计)
    • 24/7 无停机
  • 劣势
    • gas 成本(每次再平衡 $5-50)
    • 协议风险(合约漏洞、oracle 攻击)
    • 流动性较 CEX 差(大额滑点)
    • 复杂度(不同协议公式不同)
  • 结合:80% CEX + 20% DEX(捕捉 yield premium 同时控风险)

Q5:如果 funding 突然从 +0.05% 跳到 -0.05% 你怎么办?

  1. 立即评估:是 spike 还是 trend reversal?
  2. 如果是 spike(罕见短暂):等 1-2 个周期看是否回归
  3. 如果 trend reversal
    • 评估持仓是否进入亏损区
    • 平仓重建反向(reverse carry)
    • 平仓不重建(market regime 不明)
  4. 快速行动:carry 反转往往伴随 vol spike,犹豫一周可能损失年化收益的 20%
  5. 设规则:F < 0 持续 24h 自动平仓(避免人为犹豫)

明日预告

Day 98: 基差交易 — 期现基差、Roll 策略、季度合约 vs 永续。今天的 funding 是永续,明天的 basis 是 dated futures,两者结合是完整的衍生品 arb。