增加更多的历史记录,修正部份API数据获取错误,增加备用API (#5)

* 增加更多的历史记录,修正部份数据获取错误

* 增加更多的历史记录,修正部份API数据获取错误,增加备用API

---------

Co-authored-by: bathfire <>
This commit is contained in:
Eikwang
2025-10-29 16:22:18 +08:00
committed by GitHub
parent 16071f81e8
commit 91d32c6ffa
39 changed files with 2377 additions and 824 deletions
+327 -29
View File
@@ -8,6 +8,13 @@ import pandas as pd
from datetime import datetime, timedelta
import warnings
import time
import logging
import os
from dotenv import load_dotenv
from sector_strategy_db import SectorStrategyDatabase
# 加载环境变量
load_dotenv()
warnings.filterwarnings('ignore')
@@ -20,6 +27,18 @@ class SectorStrategyDataFetcher:
self.max_retries = 3 # 最大重试次数
self.retry_delay = 2 # 重试延迟(秒)
self.request_delay = 1 # 请求间隔(秒)
# 初始化数据库和日志
self.database = SectorStrategyDatabase()
self.logger = logging.getLogger(__name__)
# 配置日志
if not self.logger.handlers:
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
self.logger.addHandler(handler)
self.logger.setLevel(logging.INFO)
def _safe_request(self, func, *args, **kwargs):
"""安全的请求函数,包含重试机制"""
@@ -102,6 +121,9 @@ class SectorStrategyDataFetcher:
data["success"] = True
print("[智策] ✓ 板块数据获取完成!")
# 保存原始数据到数据库
self._save_raw_data_to_db(data)
except Exception as e:
print(f"[智策] ✗ 数据获取出错: {e}")
data["error"] = str(e)
@@ -276,39 +298,111 @@ class SectorStrategyDataFetcher:
return {}
def _get_north_money_flow(self):
"""获取北向资金流向"""
"""获取北向资金流向(优先使用Tushare,失败时使用Akshare"""
# 优先使用Tushare获取沪深港通资金流向
self.ts_pro = None
tushare_token = os.getenv('TUSHARE_TOKEN', '')
try:
# 获取沪深港通资金流向(使用重试机制
# 初始化Tushare(如果尚未初始化
if not hasattr(self, '_tushare_api'):
TUSHARE_TOKEN = os.getenv('TUSHARE_TOKEN', '')
if TUSHARE_TOKEN:
try:
import tushare as ts
ts.set_token(tushare_token)
self.ts_pro = ts.pro_api()
print(" [Tushare] ✅ 初始化成功")
except Exception as e:
print(f" [Tushare] 初始化失败: {e}")
self._tushare_api = None
else:
print(" [Tushare] 未配置Token")
self._tushare_api = None
# 如果Tushare可用,获取数据
if hasattr(self, '_tushare_api') and self._tushare_api:
print(" [Tushare] 正在获取沪深港通资金流向...")
# 获取最近30天的数据
end_date = datetime.now()
start_date = end_date - timedelta(days=20)
df = self._tushare_api.moneyflow_hsgt(
start_date=start_date.strftime('%Y%m%d'),
end_date=end_date.strftime('%Y%m%d')
)
if df is not None and not df.empty:
print(" [Tushare] ✅ 成功获取数据")
# 按日期降序排列,获取最新数据
df = df.sort_values('trade_date', ascending=False)
latest = df.iloc[0]
# 转换数据格式以匹配原有结构
north_flow = {
"date": str(latest['trade_date']),
"north_net_inflow": float(latest['north_money']),
"hgt_net_inflow": float(latest['hgt']),
"sgt_net_inflow": float(latest['sgt']),
"north_total_amount": float(latest['north_money']) # Tushare没有总成交金额,使用净流入作为近似值
}
# 获取历史趋势(最近20天)
history = []
for idx, row in df.head(20).iterrows():
history.append({
"date": str(row['trade_date']),
"net_inflow": float(row['north_money'])
})
north_flow["history"] = history
return north_flow
else:
print(" [Tushare] ❌ 未获取到数据")
else:
print(" [Tushare] 不可用")
except Exception as e:
print(f" [Tushare] 获取北向资金失败: {e}")
# Tushare失败,尝试使用Akshare
try:
print(" [Akshare] 正在获取沪深港通资金流向(备用数据源)...")
df = self._safe_request(ak.stock_hsgt_fund_flow_summary_em)
if df is None or df.empty:
return {}
# 获取最新数据
latest = df.iloc[0]
north_flow = {
"date": str(latest.get('日期', '')),
"north_net_inflow": latest.get('北向资金-成交净买额', 0),
"hgt_net_inflow": latest.get('沪股通-成交净买额', 0),
"sgt_net_inflow": latest.get('深股通-成交净买额', 0),
"north_total_amount": latest.get('北向资金-成交金额', 0)
}
# 获取历史趋势(最近10天)
history = []
for idx, row in df.head(10).iterrows():
history.append({
"date": str(row.get('日期', '')),
"net_inflow": row.get('北向资金-成交净买额', 0)
})
north_flow["history"] = history
return north_flow
if df is not None and not df.empty:
print(" [Akshare] ✅ 成功获取数据")
# 获取最新数据
latest = df.iloc[0]
north_flow = {
"date": str(latest.get('日期', '')),
"north_net_inflow": latest.get('北向资金-成交净买额', 0),
"hgt_net_inflow": latest.get('沪股通-成交净买额', 0),
"sgt_net_inflow": latest.get('深股通-成交净买额', 0),
"north_total_amount": latest.get('北向资金-成交金额', 0)
}
# 获取历史趋势(最近20天)
history = []
for idx, row in df.head(20).iterrows():
history.append({
"date": str(row.get('日期', '')),
"net_inflow": row.get('北向资金-成交净买额', 0)
})
north_flow["history"] = history
return north_flow
else:
print(" [Akshare] ❌ 未获取到数据")
except Exception as e:
print(f" 获取北向资金失败: {e}")
return {}
print(f" [Akshare] 获取北向资金失败: {e}")
# 所有数据源都失败
print(" ❌ 所有数据源均获取失败")
return {}
def _get_financial_news(self):
"""获取财经新闻"""
@@ -438,6 +532,210 @@ class SectorStrategyDataFetcher:
text_parts.append(f" {news['content'][:100]}...")
return "\n".join(text_parts)
def _save_raw_data_to_db(self, data):
"""保存原始数据到数据库"""
try:
if not data.get("success"):
self.logger.warning("[智策数据] 数据获取失败,跳过保存")
return
# 保存板块数据
if data.get("sectors"):
# 将字典转换为DataFrame并映射必要列
sectors_df = pd.DataFrame([
{
'板块名称': v.get('name', k),
'涨跌幅': v.get('change_pct', 0),
'成交额': 0,
'总市值': v.get('total_market_cap', 0),
'市盈率': v.get('pe_ratio', 0),
'市净率': v.get('pb_ratio', 0),
'最新价': 0,
'成交量': 0,
'turnover': v.get('turnover', 0) # 兼容保存方法中的fallback
}
for k, v in data["sectors"].items()
])
self.database.save_sector_raw_data(
data_date=datetime.now().strftime('%Y-%m-%d'),
data_type="industry",
data_df=sectors_df
)
self.logger.info(f"[智策数据] 保存行业板块数据: {len(data['sectors'])} 个板块")
# 保存概念板块数据
if data.get("concepts"):
concepts_df = pd.DataFrame([
{
'板块名称': v.get('name', k),
'涨跌幅': v.get('change_pct', 0),
'成交额': 0,
'总市值': v.get('total_market_cap', 0),
'市盈率': v.get('pe_ratio', 0),
'市净率': v.get('pb_ratio', 0),
'最新价': 0,
'成交量': 0,
'turnover': v.get('turnover', 0)
}
for k, v in data["concepts"].items()
])
self.database.save_sector_raw_data(
data_date=datetime.now().strftime('%Y-%m-%d'),
data_type="concept",
data_df=concepts_df
)
self.logger.info(f"[智策数据] 保存概念板块数据: {len(data['concepts'])} 个概念")
# 保存资金流向数据
if data.get("sector_fund_flow"):
flow_today = data["sector_fund_flow"].get("today", [])
fund_df = pd.DataFrame([
{
'行业': item.get('sector', ''),
'主力净流入-净额': item.get('main_net_inflow', 0),
'主力净流入-净占比': item.get('main_net_inflow_pct', 0),
'超大单净流入-净额': item.get('super_large_net_inflow', 0),
'超大单净流入-净占比': item.get('super_large_net_inflow_pct', 0),
'大单净流入-净额': item.get('large_net_inflow', 0),
'大单净流入-净占比': item.get('large_net_inflow_pct', 0)
}
for item in flow_today
])
if not fund_df.empty:
self.database.save_sector_raw_data(
data_date=datetime.now().strftime('%Y-%m-%d'),
data_type="fund_flow",
data_df=fund_df
)
self.logger.info("[智策数据] 保存资金流向数据")
# 保存市场概况数据
if data.get("market_overview"):
market = data["market_overview"]
mo_df = pd.DataFrame([
{'名称': '上证指数', '最新价': market.get('sh_index', {}).get('close', 0), '涨跌幅': market.get('sh_index', {}).get('change_pct', 0), '成交量': market.get('sh_index', {}).get('volume', 0), '成交额': market.get('sh_index', {}).get('turnover', 0)},
{'名称': '深证成指', '最新价': market.get('sz_index', {}).get('close', 0), '涨跌幅': market.get('sz_index', {}).get('change_pct', 0), '成交量': market.get('sz_index', {}).get('volume', 0), '成交额': market.get('sz_index', {}).get('turnover', 0)},
{'名称': '创业板指', '最新价': market.get('cyb_index', {}).get('close', 0), '涨跌幅': market.get('cyb_index', {}).get('change_pct', 0), '成交量': market.get('cyb_index', {}).get('volume', 0), '成交额': market.get('cyb_index', {}).get('turnover', 0)}
])
self.database.save_sector_raw_data(
data_date=datetime.now().strftime('%Y-%m-%d'),
data_type="market_overview",
data_df=mo_df
)
self.logger.info("[智策数据] 保存市场概况数据")
# 保存北向资金数据
# 注:north_flow结构与原始表不一致,此处暂不保存以避免歧义
# 保存新闻数据
if data.get("news"):
self.database.save_news_data(
news_list=data["news"],
news_date=datetime.now().strftime('%Y-%m-%d'),
source="akshare"
)
self.logger.info(f"[智策数据] 保存财经新闻: {len(data['news'])}")
except Exception as e:
self.logger.error(f"[智策数据] 保存原始数据失败: {e}")
def get_cached_data_with_fallback(self):
"""获取缓存数据,支持回退机制"""
try:
# 首先尝试获取最新数据
print("[智策] 尝试获取最新数据...")
fresh_data = self.get_all_sector_data()
if fresh_data.get("success"):
return fresh_data
# 如果获取失败,回退到缓存数据
print("[智策] 获取最新数据失败,尝试加载缓存数据...")
cached_data = self._load_cached_data()
if cached_data:
print("[智策] ✓ 成功加载缓存数据")
cached_data["from_cache"] = True
cached_data["cache_warning"] = "当前显示为缓存数据(24小时内),可能不是最新信息"
return cached_data
else:
print("[智策] ✗ 无可用缓存数据")
return {
"success": False,
"error": "无法获取数据且无可用缓存",
"timestamp": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
except Exception as e:
self.logger.error(f"[智策数据] 获取数据失败: {e}")
return {
"success": False,
"error": str(e),
"timestamp": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
def _load_cached_data(self):
"""加载缓存数据"""
try:
# 获取最近的各类数据
cached_data = {
"success": True,
"timestamp": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
"sectors": {},
"concepts": {},
"sector_fund_flow": {},
"market_overview": {},
"north_flow": {},
"news": []
}
# 加载板块数据
sectors_data = self.database.get_latest_raw_data("sectors")
if sectors_data:
cached_data["sectors"] = sectors_data.get("data_content", {})
# 加载概念数据
concepts_data = self.database.get_latest_raw_data("concepts")
if concepts_data:
cached_data["concepts"] = concepts_data.get("data_content", {})
# 加载资金流向数据
fund_flow_data = self.database.get_latest_raw_data("fund_flow")
if fund_flow_data:
cached_data["sector_fund_flow"] = fund_flow_data.get("data_content", {})
# 加载市场概况数据
market_data = self.database.get_latest_raw_data("market_overview")
if market_data:
cached_data["market_overview"] = market_data.get("data_content", {})
# 加载北向资金数据
north_data = self.database.get_latest_raw_data("north_flow")
if north_data:
cached_data["north_flow"] = north_data.get("data_content", {})
# 加载新闻数据
news_data = self.database.get_latest_news_data()
if news_data:
# 仅传递内容列表给下游分析,避免结构不一致
cached_data["news"] = news_data.get("data_content", [])
# 检查是否有有效数据
has_data = any([
cached_data["sectors"],
cached_data["concepts"],
cached_data["sector_fund_flow"],
cached_data["market_overview"],
cached_data["north_flow"],
cached_data["news"]
])
return cached_data if has_data else None
except Exception as e:
self.logger.error(f"[智策数据] 加载缓存数据失败: {e}")
return None
# 测试函数