This commit is contained in:
aaron 2026-02-15 14:30:34 +08:00
parent ffca7037ed
commit c464ae6e4d
3 changed files with 65 additions and 5 deletions

View File

@ -117,6 +117,7 @@ class Settings(BaseSettings):
paper_trading_margin_per_order: float = 1000 # 每单保证金 (USDT)
paper_trading_max_orders: int = 10 # 最大持仓+挂单总数
paper_trading_auto_close_opposite: bool = False # 是否自动平掉反向持仓(智能策略)
paper_trading_breakeven_threshold: float = 1 # 保本止损触发阈值盈利百分比0表示禁用
# 废弃的配置(保留兼容性)
paper_trading_position_a: float = 1000 # A级信号仓位 (USDT)
paper_trading_position_b: float = 500 # B级信号仓位 (USDT)

View File

@ -65,6 +65,8 @@ class CryptoAgent:
event_type = result.get('event_type', 'order_closed')
if event_type == 'order_filled':
asyncio.run_coroutine_threadsafe(self._notify_order_filled(result), self._event_loop)
elif event_type == 'breakeven_triggered':
asyncio.run_coroutine_threadsafe(self._notify_breakeven_triggered(result), self._event_loop)
else:
asyncio.run_coroutine_threadsafe(self._notify_order_closed(result), self._event_loop)
else:
@ -106,6 +108,24 @@ class CryptoAgent:
await self.telegram.send_message(message)
logger.info(f"已发送挂单撤销通知: {result.get('order_id')}")
async def _notify_breakeven_triggered(self, result: Dict[str, Any]):
"""发送保本止损触发通知"""
side_text = "做多" if result.get('side') == 'long' else "做空"
message = f"""🛡️ 保本止损已启动
交易对: {result.get('symbol')}
方向: {side_text}
开仓价: ${result.get('filled_price', 0):,.2f}
当前盈利: {result.get('current_pnl_percent', 0):.2f}%
止损已移至: ${result.get('new_stop_loss', 0):,.2f}
本单已锁定保本不会亏损"""
await self.feishu.send_text(message)
await self.telegram.send_message(message)
logger.info(f"已发送保本止损通知: {result.get('order_id')}")
async def _notify_order_closed(self, result: Dict[str, Any]):
"""发送订单平仓通知"""
status = result.get('status', '')

View File

@ -25,6 +25,7 @@ class PaperTradingService:
self.margin_per_order = self.settings.paper_trading_margin_per_order # 每单保证金
self.max_orders = self.settings.paper_trading_max_orders # 最大订单数
self.auto_close_opposite = self.settings.paper_trading_auto_close_opposite # 是否自动平掉反向持仓
self.breakeven_threshold = self.settings.paper_trading_breakeven_threshold # 保本止损触发阈值
# 确保表已创建
self._ensure_table_exists()
@ -32,7 +33,7 @@ class PaperTradingService:
# 加载活跃订单到内存
self._load_active_orders()
logger.info(f"模拟交易服务初始化完成(自动平反向持仓: {'启用' if self.auto_close_opposite else '禁用'}")
logger.info(f"模拟交易服务初始化完成(自动平反向持仓: {'启用' if self.auto_close_opposite else '禁用'},保本止损阈值: {self.breakeven_threshold}%")
def _ensure_table_exists(self):
"""确保数据表已创建"""
@ -231,8 +232,10 @@ class PaperTradingService:
if result:
triggered.append(result)
else:
# 更新最大回撤和最大盈利
self._update_order_extremes(order, current_price)
# 更新最大回撤和最大盈利,并检查保本止损
breakeven_result = self._update_order_extremes(order, current_price)
if breakeven_result:
triggered.append(breakeven_result)
return triggered
@ -413,8 +416,8 @@ class PaperTradingService:
finally:
db.close()
def _update_order_extremes(self, order: PaperOrder, current_price: float):
"""更新订单的最大回撤和最大盈利"""
def _update_order_extremes(self, order: PaperOrder, current_price: float) -> Optional[Dict[str, Any]]:
"""更新订单的最大回撤和最大盈利,并检查是否需要移动止损到保本"""
if order.side == OrderSide.LONG:
current_pnl_percent = ((current_price - order.filled_price) / order.filled_price) * 100
else:
@ -422,13 +425,34 @@ class PaperTradingService:
# 检查是否需要更新极值
needs_update = False
breakeven_triggered = False
if current_pnl_percent > order.max_profit:
order.max_profit = current_pnl_percent
needs_update = True
if current_pnl_percent < order.max_drawdown:
order.max_drawdown = current_pnl_percent
needs_update = True
# 保本止损逻辑:当盈利达到阈值时,将止损移动到开仓价
if self.breakeven_threshold > 0 and current_pnl_percent >= self.breakeven_threshold:
# 检查止损是否还没有移动到保本位
if order.side == OrderSide.LONG:
# 做多:止损应该低于开仓价,如果止损还在开仓价下方,则移动到开仓价
if order.stop_loss < order.filled_price:
order.stop_loss = order.filled_price
needs_update = True
breakeven_triggered = True
logger.info(f"保本止损触发: {order.order_id} | {order.symbol} | 盈利 {current_pnl_percent:.2f}% >= {self.breakeven_threshold}% | 止损移至开仓价 ${order.filled_price:,.2f}")
else:
# 做空:止损应该高于开仓价,如果止损还在开仓价上方,则移动到开仓价
if order.stop_loss > order.filled_price:
order.stop_loss = order.filled_price
needs_update = True
breakeven_triggered = True
logger.info(f"保本止损触发: {order.order_id} | {order.symbol} | 盈利 {current_pnl_percent:.2f}% >= {self.breakeven_threshold}% | 止损移至开仓价 ${order.filled_price:,.2f}")
# 如果有更新,持久化到数据库
if needs_update:
db = db_service.get_session()
@ -437,6 +461,7 @@ class PaperTradingService:
if db_order:
db_order.max_profit = order.max_profit
db_order.max_drawdown = order.max_drawdown
db_order.stop_loss = order.stop_loss
db.commit()
except Exception as e:
logger.error(f"更新订单极值失败: {e}")
@ -444,6 +469,20 @@ class PaperTradingService:
finally:
db.close()
# 如果触发了保本止损,返回通知信息
if breakeven_triggered:
return {
'event_type': 'breakeven_triggered',
'order_id': order.order_id,
'symbol': order.symbol,
'side': order.side.value,
'filled_price': order.filled_price,
'new_stop_loss': order.stop_loss,
'current_pnl_percent': current_pnl_percent
}
return None
def close_order_manual(self, order_id: str, exit_price: float) -> Optional[Dict[str, Any]]:
"""手动平仓或取消挂单"""
if order_id not in self.active_orders: