From 42e329cc5514aff9a96935ec14a225ef5f67f04f Mon Sep 17 00:00:00 2001 From: root Date: Thu, 26 Feb 2026 13:54:59 +0000 Subject: [PATCH] fix: add cache to prevent Binance rate limit (rates 3s, history/stats 60s) --- backend/main.py | 56 +++++++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/backend/main.py b/backend/main.py index 166e33b..005b21f 100644 --- a/backend/main.py +++ b/backend/main.py @@ -2,7 +2,7 @@ from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware import httpx from datetime import datetime, timedelta -import asyncio +import asyncio, time app = FastAPI(title="Arbitrage Engine API") @@ -16,6 +16,18 @@ app.add_middleware( BINANCE_FAPI = "https://fapi.binance.com/fapi/v1" SYMBOLS = ["BTCUSDT", "ETHUSDT"] +# 简单内存缓存(history/stats 60秒,rates 3秒) +_cache: dict = {} + +def get_cache(key: str, ttl: int): + entry = _cache.get(key) + if entry and time.time() - entry["ts"] < ttl: + return entry["data"] + return None + +def set_cache(key: str, data): + _cache[key] = {"ts": time.time(), "data": data} + @app.get("/api/health") async def health(): @@ -24,10 +36,11 @@ async def health(): @app.get("/api/rates") async def get_rates(): + cached = get_cache("rates", 3) + if cached: return cached async with httpx.AsyncClient(timeout=10) as client: tasks = [client.get(f"{BINANCE_FAPI}/premiumIndex", params={"symbol": s}) for s in SYMBOLS] responses = await asyncio.gather(*tasks) - result = {} for sym, resp in zip(SYMBOLS, responses): if resp.status_code != 200: @@ -42,55 +55,50 @@ async def get_rates(): "nextFundingTime": data["nextFundingTime"], "timestamp": data["time"], } + set_cache("rates", result) return result @app.get("/api/history") async def get_history(): + cached = get_cache("history", 60) + if cached: return cached end_time = int(datetime.utcnow().timestamp() * 1000) start_time = int((datetime.utcnow() - timedelta(days=7)).timestamp() * 1000) - async with httpx.AsyncClient(timeout=15) as client: tasks = [ - client.get( - f"{BINANCE_FAPI}/fundingRate", - params={"symbol": s, "startTime": start_time, "endTime": end_time, "limit": 1000}, - ) + client.get(f"{BINANCE_FAPI}/fundingRate", + params={"symbol": s, "startTime": start_time, "endTime": end_time, "limit": 1000}) for s in SYMBOLS ] responses = await asyncio.gather(*tasks) - result = {} for sym, resp in zip(SYMBOLS, responses): if resp.status_code != 200: raise HTTPException(status_code=502, detail=f"Binance history error for {sym}") key = sym.replace("USDT", "") result[key] = [ - { - "fundingTime": item["fundingTime"], - "fundingRate": float(item["fundingRate"]), - "timestamp": datetime.utcfromtimestamp(item["fundingTime"] / 1000).isoformat(), - } + {"fundingTime": item["fundingTime"], "fundingRate": float(item["fundingRate"]), + "timestamp": datetime.utcfromtimestamp(item["fundingTime"] / 1000).isoformat()} for item in resp.json() ] + set_cache("history", result) return result @app.get("/api/stats") async def get_stats(): + cached = get_cache("stats", 60) + if cached: return cached end_time = int(datetime.utcnow().timestamp() * 1000) start_time = int((datetime.utcnow() - timedelta(days=7)).timestamp() * 1000) - async with httpx.AsyncClient(timeout=15) as client: tasks = [ - client.get( - f"{BINANCE_FAPI}/fundingRate", - params={"symbol": s, "startTime": start_time, "endTime": end_time, "limit": 1000}, - ) + client.get(f"{BINANCE_FAPI}/fundingRate", + params={"symbol": s, "startTime": start_time, "endTime": end_time, "limit": 1000}) for s in SYMBOLS ] responses = await asyncio.gather(*tasks) - stats = {} for sym, resp in zip(SYMBOLS, responses): if resp.status_code != 200: @@ -101,14 +109,12 @@ async def get_stats(): stats[key] = {"mean7d": 0, "annualized": 0, "count": 0} continue mean = sum(rates) / len(rates) - annualized = mean * 3 * 365 * 100 # 3 settlements/day * 365 days * % + annualized = mean * 3 * 365 * 100 stats[key] = { - "mean7d": round(mean * 100, 6), # as percent - "annualized": round(annualized, 2), # as percent + "mean7d": round(mean * 100, 6), + "annualized": round(annualized, 2), "count": len(rates), } - - # 50-50 combo btc_ann = stats.get("BTC", {}).get("annualized", 0) eth_ann = stats.get("ETH", {}).get("annualized", 0) btc_mean = stats.get("BTC", {}).get("mean7d", 0) @@ -117,5 +123,5 @@ async def get_stats(): "mean7d": round((btc_mean + eth_mean) / 2, 6), "annualized": round((btc_ann + eth_ann) / 2, 2), } - + set_cache("stats", stats) return stats