init
This commit is contained in:
parent
be5fb603f3
commit
859fb7a19d
11 changed files with 436 additions and 0 deletions
120
app/api/account.py
Normal file
120
app/api/account.py
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
from fastapi import APIRouter, Query, HTTPException, Body
|
||||
from app.services.bybit_service import get_bybit_client
|
||||
import json
|
||||
import os
|
||||
from typing import List, Optional
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
ACCOUNTS_FILE = os.path.join(os.path.dirname(__file__), '../../accounts.json')
|
||||
|
||||
def get_user_api(user_id: str):
|
||||
try:
|
||||
with open(ACCOUNTS_FILE, 'r', encoding='utf-8') as f:
|
||||
accounts = json.load(f)
|
||||
for acc in accounts:
|
||||
if acc['id'] == user_id:
|
||||
return acc['bybit_api_key'], acc['bybit_api_secret']
|
||||
except Exception as e:
|
||||
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")
|
||||
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)
|
||||
client = get_bybit_client(api_key, api_secret)
|
||||
res = client.get_wallet_balance(accountType="UNIFIED")
|
||||
if 'retCode' in res and res['retCode'] != 0:
|
||||
raise HTTPException(status_code=400, detail=f"Bybit API error: {res.get('retMsg', 'Unknown error')}")
|
||||
return res
|
||||
except HTTPException as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {e}")
|
||||
106
app/api/candles.py
Normal file
106
app/api/candles.py
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
from fastapi import APIRouter, Query, HTTPException
|
||||
from typing import Optional
|
||||
from app.services.bybit_service import get_bybit_client
|
||||
import json
|
||||
import os
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
ACCOUNTS_FILE = os.path.join(os.path.dirname(__file__), '../../accounts.json')
|
||||
|
||||
def get_user_api(user_id: str):
|
||||
try:
|
||||
with open(ACCOUNTS_FILE, 'r', encoding='utf-8') as f:
|
||||
accounts = json.load(f)
|
||||
for acc in accounts:
|
||||
if acc['id'] == user_id:
|
||||
return acc['bybit_api_key'], acc['bybit_api_secret']
|
||||
except Exception as e:
|
||||
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")
|
||||
|
||||
def ema(series, period):
|
||||
return series.ewm(span=period, adjust=False).mean()
|
||||
|
||||
def macd(series, fast=45, slow=90, signal=9):
|
||||
ema_fast = ema(series, fast)
|
||||
ema_slow = ema(series, slow)
|
||||
macd_line = ema_fast - ema_slow
|
||||
signal_line = macd_line.ewm(span=signal, adjust=False).mean()
|
||||
macd_hist = macd_line - signal_line
|
||||
return macd_line, signal_line, macd_hist
|
||||
|
||||
def rsi(series, period=14):
|
||||
delta = series.diff()
|
||||
gain = (delta.where(delta > 0, 0)).rolling(window=period, min_periods=1).mean()
|
||||
loss = (-delta.where(delta < 0, 0)).rolling(window=period, min_periods=1).mean()
|
||||
loss = loss.replace(0, np.nan) # tránh chia cho 0
|
||||
rs = gain / loss
|
||||
rsi = 100 - (100 / (1 + rs))
|
||||
rsi = rsi.replace([np.inf, -np.inf], np.nan)
|
||||
return rsi
|
||||
|
||||
@router.get("/candles")
|
||||
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"),
|
||||
interval: str = Query("1", description="Khung thời gian nến, ví dụ: 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)"),
|
||||
start: Optional[int] = Query(None, description="Timestamp bắt đầu (miliseconds)"),
|
||||
end: Optional[int] = Query(None, description="Timestamp kết thúc (miliseconds)")
|
||||
):
|
||||
api_key, api_secret = get_user_api(userId)
|
||||
client = get_bybit_client(api_key, api_secret)
|
||||
params = {
|
||||
"category": "linear", # hoặc "spot" nếu lấy spot
|
||||
"symbol": symbol,
|
||||
"interval": interval,
|
||||
"limit": limit
|
||||
}
|
||||
if start:
|
||||
params["start"] = start
|
||||
if end:
|
||||
params["end"] = end
|
||||
res = client.get_kline(**params)
|
||||
if 'retCode' in res and res['retCode'] != 0:
|
||||
raise HTTPException(status_code=400, detail=f"Bybit API error: {res.get('retMsg', 'Unknown error')}")
|
||||
candles = res.get('result', {}).get('list', [])
|
||||
if not candles:
|
||||
return {"data": [], "indicators": {}}
|
||||
# Chuyển đổi sang DataFrame
|
||||
df = pd.DataFrame(candles, columns=[
|
||||
"timestamp", "open", "high", "low", "close", "volume", "turnover"
|
||||
])
|
||||
df = df.astype({
|
||||
"timestamp": np.int64,
|
||||
"open": np.float64,
|
||||
"high": np.float64,
|
||||
"low": np.float64,
|
||||
"close": np.float64,
|
||||
"volume": np.float64,
|
||||
"turnover": np.float64
|
||||
})
|
||||
df = df.sort_values("timestamp", ascending=False)
|
||||
# Tính chỉ báo
|
||||
close = df["close"]
|
||||
macd_line, macd_signal, macd_hist = macd(close, fast=45, slow=90, signal=9)
|
||||
df["macd"] = macd_line
|
||||
df["macdsignal"] = macd_signal
|
||||
df["macdhist"] = macd_hist
|
||||
df["ema34"] = ema(close, 34)
|
||||
df["ema50"] = ema(close, 50)
|
||||
df["ema100"] = ema(close, 100)
|
||||
df["ema200"] = ema(close, 200)
|
||||
df["rsi"] = rsi(close)
|
||||
# Trả về kết quả
|
||||
data = df.replace({np.nan: None}).to_dict(orient="records")
|
||||
return {
|
||||
"data": data,
|
||||
"indicators": {
|
||||
"macd": {"fast": 45, "slow": 90, "signal": 9},
|
||||
"ema": [34, 50, 100, 200],
|
||||
"rsi": True
|
||||
}
|
||||
}
|
||||
78
app/api/orders.py
Normal file
78
app/api/orders.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
from fastapi import APIRouter, Query, HTTPException, Body
|
||||
from typing import Optional
|
||||
from app.services.bybit_service import get_bybit_client
|
||||
import json
|
||||
import os
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
ACCOUNTS_FILE = os.path.join(os.path.dirname(__file__), '../../accounts.json')
|
||||
|
||||
def get_user_api(user_id: str):
|
||||
try:
|
||||
with open(ACCOUNTS_FILE, 'r', encoding='utf-8') as f:
|
||||
accounts = json.load(f)
|
||||
for acc in accounts:
|
||||
if acc['id'] == user_id:
|
||||
return acc['bybit_api_key'], acc['bybit_api_secret']
|
||||
except Exception as e:
|
||||
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")
|
||||
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"),
|
||||
category: str = Query("linear", description="Loại lệnh: linear (future) hoặc spot")
|
||||
):
|
||||
api_key, api_secret = get_user_api(userId)
|
||||
client = get_bybit_client(api_key, api_secret)
|
||||
res = client.get_open_orders(
|
||||
category=category, # "linear" cho future, "spot" cho spot
|
||||
symbol=symbol
|
||||
)
|
||||
return res
|
||||
|
||||
# Submit order
|
||||
@router.post("/orders")
|
||||
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"),
|
||||
side: str = Body(..., embed=True, description="side: Buy hoặc Sell"),
|
||||
orderType: str = Body(..., embed=True, description="Loại lệnh: Market hoặc Limit"),
|
||||
qty: float = Body(..., embed=True, description="Số lượng"),
|
||||
category: str = Body("linear", embed=True, description="Loại lệnh: linear (future) hoặc spot"),
|
||||
price: Optional[float] = Body(None, embed=True, description="Giá (bắt buộc với Limit)")
|
||||
):
|
||||
api_key, api_secret = get_user_api(userId)
|
||||
client = get_bybit_client(api_key, api_secret)
|
||||
params = {
|
||||
"category": category,
|
||||
"symbol": symbol,
|
||||
"side": side,
|
||||
"orderType": orderType,
|
||||
"qty": qty
|
||||
}
|
||||
if orderType.lower() == "limit":
|
||||
if price is None:
|
||||
raise HTTPException(status_code=400, detail="Thiếu giá cho lệnh Limit")
|
||||
params["price"] = price
|
||||
res = client.place_order(**params)
|
||||
return res
|
||||
|
||||
# Cancel order
|
||||
@router.delete("/orders/{order_id}")
|
||||
def cancel_order(
|
||||
order_id: str,
|
||||
userId: str = Query(..., description="ID của user để lấy API key/secret"),
|
||||
symbol: str = Query(..., description="Mã giao dịch, ví dụ: BTCUSDT"),
|
||||
category: str = Query("linear", description="Loại lệnh: linear (future) hoặc spot")
|
||||
):
|
||||
api_key, api_secret = get_user_api(userId)
|
||||
client = get_bybit_client(api_key, api_secret)
|
||||
res = client.cancel_order(
|
||||
category=category,
|
||||
symbol=symbol,
|
||||
orderId=order_id
|
||||
)
|
||||
return res
|
||||
36
app/api/positions.py
Normal file
36
app/api/positions.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
from fastapi import APIRouter, Query, HTTPException
|
||||
from typing import Optional
|
||||
from app.services.bybit_service import get_bybit_client
|
||||
import json
|
||||
import os
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
ACCOUNTS_FILE = os.path.join(os.path.dirname(__file__), '../../accounts.json')
|
||||
|
||||
def get_user_api(user_id: str):
|
||||
try:
|
||||
with open(ACCOUNTS_FILE, 'r', encoding='utf-8') as f:
|
||||
accounts = json.load(f)
|
||||
for acc in accounts:
|
||||
if acc['id'] == user_id:
|
||||
return acc['bybit_api_key'], acc['bybit_api_secret']
|
||||
except Exception as e:
|
||||
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")
|
||||
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"),
|
||||
category: str = Query("linear", description="Loại lệnh: linear (future) hoặc spot")
|
||||
):
|
||||
api_key, api_secret = get_user_api(userId)
|
||||
client = get_bybit_client(api_key, api_secret)
|
||||
params = {
|
||||
"category": category
|
||||
}
|
||||
if symbol:
|
||||
params["symbol"] = symbol
|
||||
res = client.get_positions(**params)
|
||||
return res
|
||||
9
app/config.py
Normal file
9
app/config.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
SUPABASE_URL = os.getenv("SUPABASE_URL")
|
||||
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
|
||||
BYBIT_API_KEY = os.getenv("BYBIT_API_KEY")
|
||||
BYBIT_API_SECRET = os.getenv("BYBIT_API_SECRET")
|
||||
9
app/main.py
Normal file
9
app/main.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from fastapi import FastAPI
|
||||
from app.api import candles, orders, account, positions
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
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")
|
||||
8
app/services/bybit_service.py
Normal file
8
app/services/bybit_service.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from pybit.unified_trading import HTTP
|
||||
|
||||
def get_bybit_client(api_key: str, api_secret: str):
|
||||
return HTTP(
|
||||
testnet=False, # Đổi thành False nếu dùng môi trường thật
|
||||
api_key=api_key,
|
||||
api_secret=api_secret
|
||||
)
|
||||
5
app/services/supabase_service.py
Normal file
5
app/services/supabase_service.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
from supabase import create_client, Client
|
||||
from app.config import SUPABASE_URL, SUPABASE_KEY
|
||||
|
||||
def get_supabase_client() -> Client:
|
||||
return create_client(SUPABASE_URL, SUPABASE_KEY)
|
||||
Loading…
Add table
Add a link
Reference in a new issue