From e7cafd6dbcde2a6d72bbc396b2c78b5d9df33652 Mon Sep 17 00:00:00 2001 From: dekun Date: Wed, 30 Sep 2026 23:56:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=B3=E9=94=AE=E4=BD=8D=E7=8E=B0=E4=BB=B7?= =?UTF-8?q?=E6=94=B9=E4=B8=8E=E9=A1=B6=E6=A0=8F=E5=90=8C=E6=BA=90=E5=85=AC?= =?UTF-8?q?=E5=BC=80=E8=A1=8C=E6=83=85=EF=BC=8C=E5=89=8D=E7=AB=AF=E6=97=A0?= =?UTF-8?q?=E4=BB=B7=E6=97=B6=E5=9B=9E=E9=80=80=20nav=20tickers=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- app.py | 76 ++++++++++++++++-------- lib/common/static/symbol_live_price.js | 73 ++++++++++++++++++++--- lib/instance/templates/embed_shell.html | 2 +- lib/instance/templates/index.html | 2 +- lib/instance/templates/key_focus_v2.html | 2 +- 5 files changed, 119 insertions(+), 36 deletions(-) diff --git a/app.py b/app.py index 06a7073..b2141f4 100644 --- a/app.py +++ b/app.py @@ -2591,7 +2591,10 @@ def get_available_trading_usdt(): def get_synced_leverage(exchange_symbol, direction): - ensure_markets_loaded() + try: + ensure_markets_loaded() + except Exception: + return None # 1) 优先读取交易所杠杆配置 try: if hasattr(exchange, "fetch_leverage"): @@ -4178,34 +4181,49 @@ def sync_trade_records_from_exchange(conn, force=False): # 获取实时价格 def get_price(symbol): - """最新价:优先 ccxt 永续 ticker;失败则走 OKX 公开接口(模拟盘无密钥也能显示).""" - ex_sym = "" + """最新价:公开 SWAP/现货 ticker 优先(与顶栏同源),再回退 ccxt.""" + sym = normalize_symbol_input(symbol) or (symbol or "").strip().upper() + base = "" + if "/" in sym: + base = sym.split("/", 1)[0].strip().upper() + elif sym: + base = sym.split(":", 1)[0].strip().upper() + # 1) 与顶栏 BTC/ETH 同一公开接口(模拟盘无密钥也可用) + if base in ("BTC", "ETH"): + try: + from lib.market.nav_spot_tickers_lib import _ticker_row + + for inst in (f"{base}-USDT-SWAP", f"{base}-USDT"): + row = _ticker_row(exchange, inst) + if not row: + continue + last = _safe_float(row.get("last") or row.get("lastPx") or row.get("markPx")) + if last is not None and last > 0: + return float(last) + except Exception: + pass + # 2) 其它币种:公开 ticker + if base and hasattr(exchange, "public_get_market_ticker"): + try: + for inst in (f"{base}-USDT-SWAP", f"{base}-USDT"): + rows = (exchange.public_get_market_ticker({"instId": inst}) or {}).get("data") or [] + if not rows: + continue + row = rows[0] or {} + last = _safe_float(row.get("last") or row.get("lastPx") or row.get("markPx")) + if last is not None and last > 0: + return float(last) + except Exception: + pass + # 3) ccxt 永续 try: - ex_sym = normalize_okx_symbol(symbol) ensure_markets_loaded() - last = exchange.fetch_ticker(ex_sym).get("last") - if last is not None: + last = exchange.fetch_ticker(normalize_okx_symbol(sym or symbol)).get("last") + last = _safe_float(last) + if last is not None and last > 0: return float(last) except Exception: pass - try: - sym = normalize_symbol_input(symbol) or (symbol or "").strip().upper() - base = (sym.split("/")[0] if "/" in sym else sym.split(":")[0]).strip().upper() - if not base: - return None - # SWAP 与现货公开 ticker 均可作为关键位现价 - for inst in (f"{base}-USDT-SWAP", f"{base}-USDT"): - if not hasattr(exchange, "public_get_market_ticker"): - break - rows = (exchange.public_get_market_ticker({"instId": inst}) or {}).get("data") or [] - if not rows: - continue - row = rows[0] or {} - last = row.get("last") or row.get("lastPx") or row.get("markPx") - if last is not None and float(last) > 0: - return float(last) - except Exception: - pass return None # 获取5分钟K线收盘价 @@ -6186,8 +6204,14 @@ def api_order_defaults(): if direction not in ("long", "short"): direction = "long" exchange_symbol = normalize_okx_symbol(symbol) - leverage = get_synced_leverage(exchange_symbol, direction) or infer_leverage(symbol) - available = get_available_trading_usdt() + try: + leverage = get_synced_leverage(exchange_symbol, direction) or infer_leverage(symbol) + except Exception: + leverage = infer_leverage(symbol) + try: + available = get_available_trading_usdt() + except Exception: + available = None last_price = get_price(symbol) return jsonify({ "ok": True, diff --git a/lib/common/static/symbol_live_price.js b/lib/common/static/symbol_live_price.js index 07db9c1..c86985b 100644 --- a/lib/common/static/symbol_live_price.js +++ b/lib/common/static/symbol_live_price.js @@ -1,5 +1,6 @@ /** * 表单币种输入:防抖 + 定时刷新,展示交易所最新价(/api/order_defaults). + * BTC/ETH 在 order_defaults 无价时回退 /api/nav_spot_tickers(与顶栏同源). */ (function (global) { "use strict"; @@ -65,6 +66,41 @@ el.title = "交易所最新价(约 " + pollMs() / 1000 + "s 刷新)"; } + function symbolBase(sym) { + return String(sym || "") + .trim() + .toUpperCase() + .split("/")[0] + .split(":")[0]; + } + + function fetchNavLast(sym) { + const base = symbolBase(sym); + if (base !== "BTC" && base !== "ETH") { + return Promise.resolve(null); + } + return fetch("/api/nav_spot_tickers") + .then(function (r) { + return r.json().catch(function () { + return null; + }); + }) + .then(function (d) { + if (!d || !d.ok || !Array.isArray(d.tickers)) return null; + for (let i = 0; i < d.tickers.length; i++) { + const t = d.tickers[i] || {}; + if (String(t.base || "").toUpperCase() === base && t.last != null) { + const n = Number(t.last); + return Number.isFinite(n) && n > 0 ? n : null; + } + } + return null; + }) + .catch(function () { + return null; + }); + } + function bindOne(el) { if (!el || bound.has(el)) return; bound.add(el); @@ -87,6 +123,15 @@ pollTimer = setInterval(refresh, pollMs()); } + function applyPrice(sym, px) { + if (px === null || typeof px === "undefined") { + paint(el, sym, null, "无法读取交易所价格"); + return; + } + paint(el, sym, px, ""); + if (!pollTimer) startPoll(); + } + function refresh() { const symEl = $(symId); const sym = symbolValue(symEl); @@ -116,21 +161,35 @@ el.classList.remove("symbol-live-price--loading"); const data = res.data || {}; if (res.status >= 400 || !data || !data.ok) { - paint(el, sym, null, (data && data.msg) || "读取失败"); - return; + return fetchNavLast(sym).then(function (navPx) { + if (seq !== fetchSeq) return; + if (navPx != null) { + applyPrice(sym, navPx); + return; + } + paint(el, sym, null, (data && data.msg) || "读取失败"); + }); } const px = data.last_price != null ? data.last_price : data.price; if (px === null || typeof px === "undefined") { - paint(el, data.symbol || sym, null, "无法读取交易所价格"); - return; + return fetchNavLast(sym).then(function (navPx) { + if (seq !== fetchSeq) return; + applyPrice(data.symbol || sym, navPx); + }); } - paint(el, data.symbol || sym, px, ""); - if (!pollTimer) startPoll(); + applyPrice(data.symbol || sym, px); }) .catch(function () { if (seq !== fetchSeq) return; el.classList.remove("symbol-live-price--loading"); - paint(el, sym, null, "网络错误"); + fetchNavLast(sym).then(function (navPx) { + if (seq !== fetchSeq) return; + if (navPx != null) { + applyPrice(sym, navPx); + return; + } + paint(el, sym, null, "网络错误"); + }); }); } diff --git a/lib/instance/templates/embed_shell.html b/lib/instance/templates/embed_shell.html index fa602fc..5feb212 100644 --- a/lib/instance/templates/embed_shell.html +++ b/lib/instance/templates/embed_shell.html @@ -135,7 +135,7 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj - + {% include 'embed_boot_scripts.html' %} diff --git a/lib/instance/templates/index.html b/lib/instance/templates/index.html index 08c81c3..ac05576 100644 --- a/lib/instance/templates/index.html +++ b/lib/instance/templates/index.html @@ -458,7 +458,7 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj - + {{ exchange_display }} | 关键位放大 - +