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 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", 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"), 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 } }