优化数据获取
This commit is contained in:
@@ -0,0 +1,300 @@
|
||||
"""
|
||||
资金流向数据获取模块(akshare版本)
|
||||
使用akshare的stock_individual_fund_flow接口获取个股资金流向
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import sys
|
||||
import io
|
||||
import warnings
|
||||
from datetime import datetime
|
||||
import akshare as ak
|
||||
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
# 设置标准输出编码为UTF-8(仅在命令行环境,避免streamlit冲突)
|
||||
def _setup_stdout_encoding():
|
||||
"""仅在命令行环境设置标准输出编码"""
|
||||
if sys.platform == 'win32' and not hasattr(sys.stdout, '_original_stream'):
|
||||
try:
|
||||
# 检测是否在streamlit环境中
|
||||
import streamlit
|
||||
# 在streamlit中不修改stdout
|
||||
return
|
||||
except ImportError:
|
||||
# 不在streamlit环境,可以安全修改
|
||||
try:
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='ignore')
|
||||
except:
|
||||
pass
|
||||
|
||||
_setup_stdout_encoding()
|
||||
|
||||
|
||||
class FundFlowAkshareDataFetcher:
|
||||
"""资金流向数据获取类(使用akshare数据源)"""
|
||||
|
||||
def __init__(self):
|
||||
self.days = 20 # 获取最近20个交易日
|
||||
self.available = True
|
||||
print("[OK] 资金流向数据获取器初始化成功(akshare数据源)")
|
||||
|
||||
def get_fund_flow_data(self, symbol):
|
||||
"""
|
||||
获取个股资金流向数据
|
||||
|
||||
Args:
|
||||
symbol: 股票代码(6位数字)
|
||||
|
||||
Returns:
|
||||
dict: 包含资金流向数据的字典
|
||||
"""
|
||||
data = {
|
||||
"symbol": symbol,
|
||||
"fund_flow_data": None,
|
||||
"data_success": False,
|
||||
"source": "akshare"
|
||||
}
|
||||
|
||||
# 只支持中国股票
|
||||
if not self._is_chinese_stock(symbol):
|
||||
data["error"] = "资金流向数据仅支持中国A股股票"
|
||||
return data
|
||||
|
||||
try:
|
||||
print(f"[资金流向] 正在获取 {symbol} 的资金流向数据...")
|
||||
|
||||
# 确定市场
|
||||
market = self._get_market(symbol)
|
||||
|
||||
# 获取资金流向数据
|
||||
fund_flow_data = self._get_individual_fund_flow(symbol, market)
|
||||
|
||||
if fund_flow_data:
|
||||
data["fund_flow_data"] = fund_flow_data
|
||||
print(f" [OK] 成功获取 {len(fund_flow_data.get('data', []))} 个交易日的资金流向数据")
|
||||
data["data_success"] = True
|
||||
print("[完成] 资金流向数据获取完成")
|
||||
else:
|
||||
print("[警告] 未能获取到资金流向数据")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] 获取资金流向数据失败: {e}")
|
||||
data["error"] = str(e)
|
||||
|
||||
return data
|
||||
|
||||
def _is_chinese_stock(self, symbol):
|
||||
"""判断是否为中国股票"""
|
||||
return symbol.isdigit() and len(symbol) == 6
|
||||
|
||||
def _get_market(self, symbol):
|
||||
"""
|
||||
根据股票代码判断市场
|
||||
上海证券交易所: sh (60开头, 688开头)
|
||||
深圳证券交易所: sz (00开头, 30开头)
|
||||
北京证券交易所: bj (8开头, 4开头)
|
||||
"""
|
||||
if symbol.startswith('60') or symbol.startswith('688'):
|
||||
return 'sh'
|
||||
elif symbol.startswith('00') or symbol.startswith('30'):
|
||||
return 'sz'
|
||||
elif symbol.startswith('8') or symbol.startswith('4'):
|
||||
return 'bj'
|
||||
else:
|
||||
# 默认深圳
|
||||
return 'sz'
|
||||
|
||||
def _get_individual_fund_flow(self, symbol, market):
|
||||
"""获取个股资金流向数据"""
|
||||
try:
|
||||
# 使用akshare的stock_individual_fund_flow接口
|
||||
print(f" 使用 akshare 获取资金流向 (市场: {market})...")
|
||||
|
||||
df = ak.stock_individual_fund_flow(stock=symbol, market=market)
|
||||
|
||||
if df is None or df.empty:
|
||||
print(f" 未找到资金流向数据")
|
||||
return None
|
||||
|
||||
# akshare 返回的数据是按时间正序排列(从旧到新),所以使用 tail() 获取最近N天的数据
|
||||
df = df.tail(self.days)
|
||||
|
||||
# 按日期倒序排列,让最新的数据在前面
|
||||
df = df.iloc[::-1].reset_index(drop=True)
|
||||
|
||||
# 转换为字典列表
|
||||
data_list = []
|
||||
for idx, row in df.iterrows():
|
||||
item = {}
|
||||
for col in df.columns:
|
||||
value = row.get(col)
|
||||
if value is None or (isinstance(value, float) and pd.isna(value)):
|
||||
continue
|
||||
try:
|
||||
# 保持数值类型
|
||||
if isinstance(value, (int, float)):
|
||||
item[col] = value
|
||||
else:
|
||||
item[col] = str(value)
|
||||
except:
|
||||
item[col] = "N/A"
|
||||
if item:
|
||||
data_list.append(item)
|
||||
|
||||
return {
|
||||
"data": data_list,
|
||||
"days": len(data_list),
|
||||
"columns": df.columns.tolist(),
|
||||
"market": market,
|
||||
"query_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f" 获取资金流向数据异常: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def format_fund_flow_for_ai(self, data):
|
||||
"""
|
||||
将资金流向数据格式化为适合AI阅读的文本
|
||||
"""
|
||||
if not data or not data.get("data_success"):
|
||||
return "未能获取资金流向数据"
|
||||
|
||||
text_parts = []
|
||||
|
||||
fund_flow_data = data.get("fund_flow_data")
|
||||
if fund_flow_data:
|
||||
text_parts.append(f"""
|
||||
【个股资金流向数据 - akshare数据源】
|
||||
股票代码:{data.get('symbol', 'N/A')}
|
||||
市场:{fund_flow_data.get('market', 'N/A').upper()}
|
||||
交易日数:最近{fund_flow_data.get('days', 0)}个交易日
|
||||
查询时间:{fund_flow_data.get('query_time', 'N/A')}
|
||||
|
||||
═══════════════════════════════════════
|
||||
[资金流向详细数据]
|
||||
═══════════════════════════════════════
|
||||
""")
|
||||
|
||||
# 显示每个交易日的数据
|
||||
for idx, item in enumerate(fund_flow_data.get('data', []), 1):
|
||||
date = item.get('日期', 'N/A')
|
||||
close_price = item.get('收盘价', 'N/A')
|
||||
change_pct = item.get('涨跌幅', 'N/A')
|
||||
|
||||
text_parts.append(f"""
|
||||
第 {idx} 个交易日 ({date}):
|
||||
基本信息:
|
||||
- 收盘价: {close_price}
|
||||
- 涨跌幅: {change_pct}%
|
||||
|
||||
主力资金:
|
||||
- 主力净流入-净额: {item.get('主力净流入-净额', 'N/A')}
|
||||
- 主力净流入-净占比: {item.get('主力净流入-净占比', 'N/A')}%
|
||||
|
||||
超大单:
|
||||
- 超大单净流入-净额: {item.get('超大单净流入-净额', 'N/A')}
|
||||
- 超大单净流入-净占比: {item.get('超大单净流入-净占比', 'N/A')}%
|
||||
|
||||
大单:
|
||||
- 大单净流入-净额: {item.get('大单净流入-净额', 'N/A')}
|
||||
- 大单净流入-净占比: {item.get('大单净流入-净占比', 'N/A')}%
|
||||
|
||||
中单:
|
||||
- 中单净流入-净额: {item.get('中单净流入-净额', 'N/A')}
|
||||
- 中单净流入-净占比: {item.get('中单净流入-净占比', 'N/A')}%
|
||||
|
||||
小单:
|
||||
- 小单净流入-净额: {item.get('小单净流入-净额', 'N/A')}
|
||||
- 小单净流入-净占比: {item.get('小单净流入-净占比', 'N/A')}%
|
||||
""")
|
||||
|
||||
# 添加统计汇总
|
||||
text_parts.append("""
|
||||
═══════════════════════════════════════
|
||||
[统计汇总 - 最近20个交易日]
|
||||
═══════════════════════════════════════
|
||||
""")
|
||||
|
||||
# 计算统计数据
|
||||
data_list = fund_flow_data.get('data', [])
|
||||
if data_list:
|
||||
# 主力净流入统计
|
||||
main_inflow_list = [item.get('主力净流入-净额', 0) for item in data_list if isinstance(item.get('主力净流入-净额'), (int, float))]
|
||||
if main_inflow_list:
|
||||
total_main_inflow = sum(main_inflow_list)
|
||||
avg_main_inflow = total_main_inflow / len(main_inflow_list)
|
||||
positive_days = len([x for x in main_inflow_list if x > 0])
|
||||
negative_days = len([x for x in main_inflow_list if x < 0])
|
||||
|
||||
text_parts.append(f"""
|
||||
主力资金统计:
|
||||
- 累计净流入: {total_main_inflow:.2f}
|
||||
- 平均每日净流入: {avg_main_inflow:.2f}
|
||||
- 净流入天数: {positive_days}天
|
||||
- 净流出天数: {negative_days}天
|
||||
- 净流入占比: {positive_days/len(main_inflow_list)*100:.1f}%
|
||||
""")
|
||||
|
||||
# 涨跌幅统计
|
||||
change_pct_list = [item.get('涨跌幅', 0) for item in data_list if isinstance(item.get('涨跌幅'), (int, float))]
|
||||
if change_pct_list:
|
||||
avg_change = sum(change_pct_list) / len(change_pct_list)
|
||||
up_days = len([x for x in change_pct_list if x > 0])
|
||||
down_days = len([x for x in change_pct_list if x < 0])
|
||||
|
||||
text_parts.append(f"""
|
||||
股价统计:
|
||||
- 平均涨跌幅: {avg_change:.2f}%
|
||||
- 上涨天数: {up_days}天
|
||||
- 下跌天数: {down_days}天
|
||||
- 上涨占比: {up_days/len(change_pct_list)*100:.1f}%
|
||||
""")
|
||||
|
||||
return "\n".join(text_parts)
|
||||
|
||||
|
||||
# 测试函数
|
||||
if __name__ == "__main__":
|
||||
print("测试资金流向数据获取(akshare数据源)...")
|
||||
print("="*60)
|
||||
|
||||
fetcher = FundFlowAkshareDataFetcher()
|
||||
|
||||
if not fetcher.available:
|
||||
print("[ERROR] 资金流向数据获取器不可用")
|
||||
sys.exit(1)
|
||||
|
||||
# 测试股票
|
||||
test_symbols = [
|
||||
("000001", "平安银行"),
|
||||
("600519", "贵州茅台"),
|
||||
("000858", "五粮液")
|
||||
]
|
||||
|
||||
for symbol, name in test_symbols:
|
||||
print(f"\n{'='*60}")
|
||||
print(f"正在测试股票: {name} ({symbol})")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
data = fetcher.get_fund_flow_data(symbol)
|
||||
|
||||
if data.get("data_success"):
|
||||
print("\n" + "="*60)
|
||||
print("资金流向数据获取成功!")
|
||||
print("="*60)
|
||||
|
||||
formatted_text = fetcher.format_fund_flow_for_ai(data)
|
||||
# 只显示前2000个字符
|
||||
preview = formatted_text[:2000] if len(formatted_text) > 2000 else formatted_text
|
||||
print(preview)
|
||||
if len(formatted_text) > 2000:
|
||||
print(f"... (共 {len(formatted_text)} 字符)")
|
||||
else:
|
||||
print(f"\n获取失败: {data.get('error', '未知错误')}")
|
||||
|
||||
print("\n")
|
||||
|
||||
Reference in New Issue
Block a user