diff --git a/backend/ABNORMAL_ORDERS_README.md b/backend/ABNORMAL_ORDERS_README.md new file mode 100644 index 0000000..72ab106 --- /dev/null +++ b/backend/ABNORMAL_ORDERS_README.md @@ -0,0 +1,124 @@ +# 异常订单检查工具 + +用于检查和修复数据库中导致错误平仓消息的异常订单。 + +## 问题原因 + +根据之前的分析,"莫名其妙出现的平仓消息"通常是由以下异常订单导致的: + +1. **OPEN 状态但成交价为 0 的订单**:这些订单被系统认为是"已成交"但价格无效,导致平仓计算时显示 `$0.00` +2. **PENDING 状态但有成交价的订单**:状态不一致,可能导致重复处理 + +## 使用方法 + +### 方法 1: Python 脚本(推荐) + +在服务器上执行: + +```bash +cd /path/to/Stock_Agent/backend + +# 检查异常订单(不会修改数据) +python3 check_abnormal_orders.py + +# 自动修复异常订单(会删除/修改数据,需要确认) +python3 fix_abnormal_orders.py +``` + +### 方法 2: SQL 查询 + +如果有 SQLite 命令行工具: + +```bash +cd /path/to/Stock_Agent/backend + +# 需要先确认数据库路径 +sqlite3 /path/to/your/database.db < check_abnormal_orders.sql +``` + +或者直接进入 SQLite: + +```bash +sqlite3 /path/to/your/database.db + +# 然后复制粘贴 check_abnormal_orders.sql 中的查询语句 +``` + +### 方法 3: Bash 脚本 + +```bash +cd /path/to/Stock_Agent/backend + +# 需要指定数据库路径 +./check_orders.sh /path/to/your/database.db +``` + +## 检查结果说明 + +脚本会检查以下问题: + +| 问题 | 说明 | 影响 | +|------|------|------| +| OPEN 状态但成交价无效 | 订单已成交但 `filled_price` 为 0 或 NULL | 可能导致 `$0.00` 平仓消息 | +| PENDING 状态但有成交价 | 状态应该是 OPEN 但实际是 PENDING | 状态不一致 | +| 平仓订单价格异常 | 出场价或成交价为 0 | 历史记录错误 | +| 重复订单 | 同一交易对短时间内创建多个订单 | 可能是重复信号 | + +## 修复操作 + +确认有异常订单后,可以: + +1. **手动删除异常订单**(最安全): + +```sql +-- 删除 OPEN 状态但成交价无效的订单 +DELETE FROM paper_orders +WHERE status = 'OPEN' + AND (filled_price IS NULL OR filled_price = 0); +``` + +2. **使用 Python 自动修复**: + +```bash +python3 fix_abnormal_orders.py +# 脚本会要求确认,输入 yes 确认删除/修复 +``` + +3. **重启服务**(推荐): + +修复后重启服务,让系统重新加载正确的活跃订单: + +```bash +# 重启 FastAPI 服务 +systemctl restart stock-agent # 或你的服务名称 +``` + +## 防止再次出现 + +代码中已添加防御性检查(在 `paper_trading_service.py` 中): + +1. `_load_active_orders()` 会跳过异常订单 +2. `_check_order_trigger()` 会检查成交价有效性 +3. `_close_order()` 会防止无效订单平仓 + +但建议定期运行检查脚本,确保数据库状态正常。 + +## 紧急处理 + +如果正在出现大量错误消息,可以: + +1. **临时停止服务**: +```bash +systemctl stop stock-agent +``` + +2. **运行检查和修复**: +```bash +python3 check_abnormal_orders.py +python3 fix_abnormal_orders.py +``` + +3. **重启服务**: +```bash +systemctl start stock-agent +``` diff --git a/backend/check_abnormal_orders.py b/backend/check_abnormal_orders.py new file mode 100644 index 0000000..a523d77 --- /dev/null +++ b/backend/check_abnormal_orders.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +""" +检查数据库中的异常订单 +用于调试莫名奇妙出现的平仓消息问题 +""" +import sys +import os + +# 添加项目路径 +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from app.services.db_service import db_service +from app.models.paper_trading import PaperOrder, OrderStatus +from sqlalchemy import text +from datetime import datetime, timedelta + + +def check_abnormal_orders(): + """检查所有异常订单""" + + db = db_service.get_session() + + print("=" * 80) + print("异常订单检查报告") + print("=" * 80) + print(f"检查时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + print() + + # 1. 检查 OPEN 状态但 filled_price 为 0 或 None 的订单 + print("【1】OPEN 状态但成交价无效的订单(可能导致错误平仓):") + print("-" * 80) + invalid_filled_orders = db.query(PaperOrder).filter( + PaperOrder.status == OrderStatus.OPEN, + (PaperOrder.filled_price == None) | (PaperOrder.filled_price == 0) + ).all() + + if invalid_filled_orders: + for order in invalid_filled_orders: + print(f" ⚠️ 订单ID: {order.order_id}") + print(f" 交易对: {order.symbol}") + print(f" 方向: {order.side.value}") + print(f" 状态: {order.status.value}") + print(f" 入场价: {order.entry_price}") + print(f" 成交价: {order.filled_price} ❌ 无效") + print(f" 创建时间: {order.created_at}") + print() + else: + print(" ✅ 无异常订单") + print() + + # 2. 检查 PENDING 状态但已有成交价的订单 + print("【2】PENDING 状态但有成交价的订单(状态不一致):") + print("-" * 80) + pending_with_filled = db.query(PaperOrder).filter( + PaperOrder.status == OrderStatus.PENDING, + PaperOrder.filled_price != None, + PaperOrder.filled_price > 0 + ).all() + + if pending_with_filled: + for order in pending_with_filled: + print(f" ⚠️ 订单ID: {order.order_id}") + print(f" 交易对: {order.symbol}") + print(f" 成交价: {order.filled_price}") + print(f" 状态: PENDING(应该是 OPEN)") + print() + else: + print(" ✅ 无异常订单") + print() + + # 3. 检查最近1小时内平仓的订单(入场价或出场价为0) + print("【3】最近平仓订单中价格异常的记录:") + print("-" * 80) + one_hour_ago = datetime.now() - timedelta(hours=1) + closed_abnormal = db.query(PaperOrder).filter( + PaperOrder.status.in_([OrderStatus.CLOSED, OrderStatus.CLOSED_TP, OrderStatus.CLOSED_SL, OrderStatus.CLOSED_BE]), + PaperOrder.closed_at >= one_hour_ago, + ( + (PaperOrder.filled_price == None) | (PaperOrder.filled_price == 0) | + (PaperOrder.exit_price == None) | (PaperOrder.exit_price == 0) + ) + ).all() + + if closed_abnormal: + for order in closed_abnormal: + print(f" ⚠️ 订单ID: {order.order_id}") + print(f" 交易对: {order.symbol}") + print(f" 成交价: {order.filled_price}") + print(f" 出场价: {order.exit_price}") + print(f" 平仓时间: {order.closed_at}") + print(f" 平仓原因: {order.status.value}") + print() + else: + print(" ✅ 无异常记录") + print() + + # 4. 检查所有活跃订单的状态统计 + print("【4】活跃订单状态统计:") + print("-" * 80) + active_stats = db.execute(text(""" + SELECT + status, + COUNT(*) as count, + SUM(CASE WHEN filled_price IS NULL OR filled_price = 0 THEN 1 ELSE 0 END) as invalid_count + FROM paper_orders + WHERE status IN ('PENDING', 'OPEN') + GROUP BY status + """)) + + for row in active_stats: + status_name = row[0] + count = row[1] + invalid = row[2] + print(f" {status_name}: {count} 个(异常: {invalid} 个)") + print() + + # 5. 检查重复订单 + print("【5】可能重复的订单(同一交易对、同一方向、相近创建时间):") + print("-" * 80) + duplicates = db.execute(text(""" + SELECT symbol, side, created_at, order_id, entry_price + FROM paper_orders + WHERE status IN ('PENDING', 'OPEN') + AND created_at >= datetime('now', '-1 hour') + ORDER BY symbol, side, created_at DESC + """)).fetchall() + + # 简单的重复检测:同一交易对+方向在1分钟内创建 + seen = {} + duplicate_found = False + for row in duplicates: + key = (row[0], row[1]) # symbol + side + if key in seen: + prev_time = seen[key] + if abs((row[2] - prev_time).total_seconds()) < 60: + print(f" ⚠️ 可能重复: {row[0]} {row[1]}") + print(f" 订单ID: {row[3]}") + print(f" 时间: {row[2]}") + duplicate_found = True + seen[key] = row[2] + + if not duplicate_found: + print(" ✅ 无明显重复") + print() + + # 6. 最近的活动日志 + print("【6】最近10条订单变更记录(按时间倒序):") + print("-" * 80) + recent_orders = db.query(PaperOrder).order_by( + PaperOrder.updated_at.desc() + ).limit(10).all() + + for order in recent_orders: + print(f" {order.updated_at.strftime('%H:%M:%S')} | {order.symbol} | " + f"{order.side.value:4s} | {order.status.value:15s} | " + f"入场:{order.entry_price} 成交:{order.filled_price}") + print() + + print("=" * 80) + print("检查完成") + print("=" * 80) + + # 输出清理建议 + if invalid_filled_orders: + print() + print("🔧 发现异常订单!建议执行以下操作:") + print() + print("1. 删除 OPEN 状态但成交价无效的订单:") + for order in invalid_filled_orders: + print(f" DELETE FROM paper_orders WHERE order_id = '{order.order_id}';") + print() + print("2. 或者运行 python fix_abnormal_orders.py 自动修复") + + db.close() + + +if __name__ == "__main__": + check_abnormal_orders() diff --git a/backend/check_abnormal_orders.sql b/backend/check_abnormal_orders.sql new file mode 100644 index 0000000..680e5cf --- /dev/null +++ b/backend/check_abnormal_orders.sql @@ -0,0 +1,91 @@ +-- ============================================================ +-- 异常订单检查 SQL +-- 可以直接在 SQLite 命令行或数据库管理工具中执行 +-- ============================================================ + +-- 1. 检查 OPEN 状态但成交价无效的订单(最可能的问题来源) +SELECT + order_id, + symbol, + side, + status, + entry_price, + filled_price, + created_at, + 'OPEN状态但成交价无效' as issue +FROM paper_orders +WHERE status = 'OPEN' + AND (filled_price IS NULL OR filled_price = 0); + +-- 2. 检查 PENDING 状态但有成交价的订单(状态不一致) +SELECT + order_id, + symbol, + side, + status, + entry_price, + filled_price, + created_at, + 'PENDING状态但有成交价' as issue +FROM paper_orders +WHERE status = 'PENDING' + AND filled_price IS NOT NULL + AND filled_price > 0; + +-- 3. 检查最近平仓订单中价格异常的记录 +SELECT + order_id, + symbol, + side, + status, + entry_price, + filled_price, + exit_price, + closed_at, + '平仓订单价格异常' as issue +FROM paper_orders +WHERE status IN ('CLOSED', 'CLOSED_TP', 'CLOSED_SL', 'CLOSED_BE') + AND closed_at >= datetime('now', '-1 hour') + AND ( + filled_price IS NULL OR filled_price = 0 OR + exit_price IS NULL OR exit_price = 0 + ); + +-- 4. 活跃订单状态统计 +SELECT + status, + COUNT(*) as total, + SUM(CASE WHEN filled_price IS NULL OR filled_price = 0 THEN 1 ELSE 0 END) as invalid_count +FROM paper_orders +WHERE status IN ('PENDING', 'OPEN') +GROUP BY status; + +-- 5. 检查可能重复的订单(同一交易对+方向,最近创建) +SELECT + symbol, + side, + COUNT(*) as count, + MIN(created_at) as first_created, + MAX(created_at) as last_created +FROM paper_orders +WHERE status IN ('PENDING', 'OPEN') + AND created_at >= datetime('now', '-1 hour') +GROUP BY symbol, side +HAVING COUNT(*) > 1; + +-- ============================================================ +-- 清理异常订单的 SQL(谨慎执行!) +-- ============================================================ + +-- 删除 OPEN 状态但成交价无效的订单 +-- WARNING: 执行前请先确认上面的查询结果! +-- DELETE FROM paper_orders +-- WHERE status = 'OPEN' +-- AND (filled_price IS NULL OR filled_price = 0); + +-- 修复 PENDING 状态但有成交价的订单 +-- UPDATE paper_orders +-- SET status = 'OPEN' +-- WHERE status = 'PENDING' +-- AND filled_price IS NOT NULL +-- AND filled_price > 0; diff --git a/backend/check_orders.sh b/backend/check_orders.sh new file mode 100755 index 0000000..6eecfdb --- /dev/null +++ b/backend/check_orders.sh @@ -0,0 +1,88 @@ +#!/bin/bash +# ============================================================ +# 异常订单快速检查脚本 +# 用法: ./check_orders.sh [database_path] +# ============================================================ + +DB_PATH="${1:-/path/to/your/database.db}" + +echo "==========================================" +echo "异常订单检查报告" +echo "==========================================" +echo "数据库: $DB_PATH" +echo "检查时间: $(date '+%Y-%m-%d %H:%M:%S')" +echo "" + +if [ ! -f "$DB_PATH" ]; then + echo "错误: 找不到数据库文件 $DB_PATH" + echo "请指定正确的数据库路径" + exit 1 +fi + +# 1. 检查 OPEN 状态但成交价无效的订单 +echo "【1】OPEN 状态但成交价无效的订单:" +echo "----------------------------------------" +sqlite3 "$DB_PATH" " +SELECT + ' ' || substr(order_id, 1, 8) || '...' || + ' | ' || symbol || + ' | ' || side || + ' | 成交价:' || COALESCE(CAST(filled_price AS TEXT), 'NULL') +FROM paper_orders +WHERE status = 'OPEN' + AND (filled_price IS NULL OR filled_price = 0) +LIMIT 10; +" || echo " ✅ 无异常订单" +echo "" + +# 2. 检查 PENDING 状态但有成交价的订单 +echo "【2】PENDING 状态但有成交价的订单:" +echo "----------------------------------------" +sqlite3 "$DB_PATH" " +SELECT + ' ' || substr(order_id, 1, 8) || '...' || + ' | ' || symbol || + ' | 状态应该是OPEN' +FROM paper_orders +WHERE status = 'PENDING' + AND filled_price IS NOT NULL + AND filled_price > 0 +LIMIT 10; +" || echo " ✅ 无异常订单" +echo "" + +# 3. 活跃订单统计 +echo "【3】活跃订单统计:" +echo "----------------------------------------" +sqlite3 "$DB_PATH" " +SELECT + ' ' || status || ': ' || COUNT(*) || ' 个' +FROM paper_orders +WHERE status IN ('PENDING', 'OPEN') +GROUP BY status; +" +echo "" + +# 4. 最近异常平仓记录 +echo "【4】最近1小时内价格异常的平仓记录:" +echo "----------------------------------------" +sqlite3 "$DB_PATH" " +SELECT + ' ' || substr(order_id, 1, 8) || '...' || + ' | ' || symbol || + ' | 成交:' || COALESCE(CAST(filled_price AS TEXT), 'NULL') || + ' | 出场:' || COALESCE(CAST(exit_price AS TEXT), 'NULL') +FROM paper_orders +WHERE status IN ('CLOSED', 'CLOSED_TP', 'CLOSED_SL', 'CLOSED_BE') + AND closed_at >= datetime('now', '-1 hour') + AND ( + filled_price IS NULL OR filled_price = 0 OR + exit_price IS NULL OR exit_price = 0 + ) +LIMIT 10; +" || echo " ✅ 无异常记录" +echo "" + +echo "==========================================" +echo "检查完成" +echo "==========================================" diff --git a/backend/fix_abnormal_orders.py b/backend/fix_abnormal_orders.py new file mode 100644 index 0000000..0eaf616 --- /dev/null +++ b/backend/fix_abnormal_orders.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +""" +自动修复数据库中的异常订单 +""" +import sys +import os + +# 添加项目路径 +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from app.services.db_service import db_service +from app.models.paper_trading import PaperOrder, OrderStatus +from datetime import datetime + + +def fix_abnormal_orders(): + """修复异常订单""" + + db = db_service.get_session() + + print("=" * 80) + print("异常订单自动修复") + print("=" * 80) + print() + + fixed_count = 0 + + # 1. 删除 OPEN 状态但 filled_price 为 0 或 None 的订单 + print("【1】清理 OPEN 状态但成交价无效的订单...") + invalid_orders = db.query(PaperOrder).filter( + PaperOrder.status == OrderStatus.OPEN, + (PaperOrder.filled_price == None) | (PaperOrder.filled_price == 0) + ).all() + + if invalid_orders: + print(f" 发现 {len(invalid_orders)} 个异常订单:") + for order in invalid_orders: + print(f" - {order.order_id} | {order.symbol} | {order.side.value} | 成交价: {order.filled_price}") + + # 确认删除 + confirm = input("\n 确认删除这些订单? (yes/no): ") + if confirm.lower() == 'yes': + for order in invalid_orders: + db.delete(order) + fixed_count += 1 + db.commit() + print(f" ✅ 已删除 {len(invalid_orders)} 个异常订单") + else: + print(" ⏭️ 跳过删除") + else: + print(" ✅ 无异常订单") + print() + + # 2. 修复 PENDING 状态但有成交价的订单 + print("【2】修复 PENDING 状态但有成交价的订单...") + pending_orders = db.query(PaperOrder).filter( + PaperOrder.status == OrderStatus.PENDING, + PaperOrder.filled_price != None, + PaperOrder.filled_price > 0 + ).all() + + if pending_orders: + print(f" 发现 {len(pending_orders)} 个状态不一致的订单:") + for order in pending_orders: + print(f" - {order.order_id} | {order.symbol} | 状态: PENDING → OPEN") + + confirm = input("\n 确认修复这些订单? (yes/no): ") + if confirm.lower() == 'yes': + for order in pending_orders: + order.status = OrderStatus.OPEN + fixed_count += 1 + db.commit() + print(f" ✅ 已修复 {len(pending_orders)} 个订单") + else: + print(" ⏭️ 跳过修复") + else: + print(" ✅ 无异常订单") + print() + + # 3. 删除无用的测试数据(可选) + print("【3】检查是否有测试数据需要清理...") + test_orders = db.query(PaperOrder).filter( + PaperOrder.symbol.like('%TEST%') + ).count() + + if test_orders > 0: + print(f" 发现 {test_orders} 个测试订单") + confirm = input(" 是否删除测试数据? (yes/no): ") + if confirm.lower() == 'yes': + db.execute("DELETE FROM paper_orders WHERE symbol LIKE '%TEST%'") + db.commit() + print(f" ✅ 已删除测试数据") + else: + print(" ✅ 无测试数据") + print() + + print("=" * 80) + print(f"修复完成!共处理 {fixed_count} 个异常订单") + print("=" * 80) + + db.close() + + +if __name__ == "__main__": + fix_abnormal_orders()