auto_trade_sys/backend/api/routes/account.py
薇薇安 532adfe348 a
2026-01-15 18:46:20 +08:00

398 lines
16 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)
# 计算开仓时的USDT数量名义价值
entry_value_usdt = abs(position_amt) * entry_price
# 计算收益率:盈亏 / 保证金(与币安一致)
# 保证金 = 名义价值 / 杠杆
leverage = float(pos.get('leverage', 1))
margin = entry_value_usdt / leverage if leverage > 0 else entry_value_usdt
pnl_percent = 0
if margin > 0:
pnl_percent = (unrealized_pnl / margin) * 100
# 尝试从数据库获取开仓时间
entry_time = None
try:
from database.models import Trade
db_trades = Trade.get_by_symbol(pos.get('symbol'), status='open')
if db_trades:
# 找到匹配的交易记录通过symbol和entry_price匹配
for db_trade in db_trades:
if abs(float(db_trade.get('entry_price', 0)) - entry_price) < 0.01:
entry_time = db_trade.get('entry_time')
break
except Exception as e:
logger.debug(f"获取开仓时间失败: {e}")
formatted_positions.append({
"symbol": pos.get('symbol'),
"side": "BUY" if position_amt > 0 else "SELL",
"quantity": abs(position_amt),
"entry_price": entry_price,
"entry_value_usdt": entry_value_usdt, # 开仓时的USDT数量
"mark_price": mark_price,
"pnl": unrealized_pnl,
"pnl_percent": pnl_percent,
"leverage": int(pos.get('leverage', 1)),
"entry_time": entry_time # 开仓时间
})
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)
@router.post("/positions/{symbol}/close")
async def close_position(symbol: str):
"""手动平仓指定交易对的持仓"""
try:
logger.info(f"=" * 60)
logger.info(f"收到平仓请求: {symbol}")
logger.info(f"=" * 60)
# 从数据库读取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)
if not api_key or not api_secret:
error_msg = "API密钥未配置"
logger.warning(error_msg)
raise HTTPException(status_code=400, detail=error_msg)
# 导入必要的模块
try:
from binance_client import BinanceClient
from risk_manager import RiskManager
from position_manager import PositionManager
logger.info("✓ 成功导入交易系统模块")
except ImportError as import_error:
logger.warning(f"首次导入失败: {import_error}尝试从trading_system路径导入")
trading_system_path = project_root / 'trading_system'
sys.path.insert(0, str(trading_system_path))
from binance_client import BinanceClient
from risk_manager import RiskManager
from position_manager import PositionManager
logger.info("✓ 从trading_system路径导入成功")
# 创建客户端和仓位管理器
logger.info(f"创建BinanceClient (testnet={use_testnet})...")
client = BinanceClient(
api_key=api_key,
api_secret=api_secret,
testnet=use_testnet
)
logger.info("连接币安API...")
await client.connect()
logger.info("✓ 币安API连接成功")
try:
logger.info("初始化RiskManager和PositionManager...")
risk_manager = RiskManager(client)
position_manager = PositionManager(client, risk_manager)
logger.info("✓ 管理器初始化成功")
# 执行平仓reason='manual' 表示手动平仓)
logger.info(f"开始执行平仓操作: {symbol}...")
success = await position_manager.close_position(symbol, reason='manual')
if success:
logger.info(f"{symbol} 平仓成功")
return {
"message": f"{symbol} 平仓成功",
"symbol": symbol,
"status": "closed"
}
else:
logger.warning(f"{symbol} 平仓失败或持仓不存在")
return {
"message": f"{symbol} 平仓失败或持仓不存在",
"symbol": symbol,
"status": "failed"
}
finally:
logger.info("断开币安API连接...")
await client.disconnect()
logger.info("✓ 已断开连接")
except HTTPException:
raise
except Exception as e:
error_msg = f"平仓失败: {str(e)}"
logger.error("=" * 60)
logger.error(f"平仓操作异常: {error_msg}")
logger.error(f"错误类型: {type(e).__name__}")
logger.error("=" * 60, exc_info=True)
raise HTTPException(status_code=500, detail=error_msg)