Files

182 lines
6.7 KiB
Python
Raw Permalink Normal View History

2025-12-29 03:06:49 +08:00
"""
K-line data service
2025-12-29 03:06:49 +08:00
"""
from typing import Any, Dict, List, Optional
from app.config import CacheConfig
2025-12-29 03:06:49 +08:00
from app.data_sources import DataSourceFactory
from app.utils.cache import CacheManager
from app.utils.logger import get_logger
logger = get_logger(__name__)
class KlineService:
"""K-line data service"""
2025-12-29 03:06:49 +08:00
def __init__(self):
self.cache = CacheManager()
self.cache_ttl = CacheConfig.KLINE_CACHE_TTL
2025-12-29 03:06:49 +08:00
def get_kline(
self, market: str, symbol: str, timeframe: str, limit: int = 300, before_time: Optional[int] = None
2025-12-29 03:06:49 +08:00
) -> List[Dict[str, Any]]:
"""
Get K-line data
2025-12-29 03:06:49 +08:00
Args:
market: market type (Crypto, USStock, Forex, Futures)
symbol: trading pair/stock code
timeframe: time period
limit: number of data items
before_time: Get data before this time
2025-12-29 03:06:49 +08:00
Returns:
K-line data list
2025-12-29 03:06:49 +08:00
"""
# Build a cache key (historical data is not cached)
2025-12-29 03:06:49 +08:00
if not before_time:
cache_key = f"kline:{market}:{symbol}:{timeframe}:{limit}"
cached = self.cache.get(cache_key)
if cached:
# logger.info(f"Hit cache: {cache_key}")
2025-12-29 03:06:49 +08:00
return cached
# Get data
2025-12-29 03:06:49 +08:00
klines = DataSourceFactory.get_kline(
market=market, symbol=symbol, timeframe=timeframe, limit=limit, before_time=before_time
2025-12-29 03:06:49 +08:00
)
# Set cache (latest data only)
2025-12-29 03:06:49 +08:00
if klines and not before_time:
ttl = self.cache_ttl.get(timeframe, 300)
self.cache.set(cache_key, klines, ttl)
# logger.info(f"Cache settings: {cache_key}, TTL: {ttl}s")
2025-12-29 03:06:49 +08:00
return klines
2025-12-29 03:06:49 +08:00
def get_latest_price(self, market: str, symbol: str) -> Optional[Dict[str, Any]]:
"""Get the latest price (use 1-minute K-line, deprecated, it is recommended to use get_realtime_price)"""
klines = self.get_kline(market, symbol, "1m", 1)
2025-12-29 03:06:49 +08:00
if klines:
return klines[-1]
return None
def get_realtime_price(self, market: str, symbol: str, force_refresh: bool = False) -> Dict[str, Any]:
"""
Get real-time prices (priority to use ticker API, downgrade to minute K-line)
Args:
market: market type (Crypto, USStock, Forex, Futures)
symbol: trading pair/stock code
force_refresh: whether to force refresh (skip cache)
Returns:
Real-time price data: {
'price': latest price,
'change': change amount,
'changePercent': increase or decrease,
'high': highest price,
'low': lowest price,
'open': opening price,
'previousClose': yesterday's closing price,
'source': data source ('ticker' or 'kline')
}
"""
# Build a cache key (short-term cache to avoid frequent requests)
cache_key = f"realtime_price:{market}:{symbol}"
# If it is not a forced refresh, try using caching
if not force_refresh:
cached = self.cache.get(cache_key)
if cached:
return cached
result = {
"price": 0,
"change": 0,
"changePercent": 0,
"high": 0,
"low": 0,
"open": 0,
"previousClose": 0,
"source": "unknown",
}
# First try to use the ticker API to get real-time prices
try:
ticker = DataSourceFactory.get_ticker(market, symbol)
if ticker and ticker.get("last", 0) > 0:
result = {
"price": ticker.get("last", 0),
"change": ticker.get("change", 0),
"changePercent": ticker.get("changePercent", 0),
"high": ticker.get("high", 0),
"low": ticker.get("low", 0),
"open": ticker.get("open", 0),
"previousClose": ticker.get("previousClose", 0),
"source": "ticker",
}
# Cache for 30 seconds
self.cache.set(cache_key, result, 30)
return result
except Exception as e:
logger.debug(f"Ticker API failed for {market}:{symbol}, falling back to kline: {e}")
# Downgrade: Use 1 minute candlestick
try:
klines = self.get_kline(market, symbol, "1m", 2)
if klines and len(klines) > 0:
latest = klines[-1]
prev_close = klines[-2]["close"] if len(klines) > 1 else latest.get("open", 0)
current_price = latest.get("close", 0)
change = round(current_price - prev_close, 4) if prev_close else 0
change_pct = round(change / prev_close * 100, 2) if prev_close and prev_close > 0 else 0
result = {
"price": current_price,
"change": change,
"changePercent": change_pct,
"high": latest.get("high", 0),
"low": latest.get("low", 0),
"open": latest.get("open", 0),
"previousClose": prev_close,
"source": "kline_1m",
}
# Cache for 30 seconds
self.cache.set(cache_key, result, 30)
return result
except Exception as e:
logger.debug(f"1m kline failed for {market}:{symbol}, trying daily: {e}")
# Last downgrade: using daily data (applies to non-trading hours)
try:
klines = self.get_kline(market, symbol, "1D", 2)
if klines and len(klines) > 0:
latest = klines[-1]
prev_close = klines[-2]["close"] if len(klines) > 1 else latest.get("open", 0)
current_price = latest.get("close", 0)
change = round(current_price - prev_close, 4) if prev_close else 0
change_pct = round(change / prev_close * 100, 2) if prev_close and prev_close > 0 else 0
result = {
"price": current_price,
"change": change,
"changePercent": change_pct,
"high": latest.get("high", 0),
"low": latest.get("low", 0),
"open": latest.get("open", 0),
"previousClose": prev_close,
"source": "kline_1d",
}
# Daily data cache for 5 minutes
self.cache.set(cache_key, result, 300)
return result
except Exception as e:
logger.error(f"All price sources failed for {market}:{symbol}: {e}")
2025-12-29 03:06:49 +08:00
return result