feat(datasource): 实现加密货币数据获取功能

- 新增 ccxt_source.py: CCXT + OKX 加密货币数据源
- 新增 socks2http.py: SOCKS5 转 HTTP 代理转换器
- 修改 universal_fetcher.py: 添加 _fetch_crypto 方法,支持 timeframe 参数
- 修改 flask_server.py: API 支持 timeframe 参数,加密货币不缓存

支持的 timeframe: 1d, 1h, 4h, 15m, 1m
测试验证: BTC 数据获取成功
This commit is contained in:
2026-05-13 23:30:32 +08:00
parent 105af19690
commit 416f708d53
4 changed files with 513 additions and 13 deletions

View File

@@ -230,20 +230,22 @@ def fetch_data_with_ttl(
code: str,
start: str,
end: str,
nocache: bool = False
nocache: bool = False,
timeframe: str = '1d'
) -> Tuple[Optional[Dict], bool]:
"""
获取数据,支持 TTL 缓存(加密货币不缓存)
缓存策略:
- 日级别数据(股票/指数/ETF/期货): Key=(code, today), 缓存全量数据,切片返回
- 加密货币: 每次实时下载,不缓存
- 加密货币: 每次实时下载,不缓存,必须指定 timeframe
Args:
code: 标的代码
start: 用户请求的开始日期
end: 用户请求的结束日期
nocache: 是否跳过缓存
timeframe: K线周期仅加密货币需要
Returns:
(data, is_cached): 数据和是否命中缓存
@@ -254,12 +256,12 @@ def fetch_data_with_ttl(
# 检查资产类型
asset_type = AssetTypeDetector.detect(code)
# 加密货币:直接下载,不缓存
# 加密货币:直接下载,不缓存,必须指定 timeframe
if asset_type == AssetType.CRYPTO:
f = get_fetcher()
try:
with f:
df = f.fetch(code, start, end)
df = f.fetch(code, start, end, timeframe=timeframe)
if df is None or len(df) == 0:
return None, False
result = dataframe_to_json(df)
@@ -267,6 +269,7 @@ def fetch_data_with_ttl(
result['asset_type'] = asset_type.value
result['cache_strategy'] = 'no_cache_crypto'
result['requested_range'] = {'start': start, 'end': end}
result['timeframe'] = timeframe
return result, False
except Exception as e:
return {'error': str(e), 'code': code, 'asset_type': asset_type.value}, False
@@ -459,12 +462,20 @@ def index():
"asset_type": "/api/v1/asset-type?code={code}",
"ohlcv": "/api/v1/ohlcv?code={code}&start={YYYY-MM-DD}&end={YYYY-MM-DD}&asset_type={type}",
"ohlcv_nocache": "/api/v1/ohlcv?code={code}&nocache=true",
"ohlcv_crypto": "/api/v1/ohlcv?code=BTC&timeframe=1d (加密货币必须指定 timeframe)",
"ohlcv_asset_type": "/api/v1/ohlcv?code={code}&asset_type=china_index (强制覆盖类型)",
"batch": "POST /api/v1/ohlcv/batch",
"etf_nav": "/api/v1/etf/nav?code={code}",
"cache_clear": "POST /api/v1/cache/clear",
"cache_stats": "/api/v1/cache/stats",
},
"crypto_timeframes": {
"1d": "日线",
"1h": "小时线",
"4h": "4小时线",
"15m": "15分钟线",
"1m": "分钟线",
},
"asset_types": {
"china_index": "中国指数 (000300.SH, 399006.SZ等)",
"china_etf": "中国ETF (159915.SZ, 513100.SH等)",
@@ -534,12 +545,19 @@ def get_ohlcv():
- futures: 期货
- crypto: 加密货币
注:指定后会覆盖自动检测,用于修复检测逻辑问题
timeframe: K线周期 (optional, 仅加密货币需要)
- 1d: 日线(默认)
- 1h: 小时线
- 4h: 4小时线
- 15m: 15分钟线
- 1m: 分钟线
nocache: 是否跳过缓存 (optional, 默认false)
"""
code = request.args.get('code', '').strip()
start = request.args.get('start', '').strip()
end = request.args.get('end', '').strip()
asset_type_param = request.args.get('asset_type', '').strip().lower()
timeframe = request.args.get('timeframe', '1d').strip().lower()
nocache = request.args.get('nocache', 'false').lower() == 'true'
# 参数验证
@@ -577,8 +595,18 @@ def get_ohlcv():
"valid_types": [t.value for t in AssetType],
}), 400
# 使用缓存获取数据
result, is_cached = fetch_data_with_ttl(code, start, end, nocache)
# 加密货币必须指定 timeframe无论自动检测还是手动指定
if final_type == AssetType.CRYPTO:
valid_timeframes = ['1d', '1h', '4h', '15m', '1m', 'daily', 'hourly']
if timeframe not in valid_timeframes:
return jsonify({
"error": f"Invalid timeframe for crypto: {timeframe}",
"valid_timeframes": valid_timeframes,
"hint": "加密货币必须指定 timeframe 参数",
}), 400
# 使用缓存获取数据(加密货币不缓存)
result, is_cached = fetch_data_with_ttl(code, start, end, nocache, timeframe)
if result is None:
return jsonify({