增加主力资金选股
This commit is contained in:
@@ -0,0 +1,386 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
主力选股模块
|
||||
使用pywencai获取主力资金净流入前100名股票,并进行智能筛选
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import pywencai
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Tuple
|
||||
import time
|
||||
|
||||
class MainForceStockSelector:
|
||||
"""主力选股类"""
|
||||
|
||||
def __init__(self):
|
||||
self.raw_data = None
|
||||
self.filtered_stocks = None
|
||||
|
||||
def get_main_force_stocks(self, start_date: str = None, days_ago: int = 90) -> Tuple[bool, pd.DataFrame, str]:
|
||||
"""
|
||||
获取主力资金净流入前100名股票
|
||||
|
||||
Args:
|
||||
start_date: 开始日期,格式如"2025年10月1日",如果不提供则使用days_ago
|
||||
days_ago: 距今多少天,默认90天(约3个月)
|
||||
|
||||
Returns:
|
||||
(success, dataframe, message)
|
||||
"""
|
||||
try:
|
||||
# 如果没有提供开始日期,根据days_ago计算
|
||||
if not start_date:
|
||||
date_obj = datetime.now() - timedelta(days=days_ago)
|
||||
start_date = f"{date_obj.year}年{date_obj.month}月{date_obj.day}日"
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"🔍 主力选股 - 数据获取中")
|
||||
print(f"{'='*60}")
|
||||
print(f"开始日期: {start_date}")
|
||||
print(f"目标: 获取主力资金净流入排名前100名股票")
|
||||
|
||||
# 构建查询语句 - 使用多个备选方案,所有方案都要求计算区间涨跌幅
|
||||
queries = [
|
||||
# 方案1: 完整查询(最优)
|
||||
f"{start_date}以来主力资金净流入排名,并计算区间涨跌幅,市值50-5000亿之间,非科创非st,"
|
||||
f"所属同花顺行业,总市值,净利润,营收,市盈率,市净率,"
|
||||
f"盈利能力评分,成长能力评分,营运能力评分,偿债能力评分,"
|
||||
f"现金流评分,资产质量评分,流动性评分,资本充足性评分",
|
||||
|
||||
# 方案2: 简化查询
|
||||
f"{start_date}以来主力资金净流入,并计算区间涨跌幅,市值50-5000亿,非科创非st,"
|
||||
f"所属同花顺行业,总市值,净利润,营收,市盈率,市净率",
|
||||
|
||||
# 方案3: 基础查询
|
||||
f"{start_date}以来主力资金净流入排名,并计算区间涨跌幅,市值50-5000亿,非科创非st,"
|
||||
f"所属行业,总市值",
|
||||
|
||||
# 方案4: 最简查询
|
||||
f"{start_date}以来主力资金净流入前100名,并计算区间涨跌幅,市值50-5000亿,非st非科创板,所属行业,总市值",
|
||||
]
|
||||
|
||||
# 尝试不同的查询方案
|
||||
for i, query in enumerate(queries, 1):
|
||||
print(f"\n尝试方案 {i}/{len(queries)}...")
|
||||
print(f"查询语句: {query[:100]}...")
|
||||
|
||||
try:
|
||||
result = pywencai.get(query=query, loop=True)
|
||||
|
||||
if result is None:
|
||||
print(f" ⚠️ 方案{i}返回None,尝试下一个方案")
|
||||
continue
|
||||
|
||||
# 转换为DataFrame
|
||||
df_result = self._convert_to_dataframe(result)
|
||||
|
||||
if df_result is None or df_result.empty:
|
||||
print(f" ⚠️ 方案{i}数据为空,尝试下一个方案")
|
||||
continue
|
||||
|
||||
# 成功获取数据
|
||||
print(f" ✅ 方案{i}成功!获取到 {len(df_result)} 只股票")
|
||||
self.raw_data = df_result
|
||||
|
||||
# 显示获取到的列名
|
||||
print(f"\n获取到的数据字段:")
|
||||
for col in df_result.columns[:15]: # 只显示前15个字段
|
||||
print(f" - {col}")
|
||||
if len(df_result.columns) > 15:
|
||||
print(f" ... 还有 {len(df_result.columns) - 15} 个字段")
|
||||
|
||||
return True, df_result, f"成功获取{len(df_result)}只股票数据"
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 方案{i}失败: {str(e)}")
|
||||
time.sleep(2) # 失败后等待2秒再试
|
||||
continue
|
||||
|
||||
# 所有方案都失败
|
||||
error_msg = "所有查询方案都失败了,请检查网络或稍后重试"
|
||||
print(f"\n❌ {error_msg}")
|
||||
return False, None, error_msg
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"获取主力选股数据失败: {str(e)}"
|
||||
print(f"\n❌ {error_msg}")
|
||||
return False, None, error_msg
|
||||
|
||||
def _convert_to_dataframe(self, result) -> pd.DataFrame:
|
||||
"""转换问财返回结果为DataFrame"""
|
||||
try:
|
||||
if isinstance(result, pd.DataFrame):
|
||||
return result
|
||||
elif isinstance(result, dict):
|
||||
# 检查是否有嵌套的tableV1结构
|
||||
if 'tableV1' in result:
|
||||
table_data = result['tableV1']
|
||||
if isinstance(table_data, pd.DataFrame):
|
||||
return table_data
|
||||
elif isinstance(table_data, list):
|
||||
return pd.DataFrame(table_data)
|
||||
# 直接转换字典
|
||||
return pd.DataFrame([result])
|
||||
elif isinstance(result, list):
|
||||
return pd.DataFrame(result)
|
||||
else:
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f" 转换DataFrame失败: {e}")
|
||||
return None
|
||||
|
||||
def filter_stocks(self, df: pd.DataFrame,
|
||||
max_range_change: float = 30.0,
|
||||
min_market_cap: float = 50.0,
|
||||
max_market_cap: float = 1300.0) -> pd.DataFrame:
|
||||
"""
|
||||
智能筛选股票
|
||||
|
||||
Args:
|
||||
df: 原始数据
|
||||
max_range_change: 区间涨跌幅上限(%),默认30%
|
||||
min_market_cap: 最小市值(亿),默认50亿
|
||||
max_market_cap: 最大市值(亿),默认1300亿
|
||||
|
||||
Returns:
|
||||
筛选后的DataFrame
|
||||
"""
|
||||
if df is None or df.empty:
|
||||
return df
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"🔍 智能筛选中...")
|
||||
print(f"{'='*60}")
|
||||
print(f"筛选条件:")
|
||||
print(f" - 区间涨跌幅 < {max_range_change}%")
|
||||
print(f" - 市值范围: {min_market_cap}-{max_market_cap}亿")
|
||||
|
||||
original_count = len(df)
|
||||
filtered_df = df.copy()
|
||||
|
||||
# 1. 筛选区间涨跌幅(智能匹配列名)
|
||||
# 优先精确匹配,按优先级查找
|
||||
interval_pct_col = None
|
||||
possible_interval_pct_names = [
|
||||
'区间涨跌幅:前复权',
|
||||
'区间涨跌幅:前复权(%)',
|
||||
'区间涨跌幅(%)',
|
||||
'区间涨跌幅',
|
||||
'涨跌幅:前复权',
|
||||
'涨跌幅:前复权(%)',
|
||||
'涨跌幅(%)',
|
||||
'涨跌幅'
|
||||
]
|
||||
|
||||
# 优先精确匹配
|
||||
for name in possible_interval_pct_names:
|
||||
for col in df.columns:
|
||||
if name in col:
|
||||
interval_pct_col = col
|
||||
break
|
||||
if interval_pct_col:
|
||||
break
|
||||
|
||||
if interval_pct_col:
|
||||
print(f"\n使用字段: {interval_pct_col}")
|
||||
|
||||
# 转换为数值并筛选
|
||||
filtered_df[interval_pct_col] = pd.to_numeric(filtered_df[interval_pct_col], errors='coerce')
|
||||
before = len(filtered_df)
|
||||
filtered_df = filtered_df[
|
||||
(filtered_df[interval_pct_col].notna()) &
|
||||
(filtered_df[interval_pct_col] < max_range_change)
|
||||
]
|
||||
print(f" 区间涨跌幅筛选: {before} -> {len(filtered_df)} 只")
|
||||
else:
|
||||
print(f" ⚠️ 未找到区间涨跌幅字段,跳过涨跌幅筛选")
|
||||
print(f" 可用字段: {list(df.columns[:10])}")
|
||||
|
||||
# 2. 筛选市值
|
||||
market_cap_cols = [col for col in df.columns if '总市值' in col or '市值' in col]
|
||||
if market_cap_cols:
|
||||
col_name = market_cap_cols[0]
|
||||
print(f"\n使用字段: {col_name}")
|
||||
|
||||
# 转换为数值(单位可能是亿或元)
|
||||
filtered_df[col_name] = pd.to_numeric(filtered_df[col_name], errors='coerce')
|
||||
|
||||
# 判断单位(如果值很大,可能是元)
|
||||
max_val = filtered_df[col_name].max()
|
||||
if max_val > 100000: # 大于10万,认为是元
|
||||
print(f" 检测到单位为元,转换为亿")
|
||||
filtered_df[col_name] = filtered_df[col_name] / 100000000
|
||||
|
||||
before = len(filtered_df)
|
||||
filtered_df = filtered_df[
|
||||
(filtered_df[col_name].notna()) &
|
||||
(filtered_df[col_name] >= min_market_cap) &
|
||||
(filtered_df[col_name] <= max_market_cap)
|
||||
]
|
||||
print(f" 市值筛选: {before} -> {len(filtered_df)} 只")
|
||||
|
||||
# 3. 去除ST股票(额外保险)
|
||||
if '股票简称' in filtered_df.columns:
|
||||
before = len(filtered_df)
|
||||
filtered_df = filtered_df[~filtered_df['股票简称'].str.contains('ST', na=False)]
|
||||
if before != len(filtered_df):
|
||||
print(f" ST股票过滤: {before} -> {len(filtered_df)} 只")
|
||||
|
||||
print(f"\n筛选完成: {original_count} -> {len(filtered_df)} 只股票")
|
||||
|
||||
self.filtered_stocks = filtered_df
|
||||
return filtered_df
|
||||
|
||||
def get_top_stocks(self, df: pd.DataFrame, top_n: int = 20) -> pd.DataFrame:
|
||||
"""
|
||||
获取主力资金净流入最多的前N只股票
|
||||
|
||||
Args:
|
||||
df: 筛选后的数据
|
||||
top_n: 取前N名,默认20
|
||||
|
||||
Returns:
|
||||
前N名股票DataFrame
|
||||
"""
|
||||
if df is None or df.empty:
|
||||
return df
|
||||
|
||||
# 查找主力资金相关列(智能匹配)
|
||||
main_fund_col = None
|
||||
main_fund_patterns = [
|
||||
'区间主力资金流向', # 实际列名
|
||||
'区间主力资金净流入',
|
||||
'主力资金流向',
|
||||
'主力资金净流入',
|
||||
'主力净流入'
|
||||
]
|
||||
for pattern in main_fund_patterns:
|
||||
matching = [col for col in df.columns if pattern in col]
|
||||
if matching:
|
||||
main_fund_col = matching[0]
|
||||
break
|
||||
|
||||
if main_fund_col:
|
||||
print(f"\n使用字段排序: {main_fund_col}")
|
||||
|
||||
# 转换为数值并排序
|
||||
df[main_fund_col] = pd.to_numeric(df[main_fund_col], errors='coerce')
|
||||
top_df = df.nlargest(top_n, main_fund_col)
|
||||
|
||||
print(f"获取主力资金净流入前 {len(top_df)} 名")
|
||||
return top_df
|
||||
else:
|
||||
# 如果没有主力资金列,直接返回前N条
|
||||
print(f"未找到主力资金列,返回前{top_n}条数据")
|
||||
return df.head(top_n)
|
||||
|
||||
def format_stock_list_for_analysis(self, df: pd.DataFrame) -> List[Dict]:
|
||||
"""
|
||||
格式化股票列表,准备提交给AI分析师
|
||||
|
||||
Args:
|
||||
df: 股票数据DataFrame
|
||||
|
||||
Returns:
|
||||
格式化后的股票列表
|
||||
"""
|
||||
if df is None or df.empty:
|
||||
return []
|
||||
|
||||
stock_list = []
|
||||
|
||||
for idx, row in df.iterrows():
|
||||
stock_data = {
|
||||
'symbol': row.get('股票代码', 'N/A'),
|
||||
'name': row.get('股票简称', 'N/A'),
|
||||
'industry': row.get('所属同花顺行业', row.get('所属行业', 'N/A')),
|
||||
'market_cap': row.get('总市值[20241209]', row.get('总市值', 'N/A')),
|
||||
'range_change': None,
|
||||
'main_fund_inflow': None,
|
||||
'pe_ratio': row.get('市盈率', 'N/A'),
|
||||
'pb_ratio': row.get('市净率', 'N/A'),
|
||||
'revenue': row.get('营业收入', row.get('营收', 'N/A')),
|
||||
'net_profit': row.get('净利润', 'N/A'),
|
||||
'scores': {},
|
||||
'raw_data': row.to_dict()
|
||||
}
|
||||
|
||||
# 提取区间涨跌幅(使用智能匹配)
|
||||
interval_pct_col = None
|
||||
possible_names = [
|
||||
'区间涨跌幅:前复权', '区间涨跌幅:前复权(%)', '区间涨跌幅(%)',
|
||||
'区间涨跌幅', '涨跌幅:前复权', '涨跌幅:前复权(%)', '涨跌幅(%)', '涨跌幅'
|
||||
]
|
||||
for name in possible_names:
|
||||
for col in df.columns:
|
||||
if name in col:
|
||||
interval_pct_col = col
|
||||
break
|
||||
if interval_pct_col:
|
||||
break
|
||||
if interval_pct_col:
|
||||
stock_data['range_change'] = row.get(interval_pct_col, 'N/A')
|
||||
|
||||
# 提取主力资金(智能匹配)
|
||||
main_fund_col = None
|
||||
main_fund_patterns = [
|
||||
'区间主力资金流向', '区间主力资金净流入',
|
||||
'主力资金流向', '主力资金净流入', '主力净流入'
|
||||
]
|
||||
for pattern in main_fund_patterns:
|
||||
matching = [col for col in df.columns if pattern in col]
|
||||
if matching:
|
||||
main_fund_col = matching[0]
|
||||
break
|
||||
if main_fund_col:
|
||||
stock_data['main_fund_inflow'] = row.get(main_fund_col, 'N/A')
|
||||
|
||||
# 提取评分
|
||||
score_keywords = ['评分', '能力']
|
||||
for col in df.columns:
|
||||
if any(keyword in col for keyword in score_keywords):
|
||||
stock_data['scores'][col] = row.get(col, 'N/A')
|
||||
|
||||
stock_list.append(stock_data)
|
||||
|
||||
return stock_list
|
||||
|
||||
def print_stock_summary(self, stock_list: List[Dict]):
|
||||
"""打印股票摘要信息"""
|
||||
print(f"\n{'='*80}")
|
||||
print(f"📊 候选股票列表 ({len(stock_list)}只)")
|
||||
print(f"{'='*80}")
|
||||
print(f"{'序号':<4} {'代码':<8} {'名称':<12} {'行业':<15} {'主力资金':<12} {'涨跌幅':<8}")
|
||||
print(f"{'-'*80}")
|
||||
|
||||
for i, stock in enumerate(stock_list, 1):
|
||||
symbol = stock['symbol']
|
||||
name = stock['name'][:10] if isinstance(stock['name'], str) else 'N/A'
|
||||
industry = stock['industry'][:13] if isinstance(stock['industry'], str) else 'N/A'
|
||||
|
||||
# 格式化主力资金
|
||||
main_fund = stock['main_fund_inflow']
|
||||
if isinstance(main_fund, (int, float)):
|
||||
if abs(main_fund) >= 100000000: # 大于1亿
|
||||
main_fund_str = f"{main_fund/100000000:.2f}亿"
|
||||
else:
|
||||
main_fund_str = f"{main_fund/10000:.2f}万"
|
||||
else:
|
||||
main_fund_str = 'N/A'
|
||||
|
||||
# 格式化涨跌幅
|
||||
change = stock['range_change']
|
||||
if isinstance(change, (int, float)):
|
||||
change_str = f"{change:.2f}%"
|
||||
else:
|
||||
change_str = 'N/A'
|
||||
|
||||
print(f"{i:<4} {symbol:<8} {name:<12} {industry:<15} {main_fund_str:<12} {change_str:<8}")
|
||||
|
||||
print(f"{'='*80}\n")
|
||||
|
||||
# 全局实例
|
||||
main_force_selector = MainForceStockSelector()
|
||||
|
||||
Reference in New Issue
Block a user