From 50d6b79e43562065903231a2281b80ab4223d039 Mon Sep 17 00:00:00 2001 From: aaron <> Date: Thu, 26 Feb 2026 13:00:37 +0800 Subject: [PATCH] udpate --- backend/app/config.py | 7 +- backend/app/services/yfinance_service.py | 7 +- .../app/stock_agent/market_signal_analyzer.py | 175 +++++++++++++----- backend/app/stock_agent/stock_agent.py | 114 ++++-------- backend/app/utils/signal_formatter.py | 14 +- scripts/test_stock.py | 43 ++--- 6 files changed, 204 insertions(+), 156 deletions(-) diff --git a/backend/app/config.py b/backend/app/config.py index 806f05c..1523f7c 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -169,7 +169,12 @@ class Settings(BaseSettings): # 股票智能体配置 stock_symbols_us: str = "" # 美股代码,逗号分隔 - stock_symbols_hk: str = "" # 港股代码,逗号分隔(以.HK结尾) + # 港股代码:科技股一线+新能源+芯片半导体+AI + # 科技:腾讯/阿里/美团/小米/京东/网易/百度/快手/知乎/B站 + # 新能源:比亚迪/理想/小鹏/赣锋锂业/龙源电力/信义能源 + # 芯片:中芯国际/华虹半导体/上海复旦 + # AI:商汤/第四范式/创新奇智/美图/联易融/百融云 + stock_symbols_hk: str = "00700.HK,09988.HK,03690.HK,01810.HK,09618.HK,00999.HK,09888.HK,01024.HK,01211.HK,02015.HK,09868.HK,01772.HK,00916.HK,03868.HK,00981.HK,01347.HK,01385.HK,00020.HK,06669.HK,02121.HK,01357.HK,02390.HK,09626.HK,02599.HK,06608.HK" stock_analysis_interval: int = 300 # 分析间隔(秒,默认5分钟) stock_llm_threshold: float = 0.70 # 触发 LLM 分析的置信度阈值 diff --git a/backend/app/services/yfinance_service.py b/backend/app/services/yfinance_service.py index c96fa00..0643c5e 100644 --- a/backend/app/services/yfinance_service.py +++ b/backend/app/services/yfinance_service.py @@ -191,12 +191,17 @@ class YFinanceService: """ df = df.copy() - # 移动平均线 + # 移动平均线(简单移动平均 MA) df['ma5'] = df['close'].rolling(window=5).mean() df['ma10'] = df['close'].rolling(window=10).mean() df['ma20'] = df['close'].rolling(window=20).mean() df['ma50'] = df['close'].rolling(window=50).mean() + # 指数移动平均线(EMA)- 用于趋势判断 + df['ema20'] = df['close'].ewm(span=20, adjust=False).mean() + df['ema50'] = df['close'].ewm(span=50, adjust=False).mean() + df['ema200'] = df['close'].ewm(span=200, adjust=False).mean() + # RSI(使用 Wilder's Smoothing 方法) delta = df['close'].diff() gain = delta.where(delta > 0, 0) diff --git a/backend/app/stock_agent/market_signal_analyzer.py b/backend/app/stock_agent/market_signal_analyzer.py index 3af059f..621db38 100644 --- a/backend/app/stock_agent/market_signal_analyzer.py +++ b/backend/app/stock_agent/market_signal_analyzer.py @@ -24,15 +24,71 @@ class StockMarketSignalAnalyzer: """股票市场信号分析器 - 只关注市场,输出客观信号""" # 股票市场分析系统提示词 - MARKET_ANALYSIS_PROMPT = """你是一位专业的股票交易员和技术分析师。你的任务是综合分析**技术面(K线、量价、技术指标)、基本面(估值、盈利、成长)、新闻舆情**,给出交易信号。 + MARKET_ANALYSIS_PROMPT = """你是一位专业的股票交易员和技术分析师。你的任务是综合分析**趋势方向、技术面(K线、量价、技术指标)、基本面(估值、盈利、成长)、新闻舆情**,给出交易信号。 ## 核心理念 -股票市场有其独特的运行规律: -- **趋势为王**:股票更容易形成持续性趋势,顺势而为最重要 -- **基本面支撑**:技术信号需要结合公司基本面来判断有效性 -- **新闻催化**:重大新闻会改变短期趋势,需要重点关注 -- **关注财报季**:财报前后波动加大,需要更谨慎 -- **板块效应**:同板块股票往往联动,关注板块整体表现 +**趋势是你的朋友,顺势交易是稳定盈利的关键。** + +### 🚨 铁律(必须遵守) +1. **先判断趋势,再寻找信号** - 趋势方向错误,信号再强也不做 +2. **顺势交易为主** - 上涨趋势只做多或观望,下跌趋势只做空或观望 +3. **逆势交易极其谨慎** - 必须有多重反转信号才能考虑逆势 +4. **单边行情不逆势** - 强趋势中(日线连续3根以上同向K线)严禁逆势开仓 + +### 交易目标 +- **稳健为主**:宁可错过,不做错 +- **顺势而为**:在大趋势方向上寻找入场点 +- **严控风险**:每次交易风险不超过本金的2% + +## 零、趋势方向判断(第一步,最重要!) +**在分析任何信号之前,先判断当前趋势方向和强度。** + +### 趋势判断标准(使用 EMA 和均线系统) +**上升趋势(多头市场)**: +- EMA20 > EMA50 > EMA200(短中长期均线多头排列) +- 价格站稳在 EMA20 之上 +- MA5 > MA10 > MA20 > MA50 +- 最近高点逐步抬高,低点也逐步抬高 + +**下降趋势(空头市场)**: +- EMA20 < EMA50 < EMA200(短中长期均线空头排列) +- 价格持续在 EMA20 之下 +- MA5 < MA10 < MA20 < MA50 +- 最近高点逐步降低,低点也逐步降低 + +**震荡市(无明确趋势)**: +- 均线纠缠,无明显排列 +- 价格在 EMA20 上下波动 +- 高点低点无规律 +- 此时可双向交易,但降低仓位 + +### 趋势强度判断 +- **强趋势**:均线完美排列 + 价格远离均线 + 成交量配合 +- **中等趋势**:均线有排列 + 价格偶尔回踩均线 +- **弱趋势/震荡**:均线纠缠 + 价格在均线上下反复 + +### 顺势交易规则(必须执行) +| 当前趋势 | 允许操作 | 条件 | +|---------|---------|------| +| **强上升趋势** | ✅ 只做多 | 回调到支撑位、RSI超卖区、金叉 | +| **强上升趋势** | ❌ 严禁做空 | 除非出现明确的顶背离+放量反转信号 | +| **强下降趋势** | ✅ 只做空 | 反弹到阻力位、RSI超买区、死叉 | +| **强下降趋势** | ❌ 严禁做多 | 除非出现明确的底背离+放量反转信号 | +| **震荡市** | ✅ 双向交易 | 但降低仓位(轻仓),提高止损要求 | +| **趋势不明确** | ⚠️ 观望为主 | 等待趋势明确后再入场 | + +### 逆势交易的条件(极其严格) +**只有在满足以下全部条件时,才允许考虑逆势交易:** +1. **多重反转信号**: + - 明确的背离(顶背离或底背离) + - 关键形态反转(头肩顶/底、双顶/底、吞没形态) + - 放量突破关键位 +2. **多周期确认**:周线、日线、1h 三个周期同时出现反转信号 +3. **风险收益比合理**:潜在盈利至少是风险的3倍以上 +4. **基本面支持**:重大利好/利空改变趋势 +5. **降低仓位**:逆势交易必须轻仓(不超过顺势仓位的50%) + +**如果不符合上述条件,即使有买入/卖出信号,也必须选择 hold(观望)。** ## 数据说明 你将获得三个维度的数据: @@ -115,14 +171,22 @@ class StockMarketSignalAnalyzer: **均线系统是趋势判断的核心:** - **多头排列**(MA5 > MA10 > MA20 > MA50):强势上涨趋势,回调做多 - **空头排列**(MA5 < MA10 < MA20 < MA50):强势下跌趋势,反弹做空 -- **价格与 MA 的关系**: +- **EMA 趋势判断**(比 MA 更平滑,更适合判断长期趋势): + - **多头排列**(EMA20 > EMA50 > EMA200):长期上涨趋势确立 + - **空头排列**(EMA20 < EMA50 < EMA200):长期下跌趋势确立 + - 价格站稳 EMA20 上方:中期上涨趋势 + - 价格跌破 EMA20:中期转为下跌趋势 + - EMA50 是长期趋势的生命线 +- **价格与 MA/EMA 的关系**: - 价格站稳 MA5/MA10 上方:短线上涨 - - 价格突破 MA20:中线转多 - - 价格跌破 MA20:中线转空 - - MA50 是中期趋势的分水岭 + - 价格突破 MA20/EMA20:中线转多 + - 价格跌破 MA20/EMA20:中线转空 + - MA50/EMA50 是中期趋势的分水岭 - **均线金叉死叉**: - MA5 上穿 MA10:短线买入信号 - MA5 下穿 MA10:短线卖出信号 + - EMA20 上穿 EMA50:中线买入信号(重要) + - EMA20 下穿 EMA50:中线卖出信号(重要) ## 四、多周期共振(关键分析框架) **多周期共振是提高信号质量的核心方法:** @@ -249,8 +313,12 @@ class StockMarketSignalAnalyzer: ```json { + "trend_direction": "uptrend/downtrend/neutral", + "trend_strength": "strong/medium/weak", "analysis_summary": "简要描述当前市场状态(50字以内)", "volume_analysis": "量价分析结论(30字以内)", + "news_sentiment": "positive/negative/neutral", + "news_impact": "新闻对市场的影响分析(30字以内)", "signals": [ { "type": "short_term/medium_term/long_term", @@ -261,7 +329,7 @@ class StockMarketSignalAnalyzer: "entry_zone": 150.50, "stop_loss": 148.00, "take_profit": 155.00, - "reasoning": "详细的入场理由(必须包含量价分析)", + "reasoning": "详细的入场理由(必须包含趋势判断和量价分析)", "key_factors": ["关键因素1", "关键因素2"] } ], @@ -395,7 +463,18 @@ class StockMarketSignalAnalyzer: context_parts.append(f"MA20: {latest.get('ma20', 'N/A')}") context_parts.append(f"MA50: {latest.get('ma50', 'N/A')}") - # 判断均线排列 + # EMA 均线(用于趋势判断) + ema20 = latest.get('ema20', None) + ema50 = latest.get('ema50', None) + ema200 = latest.get('ema200', None) + if ema20 is not None: + context_parts.append(f"EMA20: {ema20:.2f}") + if ema50 is not None: + context_parts.append(f"EMA50: {ema50:.2f}") + if ema200 is not None: + context_parts.append(f"EMA200: {ema200:.2f}") + + # 判断 MA 排列 ma5 = latest.get('ma5', 0) ma10 = latest.get('ma10', 0) ma20 = latest.get('ma20', 0) @@ -403,11 +482,29 @@ class StockMarketSignalAnalyzer: if all([ma5, ma10, ma20, ma50]): if ma5 > ma10 > ma20 > ma50: - context_parts.append("均线排列: 多头排列 📈") + context_parts.append("MA排列: 多头排列 📈") elif ma5 < ma10 < ma20 < ma50: - context_parts.append("均线排列: 空头排列 📉") + context_parts.append("MA排列: 空头排列 📉") else: - context_parts.append("均线排列: 交织,方向不明") + context_parts.append("MA排列: 交织,方向不明") + + # EMA 排列(更重要的趋势判断) + if all([ema20, ema50, ema200]): + if ema20 > ema50 > ema200: + context_parts.append("EMA排列: 多头排列 (长期趋势向上) 📈") + elif ema20 < ema50 < ema200: + context_parts.append("EMA排列: 空头排列 (长期趋势向下) 📉") + else: + context_parts.append("EMA排列: 交织 (震荡市) ➡️") + + # 价格与 EMA20 的关系(趋势判断关键) + if ema20 is not None: + if latest['close'] > ema20: + context_parts.append("价格位置: 站稳 EMA20 之上 (多头强势)") + elif latest['close'] < ema20: + context_parts.append("价格位置: 跌破 EMA20 (空头强势)") + else: + context_parts.append("价格位置: 接近 EMA20") # 量比分析(使用日线数据) df_1d = data.get('1d') @@ -473,6 +570,16 @@ class StockMarketSignalAnalyzer: result = json.loads(json_str) + # 向后兼容:确保新字段存在 + if 'trend_direction' not in result: + result['trend_direction'] = 'neutral' + if 'trend_strength' not in result: + result['trend_strength'] = 'weak' + if 'news_sentiment' not in result: + result['news_sentiment'] = 'neutral' + if 'news_impact' not in result: + result['news_impact'] = '' + # 清理价格字段 - 转换为 float result = self._clean_price_fields(result) @@ -504,32 +611,6 @@ class StockMarketSignalAnalyzer: else: sig['grade'] = 'D' - # 从信号中推断 market_state 和 trend - if 'signals' in result and result['signals']: - best_signal = max(result['signals'], key=lambda s: s.get('confidence', 0)) - action = best_signal.get('action', 'wait') - confidence = best_signal.get('confidence', 0) - - if confidence >= 70: - if action == 'buy': - result['market_state'] = '强势上涨' - elif action == 'sell': - result['market_state'] = '强势下跌' - else: - result['market_state'] = '震荡整理' - else: - result['market_state'] = '震荡整理' - - if action == 'buy': - result['trend'] = 'up' - elif action == 'sell': - result['trend'] = 'down' - else: - result['trend'] = 'sideways' - else: - result['market_state'] = '无明确信号' - result['trend'] = 'sideways' - logger.info(f"✅ 市场信号分析完成: {symbol}") return result @@ -727,10 +808,12 @@ class StockMarketSignalAnalyzer: """返回空信号""" return { 'symbol': symbol, - 'analysis_summary': 'unknown', - 'volume_analysis': '分析失败', - 'market_state': '分析失败', - 'trend': 'sideways', + 'trend_direction': 'neutral', + 'trend_strength': 'weak', + 'analysis_summary': '分析失败', + 'volume_analysis': '', + 'news_sentiment': 'neutral', + 'news_impact': '', 'signals': [], 'key_levels': {}, 'timestamp': datetime.now().isoformat(), diff --git a/backend/app/stock_agent/stock_agent.py b/backend/app/stock_agent/stock_agent.py index c32c3da..cf48fad 100644 --- a/backend/app/stock_agent/stock_agent.py +++ b/backend/app/stock_agent/stock_agent.py @@ -19,66 +19,6 @@ from app.stock_agent.market_signal_analyzer import StockMarketSignalAnalyzer from app.utils.system_status import get_system_monitor, AgentStatus -# 股票名称映射表 -STOCK_NAMES = { - # 美股 - 科技龙头 - 'AAPL': '苹果', - 'MSFT': '微软', - 'GOOGL': '谷歌', - 'META': 'Meta', - 'AMZN': '亚马逊', - 'NVDA': '英伟达', - 'AMD': 'AMD', - 'AVGO': '博通', - 'ARM': 'ARM', - 'PLTR': 'Palantir', - 'SNOW': 'Snowflake', - - # 美股 - 生物医疗 - 'LLY': '礼来', - 'NVO': '诺和诺德', - 'VRTX': 'Vertex', - - # 美股 - 新能源/汽车 - 'TSLA': '特斯拉', - 'ENPH': 'Enphase', - - # 美股 - 金融 - 'V': 'Visa', - 'MA': 'Mastercard', - - # 美股 - 消费 - 'HD': 'Home Depot', - 'COST': 'Costco', - - # 美股 - 其他 - 'RKLB': 'Relativity Space', - 'HOOD': 'Robinhood', - 'DXYZ': 'DEX', - 'GLW': '康宁', - 'UNTY': 'Unity', - 'CRM': 'Salesforce', - 'ADBE': 'Adobe', - 'INTC': '英特尔', - 'FSLR': 'First Solar', - 'CRWD': 'CrowdStrike', - 'SHOP': 'Shopify', - 'NET': 'Cloudflare', - 'COIN': 'Coinbase', - 'MSTR': 'MicroStrategy', - - # 港股 - '0700.HK': '腾讯', - '9988.HK': '阿里巴巴', - '1810.HK': '小米', - '2015.HK': '理想汽车', - '9866.HK': '蔚来', - '9992.HK': '泡泡玛特', - '9626.HK': '哔哩哔哩', - '9880.HK': '优必选', -} - - class StockAgent: """美股交易信号智能体(LLM 驱动,仅分析通知)""" @@ -130,11 +70,6 @@ class StockAgent: logger.info(f"股票智能体初始化完成 - 美股: {us_count}只, 港股: {hk_count}只, 总计: {len(self.symbols)}只") - @staticmethod - def get_stock_name(symbol: str) -> str: - """获取股票中文名称""" - return STOCK_NAMES.get(symbol, symbol) - async def start(self): """启动智能体""" if self.running: @@ -402,6 +337,7 @@ class StockAgent: result = { 'symbol': symbol, + 'stock_name': '', # 从基本面数据获取的公司名称 'current_price': 0, 'signals': [], 'analysis_summary': '', @@ -425,34 +361,34 @@ class StockAgent: current_price = ticker['lastPrice'] result['current_price'] = current_price - # 获取股票中文名称 - stock_name = STOCK_NAMES.get(symbol, '') - symbol_display = f"{stock_name}({symbol})" if stock_name else symbol - - logger.info(f"\n{'='*60}") - logger.info(f"📊 分析 {symbol_display} @ ${current_price:,.2f}") - logger.info(f"{'='*60}") - - # 4. 获取基本面数据 + # 4. 获取基本面数据(包含公司名称) logger.info(f"\n📈 【基本面分析】") fundamental_data = None fundamental_summary = "" + stock_name = "" # 从基本面数据获取公司名称 try: fundamental_data = self.fundamental.get_fundamental_data(symbol) if fundamental_data: # 传递已获取的数据,避免重复调用 fundamental_summary = self.fundamental.get_fundamental_summary(symbol, fundamental_data) + # 从基本面数据获取公司名称 + stock_name = fundamental_data.get('company_name', '') + result['stock_name'] = stock_name # 保存到结果中 # 基本面评分已经在 fundamental_service 中输出 else: logger.warning(f" ⚠️ 无法获取基本面数据") except Exception as e: logger.warning(f" ⚠️ 获取基本面数据失败: {e}") + symbol_display = f"{stock_name}({symbol})" if stock_name else symbol + logger.info(f"\n{'='*60}") + logger.info(f"📊 分析 {symbol_display} @ ${current_price:,.2f}") + logger.info(f"{'='*60}") + # 5. 获取新闻数据 logger.info(f"\n📰 【新闻分析】") news_data = None try: - stock_name = STOCK_NAMES.get(symbol, '') news_data = await self.news.search_stock_news(symbol, stock_name, max_results=5) if news_data: logger.info(f" 获取到 {len(news_data)} 条相关新闻") @@ -725,7 +661,7 @@ class StockAgent: sig['symbol'] = r['symbol'] sig['current_price'] = r.get('current_price', 0) sig['is_hk'] = r['symbol'].endswith('.HK') - sig['stock_name'] = STOCK_NAMES.get(r['symbol'], '') + sig['stock_name'] = r.get('stock_name', '') if sig.get('action') == 'buy': buy_signals.append(sig) @@ -817,7 +753,10 @@ class StockAgent: f"", ] - # 高等级信号 + # 所有信号(按等级分组) + all_signals = buy_signals + sell_signals + + # 高等级信号 (A/B级) if high_quality_signals: content_parts.append(f"⭐ **高等级信号 (A/B级)**") for sig in high_quality_signals[:5]: @@ -834,6 +773,27 @@ class StockAgent: content_parts.append(f"• {market_tag} {symbol_display} {action} {grade}级 {confidence}%") content_parts.append(f"") + # 其他等级信号 (C/D级) + other_signals = [s for s in all_signals if s.get('grade', 'D') not in ['A', 'B']] + if other_signals: + content_parts.append(f"📋 **其他信号 (C/D级)**") + for sig in other_signals[:10]: # 最多显示10个 + symbol = sig['symbol'] + stock_name = sig.get('stock_name', '') + market_tag = '[港股]' if sig.get('is_hk') else '[美股]' + action = '🟢 做多' if sig.get('action') == 'buy' else '🔴 做空' + grade = sig.get('grade', 'D') + confidence = sig.get('confidence', 0) + + # 构建带名称的股票显示 + symbol_display = f"{stock_name}({symbol})" if stock_name else symbol + + content_parts.append(f"• {market_tag} {symbol_display} {action} {grade}级 {confidence}%") + + if len(other_signals) > 10: + content_parts.append(f" *...还有 {len(other_signals) - 10} 个信号*") + content_parts.append(f"") + # 信号统计 content_parts.extend([ f"📈 做多信号: {len(buy_signals)} 个", diff --git a/backend/app/utils/signal_formatter.py b/backend/app/utils/signal_formatter.py index ad69b29..d065adf 100644 --- a/backend/app/utils/signal_formatter.py +++ b/backend/app/utils/signal_formatter.py @@ -13,7 +13,7 @@ class SignalFormatter: """信号格式化工具""" @staticmethod - def format_signal_message(signal: Dict[str, Any], symbol: str, agent_type: str = 'crypto') -> str: + def format_signal_message(signal: Dict[str, Any], symbol: str, agent_type: str = 'crypto', stock_name: str = '') -> str: """ 格式化信号消息(用于 Telegram 通知) @@ -21,14 +21,11 @@ class SignalFormatter: signal: 信号数据 symbol: 交易对 agent_type: 智能体类型 (crypto/stock) + stock_name: 股票名称(可选,从基本面数据获取) Returns: 格式化的消息文本 """ - # 获取股票名称 - from app.stock_agent.stock_agent import STOCK_NAMES - stock_name = STOCK_NAMES.get(symbol, '') - type_map = { 'short_term': '短线', 'medium_term': '中线', @@ -102,7 +99,7 @@ class SignalFormatter: return message @staticmethod - def format_feishu_card(signal: Dict[str, Any], symbol: str, agent_type: str = 'crypto') -> Dict[str, Any]: + def format_feishu_card(signal: Dict[str, Any], symbol: str, agent_type: str = 'crypto', stock_name: str = '') -> Dict[str, Any]: """ 格式化飞书卡片消息 @@ -110,14 +107,11 @@ class SignalFormatter: signal: 信号数据 symbol: 交易对 agent_type: 智能体类型 (crypto/stock) + stock_name: 股票名称(可选,从基本面数据获取) Returns: 包含 title, content, color 的字典 """ - # 获取股票名称 - from app.stock_agent.stock_agent import STOCK_NAMES - stock_name = STOCK_NAMES.get(symbol, '') - type_map = { 'short_term': '短线', 'medium_term': '中线', diff --git a/scripts/test_stock.py b/scripts/test_stock.py index 7b8596e..efb6b3d 100755 --- a/scripts/test_stock.py +++ b/scripts/test_stock.py @@ -36,11 +36,17 @@ async def analyze(symbol: str, send_notification: bool = True): Returns: 分析结果字典 """ - # 导入股票名称映射 - from app.stock_agent.stock_agent import STOCK_NAMES + # 获取服务 + yf_service = get_yfinance_service() + market_analyzer = StockMarketSignalAnalyzer() # 使用新的市场信号分析器 + fundamental = get_fundamental_service() # 基本面服务 + news = get_news_service() # 新闻服务 + feishu = get_feishu_service() + telegram = get_telegram_service() result = { 'symbol': symbol, + 'stock_name': '', # 从基本面数据获取 'price': 0, 'signals': [], 'notified': False @@ -50,23 +56,11 @@ async def analyze(symbol: str, send_notification: bool = True): settings = get_settings() threshold = settings.stock_llm_threshold * 100 # 转换为百分比 - # 获取股票中文名称 - stock_name = STOCK_NAMES.get(symbol, '') - symbol_display = f"{stock_name}({symbol})" if stock_name else symbol - print(f"\n{'='*60}") - print(f"📊 分析 {symbol_display}") + print(f"📊 分析 {symbol}") print(f"{'='*60}") try: - # 获取服务 - yf_service = get_yfinance_service() - market_analyzer = StockMarketSignalAnalyzer() # 使用新的市场信号分析器 - fundamental = get_fundamental_service() # 基本面服务 - news = get_news_service() # 新闻服务 - feishu = get_feishu_service() - telegram = get_telegram_service() - # 获取行情 print(f"获取行情...") ticker = yf_service.get_ticker(symbol) @@ -90,15 +84,19 @@ async def analyze(symbol: str, send_notification: bool = True): print(f"时间周期: {', '.join(data.keys())}") - # 获取基本面数据 + # 获取基本面数据(包含公司名称) print(f"\n📈 基本面分析中...") fundamental_data = None fundamental_summary = "" + stock_name = "" try: fundamental_data = fundamental.get_fundamental_data(symbol) if fundamental_data: # 传递已获取的数据,避免重复调用 fundamental_summary = fundamental.get_fundamental_summary(symbol, fundamental_data) + # 获取公司名称 + stock_name = fundamental_data.get('company_name', '') + result['stock_name'] = stock_name # 输出基本面详细信息 score = fundamental_data.get('score', {}) print(f" ✓ 基本面数据获取成功") @@ -108,6 +106,12 @@ async def analyze(symbol: str, send_notification: bool = True): sector = fundamental_data.get('sector', 'N/A') print(f" 【公司】{company} | {sector}") + # 更新显示 + symbol_display = f"{stock_name}({symbol})" if stock_name else symbol + print(f"\n{'='*60}") + print(f"📊 分析 {symbol_display} @ ${price:,.2f}") + print(f"{'='*60}") + # 评分 print(f" 【评分】总分: {score.get('total', 0):.0f}/100 ({score.get('rating', 'N/A')}级) | " f"估值:{score.get('valuation', 0)} 盈利:{score.get('profitability', 0)} " @@ -168,7 +172,6 @@ async def analyze(symbol: str, send_notification: bool = True): print(f"\n📰 新闻分析...") news_data = None try: - stock_name = STOCK_NAMES.get(symbol, '') news_data = await news.search_stock_news(symbol, stock_name, max_results=5) if news_data: print(f" 获取到 {len(news_data)} 条相关新闻") @@ -266,7 +269,6 @@ def print_summary_report(results: list, send_notification: bool = True): send_notification: 是否发送通知(默认True) """ from app.config import get_settings - from app.stock_agent.stock_agent import STOCK_NAMES settings = get_settings() threshold = settings.stock_llm_threshold * 100 # 获取阈值 @@ -289,7 +291,7 @@ def print_summary_report(results: list, send_notification: bool = True): sig['symbol'] = r['symbol'] sig['current_price'] = r.get('price', 0) sig['is_hk'] = r['symbol'].endswith('.HK') - sig['stock_name'] = STOCK_NAMES.get(r['symbol'], '') + sig['stock_name'] = r.get('stock_name', '') all_signals.append(sig) if sig.get('action') == 'buy': @@ -538,7 +540,6 @@ async def main(): async def send_summary_notification_async(results: list): """异步发送汇总通知""" from app.config import get_settings - from app.stock_agent.stock_agent import STOCK_NAMES settings = get_settings() threshold = settings.stock_llm_threshold * 100 # 获取阈值 @@ -561,7 +562,7 @@ async def send_summary_notification_async(results: list): sig['symbol'] = r['symbol'] sig['current_price'] = r.get('price', 0) sig['is_hk'] = r['symbol'].endswith('.HK') - sig['stock_name'] = STOCK_NAMES.get(r['symbol'], '') + sig['stock_name'] = r.get('stock_name', '') all_signals.append(sig) if sig.get('action') == 'buy':