udpate
This commit is contained in:
parent
fc5afb0e84
commit
50d6b79e43
@ -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 分析的置信度阈值
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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)} 个",
|
||||
|
||||
@ -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': '中线',
|
||||
|
||||
@ -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':
|
||||
|
||||
Loading…
Reference in New Issue
Block a user