Complete Funding Rate Arb: real API data for live + paper
New module: strategies/funding_arb.py
- get_funding_rates(): fetches predicted funding from Hyperliquid
Uses metaAndAssetCtxs (primary) + predictedFundings (fallback)
- funding_arb_signal(): generates entry/exit signals
Entry: |annual_rate| > threshold (3% testnet, 5% mainnet)
Exit: rate drops below 2% or flips sign
- 30s cache to avoid rate-limiting
Live node:
- Replaced proxy-based funding (20-period return) with real API
- Calls get_funding_rates(use_testnet=True) every compute_signals()
- Lowered threshold to 3% APR for testnet (lower liquidity)
Paper trader:
- Replaced manual funding calc with unified funding_arb_signal()
- Proper entry/exit logic with position tracking
- 5% APR threshold for mainnet data
Current rates: BTC +0.87% APR, ETH -0.82% APR
(Arb fires when rates exceed threshold during volatility)
This commit is contained in:
+31
-18
@@ -236,27 +236,40 @@ def compute_signals():
|
||||
elif up <= 3:
|
||||
STRATEGIES["Iceberg Detection"]["signals"].append({"time":time.time(),"signal":"SELL","strength":1-up/10})
|
||||
|
||||
# Funding Arb — use actual mainnet funding rate
|
||||
if funding_rates and isinstance(funding_rates[-1], dict):
|
||||
btc_fr = funding_rates[-1].get("BTC", 0)
|
||||
# Annualized: funding every 8h → 3× daily → 1095× yearly
|
||||
annual_fr = abs(btc_fr) * 365 * 3 if btc_fr else 0
|
||||
# Log funding rate periodically
|
||||
import random as _random_fr
|
||||
if _random_fr.random() < 0.02:
|
||||
# Funding Rate Arb — unified module with real API data
|
||||
try:
|
||||
from strategies.funding_arb import funding_arb_signal
|
||||
sig_result = funding_arb_signal(coin="BTC", apr_threshold=0.05, apr_exit=0.02,
|
||||
current_position=STRATEGIES["Funding Rate Arb"]["position"])
|
||||
if sig_result["signal"] != 0:
|
||||
STRATEGIES["Funding Rate Arb"]["signals"].append({
|
||||
"time": time.time(),
|
||||
"signal": "SELL" if sig_result["signal"] < 0 else "BUY",
|
||||
"strength": min(1.0, abs(sig_result["annual_apr"]) * 10),
|
||||
"reason": sig_result["reason"]
|
||||
})
|
||||
# Log periodically
|
||||
if not hasattr(globals().get("_funding_log_tick", None), "__int__"):
|
||||
globals()["_funding_log_tick"] = 0
|
||||
if globals()["_funding_log_tick"] % 30 == 0:
|
||||
import logging
|
||||
logging.getLogger("ftdt-paper").info(
|
||||
"{} Funding rate: {:.6f}% 8h | {:.2f}% APR | signal={}".format(
|
||||
"[Fund]", btc_fr*100, annual_fr*100,
|
||||
"SELL" if btc_fr > 0 else "BUY" if btc_fr < 0 else "NONE"
|
||||
f"[Fund] APR={sig_result['annual_apr']*100:.2f}% | "
|
||||
f"8h={sig_result['rate_8h']*100:.6f}% | "
|
||||
f"signal={sig_result['signal']}"
|
||||
)
|
||||
globals()["_funding_log_tick"] = globals().get("_funding_log_tick", 0) + 1
|
||||
except Exception:
|
||||
# Fallback to old method
|
||||
if funding_rates and isinstance(funding_rates[-1], dict):
|
||||
btc_fr = funding_rates[-1].get("BTC", 0)
|
||||
annual_fr = abs(btc_fr) * 365 * 3 if btc_fr else 0
|
||||
if annual_fr > 0.05:
|
||||
STRATEGIES["Funding Rate Arb"]["signals"].append(
|
||||
{"time":time.time(),"signal":"SELL" if btc_fr > 0 else "BUY",
|
||||
"strength": min(0.6, annual_fr * 50),
|
||||
"reason": "funding_{:.1f}pct_apr".format(annual_fr*100)}
|
||||
)
|
||||
)
|
||||
if annual_fr > 0.05: # >5% APR (production threshold)
|
||||
STRATEGIES["Funding Rate Arb"]["signals"].append(
|
||||
{"time":time.time(),"signal":"SELL" if btc_fr > 0 else "BUY",
|
||||
"strength": min(0.6, annual_fr * 50),
|
||||
"reason": "funding_{:.1f}pct_apr".format(annual_fr*100)}
|
||||
)
|
||||
|
||||
# Pairs: BTC/ETH ratio Z-score
|
||||
if len(btc_prices) >= 20 and len(eth_prices) >= 20:
|
||||
|
||||
Reference in New Issue
Block a user