181 lines
6.2 KiB
Python
181 lines
6.2 KiB
Python
"""
|
||
账户实时数据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实时获取账户数据"""
|
||
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)
|
||
|
||
if not api_key or not api_secret:
|
||
raise HTTPException(
|
||
status_code=400,
|
||
detail="API密钥未配置,请在配置界面设置BINANCE_API_KEY和BINANCE_API_SECRET"
|
||
)
|
||
|
||
# 导入交易系统的BinanceClient
|
||
try:
|
||
from binance_client import BinanceClient
|
||
except ImportError:
|
||
# 如果直接导入失败,尝试从trading_system导入
|
||
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
|
||
)
|
||
|
||
await client.connect()
|
||
|
||
# 获取账户余额
|
||
balance = await client.get_account_balance()
|
||
|
||
# 获取持仓
|
||
positions = await client.get_open_positions()
|
||
|
||
# 计算总仓位价值和总盈亏
|
||
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
|
||
|
||
await client.disconnect()
|
||
|
||
return {
|
||
"total_balance": balance.get('total', 0),
|
||
"available_balance": balance.get('available', 0),
|
||
"total_position_value": total_position_value,
|
||
"total_pnl": total_pnl,
|
||
"open_positions": open_positions_count
|
||
}
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
logger.error(f"获取账户数据失败: {e}", exc_info=True)
|
||
raise HTTPException(status_code=500, detail=f"获取账户数据失败: {str(e)}")
|
||
|
||
|
||
@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)
|