diff --git a/backend/signal_pusher.py b/backend/signal_pusher.py new file mode 100644 index 0000000..0be55b5 --- /dev/null +++ b/backend/signal_pusher.py @@ -0,0 +1,108 @@ +import os +import sqlite3 +from datetime import datetime, timedelta +import httpx + +DB_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "arb.db") +BINANCE_FAPI = "https://fapi.binance.com/fapi/v1" +SYMBOLS = ["BTCUSDT", "ETHUSDT"] +DISCORD_TOKEN = os.getenv("DISCORD_BOT_TOKEN", "MTQ3Mjk4NzY1NjczNTU1OTg0Mg.GgeYh5.NYSbivZKBUc5S2iKXeB-hnC33w3SUUPzDDdviM") +DISCORD_CHANNEL = os.getenv("DISCORD_SIGNAL_CHANNEL", "1472986545635197033") +BINANCE_HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"} + + +def ensure_tables(conn): + conn.execute( + """ + CREATE TABLE IF NOT EXISTS signal_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + symbol TEXT NOT NULL, + rate REAL NOT NULL, + annualized REAL NOT NULL, + sent_at TEXT NOT NULL, + message TEXT NOT NULL + ) + """ + ) + conn.commit() + + +def annualized_from_7d(symbol: str) -> float: + end_time = int(datetime.utcnow().timestamp() * 1000) + start_time = int((datetime.utcnow() - timedelta(days=7)).timestamp() * 1000) + with httpx.Client(timeout=20, headers=BINANCE_HEADERS) as client: + r = client.get( + f"{BINANCE_FAPI}/fundingRate", + params={"symbol": symbol, "startTime": start_time, "endTime": end_time, "limit": 1000}, + ) + if r.status_code != 200: + print(f"Binance error {r.status_code} for {symbol}, using fallback") + # 直接用最新单条费率 + r2 = client.get(f"{BINANCE_FAPI}/fundingRate", params={"symbol": symbol, "limit": 1}) + if r2.status_code != 200: + return 0.0 + rates = [float(x["fundingRate"]) for x in r2.json()] + else: + rates = [float(x["fundingRate"]) for x in r.json()] + if not rates: + return 0.0 + mean = sum(rates) / len(rates) + return mean * 3 * 365 * 100 + + +def send_discord(content: str): + headers = { + "Authorization": f"Bot {DISCORD_TOKEN}", + "Content-Type": "application/json", + } + with httpx.Client(timeout=15) as client: + resp = client.post( + f"https://discord.com/api/v10/channels/{DISCORD_CHANNEL}/messages", + headers=headers, + json={"content": content}, + ) + if resp.status_code >= 300: + raise RuntimeError(f"Discord send failed: {resp.status_code} {resp.text}") + print(f"Discord sent OK: {resp.status_code}") + + +def main(): + btc = round(annualized_from_7d("BTCUSDT"), 2) + eth = round(annualized_from_7d("ETHUSDT"), 2) + + target_symbol = None + if btc > 10: + target_symbol = "BTC" + elif eth > 10: + target_symbol = "ETH" + + now_utc8 = datetime.utcnow() + now_str = now_utc8.strftime("%Y-%m-%d %H:%M UTC") + + if not target_symbol: + print(f"No signal. BTC={btc}% ETH={eth}%") + return + + msg = ( + f"📊 **套利信号**\n" + f"BTC 7日年化: **{btc}%**\n" + f"ETH 7日年化: **{eth}%**\n" + f"建议:{target_symbol} 现货+永续对冲可开仓\n" + f"时间: {now_str}" + ) + + print(msg) + send_discord(msg) + + conn = sqlite3.connect(DB_PATH) + ensure_tables(conn) + conn.execute( + "INSERT INTO signal_logs (symbol, rate, annualized, sent_at, message) VALUES (?, ?, ?, ?, ?)", + (target_symbol, 0.0, btc if target_symbol == "BTC" else eth, datetime.utcnow().isoformat(), msg), + ) + conn.commit() + conn.close() + + +if __name__ == "__main__": + main()