增加资金费率
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
"""资金费率:当前 premiumIndex + 历史 fundingRate 存 SQLite。"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from .binance import binance_client
|
||||
from .config import settings
|
||||
from .db import (
|
||||
get_funding_history_from_db,
|
||||
get_funding_meta,
|
||||
save_funding_current_bulk,
|
||||
save_funding_history,
|
||||
)
|
||||
from .exceptions import BinanceRateLimitedError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_premium_cache: dict[str, Any] = {}
|
||||
_premium_cache_at: float = 0.0
|
||||
|
||||
|
||||
def _premium_ttl_sec() -> int:
|
||||
return settings.funding_cache_minutes * 60
|
||||
|
||||
|
||||
async def get_current_funding_map(force: bool = False) -> dict[str, dict]:
|
||||
"""全市场当前资金费率(一次 premiumIndex)。"""
|
||||
global _premium_cache, _premium_cache_at
|
||||
now = time.time()
|
||||
if not force and _premium_cache and now - _premium_cache_at < _premium_ttl_sec():
|
||||
return _premium_cache
|
||||
|
||||
if binance_client.is_rate_limited():
|
||||
if _premium_cache:
|
||||
return _premium_cache
|
||||
raise BinanceRateLimitedError(binance_client.rate_limit_remaining_sec(), "premiumIndex")
|
||||
|
||||
data = await binance_client.get_premium_index_all()
|
||||
_premium_cache = data
|
||||
_premium_cache_at = now
|
||||
save_funding_current_bulk(data)
|
||||
return data
|
||||
|
||||
|
||||
def _is_history_fresh(symbol: str, min_bars: int) -> bool:
|
||||
meta = get_funding_meta(symbol)
|
||||
if not meta or meta.get("bar_count", 0) < min_bars:
|
||||
return False
|
||||
try:
|
||||
last = datetime.fromisoformat(meta["last_fetch_at"])
|
||||
except ValueError:
|
||||
return False
|
||||
return (datetime.now() - last).total_seconds() < _premium_ttl_sec()
|
||||
|
||||
|
||||
async def sync_funding_history(symbol: str, limit: int | None = None) -> list[dict]:
|
||||
sym = symbol.upper()
|
||||
n = min(limit or settings.funding_history_limit, 1000)
|
||||
rows = await binance_client.get_funding_rate_history(sym, n)
|
||||
try:
|
||||
cur_map = await get_current_funding_map()
|
||||
nft = int(cur_map.get(sym, {}).get("nextFundingTime", 0) or 0)
|
||||
except Exception:
|
||||
nft = 0
|
||||
save_funding_history(sym, rows, next_funding_time=nft)
|
||||
logger.info("Saved %d funding records for %s", len(rows), sym)
|
||||
return rows
|
||||
|
||||
|
||||
async def get_funding_bundle(
|
||||
symbol: str,
|
||||
limit: int | None = None,
|
||||
force_refresh: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
sym = symbol.upper()
|
||||
n = min(limit or settings.funding_history_limit, 1000)
|
||||
min_bars = min(n, 10)
|
||||
|
||||
current_map = await get_current_funding_map()
|
||||
cur = current_map.get(sym, {})
|
||||
current = {
|
||||
"rate": float(cur.get("lastFundingRate", 0) or 0),
|
||||
"rate_pct": float(cur.get("lastFundingRate", 0) or 0) * 100,
|
||||
"next_funding_time": int(cur.get("nextFundingTime", 0) or 0),
|
||||
"mark_price": float(cur.get("markPrice", 0) or 0),
|
||||
}
|
||||
|
||||
if not force_refresh and _is_history_fresh(sym, min_bars):
|
||||
history = get_funding_history_from_db(sym, n)
|
||||
if len(history) >= min_bars:
|
||||
return {
|
||||
"symbol": sym,
|
||||
"current": current,
|
||||
"history": history,
|
||||
"source": "db",
|
||||
}
|
||||
|
||||
stored = get_funding_history_from_db(sym, n)
|
||||
if binance_client.is_rate_limited():
|
||||
if stored:
|
||||
return {"symbol": sym, "current": current, "history": stored, "source": "db_stale"}
|
||||
raise BinanceRateLimitedError(binance_client.rate_limit_remaining_sec(), sym)
|
||||
|
||||
try:
|
||||
history = await sync_funding_history(sym, n)
|
||||
return {"symbol": sym, "current": current, "history": history, "source": "binance"}
|
||||
except BinanceRateLimitedError:
|
||||
if stored:
|
||||
return {"symbol": sym, "current": current, "history": stored, "source": "db_stale"}
|
||||
raise
|
||||
except Exception:
|
||||
if stored:
|
||||
return {"symbol": sym, "current": current, "history": stored, "source": "db_stale"}
|
||||
raise
|
||||
|
||||
|
||||
async def enrich_items_with_funding(items: list[dict]) -> list[dict]:
|
||||
try:
|
||||
current_map = await get_current_funding_map()
|
||||
except Exception as e:
|
||||
logger.warning("Funding current map failed: %s", e)
|
||||
current_map = {}
|
||||
|
||||
for item in items:
|
||||
sym = item.get("symbol", "")
|
||||
info = current_map.get(sym, {})
|
||||
rate = float(info.get("lastFundingRate", 0) or 0)
|
||||
item["funding_rate"] = rate
|
||||
item["funding_rate_pct"] = rate * 100
|
||||
item["funding_rate_fmt"] = f"{rate * 100:.4f}%"
|
||||
nft = info.get("nextFundingTime")
|
||||
item["next_funding_time"] = int(nft) if nft else None
|
||||
return items
|
||||
|
||||
|
||||
async def prefetch_funding(symbols: list[str]) -> None:
|
||||
seen: set[str] = set()
|
||||
try:
|
||||
await get_current_funding_map()
|
||||
except Exception as e:
|
||||
logger.warning("Prefetch premiumIndex failed: %s", e)
|
||||
|
||||
for raw in symbols:
|
||||
sym = raw.upper().strip()
|
||||
if not sym or sym in seen:
|
||||
continue
|
||||
seen.add(sym)
|
||||
if _is_history_fresh(sym, 10):
|
||||
continue
|
||||
if binance_client.is_rate_limited():
|
||||
logger.warning("Prefetch funding stopped — rate limited")
|
||||
break
|
||||
try:
|
||||
await sync_funding_history(sym)
|
||||
except BinanceRateLimitedError:
|
||||
break
|
||||
except Exception as e:
|
||||
logger.warning("Prefetch funding %s failed: %s", sym, e)
|
||||
Reference in New Issue
Block a user