diff --git a/backend/app/api/paper_trading.py b/backend/app/api/paper_trading.py index f6673fa..48f973c 100644 --- a/backend/app/api/paper_trading.py +++ b/backend/app/api/paper_trading.py @@ -8,6 +8,7 @@ from pydantic import BaseModel from app.services.paper_trading_service import get_paper_trading_service from app.services.price_monitor_service import get_price_monitor_service +from app.services.binance_service import binance_service from app.utils.logger import logger @@ -195,16 +196,55 @@ async def get_statistics_by_symbol(): @router.get("/monitor/status") async def get_monitor_status(): - """获取价格监控状态""" + """获取价格监控状态和实时价格""" try: monitor = get_price_monitor_service() + paper_trading = get_paper_trading_service() + + # 获取活跃订单的交易对 + active_orders = paper_trading.get_active_orders() + symbols_needed = set(order.get('symbol') for order in active_orders if order.get('symbol')) + + # 获取价格 - 优先使用监控服务的缓存价格 + latest_prices = dict(monitor.latest_prices) + + # 如果监控服务没有价格数据,直接从 Binance 获取 + for symbol in symbols_needed: + if symbol not in latest_prices or latest_prices[symbol] is None: + price = binance_service.get_current_price(symbol) + if price: + latest_prices[symbol] = price + + # 注意:止盈止损检查由后台任务 (main.py price_monitor_loop) 处理 + # 这里只返回价格数据供前端显示 return { "success": True, - "running": monitor.is_running(), - "subscribed_symbols": monitor.get_subscribed_symbols(), - "latest_prices": monitor.latest_prices + "running": True, # 后台任务始终运行 + "subscribed_symbols": list(symbols_needed), + "latest_prices": latest_prices } except Exception as e: logger.error(f"获取监控状态失败: {e}") raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/reset") +async def reset_paper_trading(): + """ + 重置所有模拟交易数据 + + 警告:此操作将删除所有订单记录,不可恢复! + """ + try: + service = get_paper_trading_service() + result = service.reset_all_data() + + return { + "success": True, + "message": f"模拟交易数据已重置,删除 {result['deleted_count']} 条订单", + "result": result + } + except Exception as e: + logger.error(f"重置模拟交易数据失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) diff --git a/backend/app/main.py b/backend/app/main.py index 29c6dda..f14bc5c 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,6 +1,7 @@ """ FastAPI主应用 """ +import asyncio from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles @@ -12,13 +13,112 @@ from app.api import chat, stock, skills, llm, auth, admin, paper_trading import os +# 后台价格监控任务 +_price_monitor_task = None + + +async def price_monitor_loop(): + """后台价格监控循环 - 检查止盈止损""" + from app.services.paper_trading_service import get_paper_trading_service + from app.services.binance_service import binance_service + from app.services.feishu_service import get_feishu_service + from app.services.telegram_service import get_telegram_service + + logger.info("后台价格监控任务已启动") + + while True: + try: + paper_trading = get_paper_trading_service() + feishu = get_feishu_service() + telegram = get_telegram_service() + + # 获取活跃订单 + active_orders = paper_trading.get_active_orders() + if not active_orders: + await asyncio.sleep(10) # 没有活跃订单时,10秒检查一次 + continue + + # 获取所有需要的交易对 + symbols = set(order.get('symbol') for order in active_orders if order.get('symbol')) + + # 获取价格并检查止盈止损 + for symbol in symbols: + try: + price = binance_service.get_current_price(symbol) + if not price: + continue + + # 检查止盈止损 + triggered = paper_trading.check_price_triggers(symbol, price) + + # 发送通知 + for result in triggered: + status = result.get('status', '') + is_win = result.get('is_win', False) + + if status == 'closed_tp': + emoji = "🎯" + status_text = "止盈平仓" + elif status == 'closed_sl': + emoji = "🛑" + status_text = "止损平仓" + else: + emoji = "📤" + status_text = "平仓" + + win_text = "盈利" if is_win else "亏损" + side_text = "做多" if result.get('side') == 'long' else "做空" + + message = f"""{emoji} 订单{status_text} + +交易对: {result.get('symbol')} +方向: {side_text} +入场: ${result.get('entry_price', 0):,.2f} +出场: ${result.get('exit_price', 0):,.2f} +{win_text}: {result.get('pnl_percent', 0):+.2f}% (${result.get('pnl_amount', 0):+.2f}) +持仓时间: {result.get('hold_duration', 'N/A')}""" + + # 发送通知 + await feishu.send_text(message) + await telegram.send_message(message) + logger.info(f"后台监控触发平仓: {result.get('order_id')} | {symbol}") + + except Exception as e: + logger.error(f"检查 {symbol} 价格失败: {e}") + + # 每 3 秒检查一次 + await asyncio.sleep(3) + + except Exception as e: + logger.error(f"价格监控循环出错: {e}") + await asyncio.sleep(5) + + @asynccontextmanager async def lifespan(app: FastAPI): """应用生命周期管理""" + global _price_monitor_task + # 启动时执行 logger.info("应用启动") + + # 启动后台价格监控任务 + settings = get_settings() + if getattr(settings, 'paper_trading_enabled', True): + _price_monitor_task = asyncio.create_task(price_monitor_loop()) + logger.info("后台价格监控任务已创建") + yield + # 关闭时执行 + if _price_monitor_task: + _price_monitor_task.cancel() + try: + await _price_monitor_task + except asyncio.CancelledError: + pass + logger.info("后台价格监控任务已停止") + logger.info("应用关闭") diff --git a/backend/app/services/paper_trading_service.py b/backend/app/services/paper_trading_service.py index 5b13d95..7552db6 100644 --- a/backend/app/services/paper_trading_service.py +++ b/backend/app/services/paper_trading_service.py @@ -442,6 +442,39 @@ class PaperTradingService: } return result + def reset_all_data(self) -> Dict[str, Any]: + """ + 重置所有模拟交易数据 + + Returns: + 重置结果,包含删除的订单数量 + """ + db = db_service.get_session() + try: + # 统计删除前的数量 + total_count = db.query(PaperOrder).count() + active_count = len(self.active_orders) + + # 删除所有订单 + db.query(PaperOrder).delete() + db.commit() + + # 清空内存缓存 + self.active_orders.clear() + + logger.info(f"模拟交易数据已重置,删除 {total_count} 条订单") + + return { + 'deleted_count': total_count, + 'active_orders_cleared': active_count + } + except Exception as e: + db.rollback() + logger.error(f"重置模拟交易数据失败: {e}") + raise + finally: + db.close() + # 全局单例 _paper_trading_service: Optional[PaperTradingService] = None diff --git a/frontend/paper-trading.html b/frontend/paper-trading.html index 79f85dc..9576c3b 100644 --- a/frontend/paper-trading.html +++ b/frontend/paper-trading.html @@ -344,6 +344,21 @@ color: var(--text-secondary); } + /* 重置按钮 */ + .reset-btn { + padding: 8px 16px; + background: transparent; + border: 1px solid #ff4444; + color: #ff4444; + font-size: 13px; + cursor: pointer; + transition: all 0.2s; + } + + .reset-btn:hover { + background: rgba(255, 68, 68, 0.1); + } + /* 操作按钮 */ .action-btn { padding: 4px 8px; @@ -441,12 +456,13 @@

模拟交易 Paper Trading

-
+
{{ monitorRunning ? '监控中' : '未启动' }}
+
@@ -747,6 +763,27 @@ } }, + async resetData() { + if (!confirm('确定要重置所有模拟交易数据吗?\n\n此操作将删除所有订单记录,不可恢复!')) { + return; + } + + try { + const response = await fetch('/api/paper-trading/reset', { + method: 'POST' + }); + const data = await response.json(); + if (data.success) { + alert(data.message); + this.refreshData(); + } else { + alert('重置失败: ' + (data.detail || '未知错误')); + } + } catch (e) { + alert('重置失败: ' + e.message); + } + }, + async fetchActiveOrders() { const response = await fetch('/api/paper-trading/orders/active'); const data = await response.json();