处理重复开仓的问题

This commit is contained in:
aaron 2026-02-25 21:50:34 +08:00
parent 7cdb24bf47
commit 91ec4ec091
2 changed files with 95 additions and 3 deletions

View File

@ -55,6 +55,12 @@ class TradingDecisionMaker:
- 同向新信号 - 同向新信号
- 趋势加强 - 趋势加强
**价格距离限制重要**
- 如果已有持仓/挂单的价格与新价格距离 < 1%**不加仓也不开新仓**
- 例如现有 BTC 做多持仓 @ $95,000新信号 @ $95,500差距 0.52% < 1%拒绝开仓
- 这是为了避免在同一价格区域重复建仓导致风险过度集中
- **例外**如果信号是 A confidence >= 90且趋势非常强劲可以考虑放宽到 0.5%
### 4. 减仓REDUCE ### 4. 减仓REDUCE
**时机** **时机**
- 部分止盈 - 部分止盈
@ -258,6 +264,13 @@ class TradingDecisionMaker:
'max_leverage': max_leverage '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 return context
def _build_decision_prompt(self, context: Dict[str, Any]) -> str: 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('available_leverage_percent', 0):.1f}%")
prompt_parts.append(f"最大杠杆限制: {lev_info.get('max_leverage', 20)}x") 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请根据以上信息,做出交易决策。") prompt_parts.append(f"\n请根据以上信息,做出交易决策。")
return "\n".join(prompt_parts) return "\n".join(prompt_parts)

View File

@ -186,19 +186,38 @@ class PaperTradingService:
result['message'] = msg result['message'] = msg
return result return result
# 2. 检查是否有接近的挂单(价格差距 < 1% # 2. 检查是否有接近的订单(包括挂单和持仓)
# 安全网阈值LLM 已在提示词中告知此规则,这是最后的安全检查)
# 设置一个更宽松的阈值,主要判断由 LLM 负责
price_distance_threshold = 0.003 # 0.3% 作为安全网
same_direction_orders = [ same_direction_orders = [
order for order in self.active_orders.values() order for order in self.active_orders.values()
if order.symbol == symbol and order.side == side if order.symbol == symbol and order.side == side
] ]
# 检查挂单距离
pending_orders = [ pending_orders = [
order for order in same_direction_orders order for order in same_direction_orders
if order.status == OrderStatus.PENDING if order.status == OrderStatus.PENDING
] ]
for pending in pending_orders: for pending in pending_orders:
price_diff = abs(pending.entry_price - entry_price) / pending.entry_price price_diff = abs(pending.entry_price - entry_price) / pending.entry_price
if price_diff < 0.01: # 价格差距小于 1% if price_diff < price_distance_threshold:
msg = f"已有接近的挂单 @ ${pending.entry_price:,.2f}(价格差距 {price_diff*100:.2f}%" 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},跳过") logger.info(f"订单限制: {symbol} {msg},新信号 @ ${entry_price:,.2f},跳过")
result['message'] = msg result['message'] = msg
return result return result
@ -866,6 +885,58 @@ class PaperTradingService:
return None 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]]: def close_order_manual(self, order_id: str, exit_price: float) -> Optional[Dict[str, Any]]:
"""手动平仓或取消挂单""" """手动平仓或取消挂单"""
if order_id not in self.active_orders: if order_id not in self.active_orders: