From 922a8f3820079c4b3b453a867ac3bd239b545ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Wed, 4 Feb 2026 13:45:30 +0800 Subject: [PATCH] 1 --- analyze_zro.py | 45 ++++++++++++++++++++++++ backend/database/connection.py | 17 ++++++++- test_db_pool.py | 42 ++++++++++++++++++++++ trading_system/config.py | 3 +- trading_system/risk_manager.py | 64 ++++++++++++++++++++++++++++++++-- 5 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 analyze_zro.py create mode 100644 test_db_pool.py diff --git a/analyze_zro.py b/analyze_zro.py new file mode 100644 index 0000000..ee8c016 --- /dev/null +++ b/analyze_zro.py @@ -0,0 +1,45 @@ +from backend.database.connection import db +import json +from datetime import datetime +import time + +def analyze_zro_trades(): + print("Querying ZROUSDT trades...") + # Get all columns + query = "SELECT * FROM trades WHERE symbol='ZROUSDT' ORDER BY id DESC LIMIT 5" + trades = db.execute_query(query) + + if not trades: + print("No ZROUSDT trades found.") + return + + # Print first trade keys to understand schema + print("Schema keys:", list(trades[0].keys())) + + for trade in trades: + print("-" * 50) + ts = trade.get('created_at') + dt = datetime.fromtimestamp(ts) if ts else "N/A" + + print(f"ID: {trade.get('id')}") + print(f"Time: {dt} ({ts})") + print(f"Symbol: {trade.get('symbol')}") + print(f"Side: {trade.get('side')}") + print(f"Entry: {trade.get('entry_price')}") + print(f"Exit: {trade.get('exit_price')}") + print(f"Qty: {trade.get('quantity')}") + print(f"Leverage: {trade.get('leverage')}") # Guessing key + print(f"Realized PnL: {trade.get('realized_pnl')}") + print(f"PnL % (DB): {trade.get('pnl_percent')}") + + # Calculate approximate ROE if leverage is known + leverage = trade.get('leverage', 1) + if leverage is None: leverage = 1 + + pnl_pct = trade.get('pnl_percent') + if pnl_pct: + roe = float(pnl_pct) * float(leverage) + print(f"Calc ROE (est): {roe:.2f}% (assuming leverage {leverage})") + +if __name__ == "__main__": + analyze_zro_trades() diff --git a/backend/database/connection.py b/backend/database/connection.py index aa80a9c..24f9995 100644 --- a/backend/database/connection.py +++ b/backend/database/connection.py @@ -77,7 +77,7 @@ class Database: pool_timeout=30, # 获取连接超时时间(秒) pool_pre_ping=True, # 预检测连接是否可用 connect_args={ - 'cursorclass': pymysql.cursors.DictCursor, + # 'cursorclass': pymysql.cursors.DictCursor, # Removed to prevent KeyError: 0 in SQLAlchemy init 'autocommit': False } ) @@ -94,6 +94,21 @@ class Database: # 获取原始pymysql连接 conn = Database._engine.raw_connection() + # Explicitly set cursor class to DictCursor since we removed it from create_engine + # We need to set it on the underlying DBAPI connection + try: + if hasattr(conn, 'driver_connection'): + # SQLAlchemy 2.0+ + conn.driver_connection.cursorclass = pymysql.cursors.DictCursor + elif hasattr(conn, 'connection'): + # Older SQLAlchemy + conn.connection.cursorclass = pymysql.cursors.DictCursor + else: + # Fallback + conn.cursorclass = pymysql.cursors.DictCursor + except Exception as e: + logger.warning(f"设置DictCursor失败: {e}") + # 设置时区为北京时间(UTC+8) # 注意:raw_connection可能不自动应用connect_args中的autocommit,需确认 # SQLAlchemy的raw_connection通常返回DBAPI连接,autocommit行为取决于驱动 diff --git a/test_db_pool.py b/test_db_pool.py new file mode 100644 index 0000000..aa1a68a --- /dev/null +++ b/test_db_pool.py @@ -0,0 +1,42 @@ +import os +import sys +import logging +import traceback +import pymysql +from backend.database.connection import Database + +# Configure logging +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger(__name__) + +def test_connection(): + print("Initializing Database...") + db = Database() + + print("Testing get_connection...") + try: + with db.get_connection() as conn: + print(f"Connection type: {type(conn)}") + # Try to force DictCursor here if the fix in connection.py isn't enough, + # but we want to test if connection.py does it automatically. + + with conn.cursor() as cursor: + cursor.execute("SELECT 1 as val") + result = cursor.fetchone() + print(f"Query result: {result}") + print(f"Result type: {type(result)}") + + if isinstance(result, dict): + print("SUCCESS: Result is a dictionary") + else: + print("FAILURE: Result is NOT a dictionary") + # Debug: try to find how to set it + if hasattr(conn, 'connection'): + print(f"Inner connection type: {type(conn.connection)}") + + except Exception as e: + print("Caught exception:") + traceback.print_exc() + +if __name__ == "__main__": + test_connection() diff --git a/trading_system/config.py b/trading_system/config.py index 813aa1a..7db71a1 100644 --- a/trading_system/config.py +++ b/trading_system/config.py @@ -256,7 +256,8 @@ def _get_trading_config(): 'MAX_LEVERAGE_SMALL_CAP': 8, # 小众币最大杠杆8倍 'ATR_LEVERAGE_REDUCTION_THRESHOLD': 0.05, # ATR超过5%时降低杠杆 'LEVERAGE': 8, # 基础杠杆降到8倍(山寨币波动大) - 'USE_DYNAMIC_LEVERAGE': False, # 不使用动态杠杆(保持简单) + 'USE_DYNAMIC_LEVERAGE': True, # 开启动态杠杆(基于止损宽度自动调整) + 'MAX_SINGLE_TRADE_LOSS_PERCENT': 20.0, # 单笔交易最大本金亏损率(20%),用于限制杠杆 'MAX_LEVERAGE': 12, # 最大杠杆12倍,不要超过 # 移动止损:必须开启!山寨币利润要保护 'USE_TRAILING_STOP': True, diff --git a/trading_system/risk_manager.py b/trading_system/risk_manager.py index 53daf0d..45dbb35 100644 --- a/trading_system/risk_manager.py +++ b/trading_system/risk_manager.py @@ -280,12 +280,70 @@ class RiskManager: current_price = ticker['price'] logger.info(f" 当前价格: {current_price:.4f} USDT") - # 重要语义:MAX_POSITION_PERCENT 表示“单笔保证金占用比例” - # 先确定实际杠杆(用于从保证金换算名义价值) - actual_leverage = leverage if leverage is not None else config.TRADING_CONFIG.get('LEVERAGE', 10) + # ------------------------------------------------------------------------- + # 动态杠杆计算逻辑 (针对 ZROUSDT 亏损案例优化) + # ------------------------------------------------------------------------- + # 如果启用了动态杠杆,根据止损宽度自动调整杠杆 + # 公式: 建议杠杆 = 目标最大单单亏损率 / 止损宽度 + # 例如: 目标亏损20%,止损宽度10%,则建议杠杆 = 20% / 10% = 2倍 + # ------------------------------------------------------------------------- + calculated_leverage = None + if config.TRADING_CONFIG.get('USE_DYNAMIC_LEVERAGE', False): + # 尝试获取止损价格(如果没有传入,尝试估算) + target_stop_loss = stop_loss_price + + # 如果没有传入止损价,尝试使用ATR估算(仅用于计算杠杆建议) + if target_stop_loss is None and atr and atr > 0 and entry_price: + atr_multiplier = config.TRADING_CONFIG.get('ATR_STOP_LOSS_MULTIPLIER', 2.5) + if side == 'BUY': + target_stop_loss = entry_price - (atr * atr_multiplier) + else: + target_stop_loss = entry_price + (atr * atr_multiplier) + + if target_stop_loss and entry_price: + # 计算止损宽度比例 + stop_loss_width = abs(entry_price - target_stop_loss) / entry_price + + if stop_loss_width > 0: + # 获取最大单笔亏损率限制 (默认20%) + max_loss_pct = config.TRADING_CONFIG.get('MAX_SINGLE_TRADE_LOSS_PERCENT', 20.0) / 100.0 + + # 计算建议杠杆 + # 理论杠杆 = 最大允许亏损比例 / 止损宽度比例 + theoretical_leverage = max_loss_pct / stop_loss_width + + # 向下取整 + suggested_leverage = int(theoretical_leverage) + + # 限制在最大杠杆范围内 + max_config_leverage = config.TRADING_CONFIG.get('MAX_LEVERAGE', 10) + final_dynamic_leverage = min(suggested_leverage, max_config_leverage) + + # 至少为1倍 + final_dynamic_leverage = max(1, final_dynamic_leverage) + + logger.info(f" ⚖️ 动态杠杆计算:") + logger.info(f" 止损价格: {target_stop_loss:.4f} (宽度: {stop_loss_width*100:.2f}%)") + logger.info(f" 最大单笔亏损限制: {max_loss_pct*100:.1f}%") + logger.info(f" 理论最大杠杆: {theoretical_leverage:.2f}x") + logger.info(f" 配置最大杠杆: {max_config_leverage}x") + logger.info(f" -> 最终建议杠杆: {final_dynamic_leverage}x") + + calculated_leverage = final_dynamic_leverage + + # 确定最终使用的杠杆 + # 优先级: 动态计算杠杆 > 传入的leverage参数 > 配置的默认LEVERAGE + if calculated_leverage is not None: + actual_leverage = calculated_leverage + if leverage is not None and leverage != actual_leverage: + logger.warning(f" ⚠️ 覆盖传入的杠杆 {leverage}x -> 动态调整为 {actual_leverage}x") + else: + actual_leverage = leverage if leverage is not None else config.TRADING_CONFIG.get('LEVERAGE', 10) + if not actual_leverage or actual_leverage <= 0: actual_leverage = 10 + # ⚠️ 优化3:固定风险百分比仓位计算(凯利公式) # 公式:仓位大小 = (总资金 * 每笔单子承受的风险%) / (入场价 - 止损价) use_fixed_risk = config.TRADING_CONFIG.get('USE_FIXED_RISK_SIZING', True)