85 lines
3.1 KiB
Python
85 lines
3.1 KiB
Python
"""
|
|
行业映射同步 — 从 QMT get_sector_list + get_stock_list_in_sector 获取
|
|
"""
|
|
from datetime import date
|
|
from core.scoring.sync.base import BaseSync
|
|
from core.scoring.models import IndustryMapping
|
|
from core.database import db
|
|
from core.logger import LogLevel, PrintLog
|
|
|
|
|
|
class IndustrySync(BaseSync):
|
|
"""行业映射同步 — QMT 行业板块 → IndustryMapping 表(全量替换)"""
|
|
|
|
def _fetch(self, **kwargs):
|
|
"""从 QMT 拉取全部行业板块的成份股映射"""
|
|
from xtquant import xtdata
|
|
|
|
all_sectors = xtdata.get_sector_list()
|
|
PrintLog(LogLevel.INFO, f'[sync] Industry: 共 {len(all_sectors)} 个板块')
|
|
|
|
# 尝试使用 get_sector_info 过滤行业板块
|
|
industry_sectors = []
|
|
try:
|
|
sector_info = xtdata.get_sector_info()
|
|
if sector_info is not None and not sector_info.empty:
|
|
for _, row in sector_info.iterrows():
|
|
cat = row.get('category', '')
|
|
if '行业' in str(cat):
|
|
industry_sectors.append(row['sector'])
|
|
except Exception:
|
|
pass
|
|
|
|
# 如果 get_sector_info 无效,回退到名称过滤
|
|
if not industry_sectors:
|
|
for s in all_sectors:
|
|
# 排除明显非行业的板块
|
|
skip_markers = ['概念', '风格', '地域', '地区', '指数', '自定义',
|
|
'ETF', 'LOF', '债券', '基金', '期货', '期权']
|
|
if any(m in s for m in skip_markers):
|
|
continue
|
|
industry_sectors.append(s)
|
|
|
|
PrintLog(LogLevel.INFO, f'[sync] Industry: 筛选出 {len(industry_sectors)} 个行业板块')
|
|
|
|
# 构建 code → {industry_name, classification} 映射
|
|
mapping = {} # code → (industry_name, classification)
|
|
today = date.today()
|
|
|
|
for sector_name in industry_sectors:
|
|
try:
|
|
stocks = xtdata.get_stock_list_in_sector(sector_name)
|
|
for full_code in stocks:
|
|
code = full_code.split('.')[0]
|
|
if code not in mapping:
|
|
mapping[code] = {
|
|
'code': code,
|
|
'industry_name': sector_name,
|
|
'industry_classification': 'QMT',
|
|
'update_date': today,
|
|
}
|
|
except Exception:
|
|
self.stats['errors'] += 1
|
|
|
|
self.stats['inserted'] = len(mapping)
|
|
return list(mapping.values())
|
|
|
|
def _upsert(self, data: list):
|
|
"""全量替换: 清空旧数据 → 批量插入新数据"""
|
|
if not data:
|
|
PrintLog(LogLevel.WARNING, '[sync] Industry: 无数据, 跳过')
|
|
return
|
|
|
|
with db.atomic():
|
|
IndustryMapping.delete().execute()
|
|
for batch in _chunked(data, 500):
|
|
IndustryMapping.insert_many(batch).execute()
|
|
|
|
PrintLog(LogLevel.INFO,
|
|
f'[sync] Industry: 全量替换完成, {len(data)} 条映射')
|
|
|
|
|
|
def _chunked(lst: list, n: int):
|
|
for i in range(0, len(lst), n):
|
|
yield lst[i:i + n]
|