auto_trade_sys/backend/api/routes/account.py
薇薇安 9160617b8e a
2026-01-13 21:37:32 +08:00

289 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
账户实时数据API - 从币安API获取实时账户和订单数据
"""
from fastapi import APIRouter, HTTPException
import sys
from pathlib import Path
import logging
project_root = Path(__file__).parent.parent.parent.parent
sys.path.insert(0, str(project_root))
sys.path.insert(0, str(project_root / 'backend'))
sys.path.insert(0, str(project_root / 'trading_system'))
from database.models import TradingConfig
logger = logging.getLogger(__name__)
router = APIRouter()
async def get_realtime_account_data():
"""从币安API实时获取账户数据"""
logger.info("=" * 60)
logger.info("开始获取实时账户数据")
logger.info("=" * 60)
try:
# 从数据库读取API密钥
logger.info("步骤1: 从数据库读取API配置...")
api_key = TradingConfig.get_value('BINANCE_API_KEY')
api_secret = TradingConfig.get_value('BINANCE_API_SECRET')
use_testnet = TradingConfig.get_value('USE_TESTNET', False)
logger.info(f" - API密钥存在: {bool(api_key)}")
if api_key:
logger.info(f" - API密钥长度: {len(api_key)} 字符")
logger.info(f" - API密钥前缀: {api_key[:10]}...")
else:
logger.warning(" - API密钥为空!")
logger.info(f" - API密钥存在: {bool(api_secret)}")
if api_secret:
logger.info(f" - API密钥长度: {len(api_secret)} 字符")
logger.info(f" - API密钥前缀: {api_secret[:10]}...")
else:
logger.warning(" - API密钥为空!")
logger.info(f" - 使用测试网: {use_testnet}")
if not api_key or not api_secret:
error_msg = "API密钥未配置请在配置界面设置BINANCE_API_KEY和BINANCE_API_SECRET"
logger.error(f"{error_msg}")
raise HTTPException(
status_code=400,
detail=error_msg
)
# 导入交易系统的BinanceClient
logger.info("步骤2: 导入BinanceClient...")
try:
from binance_client import BinanceClient
logger.info(" ✓ 从当前路径导入BinanceClient成功")
except ImportError as e:
logger.warning(f" - 从当前路径导入失败: {e}")
# 如果直接导入失败尝试从trading_system导入
trading_system_path = project_root / 'trading_system'
sys.path.insert(0, str(trading_system_path))
logger.info(f" - 添加路径到sys.path: {trading_system_path}")
try:
from binance_client import BinanceClient
logger.info(" ✓ 从trading_system路径导入BinanceClient成功")
except ImportError as e2:
logger.error(f" ✗ 导入BinanceClient失败: {e2}")
raise
# 创建客户端
logger.info("步骤3: 创建BinanceClient实例...")
client = BinanceClient(
api_key=api_key,
api_secret=api_secret,
testnet=use_testnet
)
logger.info(f" ✓ 客户端创建成功 (testnet={use_testnet})")
# 连接币安API
logger.info("步骤4: 连接币安API...")
try:
await client.connect()
logger.info(" ✓ 币安API连接成功")
except Exception as e:
logger.error(f" ✗ 币安API连接失败: {e}", exc_info=True)
raise
# 获取账户余额
logger.info("步骤5: 获取账户余额...")
try:
balance = await client.get_account_balance()
logger.info(" ✓ 账户余额获取成功")
logger.info(f" - 返回数据类型: {type(balance)}")
logger.info(f" - 返回数据内容: {balance}")
if balance:
logger.info(f" - 总余额: {balance.get('total', 'N/A')} USDT")
logger.info(f" - 可用余额: {balance.get('available', 'N/A')} USDT")
logger.info(f" - 保证金: {balance.get('margin', 'N/A')} USDT")
if balance.get('total', 0) == 0:
logger.warning(" ⚠ 账户余额为0可能是API权限问题或账户确实无余额")
else:
logger.warning(" ⚠ 返回的余额数据为空")
except Exception as e:
logger.error(f" ✗ 获取账户余额失败: {e}", exc_info=True)
raise
# 获取持仓
logger.info("步骤6: 获取持仓信息...")
try:
positions = await client.get_open_positions()
logger.info(" ✓ 持仓信息获取成功")
logger.info(f" - 返回数据类型: {type(positions)}")
logger.info(f" - 持仓数量: {len(positions)}")
if positions:
logger.info(" - 持仓详情:")
for i, pos in enumerate(positions[:5], 1): # 只显示前5个
logger.info(f" {i}. {pos.get('symbol', 'N/A')}: "
f"数量={pos.get('positionAmt', 0)}, "
f"入场价={pos.get('entryPrice', 0)}, "
f"盈亏={pos.get('unRealizedProfit', 0)}")
if len(positions) > 5:
logger.info(f" ... 还有 {len(positions) - 5} 个持仓")
else:
logger.info(" - 当前无持仓")
except Exception as e:
logger.error(f" ✗ 获取持仓信息失败: {e}", exc_info=True)
raise
# 计算总仓位价值和总盈亏
logger.info("步骤7: 计算仓位统计...")
total_position_value = 0
total_pnl = 0
open_positions_count = 0
for pos in positions:
position_amt = float(pos.get('positionAmt', 0))
if position_amt == 0:
continue
entry_price = float(pos.get('entryPrice', 0))
mark_price = float(pos.get('markPrice', 0))
unrealized_pnl = float(pos.get('unRealizedProfit', 0))
if mark_price == 0:
# 如果没有标记价格,使用入场价
mark_price = entry_price
position_value = abs(position_amt * mark_price)
total_position_value += position_value
total_pnl += unrealized_pnl
open_positions_count += 1
logger.debug(f" - {pos.get('symbol')}: 价值={position_value:.2f}, 盈亏={unrealized_pnl:.2f}")
logger.info(" ✓ 仓位统计计算完成")
logger.info(f" - 总仓位价值: {total_position_value:.2f} USDT")
logger.info(f" - 总盈亏: {total_pnl:.2f} USDT")
logger.info(f" - 持仓数量: {open_positions_count}")
# 断开连接
logger.info("步骤8: 断开币安API连接...")
try:
await client.disconnect()
logger.info(" ✓ 连接已断开")
except Exception as e:
logger.warning(f" ⚠ 断开连接时出错: {e}")
# 构建返回结果
result = {
"total_balance": balance.get('total', 0) if balance else 0,
"available_balance": balance.get('available', 0) if balance else 0,
"total_position_value": total_position_value,
"total_pnl": total_pnl,
"open_positions": open_positions_count
}
logger.info("=" * 60)
logger.info("账户数据获取成功!")
logger.info(f"最终结果: {result}")
logger.info("=" * 60)
return result
except HTTPException as e:
logger.error("=" * 60)
logger.error(f"HTTP异常: {e.status_code} - {e.detail}")
logger.error("=" * 60)
raise
except Exception as e:
error_msg = f"获取账户数据失败: {str(e)}"
logger.error("=" * 60)
logger.error(f"异常类型: {type(e).__name__}")
logger.error(f"错误信息: {error_msg}")
logger.error("=" * 60, exc_info=True)
raise HTTPException(status_code=500, detail=error_msg)
@router.get("/realtime")
async def get_realtime_account():
"""获取实时账户数据"""
return await get_realtime_account_data()
@router.get("/positions")
async def get_realtime_positions():
"""获取实时持仓数据"""
try:
# 从数据库读取API密钥
api_key = TradingConfig.get_value('BINANCE_API_KEY')
api_secret = TradingConfig.get_value('BINANCE_API_SECRET')
use_testnet = TradingConfig.get_value('USE_TESTNET', False)
logger.info(f"尝试获取实时持仓数据 (testnet={use_testnet})")
if not api_key or not api_secret:
error_msg = "API密钥未配置"
logger.warning(error_msg)
raise HTTPException(
status_code=400,
detail=error_msg
)
# 导入BinanceClient
try:
from binance_client import BinanceClient
except ImportError:
trading_system_path = project_root / 'trading_system'
sys.path.insert(0, str(trading_system_path))
from binance_client import BinanceClient
client = BinanceClient(
api_key=api_key,
api_secret=api_secret,
testnet=use_testnet
)
logger.info("连接币安API获取持仓...")
await client.connect()
positions = await client.get_open_positions()
await client.disconnect()
logger.info(f"获取到 {len(positions)} 个持仓")
# 格式化持仓数据
formatted_positions = []
for pos in positions:
position_amt = float(pos.get('positionAmt', 0))
if position_amt == 0:
continue
entry_price = float(pos.get('entryPrice', 0))
mark_price = float(pos.get('markPrice', 0))
unrealized_pnl = float(pos.get('unRealizedProfit', 0))
if mark_price == 0:
mark_price = entry_price
position_value = abs(position_amt * mark_price)
pnl_percent = 0
if entry_price > 0 and position_value > 0:
pnl_percent = (unrealized_pnl / position_value) * 100
formatted_positions.append({
"symbol": pos.get('symbol'),
"side": "BUY" if position_amt > 0 else "SELL",
"quantity": abs(position_amt),
"entry_price": entry_price,
"mark_price": mark_price,
"pnl": unrealized_pnl,
"pnl_percent": pnl_percent,
"leverage": int(pos.get('leverage', 1))
})
logger.info(f"格式化后 {len(formatted_positions)} 个有效持仓")
return formatted_positions
except HTTPException:
raise
except Exception as e:
error_msg = f"获取持仓数据失败: {str(e)}"
logger.error(error_msg, exc_info=True)
raise HTTPException(status_code=500, detail=error_msg)