feat: server.py 迁移部署到 NGCQ(1Panel Nginx 反代 eink.njcqit.com)+境内可达 BTC 行情源+页面访问令牌
- btc_api:新增 gateio/huobi_aws/binance_vision 三源,config 增 BTC_SOURCES 控制尝试顺序(境内 okx/coinbase/huobi 主域名不通) - server:IMAGE_TOKEN 非空时三个渲染端点须带 ?token=…,否则与未知路径同样 404(防爬虫扫描);推送令牌改常量时间比较 - 文档:CLAUDE.md/README/.env.example 记录 NGCQ 部署(systemd eink-server、1Panel API 建站坑)、本机 server/cloudflared 已卸载 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
52
btc_api.py
52
btc_api.py
@@ -7,7 +7,13 @@
|
||||
1. OKX GET /api/v5/market/candles?instId=BTC-USDT&bar=<1H|1D> —— 主源,本地直连可达、数据最新
|
||||
2. Coinbase GET /products/BTC-USD/candles?granularity=<3600|86400> —— 备源(USD 计价,与 USDT 数值几乎一致)
|
||||
3. Huobi GET /market/history/kline?period=<60min|1day>&symbol=btcusdt —— 备源
|
||||
(Binance 在本地区返回 restricted location,不列入。)
|
||||
4. Gate.io GET /api/v4/spot/candlesticks?currency_pair=BTC_USDT&interval=<1h|1d>
|
||||
5. Huobi AWS 同 3,域名 api-aws.huobi.pro
|
||||
6. Binance GET data-api.binance.vision/api/v3/klines?symbol=BTCUSDT&interval=<1h|1d>(公开行情镜像域名)
|
||||
(api.binance.com 在本地区返回 restricted location,不列入。)
|
||||
|
||||
顺序可用 BTC_SOURCES(逗号分隔源名)覆盖:境内服务器上 okx/coinbase/huobi 主域名均不通且逐个超时(合计 >15s,
|
||||
超出 FETCH_BUDGET),须把 gateio/huobi_aws/binance_vision 排在前面(2026-09-18 部署 NGCQ 实测)。
|
||||
|
||||
周期用统一代号 bar:"1H"(时 K)/ "1D"(日 K),由 BARS 映射到各源的写法。
|
||||
统一输出:按时间**升序**的 list[dict],每根 {"ts": epoch 秒, "o", "h", "l", "c"}(float),最后一根为当前未收盘的那根。
|
||||
@@ -19,8 +25,8 @@ import urllib.request
|
||||
TIMEOUT = 8
|
||||
UA = "eink-push/1.0 (+https://github.com/)"
|
||||
|
||||
# 周期代号 → 各源参数:(OKX bar, Coinbase granularity 秒, Huobi period)
|
||||
BARS = {"1H": ("1H", 3600, "60min"), "1D": ("1D", 86400, "1day")}
|
||||
# 周期代号 → 各源参数:(OKX bar, Coinbase granularity 秒, Huobi period, Gate.io/Binance interval)
|
||||
BARS = {"1H": ("1H", 3600, "60min", "1h"), "1D": ("1D", 86400, "1day", "1d")}
|
||||
|
||||
|
||||
def _get_json(url):
|
||||
@@ -45,8 +51,8 @@ def _coinbase(bar, limit):
|
||||
return sorted(out, key=lambda x: x["ts"])[-limit:]
|
||||
|
||||
|
||||
def _huobi(bar, limit):
|
||||
j = _get_json(f"https://api.huobi.pro/market/history/kline?period={BARS[bar][2]}&size={limit}&symbol=btcusdt")
|
||||
def _huobi(bar, limit, host="api.huobi.pro"):
|
||||
j = _get_json(f"https://{host}/market/history/kline?period={BARS[bar][2]}&size={limit}&symbol=btcusdt")
|
||||
if j.get("status") != "ok":
|
||||
raise RuntimeError(f"huobi status={j.get('status')} err={j.get('err-msg')}")
|
||||
out = [{"ts": int(r["id"]), "o": float(r["open"]), "h": float(r["high"]), "l": float(r["low"]), "c": float(r["close"])}
|
||||
@@ -54,7 +60,39 @@ def _huobi(bar, limit):
|
||||
return sorted(out, key=lambda x: x["ts"])
|
||||
|
||||
|
||||
SOURCES = (("okx", _okx), ("coinbase", _coinbase), ("huobi", _huobi))
|
||||
def _huobi_aws(bar, limit):
|
||||
return _huobi(bar, limit, host="api-aws.huobi.pro")
|
||||
|
||||
|
||||
def _gateio(bar, limit):
|
||||
rows = _get_json(f"https://api.gateio.ws/api/v4/spot/candlesticks?currency_pair=BTC_USDT&interval={BARS[bar][3]}&limit={limit}")
|
||||
# 升序:[ts 秒, 计价币成交额, close, high, low, open, 基础币成交量, 是否收盘]
|
||||
out = [{"ts": int(r[0]), "o": float(r[5]), "h": float(r[3]), "l": float(r[4]), "c": float(r[2])} for r in rows]
|
||||
return sorted(out, key=lambda x: x["ts"])
|
||||
|
||||
|
||||
def _binance_vision(bar, limit):
|
||||
rows = _get_json(f"https://data-api.binance.vision/api/v3/klines?symbol=BTCUSDT&interval={BARS[bar][3]}&limit={limit}")
|
||||
# 升序:[openTime ms, o, h, l, c, vol, closeTime, ...]
|
||||
out = [{"ts": int(r[0]) // 1000, "o": float(r[1]), "h": float(r[2]), "l": float(r[3]), "c": float(r[4])} for r in rows]
|
||||
return sorted(out, key=lambda x: x["ts"])
|
||||
|
||||
|
||||
SOURCES = (("okx", _okx), ("coinbase", _coinbase), ("huobi", _huobi),
|
||||
("gateio", _gateio), ("huobi_aws", _huobi_aws), ("binance_vision", _binance_vision))
|
||||
_BY_NAME = dict(SOURCES)
|
||||
|
||||
|
||||
def _ordered_sources():
|
||||
"""按 config.BTC_SOURCES(逗号分隔源名)排序;未列出的源按默认顺序追加到末尾,未知名字忽略。"""
|
||||
try:
|
||||
import config
|
||||
wanted = [s.strip() for s in config.BTC_SOURCES.split(",") if s.strip()]
|
||||
except Exception: # noqa: BLE001 —— 单独运行本模块或 config 缺失时用默认顺序
|
||||
wanted = []
|
||||
ordered = [(n, _BY_NAME[n]) for n in wanted if n in _BY_NAME]
|
||||
ordered += [(n, f) for n, f in SOURCES if n not in dict(ordered)]
|
||||
return ordered
|
||||
|
||||
|
||||
def get_candles(bar="1H", limit=36):
|
||||
@@ -62,7 +100,7 @@ def get_candles(bar="1H", limit=36):
|
||||
if bar not in BARS:
|
||||
raise ValueError(f"不支持的周期 {bar!r},可选:{list(BARS)}")
|
||||
errors = []
|
||||
for name, fn in SOURCES:
|
||||
for name, fn in _ordered_sources():
|
||||
try:
|
||||
rows = fn(bar, limit)
|
||||
if len(rows) >= 2:
|
||||
|
||||
Reference in New Issue
Block a user