Commit 2dfbf29

benny-dou <60535774+benny-dou@users.noreply.github.com>
2025-02-05 14:53:24
feat(price): support OKX
1 parent 700d726
Changed files (4)
src/price/binance.py
@@ -51,6 +51,7 @@ async def get_binance_price(coin: str, interval: str = "30m") -> dict:
     klines = response.json()
     if not klines:
         return {"texts": f"Binance price failed: {coin}"}
+    klines = sorted(klines, key=lambda x: x[0])
     high_price = max(float(number(x[2])) for x in klines)
     low_price = min(float(number(x[3])) for x in klines)
     open_price = float(number(klines[0][1]))
@@ -62,7 +63,7 @@ async def get_binance_price(coin: str, interval: str = "30m") -> dict:
     title = f"{symbol}•{interval}•Binance"
     subtitle = f"开: {open_price} 高: {high_price} 低: {low_price} 收: {close_price} 涨: {change_pct:+.2%} 振: {abs(amplitude):.2%}"
     text = f"{title}\n"
-    text += f"时间段: {ts_to_dt(klines[0][0]):%m-%d %H:%M} - {ts_to_dt(klines[-1][0]):%m-%d %H:%M}\n"
+    text += f"时间段: {ts_to_dt(klines[0][0]):%m-%d %H:%M} → {ts_to_dt(klines[-1][0]):%m-%d %H:%M}\n"
     text += f"开盘价: {open_price}\n"
     text += f"最高价: {high_price}\n"
     text += f"最低价: {low_price}\n"
src/price/entrypoint.py
@@ -12,6 +12,7 @@ from messages.sender import send2tg
 from messages.utils import equal_prefix, startswith_prefix
 from price.binance import get_binance_price
 from price.coinmarketcap import cmc_convert_price, get_cmc_price
+from price.okx import get_okx_price
 
 HELP = f"""
 💵**查询价格**
@@ -61,8 +62,8 @@ async def get_asset_price(client: Client, message: Message, **kwargs) -> None:
         return
 
     # match "BTC"
-    if res := await get_binance_price(text):
-        await send2tg(client, message, **res, **kwargs)
+    if (res := await get_binance_price(text)) or (res := await get_okx_price(text)):
+        await send2tg(client, message, **res, **kwargs)  # with klines chart
     elif res := await get_cmc_price(text):
         await send2tg(client, message, texts=res, **kwargs)
     else:
src/price/okx.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from config import API, PROXY, cache
+from networking import hx_req
+from price.chart import generate_chart
+from utils import number, ts_to_dt
+
+
+@cache.memoize(ttl=7200)
+async def get_okx_symbols() -> dict[str, str]:
+    """Get all symbols from OKX."""
+    res = {}
+    url = f"{API.OKX}/api/v5/public/instruments"
+    for instType in ["SWAP", "SPOT"]:
+        params = {"instType": instType}
+        response = await hx_req(url, params=params, proxy=PROXY.CRYPTO, check_has_kv=["data"])
+        data = response.json()["data"]
+        res |= {coin["instId"].replace("-", ""): coin["instId"] for coin in data if coin["state"] == "live"}
+    return res
+
+
+@cache.memoize(ttl=60)
+async def get_okx_price(coin: str, interval: str = "30m") -> dict:
+    """Get the price of a crypto asset from OKX."""
+    symbols = await get_okx_symbols()
+    coin = coin.upper().replace("-", "")
+    symbol = coin
+    suffixes = ["USDT", "USDC", "USDTSWAP", "USDCSWAP", "USDSWAP"]
+    while symbol not in symbols and suffixes:
+        symbol = f"{coin}{suffixes.pop(0)}".upper()
+    if symbol not in symbols:
+        return {}
+    inst_id = symbols[symbol]
+    url = f"{API.OKX}/api/v5/market/candles?instId={inst_id}&bar={interval}&limit=49"
+    response = await hx_req(url, proxy=PROXY.CRYPTO, check_kv={"code": "0"})
+    klines = response.json()["data"]  # ts, o, h, l, c
+    if not klines:
+        return {"texts": f"OKX price failed: {coin}"}
+    klines = sorted(klines, key=lambda x: x[0])
+    high_price = max(float(number(x[2])) for x in klines)
+    low_price = min(float(number(x[3])) for x in klines)
+    open_price = float(number(klines[0][1]))
+    close_price = float(number(klines[-1][4]))
+    change_pct = (close_price - open_price) / open_price
+    amplitude = (high_price - low_price) / low_price
+    if close_price < open_price:
+        amplitude *= -1
+    title = f"{symbol}•{interval}•OKX"
+    subtitle = f"开: {open_price} 高: {high_price} 低: {low_price} 收: {close_price} 涨: {change_pct:+.2%} 振: {abs(amplitude):.2%}"
+    text = f"{title}\n"
+    text += f"时间段: {ts_to_dt(klines[0][0]):%m-%d %H:%M} → {ts_to_dt(klines[-1][0]):%m-%d %H:%M}\n"
+    text += f"开盘价: {open_price}\n"
+    text += f"最高价: {high_price}\n"
+    text += f"最低价: {low_price}\n"
+    text += f"收盘价: {close_price} \n"
+    text += f"涨跌幅: {change_pct:+.2%}\n"
+    text += f"振幅: {amplitude:.2%}"
+    chart = await generate_chart(klines, interval, title, subtitle)
+    return {"texts": text, "media": [{"photo": chart}]}
src/config.py
@@ -81,6 +81,7 @@ class API:
     TIKHUB_WEIBO_VIDEO = os.getenv("TIKHUB_WEIBO_VIDEO_API", "https://api.tikhub.io/api/v1/weibo/web/fetch_short_video_data?share_text=")
     BINANCE_SPOT = os.getenv("BINANCE_SPOT_API", "https://data-api.binance.vision")
     BINANCE_UM = os.getenv("BINANCE_UM_API", "https://fapi.binance.com")
+    OKX = os.getenv("OKX_API", "https://www.okx.com")
 
 
 class PROVIDER:  # default API provider