""" 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))