From 64c5ce606ea540a591a75dd0c74e44338eaf6d2e Mon Sep 17 00:00:00 2001 From: aaron <> Date: Wed, 25 Feb 2026 20:47:20 +0800 Subject: [PATCH] update --- backend/app/crypto_agent/crypto_agent.py | 6 +- backend/app/main.py | 67 ++++++++--- backend/app/services/bitget_service.py | 113 ++++++++++++++++++ backend/app/services/paper_trading_service.py | 20 ++++ 4 files changed, 188 insertions(+), 18 deletions(-) diff --git a/backend/app/crypto_agent/crypto_agent.py b/backend/app/crypto_agent/crypto_agent.py index 5dea24e..c994d40 100644 --- a/backend/app/crypto_agent/crypto_agent.py +++ b/backend/app/crypto_agent/crypto_agent.py @@ -84,7 +84,7 @@ class CryptoAgent: }) logger.info(f"加密货币智能体初始化完成(LLM 驱动),监控交易对: {self.symbols}") - logger.info(f"模拟交易: 始终启用") + logger.info(f"📊 交易: 始终启用") if self.real_trading: auto_status = "启用" if self.real_trading.get_auto_trading_status() else "禁用" @@ -977,7 +977,7 @@ class CryptoAgent: decision_text = decision_map.get(decision_type, decision_type) # 账户类型标识 - account_type = "📊 模拟" if is_paper else "💰 实盘" + account_type = "📊" if is_paper else "💰" # 方向图标 if 'long' in action.lower() or 'buy' in action.lower(): @@ -1484,7 +1484,7 @@ class CryptoAgent: """发送有信号但未执行交易的通知""" try: symbol = market_signal.get('symbol') - account_type = "📊 模拟" if is_paper else "💰 实盘" + account_type = "📊" if is_paper else "💰" # 获取最佳信号 best_signal = self._get_best_signal_from_market(market_signal) diff --git a/backend/app/main.py b/backend/app/main.py index aaa1035..c4a950f 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -54,18 +54,32 @@ async def price_monitor_loop(): side_icon = "🟢" if result.get('side') == 'long' else "🔴" grade = result.get('signal_grade', 'N/A') - title = f"✅ 挂单成交 - {result.get('symbol')}" + symbol = result.get('symbol', '') + entry_price = result.get('entry_price', 0) + filled_price = result.get('filled_price', 0) + stop_loss = result.get('stop_loss', 0) + take_profit = result.get('take_profit', 0) + + # 根据交易对精度格式化价格 + try: + from app.services.bitget_service import bitget_service + precision = bitget_service.get_precision(symbol) + price_fmt = f"{{:,.{precision['pricePrecision']}f}}" + except: + price_fmt = "{:,.2f}" + + title = f"✅ 挂单成交 - {symbol}" content_parts = [ f"{side_icon} **方向**: {side_text}", f"⭐ **信号等级**: {grade}", - f"💰 **挂单价**: ${result.get('entry_price', 0):,.2f}", - f"🎯 **成交价**: ${result.get('filled_price', 0):,.2f}", + f"💰 **挂单价**: ${price_fmt.format(entry_price)}", + f"🎯 **成交价**: ${price_fmt.format(filled_price)}", f"💵 **仓位**: ${result.get('quantity', 0):,.0f}", ] - if result.get('stop_loss'): - content_parts.append(f"🛑 **止损**: ${result.get('stop_loss', 0):,.2f}") - if result.get('take_profit'): - content_parts.append(f"🎯 **止盈**: ${result.get('take_profit', 0):,.2f}") + if stop_loss: + content_parts.append(f"🛑 **止损**: ${price_fmt.format(stop_loss)}") + if take_profit: + content_parts.append(f"🎯 **止盈**: ${price_fmt.format(take_profit)}") content = "\n".join(content_parts) @@ -152,6 +166,15 @@ async def price_monitor_loop(): side_icon = '🟢' if result.get('side') == 'long' else '🔴' pnl = result.get('current_pnl_percent', 0) symbol_display = result.get('symbol', '') + new_stop_loss = result.get('new_stop_loss', 0) + + # 根据交易对精度格式化价格 + try: + from app.services.bitget_service import bitget_service + precision = bitget_service.get_precision(symbol_display) + price_fmt = f"{{:,.{precision['pricePrecision']}f}}" + except: + price_fmt = "{:,.2f}" if move_type == 'trailing_first': title = f"📈 移动止损已激活 - {symbol_display}" @@ -159,7 +182,7 @@ async def price_monitor_loop(): f"{side_icon} **方向**: {side_text}", f"", f"📈 **当前盈利**: {pnl:+.2f}%", - f"🛑 **新止损价**: ${result.get('new_stop_loss', 0):,.2f}", + f"🛑 **新止损价**: ${price_fmt.format(new_stop_loss)}", f"", f"💰 锁定利润,让利润奔跑" ] @@ -170,7 +193,7 @@ async def price_monitor_loop(): f"{side_icon} **方向**: {side_text}", f"", f"📈 **当前盈利**: {pnl:+.2f}%", - f"🛑 **新止损价**: ${result.get('new_stop_loss', 0):,.2f}", + f"🛑 **新止损价**: ${price_fmt.format(new_stop_loss)}", f"", f"🎯 继续锁定更多利润" ] @@ -181,7 +204,7 @@ async def price_monitor_loop(): f"{side_icon} **方向**: {side_text}", f"", f"📈 **当前盈利**: {pnl:+.2f}%", - f"🛑 **新止损价**: ${result.get('new_stop_loss', 0):,.2f}", + f"🛑 **新止损价**: ${price_fmt.format(new_stop_loss)}", f"", f"💰 锁定利润,让利润奔跑" ] @@ -205,19 +228,33 @@ async def price_monitor_loop(): side_icon = '🟢' if result.get('side') == 'long' else '🔴' grade = result.get('signal_grade', 'N/A') - title = f"✅ 挂单成交 - {result.get('symbol')}" + symbol = result.get('symbol', '') + entry_price = result.get('entry_price', 0) + filled_price = result.get('filled_price', 0) + stop_loss = result.get('stop_loss', 0) + take_profit = result.get('take_profit', 0) + + # 根据交易对精度格式化价格 + try: + from app.services.bitget_service import bitget_service + precision = bitget_service.get_precision(symbol) + price_fmt = f"{{:,.{precision['pricePrecision']}f}}" + except: + price_fmt = "{:,.2f}" + + title = f"✅ 挂单成交 - {symbol}" content_parts = [ f"{side_icon} **方向**: {side_text}", f"", f"⭐ **信号等级**: {grade}", f"", - f"💰 **挂单价**: ${result.get('entry_price', 0):,.2f}", - f"🎯 **成交价**: ${result.get('filled_price', 0):,.2f}", + f"💰 **挂单价**: ${price_fmt.format(entry_price)}", + f"🎯 **成交价**: ${price_fmt.format(filled_price)}", f"📊 **持仓价值**: ${result.get('quantity', 0):,.0f}", f"", - f"🛑 **止损价**: ${result.get('stop_loss', 0):,.2f}", - f"🎯 **止盈价**: ${result.get('take_profit', 0):,.2f}" + f"🛑 **止损价**: ${price_fmt.format(stop_loss)}", + f"🎯 **止盈价**: ${price_fmt.format(take_profit)}" ] content = "\n".join(content_parts) diff --git a/backend/app/services/bitget_service.py b/backend/app/services/bitget_service.py index f2ea367..93424df 100644 --- a/backend/app/services/bitget_service.py +++ b/backend/app/services/bitget_service.py @@ -370,6 +370,119 @@ class BitgetService: logger.error(f"获取 {symbol} ticker 失败: {e}") return None + def get_symbol_info(self, symbol: str, category: str = None) -> Optional[Dict[str, Any]]: + """ + 获取交易对信息(包含精度配置) + + Args: + symbol: 交易对 + category: 产品类型,默认 USDT-FUTURES + + Returns: + 交易对信息,包含 pricePrecision 和 quantityPrecision + """ + try: + if category is None: + category = self.CATEGORY_USDT_FUTURES + + url = f"{self._base_url}/api/v3/market/symbols" + params = { + 'category': category, + 'symbol': symbol + } + + response = self._session.get(url, params=params, timeout=10) + response.raise_for_status() + result = response.json() + + if result.get('code') != '00000': + logger.error(f"Bitget API 错误: {result.get('msg')}") + return None + + data = result.get('data', []) + if not data: + return None + + symbol_data = data[0] + + # 返回精度信息 + return { + 'symbol': symbol_data.get('symbol'), + 'status': symbol_data.get('status'), + 'pricePrecision': int(symbol_data.get('pricePrecision', 2)), + 'quantityPrecision': int(symbol_data.get('quantityPrecision', 2)), + 'minTradeAmount': float(symbol_data.get('minTradeAmount', 0)), + 'maxTradeAmount': float(symbol_data.get('maxTradeAmount', 0)), + 'takerFeeRate': float(symbol_data.get('takerFeeRate', 0.001)), + 'makerFeeRate': float(symbol_data.get('makerFeeRate', 0.001)), + } + + except Exception as e: + logger.error(f"获取 {symbol} 交易对信息失败: {e}") + return None + + # 缓存交易对精度信息 + _symbol_precision_cache: Dict[str, Dict[str, int]] = {} + + def get_precision(self, symbol: str, category: str = None) -> Dict[str, int]: + """ + 获取交易对价格和数量精度(带缓存) + + Args: + symbol: 交易对 + category: 产品类型,默认 USDT-FUTURES + + Returns: + {'pricePrecision': 2, 'quantityPrecision': 2} + """ + # 检查缓存 + if symbol in self._symbol_precision_cache: + return self._symbol_precision_cache[symbol] + + # 获取交易对信息 + info = self.get_symbol_info(symbol, category) + if info: + precision = { + 'pricePrecision': info['pricePrecision'], + 'quantityPrecision': info['quantityPrecision'] + } + # 缓存结果 + self._symbol_precision_cache[symbol] = precision + return precision + + # 默认精度 + return {'pricePrecision': 2, 'quantityPrecision': 2} + + def round_price(self, symbol: str, price: float, category: str = None) -> float: + """ + 根据交易对精度四舍五入价格 + + Args: + symbol: 交易对 + price: 原始价格 + category: 产品类型 + + Returns: + 四舍五入后的价格 + """ + precision = self.get_precision(symbol, category) + return round(price, precision['pricePrecision']) + + def round_quantity(self, symbol: str, quantity: float, category: str = None) -> float: + """ + 根据交易对精度四舍五入数量 + + Args: + symbol: 交易对 + quantity: 原始数量 + category: 产品类型 + + Returns: + 四舍五入后的数量 + """ + precision = self.get_precision(symbol, category) + return round(quantity, precision['quantityPrecision']) + def get_funding_rate(self, symbol: str) -> Optional[Dict[str, Any]]: """ 获取资金费率(包含标记价格和指数价格) diff --git a/backend/app/services/paper_trading_service.py b/backend/app/services/paper_trading_service.py index 5d6d671..94e5363 100644 --- a/backend/app/services/paper_trading_service.py +++ b/backend/app/services/paper_trading_service.py @@ -116,6 +116,26 @@ class PaperTradingService: finally: db.close() + def _apply_price_precision(self, symbol: str, price: float) -> float: + """ + 应用交易对价格精度 + + Args: + symbol: 交易对 + price: 原始价格 + + Returns: + 四舍五入后的价格 + """ + if price is None or price == 0: + return price + try: + from app.services.bitget_service import bitget_service + return bitget_service.round_price(symbol, price) + except Exception as e: + logger.debug(f"价格精度调整失败 {symbol}: {e},使用原始价格") + return price + def create_order_from_signal(self, signal: Dict[str, Any], current_price: float = None) -> Dict[str, Any]: """ 从交易信号创建模拟订单