109 lines
3.5 KiB
Python
109 lines
3.5 KiB
Python
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()
|