diff --git a/app/api/account.py b/app/api/account.py index 383b73c..318e268 100644 --- a/app/api/account.py +++ b/app/api/account.py @@ -1,8 +1,8 @@ -from fastapi import APIRouter, Query, HTTPException, Body +from fastapi import APIRouter, Query, HTTPException +from app.schemas.account import AccountResponseSchema from app.services.bybit_service import get_bybit_client import json import os -from typing import List, Optional router = APIRouter() @@ -19,93 +19,7 @@ def get_user_api(user_id: str): raise HTTPException(status_code=500, detail=f"Lỗi đọc file accounts: {e}") raise HTTPException(status_code=404, detail="Không tìm thấy userId") -# API lấy danh sách user -@router.get("/users") -def list_users(): - try: - with open(ACCOUNTS_FILE, 'r', encoding='utf-8') as f: - accounts = json.load(f) - return accounts - except Exception as e: - raise HTTPException(status_code=500, detail=f"Lỗi đọc file accounts: {e}") - -# API thêm user -@router.post("/users") -def add_user( - id: str = Body(..., description="ID user"), - bybit_api_key: str = Body(..., description="Bybit API Key"), - bybit_api_secret: str = Body(..., description="Bybit API Secret"), - user_name: str = Body(..., description="Tên user") -): - try: - with open(ACCOUNTS_FILE, 'r+', encoding='utf-8') as f: - accounts = json.load(f) - if any(acc['id'] == id for acc in accounts): - raise HTTPException(status_code=400, detail="ID đã tồn tại") - new_user = { - "id": id, - "bybit_api_key": bybit_api_key, - "bybit_api_secret": bybit_api_secret, - "user_name": user_name - } - accounts.append(new_user) - f.seek(0) - json.dump(accounts, f, ensure_ascii=False, indent=2) - f.truncate() - return {"message": "Thêm user thành công"} - except HTTPException as e: - raise e - except Exception as e: - raise HTTPException(status_code=500, detail=f"Lỗi ghi file accounts: {e}") - -# API sửa user -@router.put("/users/{user_id}") -def update_user( - user_id: str, - bybit_api_key: Optional[str] = Body(None), - bybit_api_secret: Optional[str] = Body(None), - user_name: Optional[str] = Body(None) -): - try: - with open(ACCOUNTS_FILE, 'r+', encoding='utf-8') as f: - accounts = json.load(f) - for acc in accounts: - if acc['id'] == user_id: - if bybit_api_key is not None: - acc['bybit_api_key'] = bybit_api_key - if bybit_api_secret is not None: - acc['bybit_api_secret'] = bybit_api_secret - if user_name is not None: - acc['user_name'] = user_name - f.seek(0) - json.dump(accounts, f, ensure_ascii=False, indent=2) - f.truncate() - return {"message": "Cập nhật user thành công"} - raise HTTPException(status_code=404, detail="Không tìm thấy userId") - except HTTPException as e: - raise e - except Exception as e: - raise HTTPException(status_code=500, detail=f"Lỗi ghi file accounts: {e}") - -# API xóa user -@router.delete("/users/{user_id}") -def delete_user(user_id: str): - try: - with open(ACCOUNTS_FILE, 'r+', encoding='utf-8') as f: - accounts = json.load(f) - new_accounts = [acc for acc in accounts if acc['id'] != user_id] - if len(new_accounts) == len(accounts): - raise HTTPException(status_code=404, detail="Không tìm thấy userId") - f.seek(0) - json.dump(new_accounts, f, ensure_ascii=False, indent=2) - f.truncate() - return {"message": "Xóa user thành công"} - except HTTPException as e: - raise e - except Exception as e: - raise HTTPException(status_code=500, detail=f"Lỗi ghi file accounts: {e}") - -@router.get("/account") +@router.get("/account", response_model=AccountResponseSchema, tags=["Account"]) def get_account_info(userId: str = Query(..., description="ID của user để lấy API key/secret")): try: api_key, api_secret = get_user_api(userId) diff --git a/app/api/candles.py b/app/api/candles.py index 76318f5..bea9e94 100644 --- a/app/api/candles.py +++ b/app/api/candles.py @@ -1,6 +1,7 @@ from fastapi import APIRouter, Query, HTTPException from typing import Optional from app.services.bybit_service import get_bybit_client +from app.schemas.candle import CandleResponseSchema import json import os import pandas as pd @@ -42,7 +43,7 @@ def rsi(series, period=14): rsi = rsi.replace([np.inf, -np.inf], np.nan) return rsi -@router.get("/candles") +@router.get("/candles", response_model=CandleResponseSchema, response_model_exclude_none=True, tags=["Candle"]) def get_candles( userId: str = Query(..., description="ID của user để lấy API key/secret"), symbol: str = Query(..., description="Mã giao dịch, ví dụ: BTCUSDT"), diff --git a/app/api/orders.py b/app/api/orders.py index 03b9fbc..2e945c0 100644 --- a/app/api/orders.py +++ b/app/api/orders.py @@ -19,7 +19,7 @@ def get_user_api(user_id: str): raise HTTPException(status_code=500, detail=f"Lỗi đọc file accounts: {e}") raise HTTPException(status_code=404, detail="Không tìm thấy userId") -@router.get("/orders") +@router.get("/orders", tags=["Order"]) def get_orders( userId: str = Query(..., description="ID của user để lấy API key/secret"), symbol: str = Query(..., description="Mã giao dịch, ví dụ: BTCUSDT"), @@ -34,7 +34,7 @@ def get_orders( return res # Submit order -@router.post("/orders") +@router.post("/orders", tags=["Order"]) def submit_order( userId: str = Body(..., embed=True, description="ID của user để lấy API key/secret"), symbol: str = Body(..., embed=True, description="Mã giao dịch, ví dụ: BTCUSDT"), @@ -61,7 +61,7 @@ def submit_order( return res # Cancel order -@router.delete("/orders/{order_id}") +@router.delete("/orders/{order_id}", tags=["Order"]) def cancel_order( order_id: str, userId: str = Query(..., description="ID của user để lấy API key/secret"), diff --git a/app/api/positions.py b/app/api/positions.py index f5e7548..ed38522 100644 --- a/app/api/positions.py +++ b/app/api/positions.py @@ -19,7 +19,7 @@ def get_user_api(user_id: str): raise HTTPException(status_code=500, detail=f"Lỗi đọc file accounts: {e}") raise HTTPException(status_code=404, detail="Không tìm thấy userId") -@router.get("/positions") +@router.get("/positions", tags=["Position"]) def get_positions( userId: str = Query(..., description="ID của user để lấy API key/secret"), symbol: Optional[str] = Query(None, description="Mã giao dịch, ví dụ: BTCUSDT"), diff --git a/app/api/user/user.py b/app/api/user/user.py new file mode 100644 index 0000000..35c84ef --- /dev/null +++ b/app/api/user/user.py @@ -0,0 +1,94 @@ +from fastapi import APIRouter, Query, HTTPException, Body +import json +import os +from typing import Optional + +router = APIRouter() + +ACCOUNTS_FILE = os.path.join(os.path.dirname(__file__), '../../../accounts.json') + +# API lấy danh sách user +@router.get("/users", tags=["User"]) +def list_users(): + try: + with open(ACCOUNTS_FILE, 'r', encoding='utf-8') as f: + accounts = json.load(f) + return accounts + except Exception as e: + raise HTTPException(status_code=500, detail=f"Lỗi đọc file accounts: {e}") + +# API thêm user +@router.post("/users", tags=["User"]) +def add_user( + id: str = Body(..., description="ID user"), + bybit_api_key: str = Body(..., description="Bybit API Key"), + bybit_api_secret: str = Body(..., description="Bybit API Secret"), + user_name: str = Body(..., description="Tên user") +): + try: + with open(ACCOUNTS_FILE, 'r+', encoding='utf-8') as f: + accounts = json.load(f) + if any(acc['id'] == id for acc in accounts): + raise HTTPException(status_code=400, detail="ID đã tồn tại") + new_user = { + "id": id, + "bybit_api_key": bybit_api_key, + "bybit_api_secret": bybit_api_secret, + "user_name": user_name + } + accounts.append(new_user) + f.seek(0) + json.dump(accounts, f, ensure_ascii=False, indent=2) + f.truncate() + return {"message": "Thêm user thành công"} + except HTTPException as e: + raise e + except Exception as e: + raise HTTPException(status_code=500, detail=f"Lỗi ghi file accounts: {e}") + +# API sửa user +@router.put("/users/{user_id}", tags=["User"]) +def update_user( + user_id: str, + bybit_api_key: Optional[str] = Body(None), + bybit_api_secret: Optional[str] = Body(None), + user_name: Optional[str] = Body(None) +): + try: + with open(ACCOUNTS_FILE, 'r+', encoding='utf-8') as f: + accounts = json.load(f) + for acc in accounts: + if acc['id'] == user_id: + if bybit_api_key is not None: + acc['bybit_api_key'] = bybit_api_key + if bybit_api_secret is not None: + acc['bybit_api_secret'] = bybit_api_secret + if user_name is not None: + acc['user_name'] = user_name + f.seek(0) + json.dump(accounts, f, ensure_ascii=False, indent=2) + f.truncate() + return {"message": "Cập nhật user thành công"} + raise HTTPException(status_code=404, detail="Không tìm thấy userId") + except HTTPException as e: + raise e + except Exception as e: + raise HTTPException(status_code=500, detail=f"Lỗi ghi file accounts: {e}") + +# API xóa user +@router.delete("/users/{user_id}", tags=["User"]) +def delete_user(user_id: str): + try: + with open(ACCOUNTS_FILE, 'r+', encoding='utf-8') as f: + accounts = json.load(f) + new_accounts = [acc for acc in accounts if acc['id'] != user_id] + if len(new_accounts) == len(accounts): + raise HTTPException(status_code=404, detail="Không tìm thấy userId") + f.seek(0) + json.dump(new_accounts, f, ensure_ascii=False, indent=2) + f.truncate() + return {"message": "Xóa user thành công"} + except HTTPException as e: + raise e + except Exception as e: + raise HTTPException(status_code=500, detail=f"Lỗi ghi file accounts: {e}") \ No newline at end of file diff --git a/app/main.py b/app/main.py index 166a6bb..e5c43f7 100644 --- a/app/main.py +++ b/app/main.py @@ -1,5 +1,6 @@ from fastapi import FastAPI from app.api import candles, orders, account, positions +from app.api.user import user app = FastAPI() @@ -7,3 +8,4 @@ app.include_router(candles.router, prefix="/api") app.include_router(orders.router, prefix="/api") app.include_router(account.router, prefix="/api") app.include_router(positions.router, prefix="/api") +app.include_router(user.router, prefix="/api") diff --git a/app/schemas/account.py b/app/schemas/account.py new file mode 100644 index 0000000..278e052 --- /dev/null +++ b/app/schemas/account.py @@ -0,0 +1,46 @@ +from pydantic import BaseModel, Field +from typing import List, Optional, Dict, Any + +class CoinInfo(BaseModel): + availableToBorrow: Optional[str] = Field(None, description="Số lượng có thể vay") + bonus: Optional[str] = Field(None, description="Tiền thưởng") + accruedInterest: Optional[str] = Field(None, description="Lãi tích lũy") + availableToWithdraw: Optional[str] = Field(None, description="Số lượng có thể rút") + totalOrderIM: Optional[str] = Field(None, description="Initial margin cho lệnh") + equity: Optional[str] = Field(None, description="Tổng tài sản coin") + totalPositionMM: Optional[str] = Field(None, description="Maintenance margin cho vị thế") + usdValue: Optional[str] = Field(None, description="Giá trị USD của coin") + unrealisedPnl: Optional[str] = Field(None, description="Lãi/lỗ chưa chốt") + collateralSwitch: Optional[bool] = Field(None, description="Có dùng làm tài sản thế chấp không") + spotHedgingQty: Optional[str] = Field(None, description="Số lượng spot hedging") + borrowAmount: Optional[str] = Field(None, description="Số lượng đã vay") + totalPositionIM: Optional[str] = Field(None, description="Initial margin cho vị thế") + walletBalance: Optional[str] = Field(None, description="Số dư ví coin") + cumRealisedPnl: Optional[str] = Field(None, description="Lãi/lỗ đã chốt tích lũy") + locked: Optional[str] = Field(None, description="Số lượng bị khóa") + marginCollateral: Optional[bool] = Field(None, description="Có dùng làm tài sản margin không") + coin: str = Field(..., description="Mã coin") + +class AccountInfo(BaseModel): + totalEquity: str = Field(..., description="Tổng tài sản quy USD") + accountIMRate: str = Field(..., description="Initial margin rate") + totalMarginBalance: str = Field(..., description="Tổng margin balance") + totalInitialMargin: str = Field(..., description="Tổng initial margin") + accountType: str = Field(..., description="Loại tài khoản (ví dụ: UNIFIED)") + totalAvailableBalance: str = Field(..., description="Tổng số dư khả dụng") + accountMMRate: str = Field(..., description="Maintenance margin rate") + totalPerpUPL: str = Field(..., description="Lãi/lỗ chưa chốt của perpetual") + totalWalletBalance: str = Field(..., description="Tổng số dư ví") + accountLTV: str = Field(..., description="Loan to value") + totalMaintenanceMargin: str = Field(..., description="Tổng maintenance margin") + coin: List[CoinInfo] = Field(..., description="Danh sách coin") + +class ResultInfo(BaseModel): + list: List[AccountInfo] = Field(..., description="Danh sách tài khoản") + +class AccountResponseSchema(BaseModel): + retCode: int = Field(..., description="Mã kết quả trả về (0 là thành công)") + retMsg: str = Field(..., description="Thông báo kết quả") + result: ResultInfo = Field(..., description="Kết quả chi tiết") + retExtInfo: Dict[str, Any] = Field(..., description="Thông tin mở rộng") + time: int = Field(..., description="Timestamp trả về") \ No newline at end of file diff --git a/app/schemas/candle.py b/app/schemas/candle.py new file mode 100644 index 0000000..ac46bd3 --- /dev/null +++ b/app/schemas/candle.py @@ -0,0 +1,23 @@ +from pydantic import BaseModel, Field +from typing import List, Optional, Any + +class CandleSchema(BaseModel): + timestamp: int = Field(..., description="Thời gian (miliseconds)") + open: float = Field(..., description="Giá mở cửa") + high: float = Field(..., description="Giá cao nhất") + low: float = Field(..., description="Giá thấp nhất") + close: float = Field(..., description="Giá đóng cửa") + volume: float = Field(..., description="Khối lượng giao dịch") + turnover: float = Field(..., description="Giá trị giao dịch") + macd: Optional[float] = Field(None, description="MACD value") + macdsignal: Optional[float] = Field(None, description="MACD signal") + macdhist: Optional[float] = Field(None, description="MACD histogram") + ema34: Optional[float] = Field(None, description="EMA 34") + ema50: Optional[float] = Field(None, description="EMA 50") + ema100: Optional[float] = Field(None, description="EMA 100") + ema200: Optional[float] = Field(None, description="EMA 200") + rsi: Optional[float] = Field(None, description="RSI") + +class CandleResponseSchema(BaseModel): + data: List[CandleSchema] + indicators: Any \ No newline at end of file