xiaowei-system/scripts/stock_daily_brief.py

157 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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()