From 91ec4ec091ec772740cd971e5b8ba940cfc0006a Mon Sep 17 00:00:00 2001 From: aaron <> Date: Wed, 25 Feb 2026 21:50:34 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=84=E7=90=86=E9=87=8D=E5=A4=8D=E5=BC=80?= =?UTF-8?q?=E4=BB=93=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crypto_agent/trading_decision_maker.py | 21 +++++ backend/app/services/paper_trading_service.py | 77 ++++++++++++++++++- 2 files changed, 95 insertions(+), 3 deletions(-) diff --git a/backend/app/crypto_agent/trading_decision_maker.py b/backend/app/crypto_agent/trading_decision_maker.py index 651f8c5..f8ebc55 100644 --- a/backend/app/crypto_agent/trading_decision_maker.py +++ b/backend/app/crypto_agent/trading_decision_maker.py @@ -55,6 +55,12 @@ class TradingDecisionMaker: - 同向新信号 - 趋势加强 +**价格距离限制(重要)**: +- 如果已有持仓/挂单的价格与新价格距离 < 1%,**不加仓也不开新仓** +- 例如:现有 BTC 做多持仓 @ $95,000,新信号 @ $95,500(差距 0.52% < 1%),拒绝开仓 +- 这是为了避免在同一价格区域重复建仓,导致风险过度集中 +- **例外**:如果信号是 A 级(confidence >= 90)且趋势非常强劲,可以考虑放宽到 0.5% + ### 4. 减仓(REDUCE) **时机**: - 部分止盈 @@ -258,6 +264,13 @@ class TradingDecisionMaker: 'max_leverage': max_leverage } + # 价格距离检查信息(用于 LLM 判断) + context['price_distance_check'] = { + 'enabled': True, + 'min_distance_percent': 1.0, # 最小价格距离 1% + 'exception_threshold': 90 # A 级信号且 confidence >= 90 时可放宽到 0.5% + } + return context def _build_decision_prompt(self, context: Dict[str, Any]) -> str: @@ -355,6 +368,14 @@ class TradingDecisionMaker: prompt_parts.append(f"可用杠杆空间: {lev_info.get('available_leverage_percent', 0):.1f}%") prompt_parts.append(f"最大杠杆限制: {lev_info.get('max_leverage', 20)}x") + # 价格距离检查规则 + price_check = context.get('price_distance_check', {}) + if price_check.get('enabled'): + prompt_parts.append(f"\n## 价格距离限制") + prompt_parts.append(f"⚠️ 重要:如果有相同方向的持仓/挂单,价格距离必须 >= {price_check.get('min_distance_percent', 1)}%") + prompt_parts.append(f"- 低于此距离不开新仓,避免风险过度集中") + prompt_parts.append(f"- A级信号(confidence >= {price_check.get('exception_threshold', 90)})可考虑放宽到 0.5%") + prompt_parts.append(f"\n请根据以上信息,做出交易决策。") return "\n".join(prompt_parts) diff --git a/backend/app/services/paper_trading_service.py b/backend/app/services/paper_trading_service.py index 86b60f8..77332f0 100644 --- a/backend/app/services/paper_trading_service.py +++ b/backend/app/services/paper_trading_service.py @@ -186,19 +186,38 @@ class PaperTradingService: result['message'] = msg return result - # 2. 检查是否有接近的挂单(价格差距 < 1%) + # 2. 检查是否有接近的订单(包括挂单和持仓) + # 安全网阈值(LLM 已在提示词中告知此规则,这是最后的安全检查) + # 设置一个更宽松的阈值,主要判断由 LLM 负责 + price_distance_threshold = 0.003 # 0.3% 作为安全网 + same_direction_orders = [ order for order in self.active_orders.values() if order.symbol == symbol and order.side == side ] + + # 检查挂单距离 pending_orders = [ order for order in same_direction_orders if order.status == OrderStatus.PENDING ] for pending in pending_orders: price_diff = abs(pending.entry_price - entry_price) / pending.entry_price - if price_diff < 0.01: # 价格差距小于 1% - msg = f"已有接近的挂单 @ ${pending.entry_price:,.2f}(价格差距 {price_diff*100:.2f}%)" + if price_diff < price_distance_threshold: + msg = f"已有接近的挂单 @ ${pending.entry_price:,.2f}(价格差距 {price_diff*100:.2f}% < {price_distance_threshold*100:.0f}%)" + logger.info(f"订单限制: {symbol} {msg},新信号 @ ${entry_price:,.2f},跳过") + result['message'] = msg + return result + + # 检查持仓距离(避免在已有持仓价格附近重复开仓) + open_positions = [ + order for order in same_direction_orders + if order.status == OrderStatus.OPEN + ] + for position in open_positions: + price_diff = abs(position.entry_price - entry_price) / position.entry_price + if price_diff < price_distance_threshold: + msg = f"已有接近的持仓 @ ${position.entry_price:,.2f}(价格差距 {price_diff*100:.2f}% < {price_distance_threshold*100:.0f}%)" logger.info(f"订单限制: {symbol} {msg},新信号 @ ${entry_price:,.2f},跳过") result['message'] = msg return result @@ -866,6 +885,58 @@ class PaperTradingService: return None + def _calculate_dynamic_price_threshold(self, symbol: str, current_price: float) -> float: + """ + 根据市场波动率动态计算价格距离阈值 + + 使用 ATR (Average True Range) 来衡量波动率: + - 高波动率币种(如山寨币)→ 更大的阈值 + - 低波动率币种(如 BTC)→ 更小的阈值 + + Args: + symbol: 交易对 + current_price: 当前价格 + + Returns: + 动态价格阈值(例如 0.01 = 1%) + """ + try: + # 获取市场数据服务 + from app.services.bitget_service import bitget_service + + # 获取 1 小时 K 线数据来计算 ATR + df = bitget_service.get_klines(symbol, interval='1h', limit=50) + + if df is None or len(df) < 14 or 'atr' not in df.columns: + # 无法获取 ATR,使用默认值 1% + logger.debug(f"{symbol} 无法获取 ATR,使用默认阈值 1%") + return 0.01 + + # 获取最新的 ATR 值 + current_atr = float(df['atr'].iloc[-1]) + + # 计算 ATR 占价格的百分比 + atr_percent = (current_atr / current_price) * 100 + + # 动态阈值:ATR% 的 50% 作为价格距离阈值 + # 例如:ATR = 3% → 阈值 = 1.5% + # ATR = 1% → 阈值 = 0.5% + dynamic_threshold = (atr_percent / 100) * 0.5 + + # 设置最小和最大阈值限制 + min_threshold = 0.005 # 最小 0.5% + max_threshold = 0.02 # 最大 2% + + dynamic_threshold = max(min_threshold, min(dynamic_threshold, max_threshold)) + + logger.debug(f"{symbol} ATR: {atr_percent:.2f}%, 动态阈值: {dynamic_threshold*100:.2f}%") + + return dynamic_threshold + + except Exception as e: + logger.warning(f"{symbol} 计算动态阈值失败: {e},使用默认值 1%") + return 0.01 + def close_order_manual(self, order_id: str, exit_price: float) -> Optional[Dict[str, Any]]: """手动平仓或取消挂单""" if order_id not in self.active_orders: