From c7f1361d997ac0dffa730931cf6636887199bd41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Tue, 17 Feb 2026 23:13:49 +0800 Subject: [PATCH] 1 --- backend/TROUBLESHOOTING.md | 103 ++++++++ backend/api/routes/trades.py | 477 +++++++++++++++++----------------- backend/check_dependencies.sh | 46 ++++ 3 files changed, 389 insertions(+), 237 deletions(-) create mode 100644 backend/TROUBLESHOOTING.md create mode 100755 backend/check_dependencies.sh diff --git a/backend/TROUBLESHOOTING.md b/backend/TROUBLESHOOTING.md new file mode 100644 index 0000000..5f0993f --- /dev/null +++ b/backend/TROUBLESHOOTING.md @@ -0,0 +1,103 @@ +# Backend 启动问题排查指南 + +## 常见问题 + +### 1. 语法错误 +**错误信息**: `SyntaxError: expected 'except' or 'finally' block` + +**解决方法**: +- 检查代码中的 `try:` 块是否都有对应的 `except` 或 `finally` +- 检查缩进是否正确 +- 运行 `python3 -m py_compile api/routes/trades.py` 检查语法 + +### 2. 缺少依赖模块 +**错误信息**: `ModuleNotFoundError: No module named 'xxx'` + +**解决方法**: +```bash +# 激活虚拟环境 +source ../.venv/bin/activate # 或 source .venv/bin/activate + +# 安装依赖 +pip install -r requirements.txt + +# 或者单独安装缺失的模块 +pip install python-jose[cryptography] +``` + +### 3. 导入错误 +**错误信息**: `ModuleNotFoundError: No module named 'api'` + +**解决方法**: +- 确保在 `backend` 目录下运行 +- 检查 `PYTHONPATH` 是否正确设置 +- 使用 `cd backend && python3 -m uvicorn api.main:app` 启动 + +## 排查步骤 + +### 步骤 1: 检查依赖 +```bash +cd backend +bash check_dependencies.sh +``` + +### 步骤 2: 检查语法 +```bash +cd backend +source ../.venv/bin/activate +python3 -m py_compile api/main.py +python3 -m py_compile api/routes/*.py +``` + +### 步骤 3: 测试导入 +```bash +cd backend +source ../.venv/bin/activate +python3 -c "import api.main; print('✓ 导入成功')" +``` + +### 步骤 4: 查看日志 +```bash +# 查看最新的错误日志 +tail -50 backend/logs/api.log +tail -50 backend/logs/uvicorn.log +``` + +### 步骤 5: 手动启动测试 +```bash +cd backend +source ../.venv/bin/activate +export DB_HOST=your_db_host +export DB_PORT=3306 +export DB_USER=your_db_user +export DB_PASSWORD=your_db_password +export DB_NAME=auto_trade_sys +uvicorn api.main:app --host 0.0.0.0 --port 8001 --log-level info +``` + +## 启动脚本 + +### 开发模式(自动重载) +```bash +cd backend +./start_dev.sh +``` + +### 生产模式(后台运行) +```bash +cd backend +./start.sh +``` + +## 检查服务状态 + +```bash +# 检查进程 +ps aux | grep uvicorn + +# 检查端口 +lsof -i :8001 + +# 测试健康检查 +curl http://localhost:8001/api/health +``` diff --git a/backend/api/routes/trades.py b/backend/api/routes/trades.py index bbab5a4..5c43a5e 100644 --- a/backend/api/routes/trades.py +++ b/backend/api/routes/trades.py @@ -588,167 +588,170 @@ async def sync_trades_from_binance( try: # 这是平仓订单(close_orders 已经筛选出 reduceOnly=True 的订单) - # 首先检查是否已经通过订单号同步过(避免重复) - existing_trade = Trade.get_by_exit_order_id(order_id) - # 如果已有 exit_order_id 且 exit_reason 不是 sync,说明已完整同步,跳过 - if existing_trade and existing_trade.get("exit_order_id") and existing_trade.get("exit_reason") not in (None, "", "sync"): - skipped_existing += 1 - logger.debug(f"订单 {order_id} 已完整同步过(exit_reason={existing_trade.get('exit_reason')}),跳过") - continue - - # 查找数据库中该交易对的 open 状态记录(仅当前账号),或已平仓但 exit_order_id 为空的记录 - open_trades = Trade.get_by_symbol(symbol, status='open', account_id=account_id or DEFAULT_ACCOUNT_ID) - closed_trades_no_exit_id = [] - if not existing_trade and not open_trades: - # 如果没有 open 记录,查找已平仓但 exit_order_id 为空的记录 - try: - closed_trades = Trade.get_by_symbol(symbol, status='closed', account_id=account_id or DEFAULT_ACCOUNT_ID) - closed_trades_no_exit_id = [ - t for t in closed_trades - if not t.get("exit_order_id") or str(t.get("exit_order_id")).strip() in ("", "0") - ] - except Exception: - pass - - if existing_trade or open_trades or closed_trades_no_exit_id: - # 找到匹配的交易记录(优先用 existing_trade,其次 open_trades,最后 closed_trades_no_exit_id) - if existing_trade: - trade = existing_trade - elif open_trades: - trade = open_trades[0] # 取第一个 open 记录 - else: - # 从已平仓但无 exit_order_id 的记录中选择(按 entry_time 最近的一条) - closed_trades_no_exit_id.sort(key=lambda x: x.get('entry_time', 0) or 0, reverse=True) - trade = closed_trades_no_exit_id[0] - trade_id = trade['id'] - - # 如果之前没有 exit_order_id,记录为补全 - if not trade.get("exit_order_id") or str(trade.get("exit_order_id")).strip() in ("", "0"): - exit_order_id_filled += 1 - - # 计算盈亏 - entry_price = float(trade['entry_price']) - entry_quantity = float(trade['quantity']) - - # 使用实际成交数量(可能部分平仓) - actual_quantity = min(quantity, entry_quantity) - - if trade['side'] == 'BUY': - pnl = (avg_price - entry_price) * actual_quantity - pnl_percent = ((avg_price - entry_price) / entry_price) * 100 - else: # SELL - pnl = (entry_price - avg_price) * actual_quantity - pnl_percent = ((entry_price - avg_price) / entry_price) * 100 - - # 细分 exit_reason:优先使用币安订单类型,其次用价格接近止损/止盈做兜底 - exit_reason = "sync" - # 检查订单的 reduceOnly 字段:如果是 true,说明是自动平仓,不应该标记为 manual - is_reduce_only = order.get("reduceOnly", False) if isinstance(order, dict) else False - - if "TRAILING" in otype: - exit_reason = "trailing_stop" - elif "TAKE_PROFIT" in otype: - exit_reason = "take_profit" - elif "STOP" in otype: - exit_reason = "stop_loss" - elif otype in ("MARKET", "LIMIT"): - # 如果是 reduceOnly 订单,说明是自动平仓(可能是保护单触发的),先标记为 sync,后续用价格判断 - if is_reduce_only: - exit_reason = "sync" # 临时标记,后续用价格判断 - else: - exit_reason = "manual" # 非 reduceOnly 的 MARKET/LIMIT 订单才是真正的手动平仓 - - try: - def _close_to(a: float, b: float, max_pct: float = 0.02) -> bool: # 放宽到2%,因为滑点可能导致价格不完全一致 - if a <= 0 or b <= 0: - return False - return abs((a - b) / b) <= max_pct - - ep = float(avg_price or 0) - if ep > 0: - sl = trade.get("stop_loss_price") - tp = trade.get("take_profit_price") - tp1 = trade.get("take_profit_1") - tp2 = trade.get("take_profit_2") - # 优先检查止损 - if sl is not None and _close_to(ep, float(sl), max_pct=0.02): - exit_reason = "stop_loss" - # 然后检查止盈 - elif tp is not None and _close_to(ep, float(tp), max_pct=0.02): - exit_reason = "take_profit" - elif tp1 is not None and _close_to(ep, float(tp1), max_pct=0.02): - exit_reason = "take_profit" - elif tp2 is not None and _close_to(ep, float(tp2), max_pct=0.02): - exit_reason = "take_profit" - # 如果价格接近入场价,可能是移动止损触发的 - elif is_reduce_only and exit_reason == "sync": - entry_price_val = float(trade.get("entry_price", 0) or 0) - if entry_price_val > 0 and _close_to(ep, entry_price_val, max_pct=0.01): - exit_reason = "trailing_stop" - except Exception: - pass - - # 从币安成交获取手续费与实际盈亏,保证统计与币安一致 - sync_commission = None - sync_commission_asset = None - sync_realized_pnl = None - try: - recent_trades = await client.get_recent_trades(symbol, limit=30) - related = [t for t in recent_trades if str(t.get('orderId')) == str(order_id)] - if related: - sync_commission = sum(float(t.get('commission', 0)) for t in related) - assets = {t.get('commissionAsset') for t in related if t.get('commissionAsset')} - sync_commission_asset = "/".join(assets) if assets else None - sync_realized_pnl = sum(float(t.get('realizedPnl', 0)) for t in related) - except Exception as fee_err: - logger.debug(f"同步订单 {order_id} 手续费失败: {fee_err}") - - # 持仓持续时间(分钟) - duration_minutes = None - try: - et = trade.get("entry_time") - if et is not None and exit_time_ts is not None: - et_i = int(et) - if exit_time_ts >= et_i: - duration_minutes = int((exit_time_ts - et_i) / 60) - except Exception: - duration_minutes = None - - # 更新数据库(包含订单号、手续费与实际盈亏) - Trade.update_exit( - trade_id=trade_id, - exit_price=avg_price, - exit_reason=exit_reason, - pnl=pnl, - pnl_percent=pnl_percent, - exit_order_id=order_id, # 保存订单号,确保唯一性 - duration_minutes=duration_minutes, - exit_time_ts=exit_time_ts, - commission=sync_commission, - commission_asset=sync_commission_asset, - realized_pnl=sync_realized_pnl, - ) - updated_count += 1 - logger.info( - f"✓ 更新平仓记录: {symbol} (ID: {trade_id}, 订单号: {order_id}, " - f"类型: {otype or '-'}, 原因: {exit_reason}, 成交价: {avg_price:.4f})" - ) + # 首先检查是否已经通过订单号同步过(避免重复) + existing_trade = Trade.get_by_exit_order_id(order_id) + # 如果已有 exit_order_id 且 exit_reason 不是 sync,说明已完整同步,跳过 + if existing_trade and existing_trade.get("exit_order_id") and existing_trade.get("exit_reason") not in (None, "", "sync"): + skipped_existing += 1 + logger.debug(f"订单 {order_id} 已完整同步过(exit_reason={existing_trade.get('exit_reason')}),跳过") + continue + + # 查找数据库中该交易对的 open 状态记录(仅当前账号),或已平仓但 exit_order_id 为空的记录 + open_trades = Trade.get_by_symbol(symbol, status='open', account_id=account_id or DEFAULT_ACCOUNT_ID) + closed_trades_no_exit_id = [] + if not existing_trade and not open_trades: + # 如果没有 open 记录,查找已平仓但 exit_order_id 为空的记录 + try: + closed_trades = Trade.get_by_symbol(symbol, status='closed', account_id=account_id or DEFAULT_ACCOUNT_ID) + closed_trades_no_exit_id = [ + t for t in closed_trades + if not t.get("exit_order_id") or str(t.get("exit_order_id")).strip() in ("", "0") + ] + except Exception: + pass + + if existing_trade or open_trades or closed_trades_no_exit_id: + # 找到匹配的交易记录(优先用 existing_trade,其次 open_trades,最后 closed_trades_no_exit_id) + if existing_trade: + trade = existing_trade + elif open_trades: + trade = open_trades[0] # 取第一个 open 记录 else: - # 没有找到匹配的记录 - if sync_all_symbols: - # 如果启用了同步所有交易对,尝试创建完整的交易记录(开仓+平仓) - try: - # 查找是否有对应的开仓订单(通过时间窗口和价格匹配) - # 注意:平仓订单通常有 reduceOnly=True,我们需要找到对应的开仓订单 - # 由于币安订单历史可能不完整,这里先跳过创建,只记录日志 - logger.debug(f"平仓订单 {order_id} ({symbol}) 无法匹配到现有记录,且 sync_all_symbols=True,但创建完整交易记录需要开仓订单信息,暂时跳过") - skipped_no_match += 1 - except Exception as e: - logger.debug(f"处理平仓订单失败 {order_id}: {e}") - skipped_no_match += 1 + # 从已平仓但无 exit_order_id 的记录中选择(按 entry_time 最近的一条) + closed_trades_no_exit_id.sort(key=lambda x: x.get('entry_time', 0) or 0, reverse=True) + trade = closed_trades_no_exit_id[0] + trade_id = trade['id'] + + # 如果之前没有 exit_order_id,记录为补全 + if not trade.get("exit_order_id") or str(trade.get("exit_order_id")).strip() in ("", "0"): + exit_order_id_filled += 1 + + # 计算盈亏 + entry_price = float(trade['entry_price']) + entry_quantity = float(trade['quantity']) + + # 使用实际成交数量(可能部分平仓) + actual_quantity = min(quantity, entry_quantity) + + if trade['side'] == 'BUY': + pnl = (avg_price - entry_price) * actual_quantity + pnl_percent = ((avg_price - entry_price) / entry_price) * 100 + else: # SELL + pnl = (entry_price - avg_price) * actual_quantity + pnl_percent = ((entry_price - avg_price) / entry_price) * 100 + + # 细分 exit_reason:优先使用币安订单类型,其次用价格接近止损/止盈做兜底 + exit_reason = "sync" + # 检查订单的 reduceOnly 字段:如果是 true,说明是自动平仓,不应该标记为 manual + is_reduce_only = order.get("reduceOnly", False) if isinstance(order, dict) else False + + if "TRAILING" in otype: + exit_reason = "trailing_stop" + elif "TAKE_PROFIT" in otype: + exit_reason = "take_profit" + elif "STOP" in otype: + exit_reason = "stop_loss" + elif otype in ("MARKET", "LIMIT"): + # 如果是 reduceOnly 订单,说明是自动平仓(可能是保护单触发的),先标记为 sync,后续用价格判断 + if is_reduce_only: + exit_reason = "sync" # 临时标记,后续用价格判断 else: + exit_reason = "manual" # 非 reduceOnly 的 MARKET/LIMIT 订单才是真正的手动平仓 + + try: + def _close_to(a: float, b: float, max_pct: float = 0.02) -> bool: # 放宽到2%,因为滑点可能导致价格不完全一致 + if a <= 0 or b <= 0: + return False + return abs((a - b) / b) <= max_pct + + ep = float(avg_price or 0) + if ep > 0: + sl = trade.get("stop_loss_price") + tp = trade.get("take_profit_price") + tp1 = trade.get("take_profit_1") + tp2 = trade.get("take_profit_2") + # 优先检查止损 + if sl is not None and _close_to(ep, float(sl), max_pct=0.02): + exit_reason = "stop_loss" + # 然后检查止盈 + elif tp is not None and _close_to(ep, float(tp), max_pct=0.02): + exit_reason = "take_profit" + elif tp1 is not None and _close_to(ep, float(tp1), max_pct=0.02): + exit_reason = "take_profit" + elif tp2 is not None and _close_to(ep, float(tp2), max_pct=0.02): + exit_reason = "take_profit" + # 如果价格接近入场价,可能是移动止损触发的 + elif is_reduce_only and exit_reason == "sync": + entry_price_val = float(trade.get("entry_price", 0) or 0) + if entry_price_val > 0 and _close_to(ep, entry_price_val, max_pct=0.01): + exit_reason = "trailing_stop" + except Exception: + pass + + # 从币安成交获取手续费与实际盈亏,保证统计与币安一致 + sync_commission = None + sync_commission_asset = None + sync_realized_pnl = None + try: + recent_trades = await client.get_recent_trades(symbol, limit=30) + related = [t for t in recent_trades if str(t.get('orderId')) == str(order_id)] + if related: + sync_commission = sum(float(t.get('commission', 0)) for t in related) + assets = {t.get('commissionAsset') for t in related if t.get('commissionAsset')} + sync_commission_asset = "/".join(assets) if assets else None + sync_realized_pnl = sum(float(t.get('realizedPnl', 0)) for t in related) + except Exception as fee_err: + logger.debug(f"同步订单 {order_id} 手续费失败: {fee_err}") + + # 持仓持续时间(分钟) + duration_minutes = None + try: + et = trade.get("entry_time") + if et is not None and exit_time_ts is not None: + et_i = int(et) + if exit_time_ts >= et_i: + duration_minutes = int((exit_time_ts - et_i) / 60) + except Exception: + duration_minutes = None + + # 更新数据库(包含订单号、手续费与实际盈亏) + Trade.update_exit( + trade_id=trade_id, + exit_price=avg_price, + exit_reason=exit_reason, + pnl=pnl, + pnl_percent=pnl_percent, + exit_order_id=order_id, # 保存订单号,确保唯一性 + duration_minutes=duration_minutes, + exit_time_ts=exit_time_ts, + commission=sync_commission, + commission_asset=sync_commission_asset, + realized_pnl=sync_realized_pnl, + ) + updated_count += 1 + logger.info( + f"✓ 更新平仓记录: {symbol} (ID: {trade_id}, 订单号: {order_id}, " + f"类型: {otype or '-'}, 原因: {exit_reason}, 成交价: {avg_price:.4f})" + ) + else: + # 没有找到匹配的记录 + if sync_all_symbols: + # 如果启用了同步所有交易对,尝试创建完整的交易记录(开仓+平仓) + try: + # 查找是否有对应的开仓订单(通过时间窗口和价格匹配) + # 注意:平仓订单通常有 reduceOnly=True,我们需要找到对应的开仓订单 + # 由于币安订单历史可能不完整,这里先跳过创建,只记录日志 + logger.debug(f"平仓订单 {order_id} ({symbol}) 无法匹配到现有记录,且 sync_all_symbols=True,但创建完整交易记录需要开仓订单信息,暂时跳过") skipped_no_match += 1 - logger.debug(f"平仓订单 {order_id} ({symbol}) 无法匹配到现有记录(无 open 状态且无 exit_order_id 为空的 closed 记录),跳过") + except Exception as e: + logger.debug(f"处理平仓订单失败 {order_id}: {e}") + skipped_no_match += 1 + else: + skipped_no_match += 1 + logger.debug(f"平仓订单 {order_id} ({symbol}) 无法匹配到现有记录(无 open 状态且无 exit_order_id 为空的 closed 记录),跳过") + except Exception as e: + logger.warning(f"同步平仓订单失败 {symbol} (订单ID: {order_id}): {e}") + continue # 2. 处理开仓订单 logger.info(f"开始处理 {len(open_orders)} 个开仓订单...") for order in open_orders: @@ -772,86 +775,86 @@ async def sync_trades_from_binance( else: # 如果不存在,尝试查找没有 entry_order_id 的记录并补全,或创建新记录 try: - # 查找该 symbol 下没有 entry_order_id 的记录(按时间匹配) - order_time_ms = order.get('time', 0) - order_time_sec = order_time_ms // 1000 if order_time_ms > 0 else 0 - - # 查找时间范围内(订单时间前后 1 小时)且没有 entry_order_id 的记录 - time_window_start = order_time_sec - 3600 - time_window_end = order_time_sec + 3600 - - trades_no_entry_id = Trade.get_all( - account_id=account_id or DEFAULT_ACCOUNT_ID, - symbol=symbol, - start_timestamp=time_window_start, - end_timestamp=time_window_end, - time_filter="entry", # 使用 entry_time 过滤 - ) - - # 过滤出没有 entry_order_id 的记录 - trades_no_entry_id = [ - t for t in trades_no_entry_id - if not t.get("entry_order_id") or str(t.get("entry_order_id")).strip() in ("", "0") - ] - - # 按价格和数量匹配(允许 5% 误差) - matched_trade = None - order_qty = float(order.get('executedQty', 0)) - order_price = float(order.get('avgPrice', 0)) - order_side = order.get('side', '').upper() - - for t in trades_no_entry_id: - t_qty = float(t.get('quantity', 0)) - t_price = float(t.get('entry_price', 0)) - # 数量匹配(允许 5% 误差)且价格匹配(允许 2% 误差) - if (order_qty > 0 and t_qty > 0 and - abs(t_qty - order_qty) / max(order_qty, 1e-8) <= 0.05 and - order_price > 0 and t_price > 0 and - abs(t_price - order_price) / max(order_price, 1e-8) <= 0.02): - matched_trade = t - break - - if matched_trade: - # 补全 entry_order_id - from database.models import Trade as TradeModel - if TradeModel.update_entry_order_id(matched_trade['id'], order_id): - entry_order_id_filled += 1 - logger.info(f"✓ 补全开仓订单号: {symbol} (ID: {matched_trade['id']}, orderId: {order_id}, qty={order_qty}, price={order_price:.4f})") - else: - logger.debug(f"补全开仓订单号失败(可能已有订单号): {symbol} (ID: {matched_trade['id']}, orderId: {order_id})") - elif sync_all_symbols: - # 如果启用了同步所有交易对,且无法匹配到现有记录,创建新记录 - try: - # 从订单信息中提取杠杆(如果有) - leverage = 10 # 默认杠杆 - try: - # 尝试从订单的 positionSide 或其他字段获取杠杆信息 - # 如果没有,使用默认值 - pass - except: - pass - - # 创建新的交易记录 - trade_id = Trade.create( - symbol=symbol, - side=order_side, - quantity=order_qty, - entry_price=order_price, - leverage=leverage, - entry_reason='sync_from_binance', - entry_order_id=order_id, - client_order_id=order.get('clientOrderId'), - account_id=account_id or DEFAULT_ACCOUNT_ID, - status='open', # 先标记为 open,如果后续有平仓订单会更新 - ) - created_count += 1 - logger.info(f"✓ 创建新交易记录: {symbol} (ID: {trade_id}, orderId: {order_id}, side={order_side}, qty={order_qty}, price={order_price:.4f})") - except Exception as create_err: - logger.warning(f"创建交易记录失败 {symbol} (orderId: {order_id}): {create_err}") + # 查找该 symbol 下没有 entry_order_id 的记录(按时间匹配) + order_time_ms = order.get('time', 0) + order_time_sec = order_time_ms // 1000 if order_time_ms > 0 else 0 + + # 查找时间范围内(订单时间前后 1 小时)且没有 entry_order_id 的记录 + time_window_start = order_time_sec - 3600 + time_window_end = order_time_sec + 3600 + + trades_no_entry_id = Trade.get_all( + account_id=account_id or DEFAULT_ACCOUNT_ID, + symbol=symbol, + start_timestamp=time_window_start, + end_timestamp=time_window_end, + time_filter="entry", # 使用 entry_time 过滤 + ) + + # 过滤出没有 entry_order_id 的记录 + trades_no_entry_id = [ + t for t in trades_no_entry_id + if not t.get("entry_order_id") or str(t.get("entry_order_id")).strip() in ("", "0") + ] + + # 按价格和数量匹配(允许 5% 误差) + matched_trade = None + order_qty = float(order.get('executedQty', 0)) + order_price = float(order.get('avgPrice', 0)) + order_side = order.get('side', '').upper() + + for t in trades_no_entry_id: + t_qty = float(t.get('quantity', 0)) + t_price = float(t.get('entry_price', 0)) + # 数量匹配(允许 5% 误差)且价格匹配(允许 2% 误差) + if (order_qty > 0 and t_qty > 0 and + abs(t_qty - order_qty) / max(order_qty, 1e-8) <= 0.05 and + order_price > 0 and t_price > 0 and + abs(t_price - order_price) / max(order_price, 1e-8) <= 0.02): + matched_trade = t + break + + if matched_trade: + # 补全 entry_order_id + from database.models import Trade as TradeModel + if TradeModel.update_entry_order_id(matched_trade['id'], order_id): + entry_order_id_filled += 1 + logger.info(f"✓ 补全开仓订单号: {symbol} (ID: {matched_trade['id']}, orderId: {order_id}, qty={order_qty}, price={order_price:.4f})") else: - logger.debug(f"发现新的开仓订单 {order_id} ({symbol}, qty={order_qty}, price={order_price:.4f}),但无法匹配到现有记录(sync_all_symbols=False,跳过创建)") - except Exception as e: - logger.debug(f"处理开仓订单失败 {order_id}: {e}") + logger.debug(f"补全开仓订单号失败(可能已有订单号): {symbol} (ID: {matched_trade['id']}, orderId: {order_id})") + elif sync_all_symbols: + # 如果启用了同步所有交易对,且无法匹配到现有记录,创建新记录 + try: + # 从订单信息中提取杠杆(如果有) + leverage = 10 # 默认杠杆 + try: + # 尝试从订单的 positionSide 或其他字段获取杠杆信息 + # 如果没有,使用默认值 + pass + except: + pass + + # 创建新的交易记录 + trade_id = Trade.create( + symbol=symbol, + side=order_side, + quantity=order_qty, + entry_price=order_price, + leverage=leverage, + entry_reason='sync_from_binance', + entry_order_id=order_id, + client_order_id=order.get('clientOrderId'), + account_id=account_id or DEFAULT_ACCOUNT_ID, + status='open', # 先标记为 open,如果后续有平仓订单会更新 + ) + created_count += 1 + logger.info(f"✓ 创建新交易记录: {symbol} (ID: {trade_id}, orderId: {order_id}, side={order_side}, qty={order_qty}, price={order_price:.4f})") + except Exception as create_err: + logger.warning(f"创建交易记录失败 {symbol} (orderId: {order_id}): {create_err}") + else: + logger.debug(f"发现新的开仓订单 {order_id} ({symbol}, qty={order_qty}, price={order_price:.4f}),但无法匹配到现有记录(sync_all_symbols=False,跳过创建)") + except Exception as e: + logger.debug(f"处理开仓订单失败 {order_id}: {e}") except Exception as e: logger.warning(f"同步订单失败 {symbol} (订单ID: {order_id}): {e}") continue diff --git a/backend/check_dependencies.sh b/backend/check_dependencies.sh new file mode 100755 index 0000000..8912060 --- /dev/null +++ b/backend/check_dependencies.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# 检查 backend 依赖是否完整安装 + +cd "$(dirname "$0")" + +echo "=== 检查 Backend 依赖 ===" +echo "" + +# 检查虚拟环境 +if [ -d "../.venv" ]; then + echo "✓ 找到虚拟环境: ../.venv" + source ../.venv/bin/activate +elif [ -d ".venv" ]; then + echo "✓ 找到虚拟环境: .venv" + source .venv/bin/activate +else + echo "⚠ 未找到虚拟环境,使用系统 Python" +fi + +echo "" +echo "Python 路径: $(which python3)" +echo "Python 版本: $(python3 --version)" +echo "" + +# 检查关键依赖 +echo "检查关键依赖..." +python3 -c "import fastapi; print('✓ fastapi:', fastapi.__version__)" 2>&1 || echo "✗ fastapi 未安装" +python3 -c "import uvicorn; print('✓ uvicorn:', uvicorn.__version__)" 2>&1 || echo "✗ uvicorn 未安装" +python3 -c "from jose import jwt; print('✓ python-jose: 已安装')" 2>&1 || echo "✗ python-jose 未安装" +python3 -c "import pymysql; print('✓ pymysql:', pymysql.__version__)" 2>&1 || echo "✗ pymysql 未安装" +python3 -c "import redis; print('✓ redis:', redis.__version__)" 2>&1 || echo "✗ redis 未安装" +python3 -c "from cryptography.fernet import Fernet; print('✓ cryptography: 已安装')" 2>&1 || echo "✗ cryptography 未安装" + +echo "" +echo "=== 尝试导入 api.main ===" +python3 -c "import api.main; print('✓ api.main 导入成功')" 2>&1 || echo "✗ api.main 导入失败" + +echo "" +echo "=== 检查完成 ===" +echo "" +echo "如果缺少依赖,请运行:" +echo " pip install -r backend/requirements.txt" +echo "" +echo "或者激活虚拟环境后运行:" +echo " source .venv/bin/activate" +echo " pip install -r backend/requirements.txt"