from fastapi import APIRouter, Body, Cookie, HTTPException from app.db.live_trading import ( create_live_order_intent, delete_live_account, get_live_trading_summary, create_live_order_intents_for_accounts, list_live_accounts, list_live_order_events, list_live_order_intents, prepare_intent_from_paper_trade, update_live_account, upsert_live_account, ) from app.services.live_trading_smoke import run_binance_testnet_smoke from app.services.live_trading_account import get_live_account_overview from app.services.live_trading_sync import sync_open_paper_trades_to_live, sync_paper_trade_to_live from app.integrations.binance_live import LiveTradingConfigError from app.web.shared import require_admin router = APIRouter() @router.get("/api/live-trading/summary") async def api_live_trading_summary(altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) return get_live_trading_summary() @router.get("/api/live-trading/accounts") async def api_live_trading_accounts(altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) return list_live_accounts() @router.get("/api/live-trading/accounts/{account_id}/overview") async def api_live_trading_account_overview(account_id: int, refresh: int = 0, altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) return get_live_account_overview(account_id, refresh=bool(refresh)) @router.post("/api/live-trading/accounts") async def api_live_trading_account(payload: dict = Body(default={}), altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) return upsert_live_account( account_code=payload.get("account_code", ""), exchange=payload.get("exchange", ""), market_type=payload.get("market_type", ""), testnet=payload.get("testnet") if "testnet" in payload else None, status=payload.get("status", ""), api_key_env=payload.get("api_key_env", ""), api_secret_env=payload.get("api_secret_env", ""), permissions=payload.get("permissions") if isinstance(payload.get("permissions"), dict) else None, risk_config=payload.get("risk_config") if isinstance(payload.get("risk_config"), dict) else None, ) @router.put("/api/live-trading/accounts/{account_id}") async def api_live_trading_update_account(account_id: int, payload: dict = Body(default={}), altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) try: result = update_live_account( account_id, account_code=payload.get("account_code", ""), exchange=payload.get("exchange", ""), market_type=payload.get("market_type", ""), testnet=payload.get("testnet") if "testnet" in payload else None, status=payload.get("status", ""), api_key_env=payload.get("api_key_env", ""), api_secret_env=payload.get("api_secret_env", ""), permissions=payload.get("permissions") if isinstance(payload.get("permissions"), dict) else None, risk_config=payload.get("risk_config") if isinstance(payload.get("risk_config"), dict) else None, ) except Exception as exc: raise HTTPException(status_code=409, detail=f"account_update_failed: {exc}") from exc if not result.get("ok"): raise HTTPException(status_code=404, detail=result.get("reason", "account_not_found")) return result @router.delete("/api/live-trading/accounts/{account_id}") async def api_live_trading_delete_account(account_id: int, altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) result = delete_live_account(account_id) if not result.get("ok"): raise HTTPException(status_code=404, detail=result.get("reason", "account_not_found")) return result @router.post("/api/live-trading/accounts/default") async def api_live_trading_default_account(altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) return upsert_live_account() @router.get("/api/live-trading/intents") async def api_live_trading_intents( limit: int = 50, offset: int = 0, status: str = "", altcoin_session: str = Cookie(default=""), ): require_admin(altcoin_session) return list_live_order_intents(limit=limit, offset=offset, status=status) @router.post("/api/live-trading/intents") async def api_live_trading_create_intent(payload: dict = Body(default={}), altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) account_ids = payload.pop("account_ids", None) if isinstance(account_ids, list) and account_ids: return create_live_order_intents_for_accounts(payload, account_ids=account_ids, source_type="manual") return create_live_order_intent(payload, source_type="manual") @router.post("/api/live-trading/intents/from-paper/{paper_trade_id}") async def api_live_trading_from_paper(paper_trade_id: int, payload: dict = Body(default={}), altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) account_ids = payload.get("account_ids") if isinstance(payload, dict) else None return prepare_intent_from_paper_trade(paper_trade_id, account_ids=account_ids if isinstance(account_ids, list) else None) @router.post("/api/live-trading/sync/from-paper/{paper_trade_id}") async def api_live_trading_sync_from_paper(paper_trade_id: int, payload: dict = Body(default={}), altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) account_ids = payload.get("account_ids") if isinstance(payload, dict) else None execute = bool(payload.get("execute", True)) if isinstance(payload, dict) else True return sync_paper_trade_to_live( paper_trade_id, account_ids=account_ids if isinstance(account_ids, list) else None, execute=execute, ) @router.post("/api/live-trading/sync/open-paper-trades") async def api_live_trading_sync_open_paper_trades(payload: dict = Body(default={}), altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) return sync_open_paper_trades_to_live( limit=int(payload.get("limit") or 20), execute=bool(payload.get("execute", True)), ) @router.get("/api/live-trading/events") async def api_live_trading_events( limit: int = 80, offset: int = 0, intent_id: int = 0, altcoin_session: str = Cookie(default=""), ): require_admin(altcoin_session) return list_live_order_events(limit=limit, offset=offset, intent_id=intent_id) @router.post("/api/live-trading/smoke/binance") async def api_live_trading_binance_smoke(payload: dict = Body(default={}), altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) try: return run_binance_testnet_smoke( account_id=int(payload.get("account_id") or 0), symbol=str(payload.get("symbol") or "BTC/USDT"), notional_usdt=float(payload.get("notional_usdt") or 10), leverage=float(payload.get("leverage") or 1), intent_id=int(payload.get("intent_id") or 0), ) except LiveTradingConfigError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc except Exception as exc: raise HTTPException(status_code=502, detail=f"exchange smoke failed: {exc}") from exc @router.post("/api/live-trading/smoke/binance-testnet") async def api_live_trading_binance_testnet_smoke(payload: dict = Body(default={}), altcoin_session: str = Cookie(default="")): return await api_live_trading_binance_smoke(payload=payload, altcoin_session=altcoin_session)