diff --git a/backend/app/models/paper_trading.py b/backend/app/models/paper_trading.py index be07c9f..efd0832 100644 --- a/backend/app/models/paper_trading.py +++ b/backend/app/models/paper_trading.py @@ -13,6 +13,7 @@ class OrderStatus(str, Enum): OPEN = "open" # 持仓中 CLOSED_TP = "closed_tp" # 止盈平仓 CLOSED_SL = "closed_sl" # 止损平仓 + CLOSED_BE = "closed_be" # 保本止损平仓 CLOSED_MANUAL = "closed_manual" # 手动平仓 CANCELLED = "cancelled" # 已取消 @@ -77,6 +78,7 @@ class PaperOrder(Base): # 风险指标 max_drawdown = Column(Float, default=0) # 持仓期间最大回撤 max_profit = Column(Float, default=0) # 持仓期间最大盈利 + breakeven_triggered = Column(Integer, default=0) # 是否触发过保本止损(0=否,1=是) # 时间戳 created_at = Column(DateTime, default=datetime.utcnow) @@ -111,6 +113,7 @@ class PaperOrder(Base): 'pnl_percent': self.pnl_percent, 'max_drawdown': self.max_drawdown, 'max_profit': self.max_profit, + 'breakeven_triggered': self.breakeven_triggered, 'created_at': self.created_at.isoformat() if self.created_at else None, 'opened_at': self.opened_at.isoformat() if self.opened_at else None, 'closed_at': self.closed_at.isoformat() if self.closed_at else None, diff --git a/backend/app/services/paper_trading_service.py b/backend/app/services/paper_trading_service.py index 98ddd62..71da12a 100644 --- a/backend/app/services/paper_trading_service.py +++ b/backend/app/services/paper_trading_service.py @@ -431,7 +431,11 @@ class PaperTradingService: exit_price = order.take_profit elif current_price <= order.stop_loss: triggered = True - new_status = OrderStatus.CLOSED_SL + # 通过标记判断是否是保本止损 + if getattr(order, 'breakeven_triggered', 0) == 1: + new_status = OrderStatus.CLOSED_BE + else: + new_status = OrderStatus.CLOSED_SL exit_price = order.stop_loss else: # 做空: 价格 <= 止盈价 触发止盈, 价格 >= 止损价 触发止损 @@ -441,7 +445,11 @@ class PaperTradingService: exit_price = order.take_profit elif current_price >= order.stop_loss: triggered = True - new_status = OrderStatus.CLOSED_SL + # 通过标记判断是否是保本止损 + if getattr(order, 'breakeven_triggered', 0) == 1: + new_status = OrderStatus.CLOSED_BE + else: + new_status = OrderStatus.CLOSED_SL exit_price = order.stop_loss if triggered: @@ -499,7 +507,7 @@ class PaperTradingService: 'signal_grade': db_order.signal_grade.value if db_order.signal_grade else None } - status_text = "止盈" if status == OrderStatus.CLOSED_TP else "止损" + status_text = {"closed_tp": "止盈", "closed_sl": "止损", "closed_be": "保本止损"}.get(status.value, "平仓") logger.info(f"订单{status_text}: {db_order.order_id} | {db_order.symbol} | 盈亏: {pnl_percent:+.2f}% (${pnl_amount:+.2f})") return result @@ -534,21 +542,24 @@ class PaperTradingService: # 保本止损逻辑:当盈利达到阈值时,将止损移动到开仓价 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 getattr(order, 'breakeven_triggered', 0) != 1: + if order.side == OrderSide.LONG: + # 做多:止损应该低于开仓价,如果止损还在开仓价下方,则移动到开仓价 + if order.stop_loss < order.filled_price: + order.stop_loss = order.filled_price + order.breakeven_triggered = 1 + 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 + order.breakeven_triggered = 1 + 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: @@ -559,6 +570,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.breakeven_triggered = order.breakeven_triggered db.commit() except Exception as e: logger.error(f"更新订单极值失败: {e}") @@ -779,6 +791,7 @@ class PaperTradingService: PaperOrder.status.in_([ OrderStatus.CLOSED_TP, OrderStatus.CLOSED_SL, + OrderStatus.CLOSED_BE, OrderStatus.CLOSED_MANUAL ]) ) @@ -800,6 +813,7 @@ class PaperTradingService: PaperOrder.status.in_([ OrderStatus.CLOSED_TP, OrderStatus.CLOSED_SL, + OrderStatus.CLOSED_BE, OrderStatus.CLOSED_MANUAL ]) ) @@ -891,6 +905,7 @@ class PaperTradingService: PaperOrder.status.in_([ OrderStatus.CLOSED_TP, OrderStatus.CLOSED_SL, + OrderStatus.CLOSED_BE, OrderStatus.CLOSED_MANUAL ]) ).all() @@ -985,6 +1000,7 @@ class PaperTradingService: PaperOrder.status.in_([ OrderStatus.CLOSED_TP, OrderStatus.CLOSED_SL, + OrderStatus.CLOSED_BE, OrderStatus.CLOSED_MANUAL ]), PaperOrder.closed_at >= cutoff_time diff --git a/frontend/paper-trading.html b/frontend/paper-trading.html index a994e69..aabf8fa 100644 --- a/frontend/paper-trading.html +++ b/frontend/paper-trading.html @@ -221,6 +221,11 @@ color: #ff4444; } + .status-badge.closed_be { + background: rgba(255, 193, 7, 0.1); + color: #ffc107; + } + .status-badge.closed_manual { background: rgba(255, 165, 0, 0.1); color: orange; @@ -1628,6 +1633,7 @@ 'open': '持仓中', 'closed_tp': '止盈', 'closed_sl': '止损', + 'closed_be': '保本止损', 'closed_manual': '手动平仓' }; return map[status] || status;