This commit is contained in:
aaron 2026-03-04 18:24:46 +08:00
parent c080962373
commit 5f11ec2308
3 changed files with 279 additions and 32 deletions

View File

@ -831,6 +831,14 @@ class CryptoAgent:
paper_executed = True
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 操作也发送执行通知
@ -883,6 +891,14 @@ class CryptoAgent:
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 操作也发送执行通知
@ -1688,6 +1704,124 @@ class CryptoAgent:
logger.error(f"执行取消挂单失败: {e}")
return False
async def _execute_update_pending(self, decision: Dict[str, Any], paper_trading: bool = True) -> bool:
"""执行更新挂单参数
Args:
decision: 交易决策
paper_trading: True=模拟交易, False=实盘交易
Returns:
是否成功更新订单
"""
try:
symbol = decision.get('symbol')
decision_action = decision.get('action', '') # buy/sell
orders_to_update = decision.get('orders_to_update', [])
# 获取新参数
new_entry_price = decision.get('entry_price')
new_stop_loss = decision.get('stop_loss')
new_take_profit = decision.get('take_profit')
if not orders_to_update:
logger.info(f" ⚠️ 没有需要更新的订单")
return False
if not all([new_entry_price, new_stop_loss, new_take_profit]):
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 "实盘"
if not trading_service:
logger.warning(f" {trading_type}交易服务未启用")
return False
# 安全检查验证要更新的订单是否属于当前symbol且方向相同
active_orders = trading_service.get_active_orders()
valid_orders = []
invalid_orders = []
wrong_direction_orders = []
for order_id in orders_to_update:
# 查找订单
order = next((o for o in active_orders if o.order_id == order_id), None)
if not order:
logger.warning(f" ⚠️ 订单不存在: {order_id}")
invalid_orders.append(order_id)
continue
# 检查订单是否已成交(只能更新未成交的挂单)
if order.filled_price and order.filled_price > 0:
logger.warning(f" ⚠️ 订单已成交,无法更新: {order_id}")
invalid_orders.append(order_id)
continue
# 检查订单是否属于当前symbol
if order.symbol != symbol:
logger.error(f" ❌ 安全拦截:订单 {order_id} 属于 {order.symbol},不是当前分析标的 {symbol}")
invalid_orders.append(order_id)
continue
# 检查订单方向是否与决策相同
order_side = order.side.value # LONG/SHORT
# 决策是buy时应该更新LONG做多决策是sell时应该更新SHORT做空
should_update = False
if decision_action == 'buy' and order_side == 'LONG':
should_update = True
elif decision_action == 'sell' and order_side == 'SHORT':
should_update = True
if not should_update:
logger.error(f" ❌ 安全拦截:订单 {order_id} 方向为 {order_side},与决策 {decision_action} 方向不一致")
wrong_direction_orders.append(order_id)
invalid_orders.append(order_id)
continue
valid_orders.append(order)
if invalid_orders:
logger.error(f" 🚫 拒绝更新 {len(invalid_orders)} 个不符合条件的订单")
if wrong_direction_orders:
logger.error(f" ⚠️ {len(wrong_direction_orders)} 个方向不一致的订单被拦截")
if not valid_orders:
logger.warning(f" ⚠️ 没有有效的订单可以更新")
return False
logger.info(f" 🔄 {trading_type}更新挂单: {symbol}")
logger.info(f" 新参数: 入场=${new_entry_price:,.2f}, 止损=${new_stop_loss:,.2f}, 止盈=${new_take_profit:,.2f}")
updated_count = 0
for order in valid_orders:
try:
# 更新订单参数
result = trading_service.update_order(
order.order_id,
entry_price=new_entry_price,
stop_loss=new_stop_loss,
take_profit=new_take_profit
)
if result and result.get('success'):
updated_count += 1
logger.info(f" ✅ 订单更新成功: {order.order_id}")
logger.info(f" 旧参数: 入场=${order.entry_price:,.2f}, 止损=${order.stop_loss:,.2f}, 止盈=${order.take_profit:,.2f}")
else:
logger.warning(f" ⚠️ 更新订单失败: {order.order_id} | {result.get('message', '')}")
except Exception as e:
logger.error(f" ❌ 更新订单异常: {order.order_id} | {e}")
logger.info(f" 📊 成功更新 {updated_count}/{len(valid_orders)} 个订单")
return updated_count > 0
except Exception as e:
logger.error(f"执行更新挂单失败: {e}")
return False
async def _execute_reduce(self, decision: Dict[str, Any], paper_trading: bool = True) -> bool:
"""执行减仓

View File

@ -129,34 +129,45 @@ class TradingDecisionMaker:
3. **HOLD观望** - 如果反转信号不强
#### 情况C无持仓 + 有同向挂单
**优先选择HOLD等待挂单成交**
**优先选择UPDATE_PENDING更新挂单参数**
** 严禁取消挂单追涨**
- 即使有新信号也不要取消挂单去市价追涨
- 挂单价格通常更优耐心等待
- 价格快速移动时更应该观望而不是追涨
**核心原则适应新信号不要等老信号**
- 新信号代表最新的市场分析
- 应该用新信号的参数更新挂单
- 不要死守过时的挂单价格
**只有以下极端情况才考虑调整**
- 趋势明确反转如从涨转跌
- 新价格更优且距离 >= 3%仍用 limit不用 market
- 挂单已无意义如突破后回踩确认
**更新挂单的条件**
- 新信号与挂单**方向相同**都是 buy 或都是 sell
- 新信号提供了**更新的入场价止损止盈**
- 新信号置信度 >= 60C级以上
**更新内容**根据新信号调整
- 入场价格使用新信号的 entry_price
- 止损价格使用新信号的 stop_loss
- 止盈价格使用新信号的 take_profit
- 入场方式保持 limit不要改成 market
**示例**
```
当前BTC 做多挂单 @ $94,000未成交
新信号BTC 做多 @ $96,500B级75%置信度
当前BTC 做多挂单 @ $94,000止损 $93,500止盈 $95,500
新信号BTC 做多 @ $93,800B级75%置信度止损 $93,200止盈 $95,200
分析
- 价格更高但挂单价格更优
- 价格正在快速移动$94,000 $96,500
- 决策HOLD继续等待挂单成交
- 理由挂单价格更优不要追涨耐心等待
- 信号更保守入场价更低
- 应该更新挂单参数以适应新的市场分析
- 决策UPDATE_PENDING更新挂单
- 理由根据新信号更新入场价 $93,800止损 $93,200止盈 $95,200
```
** 例外情况保持 HOLD**
- 价格正在快速加速移动5m 连续大阳/阴线
- 新信号置信度 < 60D级信号质量太低
- 新信号入场价距离当前价格 >= 2%追涨杀跌风险
- 新信号是 market 入场不要改成 market 去追
** 严禁**
- 取消挂单后市价追涨/杀跌
- 价格距离 < 3% 时调整挂单
- 价格快速移动时任何操作
- 价格快速移动时的任何更新操作
#### 情况D无持仓 + 有反向挂单
**优先选择**
@ -237,7 +248,7 @@ class TradingDecisionMaker:
## 输出格式
```json
{
"decision": "OPEN/CLOSE/ADD/REDUCE/CANCEL_PENDING/HOLD",
"decision": "OPEN/CLOSE/ADD/REDUCE/CANCEL_PENDING/UPDATE_PENDING/HOLD",
"action": "buy/sell",
"quantity": 保证金金额USDT,
"entry_price": 入场价格,
@ -245,6 +256,7 @@ 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": "风险分析"
}
@ -253,6 +265,7 @@ class TradingDecisionMaker:
**重要提示**
- `decision` `CLOSE` **必须** `orders_to_close` 中指定要平仓的订单ID列表
- `decision` `CANCEL_PENDING` **必须** `orders_to_cancel` 中指定要取消的订单ID列表
- `decision` `UPDATE_PENDING` **必须** `orders_to_update` 中指定要更新的订单ID列表并提供新的 entry_pricestop_losstake_profit
- 如果需要平仓所有持仓`orders_to_close` 应包含所有持仓订单的ID
## 决策示例
@ -307,29 +320,31 @@ class TradingDecisionMaker:
- 理由趋势反转及时止损
```
### 示例5有挂单 + 同向信号 - 普通情况HOLD
### 示例5有挂单 + 同向信号 - 更新挂单参数UPDATE_PENDING
```
当前状态BTC 做多挂单 @ $94,500未成交
新信号BTC 做多 @ $96,000confidence 70%B级
当前状态BTC 做多挂单 @ $94,500未成交订单ID: ord_456
新信号BTC 做多 @ $93,800confidence 75%B级回调至支撑位
分析
- 挂单价格更优$94,500 < $96,000
- 信号不是A级
- 价格正在快速移动
- 决策HOLD等待挂单成交
- 理由挂单价格更优不要追涨
- 新信号提供了更优的入场价格$93,800 < $94,500
- 市场出现回调机会应该调整挂单价格
- 决策UPDATE_PENDING更新挂单参数
- orders_to_update: ["ord_456"]
- 新参数entry_price=$93,800, stop_loss=$93,200, take_profit=$95,200
- 理由根据新信号更新挂单参数适应新的市场分析
```
### 示例6有挂单 + 同向信号 - 不要追涨
### 示例6有挂单 + 同向信号 - 价格加速时保持HOLD
```
当前状态BTC 做多挂单 @ $94,000未成交
新信号BTC 做多 @ $97,000confidence 85%B级突破
分析
- 新价格更高但挂单价格更优
- 价格快速移动$94,000 $97,000
- 决策HOLD等待挂单成交或等待回调
- 理由不要追涨挂单价格更优
- 价格快速移动$94,000 $97,000涨幅3.2%
- 5m 连续2根大阳线正在加速
- 新信号入场价距离当前价格 >= 2%
- 决策HOLD不要追涨
- 理由价格正在加速禁止追涨杀跌等待回调
```
### 示例7完全无持仓无挂单
@ -965,7 +980,7 @@ class TradingDecisionMaker:
market_signal: Dict[str, Any] = None) -> Dict[str, Any]:
"""验证决策安全性"""
# 检查杠杆限制
if decision.get('decision') in ['OPEN', 'ADD']:
if decision.get('decision') in ['OPEN', 'ADD', 'UPDATE_PENDING']:
balance = float(account.get('current_balance', 0))
total_position_value = float(account.get('total_position_value', 0))
max_leverage = 20
@ -984,6 +999,7 @@ class TradingDecisionMaker:
)
# 盈亏比检查:所有交易必须满足盈亏比 >= 1:1.5
# UPDATE_PENDING 也需要验证盈亏比
action = decision.get('action', '')
entry_price = decision.get('entry_price')
stop_loss = decision.get('stop_loss')

View File

@ -1264,6 +1264,103 @@ class PaperTradingService:
'message': '取消挂单失败'
}
def update_order(self, order_id: str, entry_price: float = None,
stop_loss: float = None, take_profit: float = None) -> Dict[str, Any]:
"""
更新挂单参数
Args:
order_id: 订单ID
entry_price: 新的入场价格
stop_loss: 新的止损价格
take_profit: 新的止盈价格
Returns:
更新结果字典
"""
if order_id not in self.active_orders:
return {
'success': False,
'message': f'订单不存在: {order_id}'
}
order = self.active_orders[order_id]
# 只能更新挂单状态的订单
if order.status != OrderStatus.PENDING:
return {
'success': False,
'message': f'只能更新挂单状态的订单,当前状态: {order.status.value}'
}
# 至少需要一个参数
if entry_price is None and stop_loss is None and take_profit is None:
return {
'success': False,
'message': '至少需要提供一个更新参数entry_price, stop_loss, 或 take_profit'
}
result = self._update_pending_order(order, entry_price, stop_loss, take_profit)
if result:
return {
'success': True,
'order_id': order_id,
'message': '挂单已更新'
}
else:
return {
'success': False,
'message': '更新挂单失败'
}
def _update_pending_order(self, order: PaperOrder, entry_price: float = None,
stop_loss: float = None, take_profit: float = None) -> Dict[str, Any]:
"""更新挂单参数"""
db = db_service.get_session()
try:
# 记录旧值用于日志
old_entry = order.entry_price
old_sl = order.stop_loss
old_tp = order.take_profit
# 更新参数
if entry_price is not None:
order.entry_price = entry_price
if stop_loss is not None:
order.stop_loss = stop_loss
if take_profit is not None:
order.take_profit = take_profit
db.merge(order)
db.commit()
# 更新内存缓存
self.active_orders[order.order_id] = order
logger.info(f"挂单已更新: {order.order_id} | {order.symbol}")
logger.info(f" 入场: ${old_entry:,.2f} → ${order.entry_price:,.2f}")
if stop_loss is not None:
logger.info(f" 止损: ${old_sl:,.2f} → ${order.stop_loss:,.2f}")
if take_profit is not None:
logger.info(f" 止盈: ${old_tp:,.2f} → ${order.take_profit:,.2f}")
return {
'order_id': order.order_id,
'symbol': order.symbol,
'side': order.side.value,
'status': 'updated',
'entry_price': order.entry_price,
'stop_loss': order.stop_loss,
'take_profit': order.take_profit,
'message': '挂单已更新'
}
except Exception as e:
logger.error(f"更新挂单失败: {e}")
db.rollback()
return None
finally:
db.close()
def _cancel_pending_order(self, order: PaperOrder) -> Dict[str, Any]:
"""取消挂单"""
db = db_service.get_session()