# TradingAgents/publish/wechat_publisher.py """ 将 TradingAgents 的股票分析结果发布到微信公众号和个人博客。 依赖:baoyu-post-to-wechat 技能(位于 workspace/skills/baoyu-post-to-wechat) """ import os import sys import logging from pathlib import Path from typing import Dict, Any, Optional logger = logging.getLogger(__name__) # 技能路径(与 baoyu-post-to-wechat 在同一目录) _SKILL_DIR = "/home/ubuntu/.nanobot/workspace/skills/baoyu-post-to-wechat" def _build_markdown(final_state: Dict[str, Any], ticker: str, trade_date) -> str: """将 final_state 组装成完整 Markdown 文章。""" date_str = trade_date if isinstance(trade_date, str) else str(trade_date) title = f"【{ticker}】股票分析报告 - {date_str}" lines = [ "---", f"title: {title}", f"date: {date_str}", f"author: TradingAgents", "---", "", f"# {title}", "", "## 📊 市场分析报告", "", final_state.get("market_report", "暂无"), "", "## 💬 情绪分析报告", "", final_state.get("sentiment_report", "暂无"), "", "## 📰 新闻分析报告", "", final_state.get("news_report", "暂无"), "", "## 📈 基本面分析报告", "", final_state.get("fundamentals_report", "暂无"), "", ] def _append_history(items): """兼容 list 和 str 类型,避免逐字符遍历字符串。 注意:debate 内容本身可能就是带 Markdown 格式的大段文章 (含标题、列表等),如果强行加 '- ' 前缀会破坏内部结构, 导致 '###' 子标题被当作列表项文本而失效。 因此直接输出原始段落,不再套一层无序列表。 """ if isinstance(items, list): for msg in items: lines.append(str(msg)) lines.append("") # 段间空行,保持可读性 elif isinstance(items, str): if items.strip(): lines.append(items.strip()) # 其他类型忽略 # 多空辩论 debate = final_state.get("investment_debate_state", {}) bull = debate.get("bull_history", []) bear = debate.get("bear_history", []) judge = debate.get("judge_decision", "") lines.extend([ "## ⚖️ 多空辩论", "", "### 多头观点", "", ]) _append_history(bull) lines.extend([ "", "### 空头观点", "", ]) _append_history(bear) lines.extend([ "", "### 裁判决策", "", judge if judge else "暂无", "", ]) # 风险辩论 risk = final_state.get("risk_debate_state", {}) agg = risk.get("aggressive_history", []) cons = risk.get("conservative_history", []) neut = risk.get("neutral_history", []) risk_judge = risk.get("judge_decision", "") lines.extend([ "## 🛡️ 风险辩论", "", "### 激进观点", "", ]) _append_history(agg) lines.extend([ "", "### 保守观点", "", ]) _append_history(cons) lines.extend([ "", "### 中性观点", "", ]) _append_history(neut) lines.extend([ "", "### 裁判决策", "", risk_judge if risk_judge else "暂无", "", ]) # 投资计划与最终决策 lines.extend([ "## 📋 投资计划", "", final_state.get("investment_plan", "暂无"), "", "## 🎯 最终交易决策", "", final_state.get("final_trade_decision", "暂无"), "", ]) return "\n".join(lines) def publish_analysis_result( final_state: Dict[str, Any], ticker: str, trade_date, results_dir: Optional[str] = None, ) -> Dict[str, Any]: """ 将股票分析结果发布到微信公众号和个人博客。 Args: final_state: 图执行后的最终状态字典。 ticker: 股票代码/公司名称。 trade_date: 交易日期。 results_dir: 结果保存目录,默认使用 ~/TradingAgents/results。 Returns: {"success": bool, ...} 发布结果字典。 """ try: date_str = trade_date if isinstance(trade_date, str) else str(trade_date) safe_ticker = ticker.replace("/", "_").replace("\\", "_") # 1. 组装 Markdown markdown_content = _build_markdown(final_state, ticker, trade_date) # 2. 保存到结果目录 if results_dir is None: results_dir = os.path.join(os.path.expanduser("~"), "TradingAgents", "results") publish_dir = Path(results_dir) / safe_ticker / "published" publish_dir.mkdir(parents=True, exist_ok=True) md_path = publish_dir / f"analysis_{date_str}.md" with open(md_path, "w", encoding="utf-8") as f: f.write(markdown_content) logger.info("Markdown report saved: %s", md_path) print(f"📄 Markdown report saved: {md_path}") # 3. 导入并调用 baoyu-post-to-wechat if _SKILL_DIR not in sys.path: sys.path.insert(0, _SKILL_DIR) from baoyu_optimized import WeChatPublisher publisher = WeChatPublisher() result = publisher.publish_markdown_with_images( markdown_path=str(md_path), title=f"【{ticker}】股票分析报告 - {date_str}", author="TradingAgents", ) return result except Exception as e: logger.exception("Failed to publish analysis result to WeChat") print(f"⚠️ 发布到公众号失败: {e}") return {"success": False, "error": str(e)}