This commit is contained in:
aaron 2026-02-26 13:00:37 +08:00
parent fc5afb0e84
commit 50d6b79e43
6 changed files with 204 additions and 156 deletions

View File

@ -169,7 +169,12 @@ class Settings(BaseSettings):
# 股票智能体配置 # 股票智能体配置
stock_symbols_us: str = "" # 美股代码,逗号分隔 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_analysis_interval: int = 300 # 分析间隔默认5分钟
stock_llm_threshold: float = 0.70 # 触发 LLM 分析的置信度阈值 stock_llm_threshold: float = 0.70 # 触发 LLM 分析的置信度阈值

View File

@ -191,12 +191,17 @@ class YFinanceService:
""" """
df = df.copy() df = df.copy()
# 移动平均线 # 移动平均线(简单移动平均 MA
df['ma5'] = df['close'].rolling(window=5).mean() df['ma5'] = df['close'].rolling(window=5).mean()
df['ma10'] = df['close'].rolling(window=10).mean() df['ma10'] = df['close'].rolling(window=10).mean()
df['ma20'] = df['close'].rolling(window=20).mean() df['ma20'] = df['close'].rolling(window=20).mean()
df['ma50'] = df['close'].rolling(window=50).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 方法) # RSI使用 Wilder's Smoothing 方法)
delta = df['close'].diff() delta = df['close'].diff()
gain = delta.where(delta > 0, 0) gain = delta.where(delta > 0, 0)

View File

@ -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强势上涨趋势回调做多
- **空头排列**MA5 < MA10 < MA20 < MA50强势下跌趋势反弹做空 - **空头排列**MA5 < MA10 < MA20 < MA50强势下跌趋势反弹做空
- **价格与 MA 的关系** - **EMA 趋势判断** MA 更平滑更适合判断长期趋势
- **多头排列**EMA20 > EMA50 > EMA200长期上涨趋势确立
- **空头排列**EMA20 < EMA50 < EMA200长期下跌趋势确立
- 价格站稳 EMA20 上方中期上涨趋势
- 价格跌破 EMA20中期转为下跌趋势
- EMA50 是长期趋势的生命线
- **价格与 MA/EMA 的关系**
- 价格站稳 MA5/MA10 上方短线上涨 - 价格站稳 MA5/MA10 上方短线上涨
- 价格突破 MA20中线转多 - 价格突破 MA20/EMA20中线转多
- 价格跌破 MA20中线转空 - 价格跌破 MA20/EMA20中线转空
- MA50 是中期趋势的分水岭 - MA50/EMA50 是中期趋势的分水岭
- **均线金叉死叉** - **均线金叉死叉**
- MA5 上穿 MA10短线买入信号 - MA5 上穿 MA10短线买入信号
- MA5 下穿 MA10短线卖出信号 - MA5 下穿 MA10短线卖出信号
- EMA20 上穿 EMA50中线买入信号重要
- EMA20 下穿 EMA50中线卖出信号重要
## 四、多周期共振(关键分析框架) ## 四、多周期共振(关键分析框架)
**多周期共振是提高信号质量的核心方法** **多周期共振是提高信号质量的核心方法**
@ -249,8 +313,12 @@ class StockMarketSignalAnalyzer:
```json ```json
{ {
"trend_direction": "uptrend/downtrend/neutral",
"trend_strength": "strong/medium/weak",
"analysis_summary": "简要描述当前市场状态50字以内", "analysis_summary": "简要描述当前市场状态50字以内",
"volume_analysis": "量价分析结论30字以内", "volume_analysis": "量价分析结论30字以内",
"news_sentiment": "positive/negative/neutral",
"news_impact": "新闻对市场的影响分析30字以内",
"signals": [ "signals": [
{ {
"type": "short_term/medium_term/long_term", "type": "short_term/medium_term/long_term",
@ -261,7 +329,7 @@ class StockMarketSignalAnalyzer:
"entry_zone": 150.50, "entry_zone": 150.50,
"stop_loss": 148.00, "stop_loss": 148.00,
"take_profit": 155.00, "take_profit": 155.00,
"reasoning": "详细的入场理由(必须包含量价分析)", "reasoning": "详细的入场理由(必须包含趋势判断和量价分析)",
"key_factors": ["关键因素1", "关键因素2"] "key_factors": ["关键因素1", "关键因素2"]
} }
], ],
@ -395,7 +463,18 @@ class StockMarketSignalAnalyzer:
context_parts.append(f"MA20: {latest.get('ma20', 'N/A')}") context_parts.append(f"MA20: {latest.get('ma20', 'N/A')}")
context_parts.append(f"MA50: {latest.get('ma50', '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) ma5 = latest.get('ma5', 0)
ma10 = latest.get('ma10', 0) ma10 = latest.get('ma10', 0)
ma20 = latest.get('ma20', 0) ma20 = latest.get('ma20', 0)
@ -403,11 +482,29 @@ class StockMarketSignalAnalyzer:
if all([ma5, ma10, ma20, ma50]): if all([ma5, ma10, ma20, ma50]):
if ma5 > ma10 > ma20 > ma50: if ma5 > ma10 > ma20 > ma50:
context_parts.append("均线排列: 多头排列 📈") context_parts.append("MA排列: 多头排列 📈")
elif ma5 < ma10 < ma20 < ma50: elif ma5 < ma10 < ma20 < ma50:
context_parts.append("均线排列: 空头排列 📉") context_parts.append("MA排列: 空头排列 📉")
else: 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') df_1d = data.get('1d')
@ -473,6 +570,16 @@ class StockMarketSignalAnalyzer:
result = json.loads(json_str) 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 # 清理价格字段 - 转换为 float
result = self._clean_price_fields(result) result = self._clean_price_fields(result)
@ -504,32 +611,6 @@ class StockMarketSignalAnalyzer:
else: else:
sig['grade'] = 'D' 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}") logger.info(f"✅ 市场信号分析完成: {symbol}")
return result return result
@ -727,10 +808,12 @@ class StockMarketSignalAnalyzer:
"""返回空信号""" """返回空信号"""
return { return {
'symbol': symbol, 'symbol': symbol,
'analysis_summary': 'unknown', 'trend_direction': 'neutral',
'volume_analysis': '分析失败', 'trend_strength': 'weak',
'market_state': '分析失败', 'analysis_summary': '分析失败',
'trend': 'sideways', 'volume_analysis': '',
'news_sentiment': 'neutral',
'news_impact': '',
'signals': [], 'signals': [],
'key_levels': {}, 'key_levels': {},
'timestamp': datetime.now().isoformat(), 'timestamp': datetime.now().isoformat(),

View File

@ -19,66 +19,6 @@ from app.stock_agent.market_signal_analyzer import StockMarketSignalAnalyzer
from app.utils.system_status import get_system_monitor, AgentStatus 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: class StockAgent:
"""美股交易信号智能体LLM 驱动,仅分析通知)""" """美股交易信号智能体LLM 驱动,仅分析通知)"""
@ -130,11 +70,6 @@ class StockAgent:
logger.info(f"股票智能体初始化完成 - 美股: {us_count}只, 港股: {hk_count}只, 总计: {len(self.symbols)}") 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): async def start(self):
"""启动智能体""" """启动智能体"""
if self.running: if self.running:
@ -402,6 +337,7 @@ class StockAgent:
result = { result = {
'symbol': symbol, 'symbol': symbol,
'stock_name': '', # 从基本面数据获取的公司名称
'current_price': 0, 'current_price': 0,
'signals': [], 'signals': [],
'analysis_summary': '', 'analysis_summary': '',
@ -425,34 +361,34 @@ class StockAgent:
current_price = ticker['lastPrice'] current_price = ticker['lastPrice']
result['current_price'] = current_price result['current_price'] = current_price
# 获取股票中文名称 # 4. 获取基本面数据(包含公司名称)
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. 获取基本面数据
logger.info(f"\n📈 【基本面分析】") logger.info(f"\n📈 【基本面分析】")
fundamental_data = None fundamental_data = None
fundamental_summary = "" fundamental_summary = ""
stock_name = "" # 从基本面数据获取公司名称
try: try:
fundamental_data = self.fundamental.get_fundamental_data(symbol) fundamental_data = self.fundamental.get_fundamental_data(symbol)
if fundamental_data: if fundamental_data:
# 传递已获取的数据,避免重复调用 # 传递已获取的数据,避免重复调用
fundamental_summary = self.fundamental.get_fundamental_summary(symbol, 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 中输出 # 基本面评分已经在 fundamental_service 中输出
else: else:
logger.warning(f" ⚠️ 无法获取基本面数据") logger.warning(f" ⚠️ 无法获取基本面数据")
except Exception as e: except Exception as e:
logger.warning(f" ⚠️ 获取基本面数据失败: {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. 获取新闻数据 # 5. 获取新闻数据
logger.info(f"\n📰 【新闻分析】") logger.info(f"\n📰 【新闻分析】")
news_data = None news_data = None
try: try:
stock_name = STOCK_NAMES.get(symbol, '')
news_data = await self.news.search_stock_news(symbol, stock_name, max_results=5) news_data = await self.news.search_stock_news(symbol, stock_name, max_results=5)
if news_data: if news_data:
logger.info(f" 获取到 {len(news_data)} 条相关新闻") logger.info(f" 获取到 {len(news_data)} 条相关新闻")
@ -725,7 +661,7 @@ class StockAgent:
sig['symbol'] = r['symbol'] sig['symbol'] = r['symbol']
sig['current_price'] = r.get('current_price', 0) sig['current_price'] = r.get('current_price', 0)
sig['is_hk'] = r['symbol'].endswith('.HK') 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': if sig.get('action') == 'buy':
buy_signals.append(sig) buy_signals.append(sig)
@ -817,7 +753,10 @@ class StockAgent:
f"", f"",
] ]
# 高等级信号 # 所有信号(按等级分组)
all_signals = buy_signals + sell_signals
# 高等级信号 (A/B级)
if high_quality_signals: if high_quality_signals:
content_parts.append(f"⭐ **高等级信号 (A/B级)**") content_parts.append(f"⭐ **高等级信号 (A/B级)**")
for sig in high_quality_signals[:5]: 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"{market_tag} {symbol_display} {action} {grade}{confidence}%")
content_parts.append(f"") 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([ content_parts.extend([
f"📈 做多信号: {len(buy_signals)}", f"📈 做多信号: {len(buy_signals)}",

View File

@ -13,7 +13,7 @@ class SignalFormatter:
"""信号格式化工具""" """信号格式化工具"""
@staticmethod @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 通知 格式化信号消息用于 Telegram 通知
@ -21,14 +21,11 @@ class SignalFormatter:
signal: 信号数据 signal: 信号数据
symbol: 交易对 symbol: 交易对
agent_type: 智能体类型 (crypto/stock) agent_type: 智能体类型 (crypto/stock)
stock_name: 股票名称可选从基本面数据获取
Returns: Returns:
格式化的消息文本 格式化的消息文本
""" """
# 获取股票名称
from app.stock_agent.stock_agent import STOCK_NAMES
stock_name = STOCK_NAMES.get(symbol, '')
type_map = { type_map = {
'short_term': '短线', 'short_term': '短线',
'medium_term': '中线', 'medium_term': '中线',
@ -102,7 +99,7 @@ class SignalFormatter:
return message return message
@staticmethod @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: 信号数据 signal: 信号数据
symbol: 交易对 symbol: 交易对
agent_type: 智能体类型 (crypto/stock) agent_type: 智能体类型 (crypto/stock)
stock_name: 股票名称可选从基本面数据获取
Returns: Returns:
包含 title, content, color 的字典 包含 title, content, color 的字典
""" """
# 获取股票名称
from app.stock_agent.stock_agent import STOCK_NAMES
stock_name = STOCK_NAMES.get(symbol, '')
type_map = { type_map = {
'short_term': '短线', 'short_term': '短线',
'medium_term': '中线', 'medium_term': '中线',

View File

@ -36,11 +36,17 @@ async def analyze(symbol: str, send_notification: bool = True):
Returns: 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 = { result = {
'symbol': symbol, 'symbol': symbol,
'stock_name': '', # 从基本面数据获取
'price': 0, 'price': 0,
'signals': [], 'signals': [],
'notified': False 'notified': False
@ -50,23 +56,11 @@ async def analyze(symbol: str, send_notification: bool = True):
settings = get_settings() settings = get_settings()
threshold = settings.stock_llm_threshold * 100 # 转换为百分比 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"\n{'='*60}")
print(f"📊 分析 {symbol_display}") print(f"📊 分析 {symbol}")
print(f"{'='*60}") print(f"{'='*60}")
try: 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"获取行情...") print(f"获取行情...")
ticker = yf_service.get_ticker(symbol) 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"时间周期: {', '.join(data.keys())}")
# 获取基本面数据 # 获取基本面数据(包含公司名称)
print(f"\n📈 基本面分析中...") print(f"\n📈 基本面分析中...")
fundamental_data = None fundamental_data = None
fundamental_summary = "" fundamental_summary = ""
stock_name = ""
try: try:
fundamental_data = fundamental.get_fundamental_data(symbol) fundamental_data = fundamental.get_fundamental_data(symbol)
if fundamental_data: if fundamental_data:
# 传递已获取的数据,避免重复调用 # 传递已获取的数据,避免重复调用
fundamental_summary = fundamental.get_fundamental_summary(symbol, 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', {}) score = fundamental_data.get('score', {})
print(f" ✓ 基本面数据获取成功") print(f" ✓ 基本面数据获取成功")
@ -108,6 +106,12 @@ async def analyze(symbol: str, send_notification: bool = True):
sector = fundamental_data.get('sector', 'N/A') sector = fundamental_data.get('sector', 'N/A')
print(f" 【公司】{company} | {sector}") 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')}级) | " print(f" 【评分】总分: {score.get('total', 0):.0f}/100 ({score.get('rating', 'N/A')}级) | "
f"估值:{score.get('valuation', 0)} 盈利:{score.get('profitability', 0)} " 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📰 新闻分析...") print(f"\n📰 新闻分析...")
news_data = None news_data = None
try: try:
stock_name = STOCK_NAMES.get(symbol, '')
news_data = await news.search_stock_news(symbol, stock_name, max_results=5) news_data = await news.search_stock_news(symbol, stock_name, max_results=5)
if news_data: if news_data:
print(f" 获取到 {len(news_data)} 条相关新闻") print(f" 获取到 {len(news_data)} 条相关新闻")
@ -266,7 +269,6 @@ def print_summary_report(results: list, send_notification: bool = True):
send_notification: 是否发送通知默认True send_notification: 是否发送通知默认True
""" """
from app.config import get_settings from app.config import get_settings
from app.stock_agent.stock_agent import STOCK_NAMES
settings = get_settings() settings = get_settings()
threshold = settings.stock_llm_threshold * 100 # 获取阈值 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['symbol'] = r['symbol']
sig['current_price'] = r.get('price', 0) sig['current_price'] = r.get('price', 0)
sig['is_hk'] = r['symbol'].endswith('.HK') 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) all_signals.append(sig)
if sig.get('action') == 'buy': if sig.get('action') == 'buy':
@ -538,7 +540,6 @@ async def main():
async def send_summary_notification_async(results: list): async def send_summary_notification_async(results: list):
"""异步发送汇总通知""" """异步发送汇总通知"""
from app.config import get_settings from app.config import get_settings
from app.stock_agent.stock_agent import STOCK_NAMES
settings = get_settings() settings = get_settings()
threshold = settings.stock_llm_threshold * 100 # 获取阈值 threshold = settings.stock_llm_threshold * 100 # 获取阈值
@ -561,7 +562,7 @@ async def send_summary_notification_async(results: list):
sig['symbol'] = r['symbol'] sig['symbol'] = r['symbol']
sig['current_price'] = r.get('price', 0) sig['current_price'] = r.get('price', 0)
sig['is_hk'] = r['symbol'].endswith('.HK') 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) all_signals.append(sig)
if sig.get('action') == 'buy': if sig.get('action') == 'buy':