Files
DinQuant/backend_api_python/app/services/kline.py
T

191 lines
6.8 KiB
Python
Raw Normal View History

2025-12-29 03:06:49 +08:00
"""
K线数据服务
"""
from typing import Dict, List, Any, Optional
from app.data_sources import DataSourceFactory
from app.utils.cache import CacheManager
from app.utils.logger import get_logger
from app.config import CacheConfig
logger = get_logger(__name__)
class KlineService:
"""K线数据服务"""
def __init__(self):
self.cache = CacheManager()
self.cache_ttl = CacheConfig.KLINE_CACHE_TTL
def get_kline(
self,
market: str,
symbol: str,
timeframe: str,
limit: int = 300,
before_time: Optional[int] = None
) -> List[Dict[str, Any]]:
"""
获取K线数据
Args:
market: 市场类型 (Crypto, USStock, Forex, Futures)
2025-12-29 03:06:49 +08:00
symbol: 交易对/股票代码
timeframe: 时间周期
limit: 数据条数
before_time: 获取此时间之前的数据
Returns:
K线数据列表
"""
# 构建缓存键(历史数据不缓存)
if not before_time:
cache_key = f"kline:{market}:{symbol}:{timeframe}:{limit}"
cached = self.cache.get(cache_key)
if cached:
# logger.info(f"命中缓存: {cache_key}")
return cached
# 获取数据
klines = DataSourceFactory.get_kline(
market=market,
symbol=symbol,
timeframe=timeframe,
limit=limit,
before_time=before_time
)
# 设置缓存(仅最新数据)
if klines and not before_time:
ttl = self.cache_ttl.get(timeframe, 300)
self.cache.set(cache_key, klines, ttl)
# logger.info(f"缓存设置: {cache_key}, TTL: {ttl}s")
return klines
def get_latest_price(self, market: str, symbol: str) -> Optional[Dict[str, Any]]:
"""获取最新价格(使用1分钟K线,已弃用,建议使用 get_realtime_price"""
2025-12-29 03:06:49 +08:00
klines = self.get_kline(market, symbol, '1m', 1)
if klines:
return klines[-1]
return None
def get_realtime_price(self, market: str, symbol: str, force_refresh: bool = False) -> Dict[str, Any]:
"""
获取实时价格(优先使用 ticker API,降级使用分钟 K 线)
Args:
market: 市场类型 (Crypto, USStock, Forex, Futures)
symbol: 交易对/股票代码
force_refresh: 是否强制刷新(跳过缓存)
Returns:
实时价格数据: {
'price': 最新价格,
'change': 涨跌额,
'changePercent': 涨跌幅,
'high': 最高价,
'low': 最低价,
'open': 开盘价,
'previousClose': 昨收价,
'source': 数据来源 ('ticker' 或 'kline')
}
"""
# 构建缓存键(短时间缓存,避免频繁请求)
cache_key = f"realtime_price:{market}:{symbol}"
# 如果不是强制刷新,尝试使用缓存
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'
}
# 优先尝试使用 ticker API 获取实时价格
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'
}
# 缓存 30 秒
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}")
# 降级:使用 1 分钟 K 线
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'
}
# 缓存 30 秒
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}")
# 最后降级:使用日线数据(适用于非交易时间)
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'
}
# 日线数据缓存 5 分钟
self.cache.set(cache_key, result, 300)
return result
except Exception as e:
logger.error(f"All price sources failed for {market}:{symbol}: {e}")
return result
2025-12-29 03:06:49 +08:00