修复 load_markets 超时后狂打交易所:失败退避 90 秒,避免反复请求导致封 IP。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-13 00:47:47 +08:00
parent 39878de7fc
commit 702d9d3be8
5 changed files with 138 additions and 6 deletions
+20 -2
View File
@@ -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):
+20 -2
View File
@@ -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):
+20 -2
View File
@@ -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):
+38
View File
@@ -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
+40
View File
@@ -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()