MoFin 初始提交
完整数据采集+分析管道: - market_watch.py:90行业板块采集(同花顺/东方财富) - 市场精选推荐 cron:全市场分析+候选池+星级推荐 - price_monitor.py:持仓/自选高频价格监控 - refresh_mtf_cache.py:多周期K线缓存 - 策略评估/知识萃取管道 文档:docs/ 含完整需求+架构设计 注意:尚未配置 git remote,笑笑接手后自行配置
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env python3
|
||||
"""market_insight.py — 基于 market.json 数据生成基础洞察 + 潜力挖掘
|
||||
|
||||
输出:更新 data/market.json 中的 insights / potential_stocks 字段
|
||||
|
||||
策略:
|
||||
1. 行业热点 vs 持仓匹配 → 相关影响
|
||||
2. 资金流向异常 → 关注信号
|
||||
3. 市场情绪 → 每日研判
|
||||
4. 潜力挖掘 → 强势行业中寻找持仓相关标的
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
DATA_DIR = Path(__file__).parent / "data"
|
||||
|
||||
# ── 持仓股 → 行业映射(从 stock_profiles 自动提取) ──
|
||||
|
||||
def load_holding_industry_map():
|
||||
"""从 stock_profiles 和 portfolio 提取持仓→行业映射"""
|
||||
try:
|
||||
with open(DATA_DIR / "stock_profiles.json", "r", encoding="utf-8") as f:
|
||||
profiles = json.load(f).get("profiles", [])
|
||||
|
||||
with open(DATA_DIR / "portfolio.json", "r", encoding="utf-8") as f:
|
||||
portfolio = json.load(f)
|
||||
except FileNotFoundError:
|
||||
return {}
|
||||
|
||||
# 构建 code→name 映射(从 portfolio)
|
||||
code_to_name = {}
|
||||
for item in portfolio.get("holdings", []):
|
||||
code_to_name[item.get("code", "")] = item.get("name", "")
|
||||
|
||||
# 构建行业→持仓列表
|
||||
industry_holdings = {}
|
||||
for p in profiles:
|
||||
code = p.get("code", "")
|
||||
name = p.get("name", "")
|
||||
sector = p.get("sector", "")
|
||||
if not sector or sector == "待补全":
|
||||
continue
|
||||
# 提取一级行业(取斜杠前第一个)
|
||||
primary = sector.split("/")[0].split("(")[0].strip()
|
||||
if primary:
|
||||
industry_holdings.setdefault(primary, []).append({
|
||||
"code": code,
|
||||
"name": name,
|
||||
"sector": sector,
|
||||
})
|
||||
return industry_holdings
|
||||
|
||||
|
||||
def generate():
|
||||
# 加载数据
|
||||
market_path = DATA_DIR / "market.json"
|
||||
with open(market_path, "r", encoding="utf-8") as f:
|
||||
market = json.load(f)
|
||||
|
||||
sectors = market.get("sectors", [])
|
||||
top_gainers = market.get("top_gainers", [])
|
||||
top_losers = market.get("top_losers", [])
|
||||
mood = market.get("mood", "unknown")
|
||||
up_ratio = market.get("up_ratio", 0)
|
||||
timestamp = market.get("timestamp", "")
|
||||
|
||||
industry_holdings = load_holding_industry_map()
|
||||
insights = []
|
||||
potentials = []
|
||||
|
||||
# ── 洞察1:市场情绪总览 ──
|
||||
mood_cn = {"bullish": "偏强", "neutral": "中性", "bearish": "偏弱", "unknown": "未知"}
|
||||
insights.append(
|
||||
f"市场情绪{mood_cn.get(mood, '未知')},上涨占比{up_ratio}%"
|
||||
)
|
||||
|
||||
# ── 洞察2:领涨行业 vs 持仓影响 ──
|
||||
gainer_insights = []
|
||||
for g in top_gainers[:3]:
|
||||
name = g.get("name", "")
|
||||
change = g.get("change", 0)
|
||||
# 看持仓中是否有该行业
|
||||
matched = []
|
||||
for industry, holdings in industry_holdings.items():
|
||||
if industry in name or name in industry:
|
||||
matched.extend([h["name"] for h in holdings])
|
||||
if matched:
|
||||
gainer_insights.append(
|
||||
f"{name}+{change}%, 关联持仓{'/'.join(matched[:3])}受益"
|
||||
)
|
||||
else:
|
||||
gainer_insights.append(f"{name}+{change}%, 暂无持仓")
|
||||
if gainer_insights:
|
||||
insights.append("领涨板块: " + " | ".join(gainer_insights[:2]))
|
||||
|
||||
# ── 洞察3:领跌行业 vs 持仓风险 ──
|
||||
loser_insights = []
|
||||
for g in top_losers[:3]:
|
||||
name = g.get("name", "")
|
||||
change = g.get("change", 0)
|
||||
matched = []
|
||||
for industry, holdings in industry_holdings.items():
|
||||
if industry in name or name in industry:
|
||||
matched.extend([h["name"] for h in holdings])
|
||||
if matched:
|
||||
loser_insights.append(
|
||||
f"{name}{change}%, {'/'.join(matched[:2])}需关注"
|
||||
)
|
||||
else:
|
||||
loser_insights.append(f"{name}{change}%")
|
||||
if loser_insights:
|
||||
insights.append("风险板块: " + " | ".join(loser_insights[:3]))
|
||||
|
||||
# ── 洞察4:资金流向异动 ──
|
||||
big_inflow = [s for s in sectors if s.get("net_inflow", 0) > 50]
|
||||
big_outflow = [s for s in sectors if s.get("net_inflow", 0) < -50]
|
||||
if big_inflow:
|
||||
top = max(big_inflow, key=lambda s: s["net_inflow"])
|
||||
insights.append(
|
||||
f"资金流入最大: {top['name']} {top['net_inflow']}亿"
|
||||
)
|
||||
if big_outflow:
|
||||
top = min(big_outflow, key=lambda s: s["net_inflow"])
|
||||
insights.append(
|
||||
f"资金流出最大: {top['name']} {top['net_inflow']}亿"
|
||||
)
|
||||
|
||||
# ── 潜力股挖掘:从强势行业中找持仓或自选相关 ──
|
||||
for g in top_gainers[:5]:
|
||||
name = g.get("name", "")
|
||||
change = g.get("change", 0)
|
||||
if change < 2:
|
||||
continue # 只关注涨>2%的
|
||||
|
||||
# 找该行业指数有没有关联持仓
|
||||
lead_stock = g.get("lead_stock", "")
|
||||
if lead_stock:
|
||||
potentials.append({
|
||||
"name": lead_stock,
|
||||
"reason": f"{name}领涨股, 板块+{change}%",
|
||||
})
|
||||
|
||||
# 看持仓中是否有该行业
|
||||
for industry, holdings in industry_holdings.items():
|
||||
if industry in name or name in industry:
|
||||
for h in holdings:
|
||||
potentials.append({
|
||||
"name": h["name"],
|
||||
"reason": f"所在行业{name}涨{change}%",
|
||||
})
|
||||
|
||||
# 去重(最多5条)
|
||||
seen = set()
|
||||
unique_potentials = []
|
||||
for p in potentials:
|
||||
key = p["name"]
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
unique_potentials.append(p)
|
||||
if len(unique_potentials) >= 5:
|
||||
break
|
||||
potentials = unique_potentials
|
||||
|
||||
# ── 写入 market.json ──
|
||||
market["insights"] = insights
|
||||
market["potential_stocks"] = potentials
|
||||
market["insight_timestamp"] = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
with open(market_path, "w", encoding="utf-8") as f:
|
||||
json.dump(market, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"生成{len(insights)}条洞察 + {len(potentials)}条潜力挖掘")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
generate()
|
||||
Reference in New Issue
Block a user