1
This commit is contained in:
parent
6bee742413
commit
922a8f3820
45
analyze_zro.py
Normal file
45
analyze_zro.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -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行为取决于驱动
|
||||
|
|
|
|||
42
test_db_pool.py
Normal file
42
test_db_pool.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user