update
This commit is contained in:
parent
41923fb693
commit
6428a401d0
@ -816,20 +816,29 @@ class CryptoAgent:
|
||||
reason = result.get('message', '订单创建失败') if result else '订单创建失败'
|
||||
logger.warning(f" ⚠️ 交易未执行: {reason}")
|
||||
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 操作也发送执行通知
|
||||
if close_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 == '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 操作也发送执行通知
|
||||
if cancel_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':
|
||||
await self._execute_reduce(paper_decision, paper_trading=True)
|
||||
reduce_success = await self._execute_reduce(paper_decision, paper_trading=True)
|
||||
# REDUCE 操作也发送执行通知
|
||||
if reduce_success:
|
||||
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 '订单创建失败'
|
||||
logger.warning(f" ⚠️ 实盘交易未执行: {reason}")
|
||||
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 操作也发送执行通知
|
||||
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':
|
||||
await self._execute_cancel_pending(real_decision, paper_trading=False)
|
||||
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 == 'REDUCE':
|
||||
await self._execute_reduce(real_decision, paper_trading=False)
|
||||
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:
|
||||
@ -1176,9 +1194,9 @@ class CryptoAgent:
|
||||
|
||||
content = "\n".join(content_parts)
|
||||
|
||||
# 发送通知 - [决策] 发送到 crypto webhook
|
||||
# 发送通知 - [决策] 发送到 paper_trading webhook(trading)
|
||||
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:
|
||||
# Telegram 使用文本格式
|
||||
message = f"{title}\n\n{content}"
|
||||
@ -1483,42 +1501,102 @@ class CryptoAgent:
|
||||
except Exception as 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:
|
||||
decision: 交易决策
|
||||
decision: 交易决策(应包含 orders_to_close 字段)
|
||||
current_price: 当前价格
|
||||
paper_trading: True=模拟交易, False=实盘交易
|
||||
|
||||
Returns:
|
||||
是否成功执行平仓
|
||||
"""
|
||||
try:
|
||||
symbol = decision.get('symbol')
|
||||
orders_to_close = decision.get('orders_to_close', [])
|
||||
|
||||
if paper_trading:
|
||||
# 平仓
|
||||
# 模拟交易平仓
|
||||
if self.paper_trading:
|
||||
# TODO: 实现平仓逻辑
|
||||
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')]
|
||||
|
||||
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:
|
||||
logger.warning(f" 交易服务未初始化")
|
||||
return False
|
||||
else:
|
||||
# 实盘平仓
|
||||
if self.real_trading and self.real_trading.get_auto_trading_status():
|
||||
# TODO: 实现实盘平仓逻辑
|
||||
logger.info(f" 🔒 实盘平仓: {symbol}")
|
||||
logger.info(f" 理由: {decision.get('reasoning', '')}")
|
||||
# TODO: 实现实盘平仓逻辑
|
||||
return True
|
||||
else:
|
||||
logger.warning(f" 实盘交易服务未启用或自动交易未开启")
|
||||
return False
|
||||
|
||||
except Exception as 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:
|
||||
decision: 交易决策
|
||||
paper_trading: True=模拟交易, False=实盘交易
|
||||
|
||||
Returns:
|
||||
是否成功取消订单
|
||||
"""
|
||||
try:
|
||||
symbol = decision.get('symbol')
|
||||
@ -1585,7 +1663,7 @@ class CryptoAgent:
|
||||
|
||||
if not valid_orders:
|
||||
logger.warning(f" ⚠️ 没有有效的订单可以取消")
|
||||
return
|
||||
return False
|
||||
|
||||
logger.info(f" 🚫 {trading_type}取消挂单: {symbol}")
|
||||
logger.info(f" 取消订单数量: {len(valid_orders)}")
|
||||
@ -1604,16 +1682,21 @@ class CryptoAgent:
|
||||
logger.error(f" ❌ 取消订单异常: {order_id} | {e}")
|
||||
|
||||
logger.info(f" 📊 成功取消 {cancelled_count}/{len(valid_orders)} 个订单")
|
||||
return cancelled_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):
|
||||
async def _execute_reduce(self, decision: Dict[str, Any], paper_trading: bool = True) -> bool:
|
||||
"""执行减仓
|
||||
|
||||
Args:
|
||||
decision: 交易决策
|
||||
paper_trading: True=模拟交易, False=实盘交易
|
||||
|
||||
Returns:
|
||||
是否成功执行减仓
|
||||
"""
|
||||
try:
|
||||
symbol = decision.get('symbol')
|
||||
@ -1626,14 +1709,16 @@ class CryptoAgent:
|
||||
|
||||
if not trading_service:
|
||||
logger.warning(f" {trading_type}交易服务未初始化")
|
||||
return
|
||||
return False
|
||||
|
||||
# TODO: 实现减仓逻辑
|
||||
# 减仓可以是部分平仓,需要根据决策中的参数执行
|
||||
logger.info(f" ⚠️ 减仓功能待实现")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"执行减仓失败: {e}")
|
||||
return False
|
||||
|
||||
def _convert_to_paper_signal(self, symbol: str, signal: Dict[str, Any],
|
||||
current_price: float) -> Dict[str, Any]:
|
||||
|
||||
@ -231,12 +231,18 @@ class TradingDecisionMaker:
|
||||
"entry_price": 入场价格,
|
||||
"stop_loss": 止损价格,
|
||||
"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": "决策理由(必须说明当前持仓/挂单状态以及为什么选择这个操作)",
|
||||
"risk_analysis": "风险分析"
|
||||
}
|
||||
```
|
||||
|
||||
**重要提示**:
|
||||
- 当 `decision` 为 `CLOSE` 时,**必须**在 `orders_to_close` 中指定要平仓的订单ID列表
|
||||
- 当 `decision` 为 `CANCEL_PENDING` 时,**必须**在 `orders_to_cancel` 中指定要取消的订单ID列表
|
||||
- 如果需要平仓所有持仓,`orders_to_close` 应包含所有持仓订单的ID
|
||||
|
||||
## 决策示例
|
||||
|
||||
### 示例1:有持仓 + 同向信号 - 普通情况(HOLD)
|
||||
@ -279,12 +285,13 @@ class TradingDecisionMaker:
|
||||
|
||||
### 示例4:有持仓 + 反向信号
|
||||
```
|
||||
当前状态:BTC 做多持仓 @ $95,000(亏损-1%)
|
||||
当前状态:BTC 做多持仓 @ $95,000(亏损-1%),订单ID: ord_123
|
||||
新信号:BTC 做空 @ $94,500(confidence 85%,趋势反转)
|
||||
|
||||
分析:
|
||||
- 趋势已明确反转
|
||||
- 决策:CLOSE(平仓止损)
|
||||
- orders_to_close: ["ord_123"]
|
||||
- 理由:趋势反转,及时止损
|
||||
```
|
||||
|
||||
@ -339,12 +346,17 @@ class TradingDecisionMaker:
|
||||
"entry_price": 入场价格,
|
||||
"stop_loss": 止损价格,
|
||||
"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": "决策理由(必须说明当前持仓/挂单状态以及为什么选择这个操作)",
|
||||
"risk_analysis": "风险分析"
|
||||
}
|
||||
```
|
||||
|
||||
**重要提示**:
|
||||
- 当 `decision` 为 `CLOSE` 时,**必须**在 `orders_to_close` 中指定要平仓的订单ID列表
|
||||
- 当 `decision` 为 `CANCEL_PENDING` 时,**必须**在 `orders_to_cancel` 中指定要取消的订单ID列表
|
||||
|
||||
## 入场价格选择策略
|
||||
|
||||
- 使用信号中的 `entry_price` 作为入场价格
|
||||
|
||||
@ -647,6 +647,20 @@ class PaperTradingService:
|
||||
# 计算持仓时间
|
||||
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.exit_price = exit_price
|
||||
@ -810,6 +824,9 @@ class PaperTradingService:
|
||||
new_stop_loss = order.filled_price * (1 - locked_profit_percent / 100)
|
||||
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}% | "
|
||||
f"锁定利润 {locked_profit_percent:.2f}% | 止损移至 ${order.stop_loss:,.2f}")
|
||||
|
||||
@ -840,6 +857,10 @@ class PaperTradingService:
|
||||
needs_update = True
|
||||
stop_moved = True
|
||||
stop_move_type = "trailing_update"
|
||||
|
||||
# === 新增:同时调整止盈价(追踪止盈)===
|
||||
self._adjust_take_profit_for_trailing_stop(order, current_price)
|
||||
|
||||
logger.info(f"移动止损更新: {order.order_id} | {order.symbol} | "
|
||||
f"最高盈利 {base_profit:.2f}% | 锁定 {new_stop_profit:.2f}% | 止损 ${old_stop:,.2f} -> ${new_stop_loss:,.2f}")
|
||||
else:
|
||||
@ -897,6 +918,7 @@ class PaperTradingService:
|
||||
db_order.max_profit = order.max_profit
|
||||
db_order.max_drawdown = order.max_drawdown
|
||||
db_order.stop_loss = order.stop_loss
|
||||
db_order.take_profit = order.take_profit # 同时更新止盈价
|
||||
db_order.breakeven_triggered = getattr(order, 'breakeven_triggered', 0)
|
||||
# 更新移动止损相关字段
|
||||
if hasattr(order, 'trailing_stop_triggered'):
|
||||
@ -925,6 +947,56 @@ class PaperTradingService:
|
||||
|
||||
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:
|
||||
"""
|
||||
根据市场波动率动态计算价格距离阈值
|
||||
|
||||
Loading…
Reference in New Issue
Block a user