This commit is contained in:
KienVT9 2025-05-22 19:04:57 +07:00
parent ac32800ba7
commit 7cc42ee6a1
7 changed files with 82 additions and 82 deletions

View file

@ -20,7 +20,7 @@ def get_user_api(user_id: str):
raise HTTPException(status_code=404, detail="Không tìm thấy userId") raise HTTPException(status_code=404, detail="Không tìm thấy userId")
@router.get("/account", response_model=AccountResponseSchema, tags=["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")): def get_account_info(userId: str = Query(..., description="User ID to get API key/secret")):
try: try:
api_key, api_secret = get_user_api(userId) api_key, api_secret = get_user_api(userId)
client = get_bybit_client(api_key, api_secret) client = get_bybit_client(api_key, api_secret)
@ -31,4 +31,4 @@ def get_account_info(userId: str = Query(..., description="ID của user để l
except HTTPException as e: except HTTPException as e:
raise e raise e
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {e}") raise HTTPException(status_code=500, detail=f"System error: {e}")

View file

@ -45,17 +45,17 @@ def rsi(series, period=14):
@router.get("/candles", response_model=CandleResponseSchema, response_model_exclude_none=True, tags=["Candle"]) @router.get("/candles", response_model=CandleResponseSchema, response_model_exclude_none=True, tags=["Candle"])
def get_candles( def get_candles(
userId: str = Query(..., description="ID của user để lấy API key/secret"), userId: str = Query(..., description="User ID to get API key/secret"),
symbol: str = Query(..., description="Mã giao dịch, ví dụ: BTCUSDT"), symbol: str = Query(..., description="Trading symbol, e.g. BTCUSDT"),
interval: str = Query("1", description="Khung thời gian nến, ví dụ: 1, 3, 5, 15, 30, 60, 240, D, W, M"), interval: str = Query("1", description="Candle interval, e.g. 1, 3, 5, 15, 30, 60, 240, D, W, M"),
limit: Optional[int] = Query(200, description="Số lượng nến trả về (tối đa 1000)"), limit: Optional[int] = Query(200, description="Number of candles to return (max 1000)"),
start: Optional[int] = Query(None, description="Timestamp bắt đầu (miliseconds)"), start: Optional[int] = Query(None, description="Start timestamp (milliseconds)"),
end: Optional[int] = Query(None, description="Timestamp kết thúc (miliseconds)") end: Optional[int] = Query(None, description="End timestamp (milliseconds)")
): ):
api_key, api_secret = get_user_api(userId) api_key, api_secret = get_user_api(userId)
client = get_bybit_client(api_key, api_secret) client = get_bybit_client(api_key, api_secret)
params = { params = {
"category": "linear", # hoặc "spot" nếu lấy spot "category": "linear", # or "spot" for spot
"symbol": symbol, "symbol": symbol,
"interval": interval, "interval": interval,
"limit": limit "limit": limit
@ -70,7 +70,7 @@ def get_candles(
candles = res.get('result', {}).get('list', []) candles = res.get('result', {}).get('list', [])
if not candles: if not candles:
return {"data": [], "indicators": {}} return {"data": [], "indicators": {}}
# Chuyển đổi sang DataFrame # Convert to DataFrame
df = pd.DataFrame(candles, columns=[ df = pd.DataFrame(candles, columns=[
"timestamp", "open", "high", "low", "close", "volume", "turnover" "timestamp", "open", "high", "low", "close", "volume", "turnover"
]) ])
@ -84,7 +84,7 @@ def get_candles(
"turnover": np.float64 "turnover": np.float64
}) })
df = df.sort_values("timestamp", ascending=False) df = df.sort_values("timestamp", ascending=False)
# Tính chỉ báo # Calculate indicators
close = df["close"] close = df["close"]
macd_line, macd_signal, macd_hist = macd(close, fast=45, slow=90, signal=9) macd_line, macd_signal, macd_hist = macd(close, fast=45, slow=90, signal=9)
df["macd"] = macd_line df["macd"] = macd_line
@ -95,7 +95,7 @@ def get_candles(
df["ema100"] = ema(close, 100) df["ema100"] = ema(close, 100)
df["ema200"] = ema(close, 200) df["ema200"] = ema(close, 200)
df["rsi"] = rsi(close) df["rsi"] = rsi(close)
# Trả về kết quả # Return result
data = df.replace({np.nan: None}).to_dict(orient="records") data = df.replace({np.nan: None}).to_dict(orient="records")
return { return {
"data": data, "data": data,

View file

@ -21,14 +21,14 @@ def get_user_api(user_id: str):
@router.get("/orders", tags=["Order"]) @router.get("/orders", tags=["Order"])
def get_orders( def get_orders(
userId: str = Query(..., description="ID của user để lấy API key/secret"), userId: str = Query(..., description="User ID to get API key/secret"),
symbol: str = Query(..., description="Mã giao dịch, ví dụ: BTCUSDT"), symbol: str = Query(..., description="Trading symbol, e.g. BTCUSDT"),
category: str = Query("linear", description="Loại lệnh: linear (future) hoặc spot") category: str = Query("linear", description="Order type: linear (future) or spot")
): ):
api_key, api_secret = get_user_api(userId) api_key, api_secret = get_user_api(userId)
client = get_bybit_client(api_key, api_secret) client = get_bybit_client(api_key, api_secret)
res = client.get_open_orders( res = client.get_open_orders(
category=category, # "linear" cho future, "spot" cho spot category=category, # "linear" for future, "spot" for spot
symbol=symbol symbol=symbol
) )
return res return res
@ -36,13 +36,13 @@ def get_orders(
# Submit order # Submit order
@router.post("/orders", tags=["Order"]) @router.post("/orders", tags=["Order"])
def submit_order( def submit_order(
userId: str = Body(..., embed=True, description="ID của user để lấy API key/secret"), userId: str = Body(..., embed=True, description="User ID to get API key/secret"),
symbol: str = Body(..., embed=True, description="Mã giao dịch, ví dụ: BTCUSDT"), symbol: str = Body(..., embed=True, description="Trading symbol, e.g. BTCUSDT"),
side: str = Body(..., embed=True, description="side: Buy hoặc Sell"), side: str = Body(..., embed=True, description="Order side: Buy or Sell"),
orderType: str = Body(..., embed=True, description="Loại lệnh: Market hoặc Limit"), orderType: str = Body(..., embed=True, description="Order type: Market or Limit"),
qty: float = Body(..., embed=True, description="Số lượng"), qty: float = Body(..., embed=True, description="Order quantity"),
category: str = Body("linear", embed=True, description="Loại lệnh: linear (future) hoặc spot"), category: str = Body("linear", embed=True, description="Order type: linear (future) or spot"),
price: Optional[float] = Body(None, embed=True, description="Giá (bắt buộc với Limit)") price: Optional[float] = Body(None, embed=True, description="Order price (required for Limit)")
): ):
api_key, api_secret = get_user_api(userId) api_key, api_secret = get_user_api(userId)
client = get_bybit_client(api_key, api_secret) client = get_bybit_client(api_key, api_secret)
@ -55,7 +55,7 @@ def submit_order(
} }
if orderType.lower() == "limit": if orderType.lower() == "limit":
if price is None: if price is None:
raise HTTPException(status_code=400, detail="Thiếu giá cho lệnh Limit") raise HTTPException(status_code=400, detail="Missing price for Limit order")
params["price"] = price params["price"] = price
res = client.place_order(**params) res = client.place_order(**params)
return res return res
@ -64,9 +64,9 @@ def submit_order(
@router.delete("/orders/{order_id}", tags=["Order"]) @router.delete("/orders/{order_id}", tags=["Order"])
def cancel_order( def cancel_order(
order_id: str, order_id: str,
userId: str = Query(..., description="ID của user để lấy API key/secret"), userId: str = Query(..., description="User ID to get API key/secret"),
symbol: str = Query(..., description="Mã giao dịch, ví dụ: BTCUSDT"), symbol: str = Query(..., description="Trading symbol, e.g. BTCUSDT"),
category: str = Query("linear", description="Loại lệnh: linear (future) hoặc spot") category: str = Query("linear", description="Order type: linear (future) or spot")
): ):
api_key, api_secret = get_user_api(userId) api_key, api_secret = get_user_api(userId)
client = get_bybit_client(api_key, api_secret) client = get_bybit_client(api_key, api_secret)

View file

@ -21,9 +21,9 @@ def get_user_api(user_id: str):
@router.get("/positions", tags=["Position"]) @router.get("/positions", tags=["Position"])
def get_positions( def get_positions(
userId: str = Query(..., description="ID của user để lấy API key/secret"), userId: str = Query(..., description="User ID to get API key/secret"),
symbol: Optional[str] = Query(None, description="Mã giao dịch, ví dụ: BTCUSDT"), symbol: Optional[str] = Query(None, description="Trading symbol, e.g. BTCUSDT"),
category: str = Query("linear", description="Loại lệnh: linear (future) hoặc spot") category: str = Query("linear", description="Order type: linear (future) or spot")
): ):
api_key, api_secret = get_user_api(userId) api_key, api_secret = get_user_api(userId)
client = get_bybit_client(api_key, api_secret) client = get_bybit_client(api_key, api_secret)

View file

@ -15,21 +15,21 @@ def list_users():
accounts = json.load(f) accounts = json.load(f)
return accounts return accounts
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Lỗi đọc file accounts: {e}") raise HTTPException(status_code=500, detail=f"Error reading accounts file: {e}")
# API thêm user # API thêm user
@router.post("/users", tags=["User"]) @router.post("/users", tags=["User"])
def add_user( def add_user(
id: str = Body(..., description="ID user"), id: str = Body(..., description="User ID"),
bybit_api_key: str = Body(..., description="Bybit API Key"), bybit_api_key: str = Body(..., description="Bybit API Key"),
bybit_api_secret: str = Body(..., description="Bybit API Secret"), bybit_api_secret: str = Body(..., description="Bybit API Secret"),
user_name: str = Body(..., description="Tên user") user_name: str = Body(..., description="User name")
): ):
try: try:
with open(ACCOUNTS_FILE, 'r+', encoding='utf-8') as f: with open(ACCOUNTS_FILE, 'r+', encoding='utf-8') as f:
accounts = json.load(f) accounts = json.load(f)
if any(acc['id'] == id for acc in accounts): if any(acc['id'] == id for acc in accounts):
raise HTTPException(status_code=400, detail="ID đã tồn tại") raise HTTPException(status_code=400, detail="ID already exists")
new_user = { new_user = {
"id": id, "id": id,
"bybit_api_key": bybit_api_key, "bybit_api_key": bybit_api_key,
@ -40,11 +40,11 @@ def add_user(
f.seek(0) f.seek(0)
json.dump(accounts, f, ensure_ascii=False, indent=2) json.dump(accounts, f, ensure_ascii=False, indent=2)
f.truncate() f.truncate()
return {"message": "Thêm user thành công"} return {"message": "User added successfully"}
except HTTPException as e: except HTTPException as e:
raise e raise e
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Lỗi ghi file accounts: {e}") raise HTTPException(status_code=500, detail=f"Error writing accounts file: {e}")
# API sửa user # API sửa user
@router.put("/users/{user_id}", tags=["User"]) @router.put("/users/{user_id}", tags=["User"])
@ -68,12 +68,12 @@ def update_user(
f.seek(0) f.seek(0)
json.dump(accounts, f, ensure_ascii=False, indent=2) json.dump(accounts, f, ensure_ascii=False, indent=2)
f.truncate() f.truncate()
return {"message": "Cập nhật user thành công"} return {"message": "User updated successfully"}
raise HTTPException(status_code=404, detail="Không tìm thấy userId") raise HTTPException(status_code=404, detail="User ID not found")
except HTTPException as e: except HTTPException as e:
raise e raise e
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Lỗi ghi file accounts: {e}") raise HTTPException(status_code=500, detail=f"Error writing accounts file: {e}")
# API xóa user # API xóa user
@router.delete("/users/{user_id}", tags=["User"]) @router.delete("/users/{user_id}", tags=["User"])
@ -83,12 +83,12 @@ def delete_user(user_id: str):
accounts = json.load(f) accounts = json.load(f)
new_accounts = [acc for acc in accounts if acc['id'] != user_id] new_accounts = [acc for acc in accounts if acc['id'] != user_id]
if len(new_accounts) == len(accounts): if len(new_accounts) == len(accounts):
raise HTTPException(status_code=404, detail="Không tìm thấy userId") raise HTTPException(status_code=404, detail="User ID not found")
f.seek(0) f.seek(0)
json.dump(new_accounts, f, ensure_ascii=False, indent=2) json.dump(new_accounts, f, ensure_ascii=False, indent=2)
f.truncate() f.truncate()
return {"message": "Xóa user thành công"} return {"message": "User deleted successfully"}
except HTTPException as e: except HTTPException as e:
raise e raise e
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Lỗi ghi file accounts: {e}") raise HTTPException(status_code=500, detail=f"Error writing accounts file: {e}")

View file

@ -2,45 +2,45 @@ from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any from typing import List, Optional, Dict, Any
class CoinInfo(BaseModel): class CoinInfo(BaseModel):
availableToBorrow: Optional[str] = Field(None, description="Số lượng có thể vay") availableToBorrow: Optional[str] = Field(None, description="Amount available to borrow")
bonus: Optional[str] = Field(None, description="Tiền thưởng") bonus: Optional[str] = Field(None, description="Bonus amount")
accruedInterest: Optional[str] = Field(None, description="Lãi tích lũy") accruedInterest: Optional[str] = Field(None, description="Accrued interest")
availableToWithdraw: Optional[str] = Field(None, description="Số lượng có thể rút") availableToWithdraw: Optional[str] = Field(None, description="Amount available to withdraw")
totalOrderIM: Optional[str] = Field(None, description="Initial margin cho lệnh") totalOrderIM: Optional[str] = Field(None, description="Initial margin for orders")
equity: Optional[str] = Field(None, description="Tổng tài sản coin") equity: Optional[str] = Field(None, description="Total coin equity")
totalPositionMM: Optional[str] = Field(None, description="Maintenance margin cho vị thế") totalPositionMM: Optional[str] = Field(None, description="Maintenance margin for positions")
usdValue: Optional[str] = Field(None, description="Giá trị USD của coin") usdValue: Optional[str] = Field(None, description="USD value of the coin")
unrealisedPnl: Optional[str] = Field(None, description="Lãi/lỗ chưa chốt") unrealisedPnl: Optional[str] = Field(None, description="Unrealized PnL")
collateralSwitch: Optional[bool] = Field(None, description="Có dùng làm tài sản thế chấp không") collateralSwitch: Optional[bool] = Field(None, description="Is used as collateral")
spotHedgingQty: Optional[str] = Field(None, description="Số lượng spot hedging") spotHedgingQty: Optional[str] = Field(None, description="Spot hedging quantity")
borrowAmount: Optional[str] = Field(None, description="Số lượng đã vay") borrowAmount: Optional[str] = Field(None, description="Borrowed amount")
totalPositionIM: Optional[str] = Field(None, description="Initial margin cho vị thế") totalPositionIM: Optional[str] = Field(None, description="Initial margin for positions")
walletBalance: Optional[str] = Field(None, description="Số dư ví coin") walletBalance: Optional[str] = Field(None, description="Coin wallet balance")
cumRealisedPnl: Optional[str] = Field(None, description="Lãi/lỗ đã chốt tích lũy") cumRealisedPnl: Optional[str] = Field(None, description="Cumulative realized PnL")
locked: Optional[str] = Field(None, description="Số lượng bị khóa") locked: Optional[str] = Field(None, description="Locked amount")
marginCollateral: Optional[bool] = Field(None, description="Có dùng làm tài sản margin không") marginCollateral: Optional[bool] = Field(None, description="Is used as margin collateral")
coin: str = Field(..., description="Mã coin") coin: str = Field(..., description="Coin symbol")
class AccountInfo(BaseModel): class AccountInfo(BaseModel):
totalEquity: str = Field(..., description="Tổng tài sản quy USD") totalEquity: str = Field(..., description="Total equity in USD")
accountIMRate: str = Field(..., description="Initial margin rate") accountIMRate: str = Field(..., description="Initial margin rate")
totalMarginBalance: str = Field(..., description="Tổng margin balance") totalMarginBalance: str = Field(..., description="Total margin balance")
totalInitialMargin: str = Field(..., description="Tổng initial margin") totalInitialMargin: str = Field(..., description="Total initial margin")
accountType: str = Field(..., description="Loại tài khoản (ví dụ: UNIFIED)") accountType: str = Field(..., description="Account type (e.g. UNIFIED)")
totalAvailableBalance: str = Field(..., description="Tổng số dư khả dụng") totalAvailableBalance: str = Field(..., description="Total available balance")
accountMMRate: str = Field(..., description="Maintenance margin rate") accountMMRate: str = Field(..., description="Maintenance margin rate")
totalPerpUPL: str = Field(..., description="Lãi/lỗ chưa chốt của perpetual") totalPerpUPL: str = Field(..., description="Unrealized PnL of perpetual positions")
totalWalletBalance: str = Field(..., description="Tổng số dư ví") totalWalletBalance: str = Field(..., description="Total wallet balance")
accountLTV: str = Field(..., description="Loan to value") accountLTV: str = Field(..., description="Loan to value")
totalMaintenanceMargin: str = Field(..., description="Tổng maintenance margin") totalMaintenanceMargin: str = Field(..., description="Total maintenance margin")
coin: List[CoinInfo] = Field(..., description="Danh sách coin") coin: List[CoinInfo] = Field(..., description="List of coins")
class ResultInfo(BaseModel): class ResultInfo(BaseModel):
list: List[AccountInfo] = Field(..., description="Danh sách tài khoản") list: List[AccountInfo] = Field(..., description="List of accounts")
class AccountResponseSchema(BaseModel): class AccountResponseSchema(BaseModel):
retCode: int = Field(..., description="Mã kết quả trả về (0 là thành công)") retCode: int = Field(..., description="Return code (0 means success)")
retMsg: str = Field(..., description="Thông báo kết quả") retMsg: str = Field(..., description="Return message")
result: ResultInfo = Field(..., description="Kết quả chi tiết") result: ResultInfo = Field(..., description="Detailed result")
retExtInfo: Dict[str, Any] = Field(..., description="Thông tin mở rộng") retExtInfo: Dict[str, Any] = Field(..., description="Extra information")
time: int = Field(..., description="Timestamp trả về") time: int = Field(..., description="Response timestamp")

View file

@ -2,13 +2,13 @@ from pydantic import BaseModel, Field
from typing import List, Optional, Any from typing import List, Optional, Any
class CandleSchema(BaseModel): class CandleSchema(BaseModel):
timestamp: int = Field(..., description="Thời gian (miliseconds)") timestamp: int = Field(..., description="Timestamp (milliseconds)")
open: float = Field(..., description="Giá mở cửa") open: float = Field(..., description="Open price")
high: float = Field(..., description="Giá cao nhất") high: float = Field(..., description="High price")
low: float = Field(..., description="Giá thấp nhất") low: float = Field(..., description="Low price")
close: float = Field(..., description="Giá đóng cửa") close: float = Field(..., description="Close price")
volume: float = Field(..., description="Khối lượng giao dịch") volume: float = Field(..., description="Volume")
turnover: float = Field(..., description="Giá trị giao dịch") turnover: float = Field(..., description="Turnover value")
macd: Optional[float] = Field(None, description="MACD value") macd: Optional[float] = Field(None, description="MACD value")
macdsignal: Optional[float] = Field(None, description="MACD signal") macdsignal: Optional[float] = Field(None, description="MACD signal")
macdhist: Optional[float] = Field(None, description="MACD histogram") macdhist: Optional[float] = Field(None, description="MACD histogram")