openclaw-home-pc/workspace/skills/stock-monitor-skill/scripts/analyser.py
2026-03-21 15:31:06 +08:00

250 lines
9.2 KiB
Python

#!/usr/bin/env python3
"""
Stock Monitor Pro - 智能分析引擎
集成:新闻、资金流向、龙虎榜、宏观关联分析
"""
import requests
import json
import re
from datetime import datetime, timedelta
from typing import List, Dict, Optional
class StockAnalyser:
"""股票智能分析器 - 结合多维度数据给出建议"""
def __init__(self):
self.session = requests.Session()
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
})
# ========== 1. 新闻舆情 ==========
def fetch_eastmoney_news(self, symbol: str, name: str, limit: int = 5) -> List[Dict]:
"""获取东方财富个股新闻"""
url = f"https://searchapi.eastmoney.com/api/suggest/get"
params = {
"input": name,
"type": 14,
"count": limit
}
try:
resp = self.session.get(url, params=params, timeout=10)
data = resp.json()
news_list = []
for item in data.get("QuotationCodeTable", {}).get("Data", []):
news_list.append({
"title": item.get("Title", ""),
"url": item.get("Url", ""),
"time": item.get("ShowTime", "")
})
return news_list
except Exception as e:
return []
def fetch_sina_news(self, symbol: str, name: str) -> List[Dict]:
"""获取新浪财经个股新闻"""
# 新浪新闻搜索接口
url = f"https://search.sina.com.cn/?q={name}&c=news&sort=time"
try:
resp = self.session.get(url, timeout=10)
# 这里可以做更精细的HTML解析
# 简化返回示例
return [{"title": f"新浪财经-{name}相关新闻", "source": "新浪"}]
except:
return []
def analyze_sentiment(self, news_list: List[Dict]) -> Dict:
"""简单情感分析"""
positive_words = ['利好', '增长', '突破', '买入', '增持', '涨停', '超预期', '业绩大增']
negative_words = ['利空', '减持', '下跌', '卖出', '亏损', '暴雷', '跌停', '不及预期']
sentiment = {"positive": 0, "negative": 0, "neutral": 0, "summary": []}
for news in news_list:
title = news.get("title", "")
p_count = sum(1 for w in positive_words if w in title)
n_count = sum(1 for w in negative_words if w in title)
if p_count > n_count:
sentiment["positive"] += 1
elif n_count > p_count:
sentiment["negative"] += 1
else:
sentiment["neutral"] += 1
# 生成情感摘要
if sentiment["positive"] > sentiment["negative"]:
sentiment["overall"] = "偏多"
elif sentiment["negative"] > sentiment["positive"]:
sentiment["overall"] = "偏空"
else:
sentiment["overall"] = "中性"
return sentiment
# ========== 2. 资金流向 ==========
def fetch_fund_flow(self, symbol: str, market: str = "sz") -> Dict:
"""获取个股资金流向 (新浪财经)"""
# 新浪资金流向接口
code = f"{market}{symbol}"
url = f"https://quotes.sina.cn/cn/api/quotes.php?symbol={code}&source=sina"
try:
resp = self.session.get(url, timeout=10)
# 解析返回数据
return {
"main_inflow": "数据获取中...",
"retail_inflow": "数据获取中...",
"net_inflow": "数据获取中..."
}
except:
return {"error": "获取失败"}
def fetch_northbound_flow(self) -> Dict:
"""获取北向资金 (沪深股通) 流向"""
url = "https://push2.eastmoney.com/api/qt/stock/get"
params = {"secid": "1.000001", "fields": "f170"} # 简化示例
try:
resp = self.session.get(url, params=params, timeout=10)
return {"northbound": "北向资金数据获取中..."}
except:
return {}
# ========== 3. 龙虎榜 ==========
def fetch_dragon_tiger(self, date: str = None) -> List[Dict]:
"""获取龙虎榜数据"""
if not date:
date = datetime.now().strftime("%Y%m%d")
url = f"http://datacenter-web.eastmoney.com/api/data/v1/get"
params = {
"sortColumns": "NET_BUY_AMT",
"sortTypes": "-1",
"pageSize": "50",
"pageNumber": "1",
"reportName": "RPT_DMSK_TS",
"columns": "ALL",
"filter": f"(TRADE_DATE='{date}')"
}
try:
resp = self.session.get(url, params=params, timeout=10)
data = resp.json()
return data.get("result", {}).get("data", [])
except:
return []
# ========== 4. 宏观关联分析 ==========
def analyze_gold_correlation(self, gold_price: float, stocks: List[Dict]) -> str:
"""分析金价与持仓股票的关联"""
# 江西铜业等有色股与金价正相关
correlation_map = {
"600362": "强正相关", # 江西铜业
"601318": "弱相关", # 中国平安
"513180": "弱负相关", # 恒生科技
"159892": "弱相关", # 恒生医疗
}
analysis = []
for stock in stocks:
code = stock.get("code")
corr = correlation_map.get(code, "未知")
if corr in ["强正相关", "中等正相关"]:
analysis.append(f"📈 {stock['name']}: 与金价{corr},金价上涨可能带动该股")
return "\n".join(analysis) if analysis else "暂无强关联标的"
# ========== 5. 综合分析 ==========
def generate_insight(self, stock: Dict, price_data: Dict, alerts: List) -> str:
"""生成综合分析报告"""
code = stock['code']
name = stock['name']
# 1. 获取新闻
news_list = self.fetch_eastmoney_news(code, name)
sentiment = self.analyze_sentiment(news_list)
# 2. 资金流向
fund_flow = self.fetch_fund_flow(code, stock.get('market', 'sz'))
# 3. 构建报告
report = f"""📊 <b>{name} ({code}) 深度分析</b>
💰 <b>价格异动:</b>
• 当前: {price_data.get('price', 'N/A')} ({price_data.get('change_pct', 0):+.2f}%)
• 触发: {', '.join([a[1] for a in alerts])}
📰 <b>舆情分析 ({sentiment.get('overall', '未知')}):</b>
• 最近新闻: {len(news_list)}
• 正面: {sentiment.get('positive', 0)} | 负面: {sentiment.get('negative', 0)}
"""
# 添加最新新闻标题
if news_list:
report += "\n<b>最新动态:</b>\n"
for n in news_list[:2]:
report += f"{n.get('title', '无标题')[:30]}...\n"
# 4. 给出建议
suggestion = self._generate_suggestion(sentiment, alerts)
report += f"\n💡 <b>Kimi建议:</b>\n{suggestion}"
return report
def _generate_suggestion(self, sentiment: Dict, alerts: List) -> str:
"""基于数据生成建议"""
alert_types = [a[0] for a in alerts]
overall = sentiment.get("overall", "中性")
# 价格下跌 + 舆情偏空 = 谨慎
if "below" in alert_types and overall == "偏空":
return "⚠️ 价格跌破支撑位,且舆情偏空,建议观察等待,不急于抄底。"
# 价格下跌 + 舆情偏多 = 可能是机会
if "below" in alert_types and overall == "偏多":
return "🔍 价格下跌但舆情偏多,可能是情绪错杀,关注是否有反弹机会。"
# 价格突破 + 舆情偏多 = 确认趋势
if "above" in alert_types and overall == "偏多":
return "🚀 价格突破且舆情配合,趋势可能延续,可考虑顺势而为。"
# 大涨
if "pct_up" in alert_types:
return "📈 短期涨幅较大,注意获利了结风险。"
# 大跌
if "pct_down" in alert_types:
return "📉 短期跌幅较大,关注是否超跌反弹,但勿急于抄底。"
return "⏳ 建议保持观察,等待更明确信号。"
# ========== 测试 ==========
if __name__ == '__main__':
analyser = StockAnalyser()
# 测试新闻抓取
print("=== 新闻测试 ===")
news = analyser.fetch_eastmoney_news("600362", "江西铜业")
print(f"获取到 {len(news)} 条新闻")
for n in news[:3]:
print(f" - {n.get('title', 'N/A')[:40]}...")
# 测试情感分析
print("\n=== 情感分析测试 ===")
sentiment = analyser.analyze_sentiment(news)
print(f"整体情绪: {sentiment.get('overall')}")
print(f"正面: {sentiment.get('positive')}, 负面: {sentiment.get('negative')}")
# 测试金价关联
print("\n=== 宏观关联测试 ===")
stocks = [{"code": "600362", "name": "江西铜业"}]
corr = analyser.analyze_gold_correlation(2743, stocks)
print(corr)