From 702d9d3be87be9fb4647f8391053019b38f41367 Mon Sep 17 00:00:00 2001 From: dekun Date: Thu, 13 Aug 2026 00:47:47 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20load=5Fmarkets=20=E8=B6=85?= =?UTF-8?q?=E6=97=B6=E5=90=8E=E7=8B=82=E6=89=93=E4=BA=A4=E6=98=93=E6=89=80?= =?UTF-8?q?=EF=BC=9A=E5=A4=B1=E8=B4=A5=E9=80=80=E9=81=BF=2090=20=E7=A7=92?= =?UTF-8?q?=EF=BC=8C=E9=81=BF=E5=85=8D=E5=8F=8D=E5=A4=8D=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E5=B0=81=20IP=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- crypto_monitor_binance/app.py | 22 +++++++++++++-- crypto_monitor_gate/app.py | 22 +++++++++++++-- crypto_monitor_okx/app.py | 22 +++++++++++++-- lib/trade/markets_load_guard_lib.py | 38 ++++++++++++++++++++++++++ tests/test_markets_load_guard_lib.py | 40 ++++++++++++++++++++++++++++ 5 files changed, 138 insertions(+), 6 deletions(-) create mode 100644 lib/trade/markets_load_guard_lib.py create mode 100644 tests/test_markets_load_guard_lib.py diff --git a/crypto_monitor_binance/app.py b/crypto_monitor_binance/app.py index 712ccfc..cf27e68 100644 --- a/crypto_monitor_binance/app.py +++ b/crypto_monitor_binance/app.py @@ -490,6 +490,18 @@ if BINANCE_API_KEY and BINANCE_API_SECRET: exchange.apiKey = BINANCE_API_KEY exchange.secret = BINANCE_API_SECRET MARKETS_LOADED = False +_MARKETS_GUARD = None + + +def _get_markets_guard(): + global _MARKETS_GUARD + if _MARKETS_GUARD is None: + from lib.trade.markets_load_guard_lib import MarketsLoadGuard + + _MARKETS_GUARD = MarketsLoadGuard(backoff_sec=90.0, label="binance markets") + return _MARKETS_GUARD + + ACCOUNT_BALANCE_CACHE = { "updated_at": 0.0, "funding_usdt": None, @@ -3583,10 +3595,16 @@ def calc_trend_manual_breakeven_stop(direction, entry_price, offset_pct=None): def ensure_markets_loaded(force=False): + """加载 Binance markets;失败后退避,避免超时循环狂打 API.""" global MARKETS_LOADED - if force or not MARKETS_LOADED: - exchange.load_markets(reload=force) + guard = _get_markets_guard() + guard.loaded = bool(MARKETS_LOADED) + try: + guard.ensure(lambda: exchange.load_markets(reload=force), force=force) MARKETS_LOADED = True + except Exception: + MARKETS_LOADED = False + raise def _abort_market_open_after_tpsl_failure(exchange_symbol, direction, order, planned_amount): diff --git a/crypto_monitor_gate/app.py b/crypto_monitor_gate/app.py index 8f21393..baad4e0 100644 --- a/crypto_monitor_gate/app.py +++ b/crypto_monitor_gate/app.py @@ -478,6 +478,18 @@ if GATE_API_KEY and GATE_API_SECRET: exchange.apiKey = GATE_API_KEY exchange.secret = GATE_API_SECRET MARKETS_LOADED = False +_MARKETS_GUARD = None + + +def _get_markets_guard(): + global _MARKETS_GUARD + if _MARKETS_GUARD is None: + from lib.trade.markets_load_guard_lib import MarketsLoadGuard + + _MARKETS_GUARD = MarketsLoadGuard(backoff_sec=90.0, label="gate markets") + return _MARKETS_GUARD + + ACCOUNT_BALANCE_CACHE = { "updated_at": 0.0, "funding_usdt": None, @@ -3376,10 +3388,16 @@ def calc_trend_manual_breakeven_stop(direction, entry_price, offset_pct=None): def ensure_markets_loaded(force=False): + """加载 Gate markets;失败后退避,避免超时循环狂打 API 导致封 IP.""" global MARKETS_LOADED - if force or not MARKETS_LOADED: - exchange.load_markets(reload=force) + guard = _get_markets_guard() + guard.loaded = bool(MARKETS_LOADED) + try: + guard.ensure(lambda: exchange.load_markets(reload=force), force=force) MARKETS_LOADED = True + except Exception: + MARKETS_LOADED = False + raise def _abort_market_open_after_tpsl_failure(exchange_symbol, direction, order, planned_amount): diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index 8b646f8..6cfb25f 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -506,6 +506,18 @@ if OKX_API_KEY and OKX_API_SECRET and OKX_API_PASSPHRASE: exchange_options.password = OKX_API_PASSPHRASE MARKETS_LOADED = False +_MARKETS_GUARD = None + + +def _get_markets_guard(): + global _MARKETS_GUARD + if _MARKETS_GUARD is None: + from lib.trade.markets_load_guard_lib import MarketsLoadGuard + + _MARKETS_GUARD = MarketsLoadGuard(backoff_sec=90.0, label="okx markets") + return _MARKETS_GUARD + + ACCOUNT_BALANCE_CACHE = { "updated_at": 0.0, "funding_usdt": None, @@ -2867,10 +2879,16 @@ def build_okx_order_params(direction, reduce_only=False): def ensure_markets_loaded(force=False): + """加载 OKX markets;失败后退避,避免超时循环狂打 API.""" global MARKETS_LOADED - if force or not MARKETS_LOADED: - exchange.load_markets(reload=force) + guard = _get_markets_guard() + guard.loaded = bool(MARKETS_LOADED) + try: + guard.ensure(lambda: exchange.load_markets(reload=force), force=force) MARKETS_LOADED = True + except Exception: + MARKETS_LOADED = False + raise def _okx_algo_trigger_price_str(exchange_symbol, price): diff --git a/lib/trade/markets_load_guard_lib.py b/lib/trade/markets_load_guard_lib.py new file mode 100644 index 0000000..d4efe37 --- /dev/null +++ b/lib/trade/markets_load_guard_lib.py @@ -0,0 +1,38 @@ +"""交易所 load_markets 失败退避:避免超时后每次调用都狂打 API 导致封 IP.""" +from __future__ import annotations + +import time +from typing import Any, Callable, Optional + + +class MarketsLoadGuard: + def __init__(self, *, backoff_sec: float = 60.0, label: str = "markets") -> None: + self.loaded = False + self.backoff_sec = float(backoff_sec) + self.label = label + self._fail_until = 0.0 + self._last_error: Optional[str] = None + + def ensure(self, load_fn: Callable[[], Any], *, force: bool = False) -> None: + if self.loaded and not force: + return + now = time.time() + if not force and now < self._fail_until: + wait = max(0.0, self._fail_until - now) + raise RuntimeError( + f"{self.label} load backing off {wait:.0f}s after: {self._last_error or 'previous failure'}" + ) + try: + load_fn() + self.loaded = True + self._fail_until = 0.0 + self._last_error = None + except Exception as e: + self.loaded = False + self._last_error = str(e) + self._fail_until = now + self.backoff_sec + print( + f"[{self.label}] load_markets failed; backoff {self.backoff_sec:.0f}s: {e}", + flush=True, + ) + raise diff --git a/tests/test_markets_load_guard_lib.py b/tests/test_markets_load_guard_lib.py new file mode 100644 index 0000000..dc518fb --- /dev/null +++ b/tests/test_markets_load_guard_lib.py @@ -0,0 +1,40 @@ +"""markets load backoff guard.""" +from __future__ import annotations + +import unittest + +from lib.trade.markets_load_guard_lib import MarketsLoadGuard + + +class TestMarketsLoadGuard(unittest.TestCase): + def test_success_sets_loaded(self): + g = MarketsLoadGuard(backoff_sec=60.0, label="t") + calls = {"n": 0} + + def load(): + calls["n"] += 1 + + g.ensure(load) + self.assertTrue(g.loaded) + g.ensure(load) + self.assertEqual(calls["n"], 1) + + def test_failure_backs_off(self): + g = MarketsLoadGuard(backoff_sec=60.0, label="t") + calls = {"n": 0} + + def load(): + calls["n"] += 1 + raise TimeoutError("boom") + + with self.assertRaises(TimeoutError): + g.ensure(load) + self.assertFalse(g.loaded) + self.assertEqual(calls["n"], 1) + with self.assertRaises(RuntimeError): + g.ensure(load) + self.assertEqual(calls["n"], 1) + + +if __name__ == "__main__": + unittest.main()