mirror of
https://github.com/ZhuLinsen/daily_stock_analysis
synced 2026-09-20 10:53:33 +08:00
fix: Tushare priority and realtime quote support (fixed #185)
- Upgrade Tushare priority to -1 when token configured - Implement realtime quote with Pro/legacy API fallback - Load .env earlier for proper proxy setup - Remove unreliable north-bound capital from market analyzer - Add ETF test scenario, extend stale issue timer (3d -> 7d)
This commit is contained in:
@@ -19,7 +19,7 @@ TUSHARE_TOKEN=your_tushare_token_here
|
||||
# 【方案一】使用 Gemini(推荐,有免费额度)
|
||||
# 从 https://aistudio.google.com/ 获取免费 API Key
|
||||
# 留空或删除此行则跳过 Gemini,直接使用方案二
|
||||
#
|
||||
# 如果是本地运行,直连gemini/openai官网需要特殊网络环境
|
||||
GEMINI_API_KEY=
|
||||
GEMINI_MODEL=gemini-3-flash-preview
|
||||
GEMINI_MODEL_FALLBACK=gemini-2.5-flash
|
||||
|
||||
8
.github/workflows/stale.yml
vendored
8
.github/workflows/stale.yml
vendored
@@ -24,16 +24,16 @@ jobs:
|
||||
uses: actions/stale@v9
|
||||
with:
|
||||
# ===== Issue 配置 =====
|
||||
days-before-issue-stale: 3
|
||||
days-before-issue-close: 3
|
||||
days-before-issue-stale: 7
|
||||
days-before-issue-close: 7
|
||||
stale-issue-label: 'stale'
|
||||
stale-issue-message: |
|
||||
👋 这个 Issue 已经 **3 天** 没有活动了。
|
||||
👋 这个 Issue 已经 **7 天** 没有活动了。
|
||||
|
||||
如果问题仍然存在,请回复说明最新情况,我们会继续跟进。
|
||||
如果问题已解决或不再需要,可以直接关闭此 Issue。
|
||||
|
||||
**如果 3 天内没有回复,此 Issue 将自动关闭。**
|
||||
**如果 7 天内没有回复,此 Issue 将自动关闭。**
|
||||
close-issue-message: |
|
||||
🔒 由于长时间没有活动,此 Issue 已自动关闭。
|
||||
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -67,3 +67,7 @@ local/
|
||||
run.sh
|
||||
|
||||
verify_*.py
|
||||
|
||||
# ignore claude files
|
||||
.claude/
|
||||
CLAUDE.md
|
||||
@@ -96,6 +96,7 @@ class RealtimeSource(Enum):
|
||||
AKSHARE_EM = "akshare_em" # 东方财富(akshare库)
|
||||
AKSHARE_SINA = "akshare_sina" # 新浪财经
|
||||
AKSHARE_QQ = "akshare_qq" # 腾讯财经
|
||||
TUSHARE = "tushare" # Tushare Pro
|
||||
TENCENT = "tencent" # 腾讯直连
|
||||
SINA = "sina" # 新浪直连
|
||||
FALLBACK = "fallback" # 降级兜底
|
||||
|
||||
@@ -104,7 +104,7 @@ class TushareFetcher(BaseFetcher):
|
||||
根据 Token 配置和 API 初始化状态确定优先级
|
||||
|
||||
策略:
|
||||
- Token 配置且 API 初始化成功:优先级 0(最高)
|
||||
- Token 配置且 API 初始化成功:优先级 -1(绝对最高,优于 efinance)
|
||||
- 其他情况:优先级 2(默认)
|
||||
|
||||
Returns:
|
||||
@@ -114,8 +114,8 @@ class TushareFetcher(BaseFetcher):
|
||||
|
||||
if config.tushare_token and self._api is not None:
|
||||
# Token 配置且 API 初始化成功,提升为最高优先级
|
||||
logger.info("✅ 检测到 TUSHARE_TOKEN 且 API 初始化成功,Tushare 数据源优先级提升为最高 (Priority 0)")
|
||||
return 0
|
||||
logger.info("✅ 检测到 TUSHARE_TOKEN 且 API 初始化成功,Tushare 数据源优先级提升为最高 (Priority -1)")
|
||||
return -1
|
||||
|
||||
# Token 未配置或 API 初始化失败,保持默认优先级
|
||||
return 2
|
||||
@@ -391,20 +391,104 @@ class TushareFetcher(BaseFetcher):
|
||||
|
||||
def get_realtime_quote(self, stock_code: str) -> Optional[dict]:
|
||||
"""
|
||||
获取实时行情(Tushare Pro 需要较高积分才能使用实时接口)
|
||||
获取实时行情
|
||||
|
||||
注意:Tushare 实时行情接口需要较高积分(>=2000),
|
||||
普通用户建议使用其他数据源的实时行情。
|
||||
策略:
|
||||
1. 优先尝试 Pro 接口(需要2000积分):数据全,稳定性高
|
||||
2. 失败降级到旧版接口:门槛低,数据较少
|
||||
|
||||
Args:
|
||||
stock_code: 股票代码
|
||||
|
||||
Returns:
|
||||
实时行情数据字典,失败返回 None
|
||||
UnifiedRealtimeQuote 对象,失败返回 None
|
||||
"""
|
||||
# Tushare 实时行情需要高积分,普通用户无法使用
|
||||
# 这里仅作为接口预留,实际应使用 efinance 或 akshare 的实时数据
|
||||
logger.debug(f"Tushare 实时行情接口需要高积分,建议使用其他数据源: {stock_code}")
|
||||
if self._api is None:
|
||||
return None
|
||||
|
||||
from .realtime_types import (
|
||||
UnifiedRealtimeQuote, RealtimeSource,
|
||||
safe_float, safe_int
|
||||
)
|
||||
|
||||
# 速率限制检查
|
||||
self._check_rate_limit()
|
||||
|
||||
# 尝试 Pro 接口
|
||||
try:
|
||||
ts_code = self._convert_stock_code(stock_code)
|
||||
# 尝试调用 Pro 实时接口 (需要积分)
|
||||
df = self._api.quotation(ts_code=ts_code)
|
||||
|
||||
if df is not None and not df.empty:
|
||||
row = df.iloc[0]
|
||||
logger.debug(f"Tushare Pro 实时行情获取成功: {stock_code}")
|
||||
|
||||
return UnifiedRealtimeQuote(
|
||||
code=stock_code,
|
||||
name=str(row.get('name', '')),
|
||||
source=RealtimeSource.TUSHARE,
|
||||
price=safe_float(row.get('price')),
|
||||
change_pct=safe_float(row.get('pct_chg')), # Pro 接口通常直接返回涨跌幅
|
||||
change_amount=safe_float(row.get('change')),
|
||||
volume=safe_int(row.get('vol')),
|
||||
amount=safe_float(row.get('amount')),
|
||||
high=safe_float(row.get('high')),
|
||||
low=safe_float(row.get('low')),
|
||||
open_price=safe_float(row.get('open')),
|
||||
pre_close=safe_float(row.get('pre_close')),
|
||||
turnover_rate=safe_float(row.get('turnover_ratio')), # Pro 接口可能有换手率
|
||||
pe_ratio=safe_float(row.get('pe')),
|
||||
pb_ratio=safe_float(row.get('pb')),
|
||||
total_mv=safe_float(row.get('total_mv')),
|
||||
)
|
||||
except Exception as e:
|
||||
# 仅记录调试日志,不报错,继续尝试降级
|
||||
logger.debug(f"Tushare Pro 实时行情不可用 (可能是积分不足): {e}")
|
||||
|
||||
# 降级:尝试旧版接口
|
||||
try:
|
||||
import tushare as ts
|
||||
|
||||
# Tushare 旧版接口使用 6 位代码
|
||||
code_6 = stock_code.split('.')[0] if '.' in stock_code else stock_code
|
||||
|
||||
# 调用旧版实时接口 (ts.get_realtime_quotes)
|
||||
df = ts.get_realtime_quotes(code_6)
|
||||
|
||||
if df is None or df.empty:
|
||||
return None
|
||||
|
||||
row = df.iloc[0]
|
||||
|
||||
# 计算涨跌幅
|
||||
price = safe_float(row['price'])
|
||||
pre_close = safe_float(row['pre_close'])
|
||||
change_pct = 0.0
|
||||
change_amount = 0.0
|
||||
|
||||
if price and pre_close and pre_close > 0:
|
||||
change_amount = price - pre_close
|
||||
change_pct = (change_amount / pre_close) * 100
|
||||
|
||||
# 构建统一对象
|
||||
return UnifiedRealtimeQuote(
|
||||
code=stock_code,
|
||||
name=str(row['name']),
|
||||
source=RealtimeSource.TUSHARE,
|
||||
price=price,
|
||||
change_pct=round(change_pct, 2),
|
||||
change_amount=round(change_amount, 2),
|
||||
volume=safe_int(row['volume']) // 100, # 转换为手
|
||||
amount=safe_float(row['amount']),
|
||||
high=safe_float(row['high']),
|
||||
low=safe_float(row['low']),
|
||||
open_price=safe_float(row['open']),
|
||||
pre_close=pre_close,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Tushare (旧版) 获取实时行情失败 {stock_code}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
|
||||
2
main.py
2
main.py
@@ -22,6 +22,8 @@ A股自选股智能分析系统 - 主调度程序
|
||||
- 买点偏好:缩量回踩 MA5/MA10 支撑
|
||||
"""
|
||||
import os
|
||||
from src.config import setup_env
|
||||
setup_env()
|
||||
|
||||
# 代理配置 - 通过 USE_PROXY 环境变量控制,默认关闭
|
||||
# GitHub Actions 环境自动跳过代理配置
|
||||
|
||||
@@ -17,6 +17,13 @@ from dotenv import load_dotenv, dotenv_values
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
def setup_env():
|
||||
"""初始化环境变量(支持从 .env 加载)"""
|
||||
# src/config.py -> src/ -> root
|
||||
env_path = Path(__file__).parent.parent / '.env'
|
||||
load_dotenv(dotenv_path=env_path)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
"""
|
||||
@@ -216,10 +223,8 @@ class Config:
|
||||
2. .env 文件
|
||||
3. 代码中的默认值
|
||||
"""
|
||||
# 加载项目根目录下的 .env 文件
|
||||
# src/config.py -> src/ -> root
|
||||
env_path = Path(__file__).parent.parent / '.env'
|
||||
load_dotenv(dotenv_path=env_path)
|
||||
# 确保环境变量已加载
|
||||
setup_env()
|
||||
|
||||
# === 智能代理配置 (关键修复) ===
|
||||
# 如果配置了代理,自动设置 NO_PROXY 以排除国内数据源,避免行情获取失败
|
||||
|
||||
@@ -461,7 +461,7 @@ class MarketAnalyzer:
|
||||
snippet = n.get('snippet', '')[:100]
|
||||
news_text += f"{i}. {title}\n {snippet}\n"
|
||||
|
||||
prompt = f"""你是一位专业的A股市场分析师,请根据以下数据生成一份简洁的大盘复盘报告。
|
||||
prompt = f"""你是一位专业的A/H/美股市场分析师,请根据以下数据生成一份简洁的大盘复盘报告。
|
||||
|
||||
【重要】输出要求:
|
||||
- 必须输出纯 Markdown 文本格式
|
||||
@@ -483,7 +483,6 @@ class MarketAnalyzer:
|
||||
- 上涨: {overview.up_count} 家 | 下跌: {overview.down_count} 家 | 平盘: {overview.flat_count} 家
|
||||
- 涨停: {overview.limit_up_count} 家 | 跌停: {overview.limit_down_count} 家
|
||||
- 两市成交额: {overview.total_amount:.0f} 亿元
|
||||
- 北向资金: {overview.north_flow:+.2f} 亿元
|
||||
|
||||
## 板块表现
|
||||
领涨: {top_sectors_text if top_sectors_text else "暂无数据"}
|
||||
@@ -507,7 +506,7 @@ class MarketAnalyzer:
|
||||
(分析上证、深证、创业板等各指数走势特点)
|
||||
|
||||
### 三、资金动向
|
||||
(解读成交额和北向资金流向的含义)
|
||||
(解读成交额流向的含义)
|
||||
|
||||
### 四、热点解读
|
||||
(分析领涨领跌板块背后的逻辑和驱动因素)
|
||||
|
||||
13
test.sh
13
test.sh
@@ -9,6 +9,7 @@
|
||||
# 测试场景:
|
||||
# market - 仅大盘复盘
|
||||
# a-stock - A股个股分析(茅台、平安银行)
|
||||
# etf - etf分析(卫星etf 563230)
|
||||
# hk-stock - 港股分析(腾讯、阿里)
|
||||
# us-stock - 美股分析(苹果、特斯拉)
|
||||
# mixed - 混合市场分析
|
||||
@@ -93,6 +94,14 @@ test_a_stock() {
|
||||
success "A股分析测试完成"
|
||||
}
|
||||
|
||||
# 测试2.5: ETF分析
|
||||
test_etf() {
|
||||
header "测试场景: ETF分析"
|
||||
info "分析ETF: 563230(卫星ETF)"
|
||||
python3 main.py --stocks 563230 --no-market-review
|
||||
success "ETF分析测试完成"
|
||||
}
|
||||
|
||||
# 测试3: 港股分析
|
||||
test_hk_stock() {
|
||||
header "测试场景: 港股分析"
|
||||
@@ -292,6 +301,9 @@ main() {
|
||||
a-stock|a_stock|astock)
|
||||
test_a_stock
|
||||
;;
|
||||
etf)
|
||||
test_etf
|
||||
;;
|
||||
hk-stock|hk_stock|hkstock|hk)
|
||||
test_hk_stock
|
||||
;;
|
||||
@@ -335,6 +347,7 @@ main() {
|
||||
echo "测试场景:"
|
||||
echo " market - 仅大盘复盘"
|
||||
echo " a-stock - A股个股分析"
|
||||
echo " etf - ETF分析"
|
||||
echo " hk-stock - 港股分析"
|
||||
echo " us-stock - 美股分析"
|
||||
echo " mixed - 混合市场分析"
|
||||
|
||||
Reference in New Issue
Block a user