268 lines
9.9 KiB
Python
268 lines
9.9 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
使用东财概念板块数据分析本周强势板块
|
||
需要5000积分
|
||
"""
|
||
|
||
import sys
|
||
from pathlib import Path
|
||
import pandas as pd
|
||
from datetime import datetime, timedelta
|
||
|
||
# 添加项目根目录到路径
|
||
current_dir = Path(__file__).parent
|
||
sys.path.insert(0, str(current_dir))
|
||
|
||
from src.data.tushare_fetcher import TushareFetcher
|
||
from loguru import logger
|
||
|
||
|
||
def get_recent_trading_dates(days_back=5):
|
||
"""获取最近的交易日期"""
|
||
dates = []
|
||
current = datetime.now()
|
||
|
||
while len(dates) < days_back:
|
||
# 排除周末
|
||
if current.weekday() < 5: # 0-4是周一到周五
|
||
dates.append(current.strftime('%Y%m%d'))
|
||
current -= timedelta(days=1)
|
||
|
||
return sorted(dates) # 升序返回
|
||
|
||
|
||
def analyze_eastmoney_concepts(fetcher: TushareFetcher):
|
||
"""使用东财概念板块数据分析"""
|
||
try:
|
||
if not fetcher.pro:
|
||
logger.error("需要Tushare Pro权限")
|
||
return
|
||
|
||
logger.info("🚀 使用东财概念板块数据分析...")
|
||
|
||
# 获取最近5个交易日
|
||
trading_dates = get_recent_trading_dates(5)
|
||
logger.info(f"分析时间范围: {trading_dates[0]} 到 {trading_dates[-1]}")
|
||
|
||
# 获取最新交易日的概念板块数据
|
||
latest_date = trading_dates[-1]
|
||
|
||
try:
|
||
# 使用东财概念板块接口
|
||
dc_concepts = fetcher.pro.dc_index(trade_date=latest_date)
|
||
logger.info(f"获取到 {len(dc_concepts)} 个东财概念板块")
|
||
|
||
if dc_concepts.empty:
|
||
logger.warning("未获取到东财概念板块数据")
|
||
return
|
||
|
||
# 打印数据结构以便调试
|
||
logger.info(f"数据列名: {list(dc_concepts.columns)}")
|
||
if not dc_concepts.empty:
|
||
logger.info(f"样本数据:\n{dc_concepts.head(2)}")
|
||
|
||
# 检查涨跌幅字段名
|
||
change_col = None
|
||
for col in ['pct_chg', 'pct_change', 'change_pct', 'chg_pct']:
|
||
if col in dc_concepts.columns:
|
||
change_col = col
|
||
break
|
||
|
||
if change_col:
|
||
# 按涨跌幅排序
|
||
dc_concepts = dc_concepts.sort_values(change_col, ascending=False)
|
||
else:
|
||
logger.warning("未找到涨跌幅字段,使用原始顺序")
|
||
change_col = 'code' # 使用code作为默认排序
|
||
|
||
print("\n" + "="*80)
|
||
print("📈 东财概念板块实时排行榜")
|
||
print("="*80)
|
||
# 显示表头
|
||
if change_col != 'code':
|
||
print(f"{'排名':<4} {'概念名称':<25} {'涨跌幅':<10} {'概念代码':<15}")
|
||
else:
|
||
print(f"{'排名':<4} {'概念名称':<25} {'概念代码':<15}")
|
||
print("-" * 80)
|
||
|
||
for i, (_, concept) in enumerate(dc_concepts.head(20).iterrows()):
|
||
rank = i + 1
|
||
name = concept.get('name', 'N/A')[:23] + '..' if len(str(concept.get('name', 'N/A'))) > 23 else concept.get('name', 'N/A')
|
||
code = concept.get('ts_code', 'N/A')
|
||
|
||
if change_col != 'code':
|
||
change_pct = f"{concept[change_col]:+.2f}%" if not pd.isna(concept.get(change_col, 0)) else "N/A"
|
||
print(f"{rank:<4} {name:<25} {change_pct:<10} {code:<15}")
|
||
else:
|
||
print(f"{rank:<4} {name:<25} {code:<15}")
|
||
|
||
# 强势概念TOP10
|
||
if change_col != 'code':
|
||
print(f"\n🚀 强势概念板块TOP10:")
|
||
for i, (_, concept) in enumerate(dc_concepts.head(10).iterrows()):
|
||
change_val = concept.get(change_col, 0)
|
||
if not pd.isna(change_val):
|
||
print(f" {i+1:2d}. {concept.get('name', 'N/A')}: {change_val:+.2f}%")
|
||
|
||
# 弱势概念TOP10
|
||
print(f"\n📉 弱势概念板块TOP10:")
|
||
weak_concepts = dc_concepts.tail(10).iloc[::-1] # 反转顺序
|
||
for i, (_, concept) in enumerate(weak_concepts.iterrows()):
|
||
change_val = concept.get(change_col, 0)
|
||
if not pd.isna(change_val):
|
||
print(f" {i+1:2d}. {concept.get('name', 'N/A')}: {change_val:+.2f}%")
|
||
else:
|
||
print(f"\n📋 概念板块列表(前10个):")
|
||
for i, (_, concept) in enumerate(dc_concepts.head(10).iterrows()):
|
||
print(f" {i+1:2d}. {concept.get('name', 'N/A')} ({concept.get('ts_code', 'N/A')})")
|
||
|
||
return dc_concepts
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取东财概念板块数据失败: {e}")
|
||
return None
|
||
|
||
except Exception as e:
|
||
logger.error(f"分析东财概念板块失败: {e}")
|
||
return None
|
||
|
||
|
||
def analyze_concept_trend(fetcher: TushareFetcher, concept_codes=None):
|
||
"""分析概念板块的趋势(多日对比)"""
|
||
try:
|
||
if not fetcher.pro:
|
||
logger.error("需要Tushare Pro权限")
|
||
return
|
||
|
||
logger.info("📊 分析概念板块趋势...")
|
||
|
||
# 获取最近5个交易日
|
||
trading_dates = get_recent_trading_dates(5)
|
||
|
||
# 如果没有指定概念代码,获取当日表现最好的前10个
|
||
if concept_codes is None:
|
||
latest_concepts = analyze_eastmoney_concepts(fetcher)
|
||
if latest_concepts is not None and not latest_concepts.empty:
|
||
concept_codes = latest_concepts.head(5)['code'].tolist()
|
||
else:
|
||
logger.warning("无法获取概念代码")
|
||
return
|
||
|
||
print(f"\n" + "="*80)
|
||
print("📈 热门概念板块多日趋势分析")
|
||
print("="*80)
|
||
|
||
for concept_code in concept_codes:
|
||
concept_trend = []
|
||
|
||
for date in trading_dates:
|
||
try:
|
||
# 获取特定日期的概念数据
|
||
daily_data = fetcher.pro.dc_index(
|
||
trade_date=date,
|
||
ts_code=concept_code
|
||
)
|
||
|
||
if not daily_data.empty:
|
||
# 检查数据结构
|
||
logger.debug(f"概念 {concept_code} 在 {date} 的数据字段: {list(daily_data.columns)}")
|
||
|
||
# 东财概念数据可能没有close字段,使用其他字段替代
|
||
close_value = daily_data.iloc[0].get('total_mv', 1) # 使用总市值代替
|
||
if close_value == 0:
|
||
close_value = 1 # 避免除零
|
||
|
||
concept_trend.append({
|
||
'date': date,
|
||
'name': daily_data.iloc[0]['name'],
|
||
'close': close_value,
|
||
'pct_chg': daily_data.iloc[0]['pct_change']
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.debug(f"获取概念 {concept_code} 在 {date} 的数据失败: {e}")
|
||
continue
|
||
|
||
# 输出趋势
|
||
if concept_trend:
|
||
concept_name = concept_trend[0]['name']
|
||
print(f"\n📊 {concept_name} ({concept_code}) 近5日走势:")
|
||
|
||
# 计算总涨跌幅
|
||
if len(concept_trend) >= 2:
|
||
start_close = concept_trend[0]['close']
|
||
end_close = concept_trend[-1]['close']
|
||
|
||
if start_close != 0 and start_close is not None:
|
||
total_change = (end_close - start_close) / start_close * 100
|
||
print(f" 总涨跌幅: {total_change:+.2f}%")
|
||
else:
|
||
print(f" 总涨跌幅: 无法计算(起始值为0)")
|
||
|
||
# 显示每日数据
|
||
for data in concept_trend:
|
||
print(f" {data['date']}: {data['pct_chg']:+6.2f}% (指数: {data['close']:8.2f})")
|
||
|
||
print("\n" + "="*80)
|
||
|
||
except Exception as e:
|
||
logger.error(f"分析概念趋势失败: {e}")
|
||
|
||
|
||
def get_concept_constituents(fetcher: TushareFetcher, concept_code: str):
|
||
"""获取概念板块成分股"""
|
||
try:
|
||
if not fetcher.pro:
|
||
logger.error("需要Tushare Pro权限")
|
||
return
|
||
|
||
logger.info(f"获取概念 {concept_code} 的成分股...")
|
||
|
||
# 尝试通过概念板块获取成分股
|
||
try:
|
||
# 使用concept_detail接口(如果可用)
|
||
constituents = fetcher.pro.concept_detail(id=concept_code)
|
||
|
||
if not constituents.empty:
|
||
print(f"\n📋 概念成分股 ({len(constituents)}只):")
|
||
for _, stock in constituents.head(10).iterrows():
|
||
print(f" {stock['ts_code']}: {stock.get('name', 'N/A')}")
|
||
else:
|
||
logger.warning(f"概念 {concept_code} 无成分股数据")
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取概念成分股失败: {e}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取概念成分股失败: {e}")
|
||
|
||
|
||
def main():
|
||
"""主函数"""
|
||
logger.info("🚀 开始使用东财概念板块数据分析...")
|
||
|
||
# 初始化Tushare数据获取器
|
||
token = "0ed6419a00d8923dc19c0b58fc92d94c9a0696949ab91a13aa58a0cc"
|
||
fetcher = TushareFetcher(token=token)
|
||
|
||
# 1. 分析当日概念板块表现
|
||
concepts_data = analyze_eastmoney_concepts(fetcher)
|
||
|
||
# 2. 分析热门概念的多日趋势
|
||
if concepts_data is not None and not concepts_data.empty:
|
||
print("\n" + "="*80 + "\n")
|
||
|
||
# 获取表现最好的前3个概念进行趋势分析
|
||
top_concepts = concepts_data.head(3)['ts_code'].tolist()
|
||
analyze_concept_trend(fetcher, top_concepts)
|
||
|
||
# 3. 获取第一个概念的成分股示例
|
||
# top_concept_code = top_concepts[0] if top_concepts else None
|
||
# if top_concept_code:
|
||
# get_concept_constituents(fetcher, top_concept_code)
|
||
|
||
logger.info("✅ 分析完成!")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main() |