stock-ai-agent/backend/app/api/bitget_live.py
2026-03-25 22:23:38 +08:00

242 lines
8.8 KiB
Python

"""
Bitget 实盘交易 API
提供 Bitget 实盘交易数据接口
"""
from fastapi import APIRouter, HTTPException, Query
from typing import Optional
from app.services.bitget_live_trading_service import get_bitget_live_service
from app.utils.logger import logger
router = APIRouter(prefix="/api/bitget", tags=["Bitget"])
def _get_service():
service = get_bitget_live_service()
if service is None:
return None
return service
@router.get("/account")
async def get_account():
"""获取 Bitget 账户状态"""
try:
service = _get_service()
if service is None:
return {"success": True, "enabled": False,
"message": "Bitget 服务未启用。请在 .env 中设置 bitget_trading_enabled=true"}
state = service.get_account_state()
positions = service.get_open_positions()
total_position_value = sum(abs(p["size"]) * p["entry_price"] for p in positions)
if state["account_value"] > 0:
current_leverage = total_position_value / state["account_value"]
else:
current_leverage = 0
if service.initial_balance and service.initial_balance > 0:
drawdown = (service.initial_balance - state["account_value"]) / service.initial_balance
else:
drawdown = 0
return {
"success": True,
"enabled": True,
"message": "Bitget 服务正常",
"data": {
"account_value": state["account_value"],
"available_balance": state["available_balance"],
"total_margin_used": state["total_margin_used"],
"total_position_value": total_position_value,
"current_total_leverage": current_leverage,
"max_total_leverage": service.max_total_leverage,
"initial_balance": service.initial_balance,
"drawdown_percent": drawdown * 100,
"circuit_breaker_threshold": service.circuit_breaker_drawdown * 100,
}
}
except Exception as e:
logger.error(f"获取 Bitget 账户状态失败: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/positions")
async def get_positions(
symbol: Optional[str] = Query(None, description="币种筛选,如 BTC")
):
"""获取 Bitget 持仓信息"""
try:
service = _get_service()
if service is None:
return {"success": True, "enabled": False, "positions": []}
all_positions = service.get_open_positions()
if symbol:
coin = symbol.replace("USDT", "").upper()
all_positions = [p for p in all_positions if p["coin"] == coin]
positions_data = []
for pos in all_positions:
coin = pos["coin"]
tp_sl = service.get_tp_sl_prices(coin)
positions_data.append({
"symbol": f"{coin}USDT",
"side": "long" if pos["size"] > 0 else "short",
"size": abs(pos["size"]),
"entry_price": pos["entry_price"],
"unrealized_pnl": pos["unrealized_pnl"],
"leverage": pos.get("leverage", "N/A"),
"liquidation_price": pos.get("liquidation_price"),
"take_profit": tp_sl.get("take_profit"),
"stop_loss": tp_sl.get("stop_loss"),
})
return {"success": True, "enabled": True, "count": len(positions_data), "positions": positions_data}
except Exception as e:
logger.error(f"获取 Bitget 持仓失败: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/orders")
async def get_orders(
symbol: Optional[str] = Query(None, description="币种筛选,如 BTC")
):
"""获取 Bitget 挂单信息"""
try:
service = _get_service()
if service is None:
return {"success": True, "enabled": False, "orders": []}
coin = symbol.replace("USDT", "").upper() if symbol else None
all_orders = service.get_open_orders(coin)
entry_orders = [o for o in all_orders if not o.get("is_reduce_only")]
tp_sl_orders = [o for o in all_orders if o.get("is_reduce_only")]
return {
"success": True,
"enabled": True,
"counts": {
"entry_orders": len(entry_orders),
"tp_sl_orders": len(tp_sl_orders),
"total": len(all_orders),
},
"entry_orders": entry_orders,
"tp_sl_orders": tp_sl_orders,
}
except Exception as e:
logger.error(f"获取 Bitget 挂单失败: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/summary")
async def get_summary():
"""获取 Bitget 交易摘要"""
try:
service = _get_service()
if service is None:
return {"success": True, "enabled": False, "message": "Bitget 服务未启用"}
state = service.get_account_state()
positions = service.get_open_positions()
orders = service.get_open_orders()
total_position_value = sum(abs(p["size"]) * p["entry_price"] for p in positions)
current_leverage = total_position_value / state["account_value"] if state["account_value"] > 0 else 0
drawdown = 0
if service.initial_balance and service.initial_balance > 0:
drawdown = (service.initial_balance - state["account_value"]) / service.initial_balance
return {
"success": True,
"enabled": True,
"data": {
"account": {
"account_value": state["account_value"],
"available_balance": state["available_balance"],
"total_margin_used": state["total_margin_used"],
},
"positions": {"count": len(positions), "total_value": total_position_value},
"orders": {
"count": len(orders),
"entry_orders": len([o for o in orders if not o.get("is_reduce_only")]),
"tp_sl_orders": len([o for o in orders if o.get("is_reduce_only")]),
},
"risk": {
"current_leverage": current_leverage,
"max_leverage": service.max_total_leverage,
"leverage_utilization": (current_leverage / service.max_total_leverage * 100) if service.max_total_leverage > 0 else 0,
"drawdown": drawdown * 100,
"circuit_breaker_threshold": service.circuit_breaker_drawdown * 100,
},
}
}
except Exception as e:
logger.error(f"获取 Bitget 摘要失败: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/orders/cancel")
async def cancel_orders(
symbol: str = Query(..., description="币种,如 BTC")
):
"""取消指定币种的所有挂单"""
try:
service = _get_service()
if service is None:
return {"success": False, "message": "Bitget 服务未启用"}
coin = symbol.replace("USDT", "")
result = service.cancel_all_orders(coin)
if result.get("success"):
return {"success": True, "message": f"已取消 {symbol} 的所有挂单"}
else:
return {"success": False, "message": result.get("error", "取消失败")}
except Exception as e:
logger.error(f"取消 Bitget 挂单失败: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/positions/close")
async def close_position(
symbol: str = Query(..., description="币种,如 BTC")
):
"""市价平仓指定币种(⚠️ 立即以市价平仓)"""
try:
service = _get_service()
if service is None:
return {"success": False, "message": "Bitget 服务未启用"}
coin = symbol.replace("USDT", "")
position = service.get_position_for_symbol(coin)
if not position:
return {"success": False, "message": f"未找到 {symbol} 的持仓"}
service.cancel_tp_sl_orders(coin)
size_in_coins = abs(position["size"])
is_long = position["size"] > 0
contracts = service.coins_to_contracts(coin, size_in_coins)
if contracts < 1:
return {"success": False, "message": f"持仓过小,无法下单 ({size_in_coins} 币)"}
result = service.place_market_order(
symbol=coin,
is_buy=not is_long,
size=contracts,
reduce_only=True
)
if result.get("success"):
return {"success": True, "message": f"已平仓 {symbol} {contracts}张 @ 市价"}
else:
return {"success": False, "message": result.get("error", "平仓失败")}
except Exception as e:
logger.error(f"Bitget 平仓失败: {e}")
raise HTTPException(status_code=500, detail=str(e))