diff --git a/backend/app/crypto_agent/crypto_agent.py b/backend/app/crypto_agent/crypto_agent.py index 258dc97..a5587e7 100644 --- a/backend/app/crypto_agent/crypto_agent.py +++ b/backend/app/crypto_agent/crypto_agent.py @@ -54,14 +54,6 @@ class CryptoAgent: # 模拟交易服务(始终启用) self.paper_trading = get_paper_trading_service() - # 实盘交易服务(如果配置了 API) - self.real_trading = None - try: - from app.services.real_trading_service import get_real_trading_service - self.real_trading = get_real_trading_service() - except Exception as e: - logger.warning(f"实盘交易服务初始化失败: {e}") - # 状态管理 self.last_signals: Dict[str, Dict[str, Any]] = {} self.signal_cooldown: Dict[str, datetime] = {} @@ -87,13 +79,7 @@ class CryptoAgent: }) logger.info(f"加密货币智能体初始化完成(LLM 驱动),监控交易对: {self.symbols}") - logger.info(f"📊 交易: 始终启用") - - if self.real_trading: - auto_status = "启用" if self.real_trading.get_auto_trading_status() else "禁用" - logger.info(f"实盘交易: 已配置 (自动交易: {auto_status})") - else: - logger.info(f"实盘交易: 未配置") + logger.info(f"📊 模拟交易: 始终启用") def _on_price_update(self, symbol: str, price: float): """处理实时价格更新(用于模拟交易)""" @@ -555,50 +541,27 @@ class CryptoAgent: # 获取配置 paper_trading_enabled = self.settings.paper_trading_enabled - real_trading_enabled = self.settings.real_trading_enabled - - # 分别存储模拟和实盘的决策 - paper_decision = None - real_decision = None # 交易决策 if paper_trading_enabled: logger.info(f"\n📊 【交易决策】") - positions, account, pending_orders = self._get_trading_state(use_real_trading=False) + positions, account, pending_orders = self._get_trading_state() # 过滤:只传递当前symbol的挂单给决策器,避免LLM搞混 pending_orders_for_symbol = [o for o in pending_orders if o.get('symbol') == symbol] - paper_decision = await self.decision_maker.make_decision( + decision = await self.decision_maker.make_decision( market_signal, positions, account, current_price, pending_orders_for_symbol ) - self._log_trading_decision(paper_decision) + self._log_trading_decision(decision) # 发送交易决策通知 - await self._send_trading_decision_notification(paper_decision, market_signal, current_price, is_paper=True) + await self._send_trading_decision_notification(decision, market_signal, current_price) else: logger.info(f"⏸️ 交易未启用") - - # 实盘交易决策 - if real_trading_enabled: - logger.info(f"\n💰 【实盘交易决策】") - # 检查是否开启自动交易 - if self.real_trading and self.real_trading.get_auto_trading_status(): - positions, account, pending_orders = self._get_trading_state(use_real_trading=True) - # 过滤:只传递当前symbol的挂单给决策器,避免LLM搞混 - pending_orders_for_symbol = [o for o in pending_orders if o.get('symbol') == symbol] - real_decision = await self.decision_maker.make_decision( - market_signal, positions, account, current_price, pending_orders_for_symbol - ) - self._log_trading_decision(real_decision) - # 发送交易决策通知 - await self._send_trading_decision_notification(real_decision, market_signal, current_price, is_paper=False) - else: - logger.info(f"⏸️ 实盘自动交易未开启") - else: - logger.info(f"⏸️ 实盘交易未启用") + decision = None # ============================================================ # 第三阶段:执行交易决策 # ============================================================ - await self._execute_decisions(paper_decision, real_decision, market_signal, current_price) + await self._execute_decisions(decision, market_signal, current_price) except Exception as e: logger.error(f"❌ 分析 {symbol} 出错: {e}") @@ -709,24 +672,16 @@ class CryptoAgent: if risk: logger.info(f" 风险: {risk}") - def _get_trading_state(self, use_real_trading: bool = False) -> tuple: + def _get_trading_state(self) -> tuple: """ 获取交易状态(持仓和账户) - Args: - use_real_trading: True 获取实盘状态,False 获取模拟交易状态 - Returns: (positions, account, pending_orders) - 持仓列表、账户状态、挂单列表 """ - if use_real_trading and self.real_trading: - # 实盘交易 - active_orders = self.real_trading.get_active_orders() - account = self.real_trading.get_account_status() - else: - # 模拟交易 - active_orders = self.paper_trading.get_active_orders() - account = self.paper_trading.get_account_status() + # 模拟交易 + active_orders = self.paper_trading.get_active_orders() + account = self.paper_trading.get_account_status() # 分离持仓和挂单 position_list = [] @@ -756,10 +711,9 @@ class CryptoAgent: return position_list, account, pending_orders - async def _execute_decisions(self, paper_decision: Dict[str, Any], - real_decision: Dict[str, Any], + async def _execute_decisions(self, decision: Dict[str, Any], market_signal: Dict[str, Any], current_price: float): - """执行交易决策(模拟和实盘分别执行)""" + """执行交易决策""" # 选择最佳信号用于保存 best_signal = self._get_best_signal_from_market(market_signal) @@ -773,20 +727,15 @@ class CryptoAgent: # 获取配置 paper_trading_enabled = self.settings.paper_trading_enabled - real_trading_enabled = self.settings.real_trading_enabled - - # 记录执行结果 - paper_executed = False - real_executed = False # ============================================================ # 执行交易决策 # ============================================================ - if paper_trading_enabled and paper_decision: - decision_type = paper_decision.get('decision', 'HOLD') + if paper_trading_enabled and decision: + decision_type = decision.get('decision', 'HOLD') if decision_type == 'HOLD': - reasoning = paper_decision.get('reasoning', '观望') + reasoning = decision.get('reasoning', '观望') logger.info(f"\n📊 交易决策: {reasoning}") # HOLD决策的理由已在交易决策通知中说明,无需单独通知 else: @@ -795,7 +744,7 @@ class CryptoAgent: if decision_type in ['OPEN', 'ADD']: # 先执行交易 logger.info(f" 准备执行交易...") - result = await self._execute_paper_trade(paper_decision, market_signal, current_price) + result = await self._execute_paper_trade(decision, market_signal, current_price) # 检查是否成功执行 order = result.get('order') if result else None @@ -806,8 +755,7 @@ class CryptoAgent: if hasattr(order, 'order_id') and order.order_id: logger.info(f" 订单验证通过: {order.order_id}") # 只有成功创建订单后才发送通知 - await self._send_signal_notification(market_signal, paper_decision, current_price, is_paper=True) - paper_executed = True + await self._send_signal_notification(market_signal, decision, current_price) else: logger.error(f" ❌ 订单对象无效: 缺少order_id属性") # 订单创建失败,理由已在日志中记录,无需单独通知 @@ -816,101 +764,28 @@ class CryptoAgent: reason = result.get('message', '订单创建失败') if result else '订单创建失败' logger.warning(f" ⚠️ 交易未执行: {reason}") elif decision_type == 'CLOSE': - close_success = await self._execute_close(paper_decision, current_price, paper_trading=True) + close_success = await self._execute_close(decision, current_price) # CLOSE 操作也发送执行通知 if close_success: - await self._send_signal_notification(market_signal, paper_decision, current_price, is_paper=True) - paper_executed = True + await self._send_signal_notification(market_signal, decision, current_price) else: logger.warning(f" ⚠️ 平仓未成功执行,跳过通知") elif decision_type == 'CANCEL_PENDING': - cancel_success = await self._execute_cancel_pending(paper_decision, paper_trading=True) + cancel_success = await self._execute_cancel_pending(decision) # CANCEL_PENDING 操作也发送执行通知 if cancel_success: - await self._send_signal_notification(market_signal, paper_decision, current_price, is_paper=True) - paper_executed = True + await self._send_signal_notification(market_signal, decision, current_price) else: logger.warning(f" ⚠️ 取消挂单未成功执行,跳过通知") - elif decision_type == 'UPDATE_PENDING': - update_success = await self._execute_update_pending(paper_decision, paper_trading=True) - # UPDATE_PENDING 操作也发送执行通知 - if update_success: - await self._send_signal_notification(market_signal, paper_decision, current_price, is_paper=True) - paper_executed = True - else: - logger.warning(f" ⚠️ 更新挂单未成功执行,跳过通知") elif decision_type == 'REDUCE': - reduce_success = await self._execute_reduce(paper_decision, paper_trading=True) + reduce_success = await self._execute_reduce(decision) # REDUCE 操作也发送执行通知 if reduce_success: - await self._send_signal_notification(market_signal, paper_decision, current_price, is_paper=True) - paper_executed = True + await self._send_signal_notification(market_signal, decision, current_price) else: logger.warning(f" ⚠️ 减仓未成功执行,跳过通知") - - # ============================================================ - # 执行实盘交易决策 - # ============================================================ - if real_trading_enabled and real_decision: - # 检查是否开启自动交易 - if self.real_trading and self.real_trading.get_auto_trading_status(): - decision_type = real_decision.get('decision', 'HOLD') - - if decision_type == 'HOLD': - reasoning = real_decision.get('reasoning', '观望') - logger.info(f"\n💰 实盘交易: {reasoning}") - # HOLD决策的理由已在交易决策通知中说明,无需单独通知 - else: - logger.info(f"\n💰 【执行实盘交易】") - - if decision_type in ['OPEN', 'ADD']: - # 先执行交易 - result = await self._execute_real_trade(real_decision, market_signal, current_price) - # 检查是否成功执行 - if result and result.get('success'): - # 只有成功创建订单后才发送通知 - await self._send_signal_notification(market_signal, real_decision, current_price, is_paper=False) - real_executed = True - else: - # 订单创建失败,理由已在日志中记录,无需单独通知 - reason = result.get('message', '订单创建失败') if result else '订单创建失败' - logger.warning(f" ⚠️ 实盘交易未执行: {reason}") - elif decision_type == 'CLOSE': - close_success = await self._execute_close(real_decision, current_price, paper_trading=False) - # CLOSE 操作也发送执行通知 - if close_success: - await self._send_signal_notification(market_signal, real_decision, current_price, is_paper=False) - real_executed = True - else: - logger.warning(f" ⚠️ 实盘平仓未成功执行,跳过通知") - elif decision_type == 'CANCEL_PENDING': - cancel_success = await self._execute_cancel_pending(real_decision, paper_trading=False) - # CANCEL_PENDING 操作也发送执行通知 - if cancel_success: - await self._send_signal_notification(market_signal, real_decision, current_price, is_paper=False) - real_executed = True - else: - logger.warning(f" ⚠️ 实盘取消挂单未成功执行,跳过通知") - elif decision_type == 'UPDATE_PENDING': - update_success = await self._execute_update_pending(real_decision, paper_trading=False) - # UPDATE_PENDING 操作也发送执行通知 - if update_success: - await self._send_signal_notification(market_signal, real_decision, current_price, is_paper=False) - real_executed = True - else: - logger.warning(f" ⚠️ 实盘更新挂单未成功执行,跳过通知") - elif decision_type == 'REDUCE': - reduce_success = await self._execute_reduce(real_decision, paper_trading=False) - # REDUCE 操作也发送执行通知 - if reduce_success: - await self._send_signal_notification(market_signal, real_decision, current_price, is_paper=False) - real_executed = True - else: - logger.warning(f" ⚠️ 实盘减仓未成功执行,跳过通知") - - # 如果都没有执行,给出提示 - if not paper_executed and not real_executed: - logger.info(f"\n⏸️ 所有交易均为观望,无需执行") + else: + logger.info(f"\n⏸️ 交易未启用或决策为空") def _get_best_signal_from_market(self, market_signal: Dict[str, Any]) -> Dict[str, Any]: """从市场信号中获取最佳信号""" @@ -1094,15 +969,14 @@ class CryptoAgent: async def _send_trading_decision_notification(self, decision: Dict[str, Any], market_signal: Dict[str, Any], - current_price: float, - is_paper: bool = True): + current_price: float): """发送交易决策通知(第二阶段)""" try: decision_type = decision.get('decision', 'HOLD') symbol = market_signal.get('symbol') # 账户类型标识 - account_type = "📊 交易" if is_paper else "💰 实盘" + account_type = "📊 交易" # 决策类型映射 decision_map = { @@ -1228,8 +1102,7 @@ class CryptoAgent: logger.debug(traceback.format_exc()) async def _send_signal_notification(self, market_signal: Dict[str, Any], - decision: Dict[str, Any], current_price: float, - is_paper: bool = True): + decision: Dict[str, Any], current_price: float): """发送交易执行通知(第三阶段)""" try: decision_type = decision.get('decision', 'HOLD') @@ -1259,7 +1132,7 @@ class CryptoAgent: decision_text = decision_map.get(decision_type, decision_type) # 账户类型标识 - account_type = "📊" if is_paper else "💰" + account_type = "📊" # 方向图标 if 'long' in action.lower() or 'buy' in action.lower(): @@ -1365,14 +1238,18 @@ class CryptoAgent: action = decision.get('action', '') position_size = decision.get('position_size', 'light') - # 直接使用 LLM 决策的 quantity - quantity = decision.get('quantity', 0) - if quantity <= 0: - logger.warning(f" ⚠️ LLM 决策的 quantity 无效: {quantity},使用默认值") - quantity = self._calculate_quantity_by_position_size(position_size, real_trading=False) + # 使用新的动态仓位计算方法 + logger.info(f" 计算动态仓位: {position_size}") + margin, position_value = self.paper_trading._calculate_dynamic_position(position_size, symbol) + + if margin <= 0: + logger.warning(f" ⚠️ 计算的保证金无效: {margin},无法开仓") + return False + + quantity = margin # 保证金金额 logger.info(f" 准备创建订单: {symbol} {action} {position_size}") - logger.info(f" LLM 决策金额: ${quantity:.2f} USDT") + logger.info(f" 保证金: ${quantity:.2f} | 持仓价值: ${position_value:.2f}") # 转换决策的 action 为 paper_trading 期望的格式 trading_action = self._convert_trading_action(action) @@ -1395,7 +1272,7 @@ class CryptoAgent: 'confidence': decision.get('confidence', 50), 'signal_grade': 'B', # 默认B级 'position_size': position_size, - 'quantity': quantity # 使用 LLM 决策的金额 + 'quantity': quantity # 使用计算后的保证金金额 } logger.debug(f" 订单数据: {order_data}") @@ -1464,66 +1341,12 @@ class CryptoAgent: return position_config.get(position_size, 200) - async def _execute_real_trade(self, decision: Dict[str, Any], market_signal: Dict[str, Any], current_price: float): - """执行实盘交易""" - try: - symbol = decision.get('symbol') - action = decision.get('action', '') - position_size = decision.get('position_size', 'light') - - # 直接使用 LLM 决策的 quantity - quantity = decision.get('quantity', 0) - if quantity <= 0: - logger.warning(f" ⚠️ LLM 决策的 quantity 无效: {quantity},使用默认值") - quantity = self._calculate_quantity_by_position_size(position_size, real_trading=True) - - logger.info(f" 实盘交易: {position_size} → 保证金 ${quantity:.2f} USDT → 持仓价值 ${quantity * 20:.2f}") - - # 转换决策的 action 为 paper_trading 期望的格式 - trading_action = self._convert_trading_action(action) - - # 从市场信号中获取入场方式和入场价格 - best_signal = self._get_best_signal_from_market(market_signal) - entry_type = best_signal.get('entry_type', 'market') if best_signal else 'market' - entry_price = best_signal.get('entry_price', current_price) if best_signal else current_price - - logger.info(f" 入场方式: {entry_type} | 入场价格: ${entry_price:,.2f}") - - # 转换决策为订单格式(价格字段已在 LLM 解析时转换为 float) - order_data = { - 'symbol': symbol, - 'action': trading_action, # 使用转换后的 action - 'entry_type': entry_type, # 使用信号中的入场方式 - 'entry_price': entry_price if entry_type == 'limit' else current_price, # limit单使用entry_price,market单使用current_price - 'stop_loss': decision.get('stop_loss'), - 'take_profit': decision.get('take_profit'), - 'confidence': decision.get('confidence', 50), - 'signal_grade': 'B', - 'position_size': position_size, - 'quantity': quantity # 使用 LLM 决策的金额 - } - - result = self.real_trading.create_order_from_signal(order_data, current_price) - - if result.get('success'): - logger.info(f" 💰 已创建实盘订单: {result.get('order_id')} | 持仓价值: ${quantity * 20:.2f}") - await self._notify_real_order_created(symbol, decision, result) - else: - logger.warning(f" ⚠️ 创建实盘订单失败: {result.get('message', 'Unknown error')}") - - # 返回结果 - return result - - except Exception as e: - logger.error(f"执行实盘交易失败: {e}") - - async def _execute_close(self, decision: Dict[str, Any], current_price: float, paper_trading: bool = True) -> bool: + async def _execute_close(self, decision: Dict[str, Any], current_price: float) -> bool: """执行平仓 Args: decision: 交易决策(应包含 orders_to_close 字段) current_price: 当前价格 - paper_trading: True=模拟交易, False=实盘交易 Returns: 是否成功执行平仓 @@ -1532,71 +1355,59 @@ class CryptoAgent: symbol = decision.get('symbol') orders_to_close = decision.get('orders_to_close', []) - if paper_trading: - # 模拟交易平仓 - if self.paper_trading: - logger.info(f" 🔒 平仓: {symbol}") - logger.info(f" 理由: {decision.get('reasoning', '')}") + if self.paper_trading: + logger.info(f" 🔒 平仓: {symbol}") + logger.info(f" 理由: {decision.get('reasoning', '')}") - # 如果决策中没有指定订单ID,则获取该交易对的所有活跃订单 - if not orders_to_close: - logger.warning(f" ⚠️ 决策中未指定 orders_to_close,将平仓 {symbol} 的所有持仓") - active_orders = self.paper_trading.get_active_orders(symbol) - orders_to_close = [o.get('order_id') for o in active_orders if o.get('status') in ('OPEN', 'FILLED', 'PENDING')] + # 如果决策中没有指定订单ID,则获取该交易对的所有活跃订单 + if not orders_to_close: + logger.warning(f" ⚠️ 决策中未指定 orders_to_close,将平仓 {symbol} 的所有持仓") + active_orders = self.paper_trading.get_active_orders(symbol) + orders_to_close = [o.get('order_id') for o in active_orders if o.get('status') in ('OPEN', 'FILLED', 'PENDING')] - if not orders_to_close: - logger.warning(f" 没有找到需要平仓的订单") - return False + if not orders_to_close: + logger.warning(f" 没有找到需要平仓的订单") + return False - logger.info(f" 待平仓订单: {orders_to_close}") + logger.info(f" 待平仓订单: {orders_to_close}") - closed_count = 0 - for order_id in orders_to_close: - try: - # 先获取订单信息 - order_info = self.paper_trading.get_order_by_id(order_id) - if not order_info: - logger.warning(f" ❌ 订单不存在: {order_id}") - continue + closed_count = 0 + for order_id in orders_to_close: + try: + # 先获取订单信息 + order_info = self.paper_trading.get_order_by_id(order_id) + if not order_info: + logger.warning(f" ❌ 订单不存在: {order_id}") + continue - status = order_info.get('status') + status = order_info.get('status') - if status == 'PENDING': - # 取消挂单 - result = self.paper_trading.cancel_order(order_id) - if result.get('success'): - logger.info(f" ✅ 已取消挂单: {order_id}") - closed_count += 1 - else: - logger.warning(f" ❌ 取消挂单失败: {order_id} - {result.get('message')}") - elif status in ('OPEN', 'FILLED'): - # 平仓已成交订单 - result = self.paper_trading.close_order_manual(order_id, current_price) - if result: - logger.info(f" ✅ 已平仓: {order_id} @ ${current_price}") - closed_count += 1 - else: - logger.warning(f" ❌ 平仓失败: {order_id}") + if status == 'PENDING': + # 取消挂单 + result = self.paper_trading.cancel_order(order_id) + if result.get('success'): + logger.info(f" ✅ 已取消挂单: {order_id}") + closed_count += 1 else: - logger.warning(f" ⚠️ 订单状态无需处理: {order_id} - {status}") - except Exception as e: - logger.error(f" ❌ 处理订单 {order_id} 失败: {e}") + logger.warning(f" ❌ 取消挂单失败: {order_id} - {result.get('message')}") + elif status in ('OPEN', 'FILLED'): + # 平仓已成交订单 + result = self.paper_trading.close_order_manual(order_id, current_price) + if result: + logger.info(f" ✅ 已平仓: {order_id} @ ${current_price}") + closed_count += 1 + else: + logger.warning(f" ❌ 平仓失败: {order_id}") + else: + logger.warning(f" ⚠️ 订单状态无需处理: {order_id} - {status}") + except Exception as e: + logger.error(f" ❌ 处理订单 {order_id} 失败: {e}") - logger.info(f" 📊 平仓汇总: {closed_count}/{len(orders_to_close)} 个订单已处理") - return closed_count > 0 - else: - logger.warning(f" 交易服务未初始化") - return False + logger.info(f" 📊 平仓汇总: {closed_count}/{len(orders_to_close)} 个订单已处理") + return closed_count > 0 else: - # 实盘平仓 - if self.real_trading and self.real_trading.get_auto_trading_status(): - logger.info(f" 🔒 实盘平仓: {symbol}") - logger.info(f" 理由: {decision.get('reasoning', '')}") - # TODO: 实现实盘平仓逻辑 - return True - else: - logger.warning(f" 实盘交易服务未启用或自动交易未开启") - return False + logger.warning(f" 交易服务未初始化") + return False except Exception as e: logger.error(f"执行平仓失败: {e}") @@ -1604,12 +1415,11 @@ class CryptoAgent: logger.error(traceback.format_exc()) return False - async def _execute_cancel_pending(self, decision: Dict[str, Any], paper_trading: bool = True) -> bool: + async def _execute_cancel_pending(self, decision: Dict[str, Any]) -> bool: """执行取消挂单 Args: decision: 交易决策 - paper_trading: True=模拟交易, False=实盘交易 Returns: 是否成功取消订单 @@ -1621,14 +1431,13 @@ class CryptoAgent: if not orders_to_cancel: logger.info(f" ⚠️ 没有需要取消的订单") - return + return False - trading_service = self.paper_trading if paper_trading else self.real_trading - trading_type = "模拟" if paper_trading else "实盘" + trading_service = self.paper_trading if not trading_service: - logger.warning(f" {trading_type}交易服务未启用") - return + logger.warning(f" 交易服务未启用") + return False # 安全检查:验证要取消的订单是否属于当前symbol且方向相反 active_orders = trading_service.get_active_orders() @@ -1681,7 +1490,7 @@ class CryptoAgent: logger.warning(f" ⚠️ 没有有效的订单可以取消") return False - logger.info(f" 🚫 {trading_type}取消挂单: {symbol}") + logger.info(f" 🚫 取消挂单: {symbol}") logger.info(f" 取消订单数量: {len(valid_orders)}") cancelled_count = 0 @@ -1704,12 +1513,11 @@ class CryptoAgent: logger.error(f"执行取消挂单失败: {e}") return False - async def _execute_update_pending(self, decision: Dict[str, Any], paper_trading: bool = True) -> bool: + async def _execute_update_pending(self, decision: Dict[str, Any]) -> bool: """执行更新挂单参数 Args: decision: 交易决策 - paper_trading: True=模拟交易, False=实盘交易 Returns: 是否成功更新订单 @@ -1732,11 +1540,10 @@ class CryptoAgent: logger.warning(f" ⚠️ 更新参数不完整: entry_price={new_entry_price}, stop_loss={new_stop_loss}, take_profit={new_take_profit}") return False - trading_service = self.paper_trading if paper_trading else self.real_trading - trading_type = "模拟" if paper_trading else "实盘" + trading_service = self.paper_trading if not trading_service: - logger.warning(f" {trading_type}交易服务未启用") + logger.warning(f" 交易服务未启用") return False # 安全检查:验证要更新的订单是否属于当前symbol且方向相同 @@ -1792,7 +1599,7 @@ class CryptoAgent: logger.warning(f" ⚠️ 没有有效的订单可以更新") return False - logger.info(f" 🔄 {trading_type}更新挂单: {symbol}") + logger.info(f" 🔄 更新挂单: {symbol}") logger.info(f" 新参数: 入场=${new_entry_price:,.2f}, 止损=${new_stop_loss:,.2f}, 止盈=${new_take_profit:,.2f}") updated_count = 0 @@ -1822,27 +1629,25 @@ class CryptoAgent: logger.error(f"执行更新挂单失败: {e}") return False - async def _execute_reduce(self, decision: Dict[str, Any], paper_trading: bool = True) -> bool: + async def _execute_reduce(self, decision: Dict[str, Any]) -> bool: """执行减仓 Args: decision: 交易决策 - paper_trading: True=模拟交易, False=实盘交易 Returns: 是否成功执行减仓 """ try: symbol = decision.get('symbol') - trading_type = "模拟" if paper_trading else "实盘" - logger.info(f" 📤 {trading_type}减仓: {symbol}") + logger.info(f" 📤 减仓: {symbol}") logger.info(f" 理由: {decision.get('reasoning', '')}") - trading_service = self.paper_trading if paper_trading else self.real_trading + trading_service = self.paper_trading if not trading_service: - logger.warning(f" {trading_type}交易服务未初始化") + logger.warning(f" 交易服务未初始化") return False # TODO: 实现减仓逻辑 @@ -1990,44 +1795,6 @@ class CryptoAgent: 'trend': signal.get('trend') } - async def _notify_real_order_created( - self, - symbol: str, - signal: Dict[str, Any], - result: Dict[str, Any] - ): - """发送实盘订单创建通知""" - side = signal.get('action', 'buy') - side_text = "做多" if side == 'buy' else "做空" - side_icon = "🟢" if side == 'buy' else "🔴" - grade = signal.get('grade', 'N/A') - position_size = result.get('position_size', 'light') - quantity = result.get('quantity', 0) # 这是保证金金额 - position_value = quantity * 20 # 持仓价值 = 保证金 × 杠杆 - - title = f"💰 实盘订单已创建 - {symbol}" - - content_parts = [ - f"{side_icon} **方向**: {side_text}", - f"⭐ **信号等级**: {grade}", - f"📊 **仓位**: {position_size}", - f"💰 **持仓价值**: ${position_value:,.2f}", - f"🆔 **订单ID**: {result.get('order_id', '')[:12]}...", - f"", - f"⚠️ **真实资金交易中**", - ] - - content = "\n".join(content_parts) - - if self.settings.feishu_enabled: - await self.feishu_paper.send_card(title, content, "red") - if self.settings.telegram_enabled: - message = f"{title}\n\n{content}" - await self.telegram.send_message(message) - if self.settings.dingtalk_enabled: - await self.dingtalk.send_action_card(title, content) - logger.info(f"已发送实盘订单创建通知: {result.get('order_id')}") - async def _notify_position_adjustment( self, symbol: str, @@ -2085,13 +1852,12 @@ class CryptoAgent: market_signal: Dict[str, Any], decision: Dict[str, Any], current_price: float, - is_paper: bool = True, reason: str = "" ): """发送有信号但未执行交易的通知""" try: symbol = market_signal.get('symbol') - account_type = "📊" if is_paper else "💰" + account_type = "📊" # 获取最佳信号 best_signal = self._get_best_signal_from_market(market_signal) diff --git a/backend/app/crypto_agent/trading_decision_maker.py b/backend/app/crypto_agent/trading_decision_maker.py index 439d8af..18112ca 100644 --- a/backend/app/crypto_agent/trading_decision_maker.py +++ b/backend/app/crypto_agent/trading_decision_maker.py @@ -129,45 +129,77 @@ class TradingDecisionMaker: 3. **HOLD(观望)** - 如果反转信号不强 #### 情况C:无持仓 + 有同向挂单 -**优先选择:UPDATE_PENDING(更新挂单参数)** +**优先选择:OPEN(新增挂单)- 金字塔式布局** -**核心原则:适应新信号,不要等老信号** -- 新信号代表最新的市场分析 -- 应该用新信号的参数更新挂单 -- 不要死守过时的挂单价格 +**核心原则:允许合理的多挂单策略** +- 同方向可以最多有3个挂单(金字塔布局) +- 新挂单价格与现有挂单价格差异 > 1% +- 不同价格位分散风险,提高成交概率 -**更新挂单的条件**: -- ✅ 新信号与挂单**方向相同**(都是 buy 或都是 sell) -- ✅ 新信号提供了**更新的入场价、止损、止盈** +**允许新增挂单的条件**: +- ✅ 当前同向挂单数量 < 3个 +- ✅ 新信号入场价与所有现有挂单价格差异 > 1% - ✅ 新信号置信度 >= 60(C级以上) +- ✅ 价格没有在快速加速移动 -**更新内容**(根据新信号调整): -- 入场价格:使用新信号的 entry_price -- 止损价格:使用新信号的 stop_loss -- 止盈价格:使用新信号的 take_profit -- 入场方式:保持 limit(不要改成 market) - -**示例**: +**示例1:新增第2个挂单** ``` -当前:BTC 做多挂单 @ $94,000,止损 $93,500,止盈 $95,500 -新信号:BTC 做多 @ $93,800(B级,75%置信度),止损 $93,200,止盈 $95,200 +当前:BTC 做多挂单1 @ $94,000 +新信号:BTC 做多 @ $92,700(B级,75%置信度) 分析: -- 新信号更保守,入场价更低 -- 应该更新挂单参数以适应新的市场分析 -- 决策:UPDATE_PENDING(更新挂单) -- 理由:根据新信号更新入场价 $93,800,止损 $93,200,止盈 $95,200 +- 价格差异:(94000-92700)/94000 = 1.38% > 1% +- 挂单数量:1个 < 3个 +- 决策:OPEN(新增挂单2 @ $92,700) +- 理由:金字塔布局,在不同价位布置挂单 +``` + +**示例2:新增第3个挂单** +``` +当前:BTC 做多挂单1 @ $94,000,挂单2 @ $92,700 +新信号:BTC 做多 @ $91,500(B级,70%置信度) + +分析: +- 新价格与挂单2差异:(92700-91500)/92700 = 1.29% > 1% +- 挂单数量:2个 < 3个 +- 决策:OPEN(新增挂单3 @ $91,500) +- 理由:金字塔布局,继续分散挂单位置 +``` + +**示例3:达到3个挂单上限** +``` +当前:BTC 做多挂单1 @ $94,000,挂单2 @ $92,700,挂单3 @ $91,500 +新信号:BTC 做多 @ $90,800(B级,70%置信度) + +分析: +- 已有3个挂单,达到上限 +- 决策:HOLD +- 理由:同向挂单已达3个上限,不再新增 +``` + +**示例4:价格差异太小** +``` +当前:BTC 做多挂单1 @ $94,000 +新信号:BTC 做多 @ $93,200(价格差异仅0.85%) + +分析: +- 价格差异:(94000-93200)/94000 = 0.85% < 1% +- 太接近现有挂单,没有意义 +- 决策:HOLD +- 理由:新价格与现有挂单太近,不新增 ``` **⚠️ 例外情况(保持 HOLD)**: - 价格正在快速加速移动(5m 连续大阳/阴线) - 新信号置信度 < 60(D级信号,质量太低) - 新信号入场价距离当前价格 >= 2%(追涨杀跌风险) -- 新信号是 market 入场(不要改成 market 去追) +- 新信号是 market 入场(改成 market 去追) +- 价格差异 < 1%(太接近现有挂单) **❌ 严禁**: +- 同向挂单数量 >= 3个时继续新增 +- 价格差异 < 1% 时新增挂单 - 取消挂单后市价追涨/杀跌 -- 价格快速移动时的任何更新操作 #### 情况D:无持仓 + 有反向挂单 **优先选择**: @@ -177,19 +209,29 @@ class TradingDecisionMaker: #### 情况E:完全无持仓无挂单 **这时才考虑开新仓(OPEN)** -### 第三步:开新仓的严格限制 -只有在满足以下所有条件时才开新仓: -- 当前交易对**没有任何持仓和挂单** +### 第三步:开新仓的规则 +**金字塔式挂单策略**: +- 无持仓无挂单:可以开新仓 +- 无持仓 + 同向挂单 < 3个:可以新增挂单(价格差异 > 1%) +- 无持仓 + 同向挂单 >= 3个:不再新增 +- 有持仓 + 无同向挂单:可以考虑加仓 +- 有持仓 + 有同向挂单:优先持仓,挂单次之 + +**开新仓条件**: - 信号质量足够高(confidence >= 60) - 可用杠杆空间充足 - 价格和止损合理 +- 不在价格加速移动中 ## 🚨 铁律(违反即拒绝) -### 1. 避免重复开仓 -- **同一标的同一方向最多只允许1个持仓 + 1个挂单** -- 如果已有持仓/挂单,不要开新仓,考虑加仓或调整 -- 价格距离 < 2% 时不加仓也不开新仓 +### 1. 金字塔挂单规则(同方向) +- **最多3个挂单**:同一标的同一方向最多允许3个挂单 +- **价格差异 > 1%**:新挂单与现有挂单价格差异必须 > 1% +- **挂单优先**:优先使用 limit 挂单,慎用 market 市价 +- **不要重复**:价格差异 < 1% 时不要新增挂单 +- **有持仓时**:优先考虑持仓管理,挂单次之 +- **价格距离 < 2% 时不加仓**:与持仓价格太近时不加仓 ### 2. 趋势与信号一致性 | 当前趋势 | 信号方向 | 允许操作 | @@ -248,7 +290,7 @@ class TradingDecisionMaker: ## 输出格式 ```json { - "decision": "OPEN/CLOSE/ADD/REDUCE/CANCEL_PENDING/UPDATE_PENDING/HOLD", + "decision": "OPEN/CLOSE/ADD/REDUCE/CANCEL_PENDING/HOLD", "action": "buy/sell", "quantity": 保证金金额(USDT), "entry_price": 入场价格, @@ -256,7 +298,6 @@ class TradingDecisionMaker: "take_profit": 止盈价格, "orders_to_close": ["order_id_1"], // CLOSE/REDUCE 时指定要平仓的订单ID "orders_to_cancel": ["order_id_1"], // CANCEL_PENDING 时指定要取消的订单ID - "orders_to_update": ["order_id_1"], // UPDATE_PENDING 时指定要更新的订单ID "reasoning": "决策理由(必须说明当前持仓/挂单状态以及为什么选择这个操作)", "risk_analysis": "风险分析" } @@ -265,7 +306,7 @@ class TradingDecisionMaker: **重要提示**: - 当 `decision` 为 `CLOSE` 时,**必须**在 `orders_to_close` 中指定要平仓的订单ID列表 - 当 `decision` 为 `CANCEL_PENDING` 时,**必须**在 `orders_to_cancel` 中指定要取消的订单ID列表 -- 当 `decision` 为 `UPDATE_PENDING` 时,**必须**在 `orders_to_update` 中指定要更新的订单ID列表,并提供新的 entry_price、stop_loss、take_profit +- 同方向允许最多3个挂单(金字塔布局),价格差异需 > 1% - 如果需要平仓所有持仓,`orders_to_close` 应包含所有持仓订单的ID ## 决策示例 @@ -320,21 +361,44 @@ class TradingDecisionMaker: - 理由:趋势反转,及时止损 ``` -### 示例5:有挂单 + 同向信号 - 更新挂单参数(UPDATE_PENDING) +### 示例5:有挂单 + 同向信号 - 金字塔布局(OPEN) ``` -当前状态:BTC 做多挂单 @ $94,500(未成交),订单ID: ord_456 -新信号:BTC 做多 @ $93,800(confidence 75%,B级,回调至支撑位) +当前状态:BTC 做多挂单1 @ $94,500(未成交),订单ID: ord_456 +新信号:BTC 做多 @ $93,500(confidence 75%,B级,回调至支撑位) 分析: -- 新信号提供了更优的入场价格($93,800 < $94,500) -- 市场出现回调机会,应该调整挂单价格 -- 决策:UPDATE_PENDING(更新挂单参数) -- orders_to_update: ["ord_456"] -- 新参数:entry_price=$93,800, stop_loss=$93,200, take_profit=$95,200 -- 理由:根据新信号更新挂单参数,适应新的市场分析 +- 同向挂单数量:1个 < 3个 +- 价格差异:(94500-93500)/94500 = 1.06% > 1% +- 满足金字塔布局条件 +- 决策:OPEN(新增挂单2) +- 理由:金字塔布局,在不同价位布置挂单,提高成交概率 ``` -### 示例6:有挂单 + 同向信号 - 价格加速时保持(HOLD) +### 示例6:有挂单 + 同向信号 - 价格差异太小(HOLD) +``` +当前状态:BTC 做多挂单1 @ $94,500,挂单2 @ $93,500 +新信号:BTC 做多 @ $93,000(confidence 70%,B级) + +分析: +- 同向挂单数量:2个 < 3个 +- 新价格与挂单2差异:(93500-93000)/93500 = 0.53% < 1% +- 太接近现有挂单 +- 决策:HOLD +- 理由:新价格与现有挂单太近,不新增 +``` + +### 示例7:有挂单 + 同向信号 - 达到上限(HOLD) +``` +当前状态:BTC 做多挂单1 @ $94,500,挂单2 @ $93,500,挂单3 @ $92,500 +新信号:BTC 做多 @ $91,500(confidence 70%,B级) + +分析: +- 同向挂单数量:3个(已达上限) +- 决策:HOLD +- 理由:同向挂单已达3个上限,不再新增 +``` + +### 示例8:有挂单 + 同向信号 - 价格加速(HOLD) ``` 当前状态:BTC 做多挂单 @ $94,000(未成交) 新信号:BTC 做多 @ $97,000(confidence 85%,B级,突破) @@ -347,7 +411,7 @@ class TradingDecisionMaker: - 理由:价格正在加速,禁止追涨杀跌,等待回调 ``` -### 示例7:完全无持仓无挂单 +### 示例9:完全无持仓无挂单 ``` 当前状态:无持仓,无挂单 新信号:BTC 做多 @ $95,000(confidence 80%,uptrend) @@ -980,7 +1044,7 @@ class TradingDecisionMaker: market_signal: Dict[str, Any] = None) -> Dict[str, Any]: """验证决策安全性""" # 检查杠杆限制 - if decision.get('decision') in ['OPEN', 'ADD', 'UPDATE_PENDING']: + if decision.get('decision') in ['OPEN', 'ADD']: balance = float(account.get('current_balance', 0)) total_position_value = float(account.get('total_position_value', 0)) max_leverage = 20 @@ -999,7 +1063,6 @@ class TradingDecisionMaker: ) # 盈亏比检查:所有交易必须满足盈亏比 >= 1:1.5 - # UPDATE_PENDING 也需要验证盈亏比 action = decision.get('action', '') entry_price = decision.get('entry_price') stop_loss = decision.get('stop_loss') diff --git a/backend/app/services/paper_trading_service.py b/backend/app/services/paper_trading_service.py index 5c4d55a..0108630 100644 --- a/backend/app/services/paper_trading_service.py +++ b/backend/app/services/paper_trading_service.py @@ -358,7 +358,7 @@ class PaperTradingService: 计算逻辑: - 根据可用保证金的倍数确定持仓价值 - - micro: 0.8x, light: 1.2x, medium: 2.0x, heavy: 3.0x + - micro: 0.5x, light: 1.0x, medium: 1.5x, heavy: 2.0x - 累计持仓价值不超过可用保证金的 15 倍 - 保证金 = 持仓价值 / 杠杆 @@ -380,11 +380,11 @@ class PaperTradingService: # 根据 position_size 确定倍数(相对于可用保证金) position_multiplier = { - 'micro': 0.8, - 'light': 1.2, - 'medium': 2.0, - 'heavy': 3.0 - }.get(position_size, 1.2) + 'micro': 0.5, + 'light': 1.0, + 'medium': 1.5, + 'heavy': 2.0 + }.get(position_size, 1.0) # 最大累计持仓价值倍数 max_total_multiplier = 15.0