#!/usr/bin/env python3 """ prepare_report_data.py — 为所有报告模板提供结构化参数值 输出JSON,每个字段有来源标注,LLM只读不修改 用法: python3 prepare_report_data.py [--report-type intraday|strategy|self-buy] """ import sqlite3, json, sys, subprocess, re, os from pathlib import Path from datetime import datetime DB = Path("/home/hmo/MoFin/data/mofin.db") HK_RATE_SCRIPT = Path("/home/hmo/MoFin/scripts/hk_rate.py") def get_hk_rate() -> float: r = subprocess.run(["python3", str(HK_RATE_SCRIPT)], capture_output=True, text=True, timeout=10) m = re.search(r"[\d.]+$", r.stdout.strip()) return float(m.group()) if m else 0.93 def get_portfolio(db, hk_rate): """总资产/现金/仓位 — 来源: portfolio_summary + holdings实时计算""" r = db.execute("SELECT cash, frozen_cash FROM portfolio_summary WHERE id=1").fetchone() cash, frozen = (r["cash"] or 0), (r["frozen_cash"] or 0) total_hkd_mv = 0.0 total_cny_mv = 0.0 holdings = [] for r in db.execute(""" SELECT code, name, shares, price, currency, market_value, cost FROM holdings WHERE is_active=1 AND (shares>0 OR shares IS NULL) ORDER BY currency, code """): shares = r["shares"] or 0 if shares == 0: continue cur = r["currency"] or "CNY" price = r["price"] or 0 mv = r["market_value"] or (shares * price) cost_total = (r["cost"] or 0) * shares pnl = mv - cost_total pnl_pct = ((price / (r["cost"] or price)) - 1) * 100 if r["cost"] and r["cost"] > 0 else 0 mv_cny = mv * hk_rate if cur == "HKD" else mv if cur == "HKD": total_hkd_mv += mv else: total_cny_mv += mv_cny holdings.append({ "code": r["code"], "name": r["name"], "shares": shares, "price": round(price, 2), "currency": cur, "price_display": f"HK${price:.2f}" if cur == "HKD" else f"CNY${price:.2f}", "mv": round(mv, 2), "mv_display": f"HK${mv:.2f}" if cur == "HKD" else f"CNY${mv:.2f}", "pnl_pct": round(pnl_pct, 2), "pnl_amount": round(pnl, 2), }) stock_value_cny = total_cny_mv + total_hkd_mv * hk_rate total_cash = cash + frozen total_assets = stock_value_cny + total_cash position_pct = (stock_value_cny / total_assets * 100) if total_assets > 0 else 0 return { "cash": round(cash, 0), "frozen_cash": round(frozen, 0), "total_cash": round(total_cash, 0), "stock_value_cny": round(stock_value_cny, 2), "total_assets": round(total_assets, 2), "position_pct": round(position_pct, 1), "currency": "CNY", "hk_rate": hk_rate, "holdings": holdings, "holdings_count": len(holdings), "source": "portfolio_summary + holdings实时计算", "updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), } def get_market(db): """大盘数据 — 来源: market_snapshots""" r = db.execute("SELECT * FROM market_snapshots ORDER BY id DESC LIMIT 1").fetchone() if not r: return {"error": "无市场数据"} d = dict(r) return { "sh_index": d.get("sh_index", 0), "sz_index": d.get("sz_index", 0), "sh_change": d.get("sh_change_pct", 0), "sz_change": d.get("sz_change_pct", 0), "turnover": d.get("turnover", 0), "advance_decline_ratio": d.get("advance_decline_ratio", 0), "mood": d.get("mood", "unknown"), "snapshot_time": d.get("snapshot_time", ""), "source": "market_snapshots DB", } def get_cash_history(db): """现金变动历史 — 来源: cash_log""" logs = [] for r in db.execute("SELECT * FROM cash_log ORDER BY id DESC LIMIT 5"): logs.append({ "id": r["id"], "timestamp": r["timestamp"], "cash": r["cash_after"], "change": r["cash_after"] - r["cash_before"], "source": r["source"], "note": r["note"], "verified": bool(r["verified"]), }) return logs def main(): report_type = "intraday" if len(sys.argv) > 1 and sys.argv[1].startswith("--"): report_type = sys.argv[1].split("=")[-1] if "=" in sys.argv[1] else sys.argv[1].lstrip("-") db = sqlite3.connect(str(DB)) db.row_factory = sqlite3.Row hk_rate = get_hk_rate() data = { "_meta": { "report_type": report_type, "generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "data_integrity": "所有数值由代码从DB获取,LLM不得修改", }, "portfolio": get_portfolio(db, hk_rate), "market": get_market(db), "cash_history": get_cash_history(db), } print(json.dumps(data, ensure_ascii=False, indent=2)) db.close() if __name__ == "__main__": main()