157 lines
5.5 KiB
Python
157 lines
5.5 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
每日投研简报 — 数据收集器(供 LLM 分析 cron 使用)
|
||
====================================================
|
||
牧尘批评(2026-08-02):每天定时任务只推送原始数据,没有主动分析和建议。
|
||
本脚本收集当天所有股票数据 → 输出结构化摘要,供 cron 的 LLM 分析后生成
|
||
"今日解读 + 持仓建议 + 明日关注" 简报推给牧尘。
|
||
|
||
数据源(全部读 stock_backtest/ 下的 JSON,不调外部 API):
|
||
1. 行业动量 industry_scan.json(周五扫描后更新)
|
||
2. 宏观评分 macro_score.json
|
||
3. 基本面 fundamental_scan.json
|
||
4. 消息面 sentiment_scan.json
|
||
5. 多账户持仓 multi_account/account_*.json
|
||
6. paper 持仓 paper_trades_*.json
|
||
7. 最近矛盾周报 contradiction_history.json
|
||
8. 盘中信号状态 intraday_signal_state.json
|
||
|
||
用法:
|
||
python3 stock_daily_brief.py # 收集当天数据 → 输出摘要(供 cron prompt 注入)
|
||
python3 stock_daily_brief.py --json # JSON 格式
|
||
"""
|
||
import json, sys
|
||
from datetime import date, datetime
|
||
from pathlib import Path
|
||
|
||
HOME = Path.home()
|
||
BT = HOME / ".hermes" / "stock_backtest"
|
||
|
||
def load(name, default=None):
|
||
f = BT / name
|
||
if f.exists():
|
||
try:
|
||
return json.load(open(f))
|
||
except Exception:
|
||
return default
|
||
return default
|
||
|
||
def fmt_pct(v):
|
||
if v is None:
|
||
return "N/A"
|
||
return f"{v*100:+.1f}%" if abs(v) < 3 else f"{v*100:+.1f}%"
|
||
|
||
def main():
|
||
today = date.today().isoformat()
|
||
out = []
|
||
out.append(f"【数据日期】{today}(周{'一二三四五六日'[date.today().weekday()]})")
|
||
out.append("")
|
||
|
||
# 1. 行业动量
|
||
scan = load("industry_scan.json")
|
||
if scan:
|
||
inds = scan.get("industries", {})
|
||
mom = inds.get("avg_mom", {})
|
||
sharpe = inds.get("avg_sharpe", {})
|
||
ranked = sorted(mom.items(), key=lambda x: x[1], reverse=True)
|
||
out.append("【行业动量排名】")
|
||
for i, (ind, m) in enumerate(ranked[:8], 1):
|
||
s = sharpe.get(ind, 0)
|
||
out.append(f" {i}. {ind}: 动量{m*100:+.1f}% Sharpe{s:+.2f}")
|
||
weak = [ind for ind, m in mom.items() if m < -0.2 and sharpe.get(ind, 0) < 0]
|
||
if weak:
|
||
out.append(f" 弱势(动量<20%且Sharpe负): {', '.join(weak)}")
|
||
out.append("")
|
||
|
||
# 2. 宏观
|
||
macro = load("macro_score.json")
|
||
if macro:
|
||
score = macro.get("macro", "N/A")
|
||
details = macro.get("details", {})
|
||
out.append(f"【宏观评分】{score}")
|
||
for k, v in details.items():
|
||
out.append(f" {k}: {v}")
|
||
out.append("")
|
||
|
||
# 3. 基本面
|
||
fund = load("fundamental_scan.json")
|
||
if fund:
|
||
out.append("【基本面扫描】")
|
||
stocks = fund.get("stocks", fund.get("results", []))
|
||
if isinstance(stocks, list):
|
||
for s in stocks[:5]:
|
||
fd = s.get("fundamental", s)
|
||
name = fd.get("name", s.get("name", ""))
|
||
score = s.get("score", "")
|
||
out.append(f" {name}: {score}")
|
||
out.append("")
|
||
|
||
# 4. 消息面
|
||
senti = load("sentiment_scan.json")
|
||
if senti:
|
||
ms = senti.get("message_score", senti.get("overall", senti.get("score", "N/A")))
|
||
if isinstance(ms, (int, float)):
|
||
ms = f"{ms:+.2f}"
|
||
out.append(f"【消息面综合】{ms}")
|
||
out.append("")
|
||
|
||
# 5. 多账户持仓
|
||
adir = BT / "multi_account"
|
||
total_cap = 0
|
||
positions = []
|
||
if adir.exists():
|
||
for f in sorted(adir.glob("account_*.json")):
|
||
try:
|
||
a = json.load(open(f))
|
||
except Exception:
|
||
continue
|
||
total_cap += a.get("current_capital", 0)
|
||
for p in a.get("positions", []):
|
||
positions.append({
|
||
"industry": a.get("industry", ""),
|
||
"stock": a.get("stock", ""),
|
||
"code": a.get("code", ""),
|
||
"shares": p.get("shares", 0),
|
||
"cost": p.get("avg_cost", 0),
|
||
})
|
||
out.append(f"【多账户】{len(list(adir.glob('account_*.json'))) if adir.exists() else 0} 账户 | 总资产 {total_cap:,.0f}")
|
||
if positions:
|
||
out.append(" 持仓:")
|
||
for p in positions:
|
||
out.append(f" {p['stock']}({p['industry']}) {p['shares']}股 成本{p['cost']:.2f}")
|
||
else:
|
||
out.append(" 持仓: 空仓")
|
||
out.append("")
|
||
|
||
# 6. paper 持仓
|
||
for f in sorted(BT.glob("paper_trades_*.json")):
|
||
try:
|
||
d = json.load(open(f))
|
||
except Exception:
|
||
continue
|
||
for p in d.get("positions", []):
|
||
code = f.stem.replace("paper_trades_", "")
|
||
out.append(f"【paper持仓】{d.get('stock','')}({code}) {p['shares']}股 成本{p['avg_cost']:.2f}")
|
||
|
||
# 7. 最近矛盾周报结论
|
||
hist = load("contradiction_history.json")
|
||
if isinstance(hist, list) and hist:
|
||
last = hist[-1]
|
||
if isinstance(last, dict):
|
||
out.append(f"【最近周报({last.get('date','')})】综合: {last.get('verdict', last.get('composite','N/A'))}")
|
||
out.append("")
|
||
|
||
# 8. 盘中信号状态
|
||
state = load("intraday_signal_state.json")
|
||
if state:
|
||
out.append(f"【盘中信号状态】{json.dumps(state, ensure_ascii=False)[:200]}")
|
||
|
||
text = "\n".join(out)
|
||
if "--json" in sys.argv:
|
||
print(json.dumps({"date": today, "brief": out}, ensure_ascii=False, indent=2))
|
||
else:
|
||
print(text)
|
||
|
||
if __name__ == "__main__":
|
||
main()
|