204 lines
7.1 KiB
Python
204 lines
7.1 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
A股短期题材选股 - 详细诊断版本
|
||
显示每个股票的筛选过程
|
||
"""
|
||
import asyncio
|
||
import sys
|
||
import os
|
||
from datetime import datetime, timedelta
|
||
|
||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||
|
||
from app.utils.logger import logger
|
||
from app.config import get_settings
|
||
from app.astock_agent.tushare_client import get_tushare_client
|
||
|
||
|
||
async def diagnose_sector_stocks():
|
||
"""诊断板块成分股的筛选过程"""
|
||
print("\n" + "=" * 60)
|
||
print("🔍 A股选股详细诊断")
|
||
print("=" * 60)
|
||
|
||
settings = get_settings()
|
||
ts_client = get_tushare_client(settings.tushare_token)
|
||
|
||
# 1. 测试获取一个板块的成分股
|
||
print("\n【测试】获取智能电网板块成分股...")
|
||
try:
|
||
# 使用智能电网板块代码(与选股器一致)
|
||
sector_code = "885311.TI"
|
||
members_df = ts_client.get_sector_members(sector_code)
|
||
|
||
if members_df.empty:
|
||
print("❌ 无法获取板块成分股")
|
||
return
|
||
|
||
stock_codes = members_df['con_code'].tolist()[:10] # 只测试前10只
|
||
print(f"✓ 获取到 {len(stock_codes)} 只成分股(测试前10只)")
|
||
print(f"股票代码: {stock_codes}")
|
||
|
||
# 2. 获取这些股票的实时行情
|
||
print("\n【测试】获取实时行情...")
|
||
today = datetime.now().strftime('%Y%m%d')
|
||
yesterday = (datetime.now() - timedelta(days=10)).strftime('%Y%m%d')
|
||
|
||
all_stocks_data = []
|
||
for stock_code in stock_codes:
|
||
try:
|
||
daily_df = ts_client.pro.daily(
|
||
ts_code=stock_code,
|
||
start_date=yesterday,
|
||
end_date=today
|
||
)
|
||
|
||
if daily_df.empty:
|
||
print(f" ⚠️ {stock_code}: 无历史数据")
|
||
continue
|
||
|
||
daily_df = daily_df.sort_values('trade_date')
|
||
latest = daily_df.iloc[-1]
|
||
|
||
stock_info = {
|
||
'ts_code': stock_code,
|
||
'name': latest.get('name', ''),
|
||
'close': float(latest['close']),
|
||
'pct_chg': float(latest['pct_chg']),
|
||
'vol': float(latest['vol']),
|
||
'amount': float(latest['amount']) * 1000,
|
||
'trade_date': str(latest['trade_date'])
|
||
}
|
||
all_stocks_data.append(stock_info)
|
||
|
||
except Exception as e:
|
||
print(f" ❌ {stock_code}: 获取失败 - {e}")
|
||
continue
|
||
|
||
print(f"\n✓ 成功获取 {len(all_stocks_data)} 只股票的行情")
|
||
|
||
# 3. 获取每日指标
|
||
print("\n【测试】获取每日指标(换手率等)...")
|
||
basic_df = ts_client.pro.daily_basic(
|
||
ts_code=','.join(stock_codes),
|
||
trade_date=all_stocks_data[0]['trade_date'],
|
||
fields='ts_code,trade_date,turnover_rate,pe,pb'
|
||
)
|
||
|
||
if basic_df.empty:
|
||
print("⚠️ 无法获取每日指标数据")
|
||
else:
|
||
print(f"✓ 获取到 {len(basic_df)} 只股票的每日指标")
|
||
|
||
# 4. 逐个检查筛选条件
|
||
print("\n【测试】逐个检查筛选条件...")
|
||
print("=" * 80)
|
||
|
||
for stock_info in all_stocks_data:
|
||
stock_code = stock_info['ts_code']
|
||
name = stock_info['name']
|
||
close = stock_info['close']
|
||
pct_chg = stock_info['pct_chg']
|
||
vol = stock_info['vol']
|
||
amount = stock_info['amount']
|
||
|
||
print(f"\n🔍 {name}({stock_code}):")
|
||
print(f" 日期: {stock_info['trade_date']}")
|
||
print(f" 现价: ¥{close:.2f}, 涨跌幅: {pct_chg:+.2f}%")
|
||
|
||
# 检查1: ST股票
|
||
if 'ST' in name or '退' in name:
|
||
print(f" ❌ ST/退市股,被过滤")
|
||
continue
|
||
print(f" ✓ 不是ST/退市股")
|
||
|
||
# 检查2: 换手率
|
||
basic_row = basic_df[basic_df['ts_code'] == stock_code]
|
||
if not basic_row.empty:
|
||
turnover = float(basic_row.iloc[0].get('turnover_rate', 0))
|
||
print(f" 换手率: {turnover:.2f}%")
|
||
if 1.0 <= turnover <= 20.0:
|
||
print(f" ✓ 换手率符合")
|
||
else:
|
||
print(f" ❌ 换手率不符合(需要1%-20%)")
|
||
continue
|
||
else:
|
||
print(f" ⚠️ 无换手率数据")
|
||
turnover = 0
|
||
|
||
# 检查3: MA多头排列
|
||
try:
|
||
start_date = (datetime.now() - timedelta(days=60)).strftime('%Y%m%d')
|
||
daily_df = ts_client.pro.daily(
|
||
ts_code=stock_code,
|
||
start_date=start_date,
|
||
end_date=today
|
||
)
|
||
|
||
if daily_df.empty or len(daily_df) < 30:
|
||
print(f" ❌ 历史数据不足(需要30天以上),无法计算MA")
|
||
continue
|
||
|
||
daily_df = daily_df.sort_values('trade_date').reset_index(drop=True)
|
||
close_series = daily_df['close']
|
||
vol_series = daily_df['vol']
|
||
|
||
ma5 = close_series.rolling(window=5).mean().iloc[-1]
|
||
ma10 = close_series.rolling(window=10).mean().iloc[-1]
|
||
ma20 = close_series.rolling(window=20).mean().iloc[-1]
|
||
|
||
print(f" MA5: ¥{ma5:.2f}, MA10: ¥{ma10:.2f}, MA20: ¥{ma20:.2f}")
|
||
|
||
if ma5 > ma20:
|
||
print(f" ✓ MA趋势符合(MA5 > MA20)")
|
||
else:
|
||
print(f" ❌ MA趋势不符合(需要 MA5 > MA20)")
|
||
continue
|
||
|
||
# 检查4: 量能
|
||
ma5_vol = vol_series.rolling(window=5).mean().iloc[-1]
|
||
volume_ratio = vol / ma5_vol if ma5_vol > 0 else 1
|
||
print(f" 量比: {volume_ratio:.2f}")
|
||
|
||
if volume_ratio >= 0.7:
|
||
print(f" ✓ 量能符合(≥0.7)")
|
||
else:
|
||
print(f" ❌ 量能不足(量比需要≥0.7)")
|
||
continue
|
||
|
||
# 检查5: 市值
|
||
if turnover > 0:
|
||
market_cap = amount / (turnover / 100)
|
||
market_cap_yi = market_cap / 100000000
|
||
print(f" 市值: {market_cap_yi:.2f}亿")
|
||
|
||
if 30 <= market_cap_yi <= 1000:
|
||
print(f" ✓ 市值符合")
|
||
else:
|
||
print(f" ❌ 市值不符合(需要30-1000亿)")
|
||
continue
|
||
|
||
print(f" ✅✅✅ {name}({stock_code}) 通过所有筛选条件!")
|
||
|
||
except Exception as e:
|
||
print(f" ❌ 计算技术指标失败: {e}")
|
||
import traceback
|
||
print(traceback.format_exc())
|
||
|
||
except Exception as e:
|
||
print(f"❌ 诊断失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
|
||
async def main():
|
||
"""主函数"""
|
||
await diagnose_sector_stocks()
|
||
print("\n" + "=" * 60)
|
||
print("诊断完成")
|
||
print("=" * 60)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
asyncio.run(main())
|