714dd47c86
- Add get_ticker() method for real-time quotes across all markets - Add get_realtime_price() service with ticker/kline fallback chain - Fix yfinance end date issue for US stocks and futures - Fix forex timezone parsing for Tiingo UTC timestamps - Add retry mechanism with exponential backoff for Tiingo API - Add API rate limiting for portfolio (3 concurrent, 0.3s interval) - Add force refresh option to bypass price cache on manual refresh
134 lines
4.3 KiB
Python
134 lines
4.3 KiB
Python
"""
|
|
数据源工厂
|
|
根据市场类型返回对应的数据源
|
|
"""
|
|
from typing import Dict, List, Any, Optional
|
|
|
|
from app.data_sources.base import BaseDataSource
|
|
from app.utils.logger import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
class DataSourceFactory:
|
|
"""数据源工厂"""
|
|
|
|
_sources: Dict[str, BaseDataSource] = {}
|
|
|
|
@classmethod
|
|
def get_source(cls, market: str) -> BaseDataSource:
|
|
"""
|
|
获取指定市场的数据源
|
|
|
|
Args:
|
|
market: 市场类型 (Crypto, USStock, AShare, HShare)
|
|
|
|
Returns:
|
|
数据源实例
|
|
"""
|
|
if market not in cls._sources:
|
|
cls._sources[market] = cls._create_source(market)
|
|
return cls._sources[market]
|
|
|
|
@classmethod
|
|
def get_data_source(cls, name: str) -> BaseDataSource:
|
|
"""
|
|
Backward compatible alias used by older code paths.
|
|
|
|
Some modules historically called `get_data_source("binance")` to fetch a crypto data source.
|
|
In the localized Python backend we primarily use `get_source("Crypto")`.
|
|
"""
|
|
key = (name or "").strip().lower()
|
|
if key in ("crypto", "binance", "okx", "bybit", "bitget", "kucoin", "gate", "mexc", "kraken", "coinbase"):
|
|
return cls.get_source("Crypto")
|
|
if key in ("futures",):
|
|
return cls.get_source("Futures")
|
|
# Default to Crypto for safety (most callers want a ticker for crypto pairs).
|
|
return cls.get_source("Crypto")
|
|
|
|
@classmethod
|
|
def _create_source(cls, market: str) -> BaseDataSource:
|
|
"""创建数据源实例"""
|
|
if market == 'Crypto':
|
|
from app.data_sources.crypto import CryptoDataSource
|
|
return CryptoDataSource()
|
|
elif market == 'USStock':
|
|
from app.data_sources.us_stock import USStockDataSource
|
|
return USStockDataSource()
|
|
elif market == 'AShare':
|
|
from app.data_sources.cn_stock import AShareDataSource
|
|
return AShareDataSource()
|
|
elif market == 'HShare':
|
|
from app.data_sources.cn_stock import HShareDataSource
|
|
return HShareDataSource()
|
|
elif market == 'Forex':
|
|
from app.data_sources.forex import ForexDataSource
|
|
return ForexDataSource()
|
|
elif market == 'Futures':
|
|
from app.data_sources.futures import FuturesDataSource
|
|
return FuturesDataSource()
|
|
else:
|
|
raise ValueError(f"不支持的市场类型: {market}")
|
|
|
|
@classmethod
|
|
def get_kline(
|
|
cls,
|
|
market: str,
|
|
symbol: str,
|
|
timeframe: str,
|
|
limit: int,
|
|
before_time: Optional[int] = None
|
|
) -> List[Dict[str, Any]]:
|
|
"""
|
|
获取K线数据的便捷方法
|
|
|
|
Args:
|
|
market: 市场类型
|
|
symbol: 交易对/股票代码
|
|
timeframe: 时间周期
|
|
limit: 数据条数
|
|
before_time: 获取此时间之前的数据
|
|
|
|
Returns:
|
|
K线数据列表
|
|
"""
|
|
try:
|
|
source = cls.get_source(market)
|
|
klines = source.get_kline(symbol, timeframe, limit, before_time)
|
|
|
|
# 确保数据按时间排序
|
|
klines.sort(key=lambda x: x['time'])
|
|
|
|
return klines
|
|
except Exception as e:
|
|
logger.error(f"Failed to fetch K-lines {market}:{symbol} - {str(e)}")
|
|
return []
|
|
|
|
@classmethod
|
|
def get_ticker(cls, market: str, symbol: str) -> Dict[str, Any]:
|
|
"""
|
|
获取实时报价的便捷方法
|
|
|
|
Args:
|
|
market: 市场类型
|
|
symbol: 交易对/股票代码
|
|
|
|
Returns:
|
|
实时报价数据: {
|
|
'last': 最新价,
|
|
'change': 涨跌额,
|
|
'changePercent': 涨跌幅,
|
|
...
|
|
}
|
|
"""
|
|
try:
|
|
source = cls.get_source(market)
|
|
return source.get_ticker(symbol)
|
|
except NotImplementedError:
|
|
logger.warning(f"get_ticker not implemented for market: {market}")
|
|
return {'last': 0, 'symbol': symbol}
|
|
except Exception as e:
|
|
logger.error(f"Failed to fetch ticker {market}:{symbol} - {str(e)}")
|
|
return {'last': 0, 'symbol': symbol}
|
|
|