This commit is contained in:
aaron 2026-03-03 16:27:06 +08:00
parent 41923fb693
commit 6428a401d0
3 changed files with 201 additions and 32 deletions

View File

@ -816,20 +816,29 @@ class CryptoAgent:
reason = result.get('message', '订单创建失败') if result else '订单创建失败' reason = result.get('message', '订单创建失败') if result else '订单创建失败'
logger.warning(f" ⚠️ 交易未执行: {reason}") logger.warning(f" ⚠️ 交易未执行: {reason}")
elif decision_type == 'CLOSE': elif decision_type == 'CLOSE':
await self._execute_close(paper_decision, paper_trading=True) close_success = await self._execute_close(paper_decision, current_price, paper_trading=True)
# CLOSE 操作也发送执行通知 # CLOSE 操作也发送执行通知
await self._send_signal_notification(market_signal, paper_decision, current_price, is_paper=True) if close_success:
paper_executed = True await self._send_signal_notification(market_signal, paper_decision, current_price, is_paper=True)
paper_executed = True
else:
logger.warning(f" ⚠️ 平仓未成功执行,跳过通知")
elif decision_type == 'CANCEL_PENDING': elif decision_type == 'CANCEL_PENDING':
await self._execute_cancel_pending(paper_decision, paper_trading=True) cancel_success = await self._execute_cancel_pending(paper_decision, paper_trading=True)
# CANCEL_PENDING 操作也发送执行通知 # CANCEL_PENDING 操作也发送执行通知
await self._send_signal_notification(market_signal, paper_decision, current_price, is_paper=True) if cancel_success:
paper_executed = True 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': elif decision_type == 'REDUCE':
await self._execute_reduce(paper_decision, paper_trading=True) reduce_success = await self._execute_reduce(paper_decision, paper_trading=True)
# REDUCE 操作也发送执行通知 # REDUCE 操作也发送执行通知
await self._send_signal_notification(market_signal, paper_decision, current_price, is_paper=True) if reduce_success:
paper_executed = True await self._send_signal_notification(market_signal, paper_decision, current_price, is_paper=True)
paper_executed = True
else:
logger.warning(f" ⚠️ 减仓未成功执行,跳过通知")
# ============================================================ # ============================================================
# 执行实盘交易决策 # 执行实盘交易决策
@ -859,20 +868,29 @@ class CryptoAgent:
reason = result.get('message', '订单创建失败') if result else '订单创建失败' reason = result.get('message', '订单创建失败') if result else '订单创建失败'
logger.warning(f" ⚠️ 实盘交易未执行: {reason}") logger.warning(f" ⚠️ 实盘交易未执行: {reason}")
elif decision_type == 'CLOSE': elif decision_type == 'CLOSE':
await self._execute_close(real_decision, paper_trading=False) close_success = await self._execute_close(real_decision, current_price, paper_trading=False)
# CLOSE 操作也发送执行通知 # CLOSE 操作也发送执行通知
await self._send_signal_notification(market_signal, real_decision, current_price, is_paper=False) if close_success:
real_executed = True 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': elif decision_type == 'CANCEL_PENDING':
await self._execute_cancel_pending(real_decision, paper_trading=False) cancel_success = await self._execute_cancel_pending(real_decision, paper_trading=False)
# CANCEL_PENDING 操作也发送执行通知 # CANCEL_PENDING 操作也发送执行通知
await self._send_signal_notification(market_signal, real_decision, current_price, is_paper=False) if cancel_success:
real_executed = True 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': elif decision_type == 'REDUCE':
await self._execute_reduce(real_decision, paper_trading=False) reduce_success = await self._execute_reduce(real_decision, paper_trading=False)
# REDUCE 操作也发送执行通知 # REDUCE 操作也发送执行通知
await self._send_signal_notification(market_signal, real_decision, current_price, is_paper=False) if reduce_success:
real_executed = True 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: if not paper_executed and not real_executed:
@ -1176,9 +1194,9 @@ class CryptoAgent:
content = "\n".join(content_parts) content = "\n".join(content_parts)
# 发送通知 - [决策] 发送到 crypto webhook # 发送通知 - [决策] 发送到 paper_trading webhooktrading
if self.settings.feishu_enabled: if self.settings.feishu_enabled:
await self.feishu.send_card(title, content, color) await self.feishu_paper.send_card(title, content, color)
if self.settings.telegram_enabled: if self.settings.telegram_enabled:
# Telegram 使用文本格式 # Telegram 使用文本格式
message = f"{title}\n\n{content}" message = f"{title}\n\n{content}"
@ -1483,42 +1501,102 @@ class CryptoAgent:
except Exception as e: except Exception as e:
logger.error(f"执行实盘交易失败: {e}") logger.error(f"执行实盘交易失败: {e}")
async def _execute_close(self, decision: Dict[str, Any], paper_trading: bool = True): async def _execute_close(self, decision: Dict[str, Any], current_price: float, paper_trading: bool = True) -> bool:
"""执行平仓 """执行平仓
Args: Args:
decision: 交易决策 decision: 交易决策应包含 orders_to_close 字段
current_price: 当前价格
paper_trading: True=模拟交易, False=实盘交易 paper_trading: True=模拟交易, False=实盘交易
Returns:
是否成功执行平仓
""" """
try: try:
symbol = decision.get('symbol') symbol = decision.get('symbol')
orders_to_close = decision.get('orders_to_close', [])
if paper_trading: if paper_trading:
# 平仓 # 模拟交易平仓
if self.paper_trading: if self.paper_trading:
# TODO: 实现平仓逻辑
logger.info(f" 🔒 平仓: {symbol}") logger.info(f" 🔒 平仓: {symbol}")
logger.info(f" 理由: {decision.get('reasoning', '')}") 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')]
if not orders_to_close:
logger.warning(f" 没有找到需要平仓的订单")
return False
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
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}")
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: else:
logger.warning(f" 交易服务未初始化") logger.warning(f" 交易服务未初始化")
return False
else: else:
# 实盘平仓 # 实盘平仓
if self.real_trading and self.real_trading.get_auto_trading_status(): if self.real_trading and self.real_trading.get_auto_trading_status():
# TODO: 实现实盘平仓逻辑
logger.info(f" 🔒 实盘平仓: {symbol}") logger.info(f" 🔒 实盘平仓: {symbol}")
logger.info(f" 理由: {decision.get('reasoning', '')}") logger.info(f" 理由: {decision.get('reasoning', '')}")
# TODO: 实现实盘平仓逻辑
return True
else: else:
logger.warning(f" 实盘交易服务未启用或自动交易未开启") logger.warning(f" 实盘交易服务未启用或自动交易未开启")
return False
except Exception as e: except Exception as e:
logger.error(f"执行平仓失败: {e}") logger.error(f"执行平仓失败: {e}")
import traceback
logger.error(traceback.format_exc())
return False
async def _execute_cancel_pending(self, decision: Dict[str, Any], paper_trading: bool = True): async def _execute_cancel_pending(self, decision: Dict[str, Any], paper_trading: bool = True) -> bool:
"""执行取消挂单 """执行取消挂单
Args: Args:
decision: 交易决策 decision: 交易决策
paper_trading: True=模拟交易, False=实盘交易 paper_trading: True=模拟交易, False=实盘交易
Returns:
是否成功取消订单
""" """
try: try:
symbol = decision.get('symbol') symbol = decision.get('symbol')
@ -1585,7 +1663,7 @@ class CryptoAgent:
if not valid_orders: if not valid_orders:
logger.warning(f" ⚠️ 没有有效的订单可以取消") logger.warning(f" ⚠️ 没有有效的订单可以取消")
return return False
logger.info(f" 🚫 {trading_type}取消挂单: {symbol}") logger.info(f" 🚫 {trading_type}取消挂单: {symbol}")
logger.info(f" 取消订单数量: {len(valid_orders)}") logger.info(f" 取消订单数量: {len(valid_orders)}")
@ -1604,16 +1682,21 @@ class CryptoAgent:
logger.error(f" ❌ 取消订单异常: {order_id} | {e}") logger.error(f" ❌ 取消订单异常: {order_id} | {e}")
logger.info(f" 📊 成功取消 {cancelled_count}/{len(valid_orders)} 个订单") logger.info(f" 📊 成功取消 {cancelled_count}/{len(valid_orders)} 个订单")
return cancelled_count > 0
except Exception as e: except Exception as e:
logger.error(f"执行取消挂单失败: {e}") logger.error(f"执行取消挂单失败: {e}")
return False
async def _execute_reduce(self, decision: Dict[str, Any], paper_trading: bool = True): async def _execute_reduce(self, decision: Dict[str, Any], paper_trading: bool = True) -> bool:
"""执行减仓 """执行减仓
Args: Args:
decision: 交易决策 decision: 交易决策
paper_trading: True=模拟交易, False=实盘交易 paper_trading: True=模拟交易, False=实盘交易
Returns:
是否成功执行减仓
""" """
try: try:
symbol = decision.get('symbol') symbol = decision.get('symbol')
@ -1626,14 +1709,16 @@ class CryptoAgent:
if not trading_service: if not trading_service:
logger.warning(f" {trading_type}交易服务未初始化") logger.warning(f" {trading_type}交易服务未初始化")
return return False
# TODO: 实现减仓逻辑 # TODO: 实现减仓逻辑
# 减仓可以是部分平仓,需要根据决策中的参数执行 # 减仓可以是部分平仓,需要根据决策中的参数执行
logger.info(f" ⚠️ 减仓功能待实现") logger.info(f" ⚠️ 减仓功能待实现")
return False
except Exception as e: except Exception as e:
logger.error(f"执行减仓失败: {e}") logger.error(f"执行减仓失败: {e}")
return False
def _convert_to_paper_signal(self, symbol: str, signal: Dict[str, Any], def _convert_to_paper_signal(self, symbol: str, signal: Dict[str, Any],
current_price: float) -> Dict[str, Any]: current_price: float) -> Dict[str, Any]:

View File

@ -231,12 +231,18 @@ class TradingDecisionMaker:
"entry_price": 入场价格, "entry_price": 入场价格,
"stop_loss": 止损价格, "stop_loss": 止损价格,
"take_profit": 止盈价格, "take_profit": 止盈价格,
"orders_to_cancel": ["order_id_1"], "orders_to_close": ["order_id_1"], // CLOSE/REDUCE 时指定要平仓的订单ID
"orders_to_cancel": ["order_id_1"], // CANCEL_PENDING 时指定要取消的订单ID
"reasoning": "决策理由(必须说明当前持仓/挂单状态以及为什么选择这个操作)", "reasoning": "决策理由(必须说明当前持仓/挂单状态以及为什么选择这个操作)",
"risk_analysis": "风险分析" "risk_analysis": "风险分析"
} }
``` ```
**重要提示**
- `decision` `CLOSE` **必须** `orders_to_close` 中指定要平仓的订单ID列表
- `decision` `CANCEL_PENDING` **必须** `orders_to_cancel` 中指定要取消的订单ID列表
- 如果需要平仓所有持仓`orders_to_close` 应包含所有持仓订单的ID
## 决策示例 ## 决策示例
### 示例1有持仓 + 同向信号 - 普通情况HOLD ### 示例1有持仓 + 同向信号 - 普通情况HOLD
@ -279,12 +285,13 @@ class TradingDecisionMaker:
### 示例4有持仓 + 反向信号 ### 示例4有持仓 + 反向信号
``` ```
当前状态BTC 做多持仓 @ $95,000亏损-1% 当前状态BTC 做多持仓 @ $95,000亏损-1%订单ID: ord_123
新信号BTC 做空 @ $94,500confidence 85%趋势反转 新信号BTC 做空 @ $94,500confidence 85%趋势反转
分析 分析
- 趋势已明确反转 - 趋势已明确反转
- 决策CLOSE平仓止损 - 决策CLOSE平仓止损
- orders_to_close: ["ord_123"]
- 理由趋势反转及时止损 - 理由趋势反转及时止损
``` ```
@ -339,12 +346,17 @@ class TradingDecisionMaker:
"entry_price": 入场价格, "entry_price": 入场价格,
"stop_loss": 止损价格, "stop_loss": 止损价格,
"take_profit": 止盈价格, "take_profit": 止盈价格,
"orders_to_cancel": ["order_id_1"], "orders_to_close": ["order_id_1"], // CLOSE/REDUCE 时指定要平仓的订单ID
"orders_to_cancel": ["order_id_1"], // CANCEL_PENDING 时指定要取消的订单ID
"reasoning": "决策理由(必须说明当前持仓/挂单状态以及为什么选择这个操作)", "reasoning": "决策理由(必须说明当前持仓/挂单状态以及为什么选择这个操作)",
"risk_analysis": "风险分析" "risk_analysis": "风险分析"
} }
``` ```
**重要提示**
- `decision` `CLOSE` **必须** `orders_to_close` 中指定要平仓的订单ID列表
- `decision` `CANCEL_PENDING` **必须** `orders_to_cancel` 中指定要取消的订单ID列表
## 入场价格选择策略 ## 入场价格选择策略
- 使用信号中的 `entry_price` 作为入场价格 - 使用信号中的 `entry_price` 作为入场价格

View File

@ -647,6 +647,20 @@ class PaperTradingService:
# 计算持仓时间 # 计算持仓时间
hold_duration = get_beijing_time() - db_order.opened_at if db_order.opened_at else timedelta(0) hold_duration = get_beijing_time() - db_order.opened_at if db_order.opened_at else timedelta(0)
# === 重新判断平仓类型(基于数据库中的实际值)===
# 如果传入的 status 是普通止损CLOSED_SL但实际盈利说明是保本止损
if status == OrderStatus.CLOSED_SL and pnl_percent > 0:
# 检查是否是保本止损
if db_order.stop_loss == db_order.filled_price:
status = OrderStatus.CLOSED_BE # 保本止损
# 检查是否是移动止盈(止损价高于入场价,且有盈利)
elif db_order.side == OrderSide.LONG and db_order.stop_loss > db_order.filled_price:
if getattr(db_order, 'trailing_stop_triggered', 0) == 1:
status = OrderStatus.CLOSED_TS # 移动止盈
elif db_order.side == OrderSide.SHORT and db_order.stop_loss < db_order.filled_price:
if getattr(db_order, 'trailing_stop_triggered', 0) == 1:
status = OrderStatus.CLOSED_TS # 移动止盈
# 更新订单 # 更新订单
db_order.status = status db_order.status = status
db_order.exit_price = exit_price db_order.exit_price = exit_price
@ -810,6 +824,9 @@ class PaperTradingService:
new_stop_loss = order.filled_price * (1 - locked_profit_percent / 100) new_stop_loss = order.filled_price * (1 - locked_profit_percent / 100)
order.stop_loss = new_stop_loss order.stop_loss = new_stop_loss
# === 新增:同时调整止盈价(追踪止盈)===
self._adjust_take_profit_for_trailing_stop(order, current_price)
logger.info(f"移动止损首次触发: {order.order_id} | {order.symbol} | 盈利 {current_pnl_percent:.2f}% >= {trailing_threshold:.2f}% | " logger.info(f"移动止损首次触发: {order.order_id} | {order.symbol} | 盈利 {current_pnl_percent:.2f}% >= {trailing_threshold:.2f}% | "
f"锁定利润 {locked_profit_percent:.2f}% | 止损移至 ${order.stop_loss:,.2f}") f"锁定利润 {locked_profit_percent:.2f}% | 止损移至 ${order.stop_loss:,.2f}")
@ -840,6 +857,10 @@ class PaperTradingService:
needs_update = True needs_update = True
stop_moved = True stop_moved = True
stop_move_type = "trailing_update" stop_move_type = "trailing_update"
# === 新增:同时调整止盈价(追踪止盈)===
self._adjust_take_profit_for_trailing_stop(order, current_price)
logger.info(f"移动止损更新: {order.order_id} | {order.symbol} | " logger.info(f"移动止损更新: {order.order_id} | {order.symbol} | "
f"最高盈利 {base_profit:.2f}% | 锁定 {new_stop_profit:.2f}% | 止损 ${old_stop:,.2f} -> ${new_stop_loss:,.2f}") f"最高盈利 {base_profit:.2f}% | 锁定 {new_stop_profit:.2f}% | 止损 ${old_stop:,.2f} -> ${new_stop_loss:,.2f}")
else: else:
@ -897,6 +918,7 @@ class PaperTradingService:
db_order.max_profit = order.max_profit db_order.max_profit = order.max_profit
db_order.max_drawdown = order.max_drawdown db_order.max_drawdown = order.max_drawdown
db_order.stop_loss = order.stop_loss db_order.stop_loss = order.stop_loss
db_order.take_profit = order.take_profit # 同时更新止盈价
db_order.breakeven_triggered = getattr(order, 'breakeven_triggered', 0) db_order.breakeven_triggered = getattr(order, 'breakeven_triggered', 0)
# 更新移动止损相关字段 # 更新移动止损相关字段
if hasattr(order, 'trailing_stop_triggered'): if hasattr(order, 'trailing_stop_triggered'):
@ -925,6 +947,56 @@ class PaperTradingService:
return None return None
def _adjust_take_profit_for_trailing_stop(self, order: PaperOrder, current_price: float):
"""
追踪止盈当止损移动时相应调整止盈价格让利润继续奔跑
策略
1. 保持固定的盈亏比距离risk:reward ratio
2. 或者止盈随价格动态移动保持一定距离
Args:
order: 订单对象
current_price: 当前价格
"""
if not order.take_profit or order.take_profit <= 0:
return
# 计算当前的止损盈利距离
if order.side == OrderSide.LONG:
stop_distance = current_price - order.stop_loss
original_tp_distance = order.take_profit - order.filled_price
else:
stop_distance = order.stop_loss - current_price
original_tp_distance = order.filled_price - order.take_profit
if stop_distance <= 0 or original_tp_distance <= 0:
return
# 计算盈亏比
risk_reward_ratio = original_tp_distance / (order.take_profit - order.stop_loss)
# 追踪止盈策略:
# 当止损移动后,止盈也相应移动,保持相同的盈亏比距离
if order.side == OrderSide.LONG:
new_take_profit = order.stop_loss + (original_tp_distance * 0.8) # 保持80%的原始距离
# 确保止盈价不会向下移动
if new_take_profit > order.take_profit:
old_tp = order.take_profit
order.take_profit = new_take_profit
logger.info(f"追踪止盈: {order.order_id} | {order.symbol} | "
f"止盈 ${old_tp:,.4f} -> ${new_take_profit:,.4f} "
f"(止损: ${order.stop_loss:,.4f}, 距离: ${stop_distance:,.4f})")
else:
new_take_profit = order.stop_loss - (original_tp_distance * 0.8)
# 确保止盈价不会向上移动
if new_take_profit < order.take_profit:
old_tp = order.take_profit
order.take_profit = new_take_profit
logger.info(f"追踪止盈: {order.order_id} | {order.symbol} | "
f"止盈 ${old_tp:,.4f} -> ${new_take_profit:,.4f} "
f"(止损: ${order.stop_loss:,.4f}, 距离: ${stop_distance:,.4f})")
def _calculate_dynamic_price_threshold(self, symbol: str, current_price: float) -> float: def _calculate_dynamic_price_threshold(self, symbol: str, current_price: float) -> float:
""" """
根据市场波动率动态计算价格距离阈值 根据市场波动率动态计算价格距离阈值