From 174943722aff9908821de038993527c9dda17444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Sat, 21 Feb 2026 00:53:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(trades,=20database,=20binance=5Fclient,=20?= =?UTF-8?q?position=5Fmanager,=20risk=5Fmanager):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=BA=A4=E6=98=93=E8=AE=B0=E5=BD=95=E6=9F=A5=E8=AF=A2=E4=B8=8E?= =?UTF-8?q?=E5=86=85=E5=AD=98=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 `trades.py` 中为获取所有有记录的交易对添加了限制条数的逻辑,避免全表加载。`models.py` 中调整了查询逻辑,未传递 limit 时使用默认上限以防内存暴增。`binance_client.py` 中为交易对信息缓存添加了最大大小限制,确保内存使用合理。`position_manager.py` 和 `risk_manager.py` 中的交易记录查询也进行了条数限制,提升了系统的稳定性与性能。此更新有助于优化内存管理与查询效率。 --- backend/api/routes/trades.py | 4 ++-- backend/database/models.py | 11 +++++++---- trading_system/binance_client.py | 11 ++++++++++- trading_system/position_manager.py | 4 ++-- trading_system/risk_manager.py | 5 ++++- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/backend/api/routes/trades.py b/backend/api/routes/trades.py index fe7e50c..2044299 100644 --- a/backend/api/routes/trades.py +++ b/backend/api/routes/trades.py @@ -434,9 +434,9 @@ async def sync_trades_from_binance( symbol_list = list({t.get("symbol") for t in (trades_in_range or []) if t.get("symbol")}) logger.info(f"从 DB 查询到 {len(trades_in_range or [])} 条记录,涉及 {len(symbol_list)} 个交易对") - # 如果时间范围内没有记录,再尝试「所有有记录的 symbol」 + # 如果时间范围内没有记录,再尝试「所有有记录的 symbol」(限制条数避免全表加载) if not symbol_list: - all_trades = Trade.get_all(account_id=aid) + all_trades = Trade.get_all(account_id=aid, limit=5000) symbol_list = list({t.get("symbol") for t in (all_trades or []) if t.get("symbol")}) logger.info(f"获取到所有有记录的 symbol: {len(symbol_list)} 个") diff --git a/backend/database/models.py b/backend/database/models.py index 8331b63..d9ad695 100644 --- a/backend/database/models.py +++ b/backend/database/models.py @@ -1134,10 +1134,13 @@ class Trade: query += " ORDER BY " + time_col + " DESC, id DESC" else: query += " ORDER BY COALESCE(exit_time, entry_time) DESC, id DESC" - if limit is not None and limit > 0: - query += " LIMIT %s" - params.append(int(limit)) - logger.debug(f"查询交易记录: time_filter={time_filter}, limit={limit}, reconciled_only={reconciled_only}, include_sync={include_sync}") + # 未传 limit 时使用默认上限,防止全表加载导致内存暴增 + _limit = limit + if _limit is None or _limit <= 0: + _limit = 20000 + query += " LIMIT %s" + params.append(int(_limit)) + logger.debug(f"查询交易记录: time_filter={time_filter}, limit={_limit}, reconciled_only={reconciled_only}, include_sync={include_sync}") result = db.execute_query(query, params) return result diff --git a/trading_system/binance_client.py b/trading_system/binance_client.py index a3589b2..f446a23 100644 --- a/trading_system/binance_client.py +++ b/trading_system/binance_client.py @@ -205,7 +205,8 @@ class BinanceClient: self.client: Optional[AsyncClient] = None self.socket_manager: Optional[BinanceSocketManager] = None - self._symbol_info_cache: Dict[str, Dict] = {} # 缓存交易对信息 + self._symbol_info_cache: Dict[str, Dict] = {} # 缓存交易对信息(有上限,防止内存无限增长) + self._symbol_info_cache_max_size = 200 self._last_request_time = {} # 记录每个API端点的最后请求时间 self._request_delay = 0.1 # 请求间隔(秒),避免频率限制 self._semaphore = asyncio.Semaphore(10) # 限制并发请求数 @@ -1405,6 +1406,10 @@ class BinanceClient: await self.redis_cache.set(cache_key, info, ttl=3600) # Redis 写入成功则不占进程内存 else: + if len(self._symbol_info_cache) >= self._symbol_info_cache_max_size and symbol not in self._symbol_info_cache: + _oldest = next(iter(self._symbol_info_cache), None) + if _oldest is not None: + self._symbol_info_cache.pop(_oldest, None) self._symbol_info_cache[symbol] = info logger.debug(f"从 DB 缓存解析 {symbol} 交易对信息") return info @@ -1489,6 +1494,10 @@ class BinanceClient: wrote = await self.redis_cache.set(cache_key, info, ttl=3600) if not wrote: + if len(self._symbol_info_cache) >= self._symbol_info_cache_max_size and symbol not in self._symbol_info_cache: + _oldest = next(iter(self._symbol_info_cache), None) + if _oldest is not None: + self._symbol_info_cache.pop(_oldest, None) self._symbol_info_cache[symbol] = info logger.debug(f"获取 {symbol} 精度信息: {info}") return info diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index 16c1eef..7feda5a 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -2412,8 +2412,8 @@ class PositionManager: binance_symbols = {p['symbol'] for p in binance_positions} logger.debug(f"币安实际持仓: {len(binance_symbols)} 个 ({', '.join(binance_symbols) if binance_symbols else '无'})") - # 2. 获取数据库中状态为open的交易记录(仅当前账号) - db_open_trades = Trade.get_all(status='open', account_id=self.account_id) + # 2. 获取数据库中状态为open的交易记录(仅当前账号,限制条数防内存暴增) + db_open_trades = Trade.get_all(status='open', account_id=self.account_id, limit=500) db_open_symbols = {t['symbol'] for t in db_open_trades} logger.debug(f"数据库open状态: {len(db_open_symbols)} 个 ({', '.join(db_open_symbols) if db_open_symbols else '无'})") diff --git a/trading_system/risk_manager.py b/trading_system/risk_manager.py index 731d87b..9ec660d 100644 --- a/trading_system/risk_manager.py +++ b/trading_system/risk_manager.py @@ -891,10 +891,13 @@ class RiskManager: from database.models import Trade # 查询最近N+1次已平仓的交易(多查一次,确保能判断是否连续) + # 限制 limit 避免单 symbol 拉取全表导致内存暴增(2 CPU 4G 多账号场景) + need_count = max_consecutive + 10 recent_trades = Trade.get_all( symbol=symbol, status='closed', - account_id=int(os.getenv("ATS_ACCOUNT_ID") or os.getenv("ACCOUNT_ID") or 1) + account_id=int(os.getenv("ATS_ACCOUNT_ID") or os.getenv("ACCOUNT_ID") or 1), + limit=min(need_count, 200), ) # 按平仓时间倒序排序(最新的在前)