feat: 统一交易日历为 pandas_market_calendars

- 移除 Tushare 交易日历依赖,A股/美股/港股统一使用 pandas_market_calendars
- 简化 get_trading_calendar() 接口,移除 exchange 参数(沪深日历一致)
- 删除冗余的 _get_china/us/hk_calendar() 独立函数,直接调用 mcal
- 新增 Flask API 端点: /api/v1/trading-calendar, /api/v1/calendar/info
- 代码减少 73 行 (-61%),逻辑更集中易维护
- 更新 API 文档描述,三个市场数据源统一
This commit is contained in:
2026-05-24 11:08:26 +08:00
parent 1bf91bdcd0
commit 100eed455d
2 changed files with 212 additions and 3 deletions

View File

@@ -545,19 +545,22 @@ def index():
"""首页 - API 信息"""
return jsonify({
"name": "Universal Data Fetcher API",
"version": "2.0.0",
"description": "统一数据获取服务(分层架构)",
"version": "2.1.0",
"description": "统一数据获取服务(分层架构 + 交易日历",
"architecture": "Unified entry + Asset-specific methods",
"features": [
"分层架构(各资产类型独立实现)",
"LRU + TTL 双缓存机制",
"SSH隧道支持港美股",
"ETF净值获取计算溢价率",
"多市场交易日历A股/美股/港股)",
],
"endpoints": {
"info": "/",
"health": "/health",
"asset_type": "/api/v1/asset-type?code={code}",
"trading_calendar": "/api/v1/trading-calendar?market={A|US|HK}&start={YYYY-MM-DD}&end={YYYY-MM-DD}",
"calendar_info": "/api/v1/calendar/info",
"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)",
@@ -565,6 +568,11 @@ def index():
"cache_clear": "POST /api/v1/cache/clear",
"cache_stats": "/api/v1/cache/stats",
},
"trading_calendar_markets": {
"A": "A股pandas_market_calendars",
"US": "美股pandas_market_calendars",
"HK": "港股pandas_market_calendars",
},
"crypto_timeframes": {
"1d": "日线",
"1h": "小时线",
@@ -590,6 +598,7 @@ def index():
},
"cache_config": get_cache_info(),
"ssh": get_fetcher().get_ssh_status(),
"calendar_info": get_fetcher().get_calendar_info(),
})
@@ -624,6 +633,108 @@ def detect_asset_type():
})
@app.route('/api/v1/trading-calendar')
def get_trading_calendar():
"""
获取交易日历
Query Parameters:
market: 市场代码 (required)
- A: A股上交所/深交所,交易日历一致)
- US: 美股NYSE
- HK: 港股HKEX
start: 开始日期 YYYY-MM-DD (required)
end: 结束日期 YYYY-MM-DD (required)
Returns:
JSON 包含 trading_dates 列表(日期字符串数组)
示例:
/api/v1/trading-calendar?market=A&start=2024-01-01&end=2024-12-31
/api/v1/trading-calendar?market=US&start=2024-01-01&end=2024-12-31
/api/v1/trading-calendar?market=HK&start=2024-01-01&end=2024-12-31
"""
market = request.args.get('market', '').strip()
start = request.args.get('start', '').strip()
end = request.args.get('end', '').strip()
# 参数验证
if not market:
return jsonify({
"error": "Missing required parameter: market",
"example": "/api/v1/trading-calendar?market=A&start=2024-01-01&end=2024-12-31",
"supported_markets": ["A", "US", "HK"],
}), 400
if not start or not end:
return jsonify({
"error": "Missing required parameters: start and end",
"example": "/api/v1/trading-calendar?market=A&start=2024-01-01&end=2024-12-31",
}), 400
# 日期格式验证
if not validate_date(start) or not validate_date(end):
return jsonify({
"error": "Invalid date format. Use YYYY-MM-DD",
"start": start,
"end": end,
}), 400
try:
# 获取交易日历
f = get_fetcher()
trading_dates = f.get_trading_calendar(market, start, end)
# 转换为日期字符串列表
dates_list = [d.strftime('%Y-%m-%d') for d in trading_dates]
# 获取默认交易所名称
exchange_map = {
'A': 'SSE',
'US': 'NYSE',
'HK': 'HKEX',
}
exchange = exchange_map.get(market.upper(), '')
return jsonify({
"market": market.upper(),
"exchange": exchange,
"start": start,
"end": end,
"trading_dates": dates_list,
"count": len(dates_list),
})
except ValueError as e:
return jsonify({
"error": str(e),
"supported_markets": ["A", "US", "HK"],
}), 400
except ImportError as e:
return jsonify({
"error": str(e),
"hint": "请安装 pandas_market_calendars: pip install pandas_market_calendars",
}), 500
except Exception as e:
return jsonify({
"error": f"Failed to fetch trading calendar: {str(e)}",
}), 500
@app.route('/api/v1/calendar/info')
def calendar_info():
"""获取交易日历支持信息"""
try:
f = get_fetcher()
info = f.get_calendar_info()
return jsonify(info)
except Exception as e:
return jsonify({
"error": f"Failed to get calendar info: {str(e)}",
}), 500
@app.route('/api/v1/ohlcv')
def get_ohlcv():
"""
@@ -828,6 +939,8 @@ def not_found(error):
"available_endpoints": [
"/", "/health",
"/api/v1/asset-type",
"/api/v1/trading-calendar",
"/api/v1/calendar/info",
"/api/v1/ohlcv",
"/api/v1/cache/clear",
"/api/v1/cache/stats",

View File

@@ -23,7 +23,7 @@
import os
import time
from typing import Optional, Dict, List, Tuple
from typing import Optional, Dict, List, Tuple, Union
from datetime import datetime
import pandas as pd
@@ -33,6 +33,12 @@ from .ssh_tunnel import SSHTunnelManager
from .asset_type_detector import AssetTypeDetector, AssetType
from .ccxt_source import CCXTSource, get_crypto_source
try:
import pandas_market_calendars as mcal
HAS_PANDAS_MARKET_CALENDARS = True
except ImportError:
HAS_PANDAS_MARKET_CALENDARS = False
class UniversalDataFetcher:
"""
@@ -564,6 +570,96 @@ class UniversalDataFetcher:
"""
return self._tushare.fetch_stock_adj(code, start_date, end_date, adj)
# ============================================================
# 交易日历获取
# ============================================================
def get_trading_calendar(
self,
market: str,
start_date: str,
end_date: str
) -> pd.DatetimeIndex:
"""
获取交易日历(支持 A股、美股、港股
Args:
market: 市场代码
- 'A''china': A股上交所/深交所,交易日历一致)
- 'US''us': 美股NYSE
- 'HK''hk': 港股HKEX
start_date: 开始日期 'YYYY-MM-DD'
end_date: 结束日期 'YYYY-MM-DD'
Returns:
DatetimeIndex: 交易日日期序列
示例:
# A股
cal = fetcher.get_trading_calendar('A', '2024-01-01', '2024-12-31')
# 美股
cal = fetcher.get_trading_calendar('US', '2024-01-01', '2024-12-31')
# 港股
cal = fetcher.get_trading_calendar('HK', '2024-01-01', '2024-12-31')
"""
if not HAS_PANDAS_MARKET_CALENDARS:
raise ImportError(
"需要安装 pandas_market_calendars: pip install pandas_market_calendars"
)
market_lower = market.lower()
# 直接调用 mcal根据市场选择日历
if market_lower in ['a', 'china']:
# A股上交所/深交所交易日历一致,统一使用 SSE
cal = mcal.get_calendar('SSE')
elif market_lower in ['us', 'usa', 'america']:
# 美股NYSE
cal = mcal.get_calendar('NYSE')
elif market_lower in ['hk', 'hongkong']:
# 港股HKEX
cal = mcal.get_calendar('HKEX')
else:
raise ValueError(f"不支持的市场: {market},支持: A/US/HK")
schedule = cal.schedule(start_date=start_date, end_date=end_date)
return pd.DatetimeIndex(schedule.index)
def get_calendar_info(self) -> Dict:
"""
获取交易日历支持信息
Returns:
支持的市场和交易所信息
"""
return {
"supported_markets": {
"A": {
"name": "A股",
"method": "pandas_market_calendars",
"exchanges": ["SSE"],
"default_exchange": "SSE",
"note": "上交所和深交所交易日历完全一致统一使用SSE",
},
"US": {
"name": "美股",
"method": "pandas_market_calendars",
"exchanges": ["NYSE", "NASDAQ"],
"default_exchange": "NYSE",
},
"HK": {
"name": "港股",
"method": "pandas_market_calendars",
"exchanges": ["HKEX"],
"default_exchange": "HKEX",
},
},
"pandas_market_calendars_installed": HAS_PANDAS_MARKET_CALENDARS,
"tushare_available": False, # 不再使用 Tushare
}
# ============================================================
# 统一复权入口(简化版,直接调用 fetch
# ============================================================