106 lines
3.8 KiB
Python
106 lines
3.8 KiB
Python
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
|
|
}
|
|
}
|