diff --git a/README.md b/README.md
index 1b7f4f2..2087e08 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,27 @@
## 投资认知提升合集:https://www.bilibili.com/video/BV1ugBMBAEbW
如果你希望能在股市中长久生存下去,建议你能把上面的合集看完,会对你有很大帮助的!
+
+## ⭐ 2026.2.27更新 - 宏观周期分析 🧭
+
+**全新板块:康波周期 × 美林投资时钟 × 中国政策分析**
+
+基于视频[康波周期理论](https://www.bilibili.com/video/BV1QNcEzREzY)和视频[美林投资时钟](https://www.bilibili.com/video/BV1Zuf5BUEhH),由4位AI分析师协同研判当前宏观经济所处的周期位置!
+
+**核心功能:**
+- 🌊 **康波周期分析** — 判断当前处于50-60年长周期的哪个阶段(回升/繁荣/衰退/萧条)
+- ⏰ **美林投资时钟** — 判断当前处于3-5年中短周期的哪个象限(复苏/过热/滞胀/衰退)
+- 🏛️ **中国政策分析** — 货币/财政/产业/房地产政策全面解读(中国化第三维度)
+- 👔 **首席宏观策略师** — 三维综合研判,"周期仪表盘"资产配置建议
+- 📊 **自动采集宏观数据** — GDP、CPI/PPI、PMI、M2、LPR利率、大宗商品等
+
+**适用场景:**
+- 战略性资产配置决策(买房/创业/大额投资)
+- 中短期投资组合调整
+- 理解当前经济环境,避免"在高点接盘、在低点割肉"
+
+---
+
## ⭐ 2026.2.27更新 - AI模型自由切换 🤖
**重大改进:AI模型全局可配置化**
diff --git a/app.py b/app.py
index 5f8abe4..4740011 100644
--- a/app.py
+++ b/app.py
@@ -276,6 +276,9 @@ def main():
""", unsafe_allow_html=True)
+ # 学习资源展示
+ st.info("📺 **新手必看干货**:为了在股市长久生存,建议您观看 👉 [股票知识讲解合集](https://www.bilibili.com/video/BV1Y2FGzzEeS/) 和 [投资认知提升合集](https://www.bilibili.com/video/BV1ugBMBAEbW) 👈,相信会对您有很大帮助!")
+
# 侧边栏
with st.sidebar:
# 快捷导航 - 移到顶部
@@ -285,7 +288,7 @@ def main():
if st.button("🏠 股票分析", width='stretch', key="nav_home", help="返回首页,进行单只股票的深度分析"):
# 清除所有功能页面标志
for key in ['show_history', 'show_monitor', 'show_config', 'show_main_force',
- 'show_sector_strategy', 'show_longhubang', 'show_portfolio', 'show_low_price_bull', 'show_news_flow']:
+ 'show_sector_strategy', 'show_longhubang', 'show_portfolio', 'show_low_price_bull', 'show_news_flow', 'show_macro_cycle']:
if key in st.session_state:
del st.session_state[key]
@@ -344,7 +347,14 @@ def main():
if st.button("📰 新闻流量", width='stretch', key="nav_news_flow", help="新闻流量监测与短线指导"):
st.session_state.show_news_flow = True
for key in ['show_history', 'show_monitor', 'show_config', 'show_main_force',
- 'show_sector_strategy', 'show_portfolio', 'show_smart_monitor', 'show_low_price_bull', 'show_longhubang']:
+ 'show_sector_strategy', 'show_portfolio', 'show_smart_monitor', 'show_low_price_bull', 'show_longhubang', 'show_macro_cycle']:
+ if key in st.session_state:
+ del st.session_state[key]
+
+ if st.button("🧭 宏观周期", width='stretch', key="nav_macro_cycle", help="康波周期 × 美林投资时钟 × 政策分析"):
+ st.session_state.show_macro_cycle = True
+ for key in ['show_history', 'show_monitor', 'show_config', 'show_main_force',
+ 'show_sector_strategy', 'show_portfolio', 'show_smart_monitor', 'show_low_price_bull', 'show_longhubang', 'show_news_flow']:
if key in st.session_state:
del st.session_state[key]
@@ -462,7 +472,18 @@ def main():
1. 数据获取 → 2. 技术分析
3. 基本面分析 → 4. 资金分析
5. 情绪数据(ARBR) → 6. 新闻(qstock)
- 7. AI团队分析 → 8. 团队讨论 → 9. 决策
+ 7. AI分析 → 8. 团队讨论 → 9. 决策
+ """)
+
+ # 学习资源
+ with st.expander("📺 学习视频合集"):
+ st.markdown("""
+ **📢 B站干货合集**
+
+ 如果你希望能在股市中长久生存下去,建议你能把下面的合集看完,会对你有很大帮助的!
+
+ - 📚 [股票知识讲解合集](https://www.bilibili.com/video/BV1Y2FGzzEeS/)
+ - 🧠 [投资认知提升合集](https://www.bilibili.com/video/BV1ugBMBAEbW)
""")
# 检查是否显示历史记录
@@ -523,6 +544,12 @@ def main():
if 'show_news_flow' in st.session_state and st.session_state.show_news_flow:
display_news_flow_monitor()
return
+
+ # 检查是否显示宏观周期分析
+ if 'show_macro_cycle' in st.session_state and st.session_state.show_macro_cycle:
+ from macro_cycle_ui import display_macro_cycle
+ display_macro_cycle()
+ return
# 检查是否显示环境配置
if 'show_config' in st.session_state and st.session_state.show_config:
diff --git a/macro_cycle_agents.py b/macro_cycle_agents.py
new file mode 100644
index 0000000..8661bb6
--- /dev/null
+++ b/macro_cycle_agents.py
@@ -0,0 +1,458 @@
+"""
+宏观周期分析 - AI智能体模块
+包含四位专业分析师:康波周期分析师、美林时钟分析师、中国政策分析师、首席宏观策略师
+"""
+
+from deepseek_client import DeepSeekClient
+from typing import Dict, Any
+import time
+import config
+
+
+class MacroCycleAgents:
+ """宏观周期AI智能体集合"""
+
+ def __init__(self, model=None):
+ self.model = model or config.DEFAULT_MODEL_NAME
+ self.deepseek_client = DeepSeekClient(model=self.model)
+ print(f"[宏观周期] AI智能体系统初始化 (模型: {self.model})")
+
+ def kondratieff_wave_agent(self, macro_data_text: str) -> Dict[str, Any]:
+ """
+ 康波周期分析师 - 判断当前处于康德拉季耶夫长波的哪个阶段
+
+ 职责:
+ - 分析当前技术革命阶段(第五轮信息技术康波的位置)
+ - 判断回升/繁荣/衰退/萧条四阶段中的位置
+ - 分析大宗商品与康波的关系
+ - 给出战略性资产配置方向
+ """
+ print("🌊 康波周期分析师正在分析...")
+ time.sleep(1)
+
+ prompt = f"""
+你是一位资深的康德拉季耶夫长波周期研究专家,拥有深厚的经济史研究功底,深入研究过周金涛先生的"人生发财靠康波"理论。
+
+以下是当前中国的宏观经济数据:
+{macro_data_text}
+
+请你基于康波周期理论,对当前全球和中国经济进行深度分析:
+
+## 一、康波周期基础判断
+
+### 1. 历史康波定位
+回顾历史五轮康波周期(蒸汽机→铁路钢铁→电力化工→汽车计算机→信息技术),判断:
+- 当前处于第五轮信息技术康波的哪个具体阶段(回升期/繁荣期/衰退期/萧条期)?
+- 给出判断依据和关键特征
+- 该阶段大约始于何年,预计持续到何年?
+
+### 2. 技术革命驱动分析
+- 第五轮康波的核心技术(互联网/移动互联网)是否已经进入红利消退期?
+- AI人工智能、新能源、生物科技等是否有可能成为第六轮康波的驱动力?
+- 新旧技术转换的当前进展如何?
+
+### 3. 大宗商品与康波
+- 当前大宗商品(黄金、原油、铜等)的表现是否符合康波阶段特征?
+- 结合数据判断是否处于康波衰退期的大宗商品超级牛市阶段?
+
+## 二、康波阶段特征验证
+
+对照当前经济数据,验证是否符合你判断的康波阶段特征:
+- GDP增速趋势是否匹配?
+- 通胀水平(CPI/PPI)是否匹配?
+- 资产价格(股市、房地产)走势是否匹配?
+- 就业与社会情绪是否匹配?
+- 政策环境是否匹配?
+
+## 三、康波视角下的战略建议
+
+### 1. 当前阶段的核心策略
+- 整体应该进攻还是防守?
+- 应该积累现金还是配置资产?
+
+### 2. 战略资产配置方向
+给出康波视角的大类资产配置建议:
+- 股票(配置比例建议及方向)
+- 债券(配置比例建议)
+- 大宗商品/黄金(配置比例建议)
+- 现金(配置比例建议)
+- 房地产(配置建议)
+
+### 3. 布局下一轮康波
+- 如果第六轮康波即将到来,应提前布局哪些方向?
+- 需要回避哪些夕阳产业?
+
+### 4. 对普通人的人生建议
+根据周金涛"人生发财靠康波"的理念,当前阶段普通人应该:
+- 在职业选择上注意什么?
+- 在投资理财上注意什么?
+- 在消费与储蓄上如何平衡?
+
+## 四、康波周期仪表盘
+
+请给出一个简洁的康波定位总结:
+- 🌊 当前康波阶段:[回升期/繁荣期/衰退期/萧条期]
+- 📍 阶段进度:[初期/中期/末期]
+- ⏱️ 预计本阶段剩余时间:约X年
+- 🎯 核心策略关键词:[如"防守为主,播种未来"]
+- ⚠️ 信心度:[1-10分]
+
+请给出专业、深入、有数据支撑的分析报告。
+"""
+ messages = [
+ {"role": "system", "content": "你是全球顶尖的康德拉季耶夫长波周期研究专家,深研周金涛的理论体系,擅长将60年长周期框架应用于当前经济形势分析,帮助投资者做出战略性决策。"},
+ {"role": "user", "content": prompt}
+ ]
+
+ analysis = self.deepseek_client.call_api(messages, max_tokens=6000)
+ print(" ✓ 康波周期分析师分析完成")
+
+ return {
+ "agent_name": "康波周期分析师",
+ "agent_icon": "🌊",
+ "agent_role": "判断当前处于康德拉季耶夫长波(50-60年大周期)的哪个阶段,给出战略性资产配置方向",
+ "analysis": analysis,
+ "focus_areas": ["康波定位", "技术革命", "大宗商品超级周期", "战略资产配置"],
+ "timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
+ }
+
+ def merrill_lynch_clock_agent(self, macro_data_text: str) -> Dict[str, Any]:
+ """
+ 美林投资时钟分析师 - 判断当前处于美林时钟的哪个象限
+
+ 职责:
+ - 根据经济增长和通胀两个维度判断象限
+ - 结合中国特色(政策第三维度)
+ - 给出中短期资产配置建议
+ """
+ print("⏰ 美林时钟分析师正在分析...")
+ time.sleep(1)
+
+ prompt = f"""
+你是一位精通美林投资时钟理论的资深资产配置策略师,同时深入了解中国经济的特殊性,能够将经典美林时钟做"中国化改造"。
+
+以下是当前中国的宏观经济数据:
+{macro_data_text}
+
+请基于美林投资时钟理论,对当前中国的经济周期进行分析:
+
+## 一、经典美林时钟定位
+
+### 1. 两大核心指标研判
+**经济增长维度(↑/→/↓):**
+- GDP增速趋势判断(同比、环比、PMI佐证)
+- 工业增加值/制造业景气度
+- 消费、投资、出口三驾马车的状态
+- 综合判断:经济增长向上还是向下?
+
+**通货膨胀维度(↑/→/↓):**
+- CPI走势分析(核心CPI)
+- PPI走势分析
+- M2/社融与信贷扩张情况
+- 综合判断:通胀向上还是向下?
+
+### 2. 象限定位
+根据以上两个维度,判断当前处于美林时钟的哪个象限:
+- 🟢 复苏期(增长↑ + 通胀↓)→ 配股票
+- 🔴 过热期(增长↑ + 通胀↑)→ 配商品
+- 🟡 滞胀期(增长↓ + 通胀↑)→ 持现金
+- 🔵 衰退期(增长↓ + 通胀↓)→ 配债券
+
+### 3. 时钟转动方向
+- 当前正从哪个象限向哪个象限转动?
+- 预计转动速度如何?(中国时钟转动通常比美国快)
+- 是否存在"跳跃"或"逆转"的可能?
+
+## 二、中国化美林时钟(三维分析)
+
+### 1. 第三维度:政策方向
+这是中国版美林时钟最重要的增量分析:
+- 货币政策方向(宽松/中性/收紧?):分析LPR、MLF、准备金率等
+- 财政政策方向(积极/稳健/收缩?):分析专项债、减税降费等
+- 产业政策方向:重点扶持哪些领域?
+- 房地产政策方向:放松还是收紧?
+- 政策的即时效果和滞后效应
+
+### 2. 中国特色修正
+- 中国利率市场化程度对时钟的影响
+- 政府干预对时钟转动的扭曲效应
+- A股散户占比高对资产定价的影响
+- 房地产作为"第五类资产"的配置考量
+
+## 三、资产配置建议
+
+### 1. 基于当前象限的配置
+给出各大类资产的具体配置比例建议:
+| 资产类别 | 配置比例 | 理由 |
+|---------|---------|------|
+| A股股票 | X% | ... |
+| 债券/固收 | X% | ... |
+| 大宗商品/黄金 | X% | ... |
+| 现金/货币基金 | X% | ... |
+| 房地产 | X% | ... |
+
+### 2. 板块/行业建议
+- 当前象限最受益的行业/板块(3-5个)
+- 应规避的行业/板块(2-3个)
+- "政策友好型"重点关注方向
+
+### 3. 时间框架
+- 当前配置建议的有效期约多久?
+- 需要密切关注哪些信号来判断时钟转动?
+
+## 四、美林时钟仪表盘
+
+请给出一个简洁的定位总结:
+- ⏰ 当前象限:[复苏期/过热期/滞胀期/衰退期]
+- 📊 经济增长:[↑上行/→持平/↓下行]
+- 📈 通胀水平:[↑上行/→持平/↓下行]
+- 🏛️ 政策方向:[宽松/中性/收紧]
+- 🎯 最优资产:[股票/商品/现金/债券]
+- ⚠️ 信心度:[1-10分]
+
+请给出专业、数据驱动的分析报告。
+"""
+ messages = [
+ {"role": "system", "content": "你是一位精通美林投资时钟理论的顶级资产配置策略师,擅长将经典框架进行中国化改造,加入政策分析作为第三维度,帮助中国投资者做出精准的中短期资产配置决策。"},
+ {"role": "user", "content": prompt}
+ ]
+
+ analysis = self.deepseek_client.call_api(messages, max_tokens=6000)
+ print(" ✓ 美林时钟分析师分析完成")
+
+ return {
+ "agent_name": "美林时钟分析师",
+ "agent_icon": "⏰",
+ "agent_role": "判断当前处于美林投资时钟的哪个象限(3-5年中短周期),给出资产配置建议",
+ "analysis": analysis,
+ "focus_areas": ["经济增长", "通胀水平", "政策方向", "资产配置"],
+ "timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
+ }
+
+ def china_policy_agent(self, macro_data_text: str) -> Dict[str, Any]:
+ """
+ 中国政策分析师 - 分析中国特色政策环境
+
+ 职责:
+ - 分析当前政策环境
+ - 评估政策对周期的影响
+ - 识别政策驱动的投资机会
+ """
+ print("🏛️ 中国政策分析师正在分析...")
+ time.sleep(1)
+
+ prompt = f"""
+你是一位资深的中国宏观政策研究专家,深谙中国经济的"有形之手"运作方式,擅长从政策信号中挖掘投资机会。
+
+以下是当前中国的宏观经济数据和新闻:
+{macro_data_text}
+
+请从政策角度进行深度分析:
+
+## 一、当前政策全景
+
+### 1. 货币政策分析
+- 央行近期操作解读(降准/降息/MLF/逆回购等)
+- LPR利率走势及未来方向
+- M2增速与社融规模分析
+- 流动性环境评估:宽松/适度/偏紧?
+- 对标历史政策宽松周期,当前处于什么阶段?
+
+### 2. 财政政策分析
+- 专项债发行规模和进度
+- 减税降费政策力度
+- 基建投资方向和力度
+- 财政赤字率水平
+- 是否有大规模刺激计划的信号?
+
+### 3. 产业政策分析
+- 当前国家战略重点方向(如AI、新能源、半导体、生物医药等)
+- "专精特新"等政策支持力度
+- 国产替代进展
+- 数字经济/新质生产力政策
+
+### 4. 房地产政策分析
+- 限购限贷政策变化
+- 房贷利率走势
+- 保交楼/房企纾困进展
+- 房地产政策的底线和目标
+
+## 二、政策对周期的影响
+
+### 1. 政策是"顺周期"还是"逆周期"?
+- 当前的政策是在托底经济还是防止过热?
+- 政策的主要目标是什么(稳增长/防风险/调结构)?
+
+### 2. 政策对美林时钟的扭曲效应
+- 政策宽松是否可能让时钟"跳跃"?
+- 政策刺激是否可能造成"短暂过热"的假象?
+- 历史上类似政策环境下市场的表现
+
+### 3. 政策拐点信号
+- 需要关注哪些政策信号来判断下一步方向?
+- 最可能的政策转向时间节点
+
+## 三、政策驱动的投资机会
+
+### 1. "政策友好型"资产
+列出当前最受政策青睐的领域和方向
+
+### 2. 政策支持的具体行业/板块
+给出3-5个受益于当前政策环境的行业
+
+### 3. 政策风险警示
+哪些领域面临政策收紧或监管风险?
+
+## 四、政策环境仪表盘
+
+请给出简洁总结:
+- 🏛️ 货币政策:[极宽松/宽松/中性/偏紧/收紧]
+- 💰 财政政策:[大力刺激/积极/稳健/收缩]
+- 🏭 产业政策重点:[3-5个关键词]
+- 🏠 房地产政策:[大力托底/政策松绑/中性/调控]
+- 🎯 政策核心目标:[一句话概括]
+- ⚠️ 政策转向风险:[高/中/低]
+
+请给出专业、深入的政策分析报告。
+"""
+ messages = [
+ {"role": "system", "content": "你是一位资深的中国宏观经济政策研究专家,深入了解中国政府的经济调控方式和政策意图,擅长从政策中发现投资机会和风险。"},
+ {"role": "user", "content": prompt}
+ ]
+
+ analysis = self.deepseek_client.call_api(messages, max_tokens=5000)
+ print(" ✓ 中国政策分析师分析完成")
+
+ return {
+ "agent_name": "中国政策分析师",
+ "agent_icon": "🏛️",
+ "agent_role": "分析中国特色政策环境,评估政策对周期的影响,识别政策驱动的投资机会",
+ "analysis": analysis,
+ "focus_areas": ["货币政策", "财政政策", "产业政策", "房地产政策", "政策拐点"],
+ "timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
+ }
+
+ def chief_macro_strategist_agent(self, kondratieff_report: str, merrill_report: str, policy_report: str, macro_data_text: str) -> Dict[str, Any]:
+ """
+ 首席宏观策略师 - 综合三位分析师的观点,形成最终策略
+
+ 职责:
+ - 整合康波、美林时钟、政策三个维度
+ - 构建"周期仪表盘"
+ - 给出最终的综合建议
+ """
+ print("👔 首席宏观策略师正在综合研判...")
+ time.sleep(1)
+
+ prompt = f"""
+你是一位顶级的首席宏观策略师,你需要整合三位分析师的报告,形成最终的综合判断和投资策略。
+
+【康波周期分析师报告】
+{kondratieff_report}
+
+【美林时钟分析师报告】
+{merrill_report}
+
+【中国政策分析师报告】
+{policy_report}
+
+请你综合以上三份报告,进行最终的综合研判:
+
+## 一、周期仪表盘(双指针+政策风向标)
+
+构建一个完整的"周期仪表盘":
+
+### 康波指针(战略方向)
+- 当前阶段:[回升期/繁荣期/衰退期/萧条期]
+- 阶段进度:[初期/中期/末期]
+- 战略方向:[进攻/稳健/防守/极度防守]
+
+### 美林指针(战术节奏)
+- 当前象限:[复苏/过热/滞胀/衰退]
+- 转动方向:向[下一个象限]转动
+- 战术方向:[积极做多/获利了结/持币观望/配置债券]
+
+### 政策风向标(中国特色)
+- 政策方向:[强力宽松/宽松/中性/收紧]
+- 对周期的影响:[加速/减缓/扭曲/逆转]
+
+### 双指针共振分析
+- 两个指针方向是否一致?
+- 共振或矛盾意味着什么?
+- 综合信号:[强烈看多/偏多/中性/偏空/强烈看空]
+
+## 二、综合资产配置建议
+
+### 1. 最终配置方案
+结合康波(战略)+ 美林(战术)+ 政策(催化),给出最终的资产配置建议:
+
+| 资产类别 | 推荐配置 | 康波逻辑 | 美林逻辑 | 政策逻辑 |
+|---------|---------|---------|---------|---------|
+| A股/港股 | X% | ... | ... | ... |
+| 债券/固收 | X% | ... | ... | ... |
+| 黄金 | X% | ... | ... | ... |
+| 大宗商品 | X% | ... | ... | ... |
+| 现金 | X% | ... | ... | ... |
+| 房地产 | X% | ... | ... | ... |
+
+### 2. 重点板块/方向(3-5个)
+结合三个维度共振的最优方向
+
+### 3. 风险警示
+需要高度警惕的风险因素
+
+## 三、不同人群的具体建议
+
+### 1. 保守型投资者(风险厌恶)
+- 资产配置建议
+- 核心策略
+
+### 2. 稳健型投资者(平衡风险收益)
+- 资产配置建议
+- 核心策略
+
+### 3. 进取型投资者(愿承受风险)
+- 资产配置建议
+- 核心策略
+
+## 四、核心观点总结
+
+请用简洁有力的语言总结:
+1. **一句话定位**:当前经济周期的核心判断
+2. **核心策略**:用一句话概括应对策略
+3. **最大机会**:当前最值得把握的方向
+4. **最大风险**:最需要警惕的风险
+5. **时间节点**:下一个重要的周期转折点大约在何时
+
+## 五、周金涛名言对照
+
+选择一句最契合当前阶段的周金涛名言,并解释为什么这句话适用于当下。
+
+请给出权威、全面、可操作的综合策略报告。
+"""
+ messages = [
+ {"role": "system", "content": "你是一位世界级的首席宏观策略师,擅长将康波长周期、美林投资时钟和中国政策环境三个维度有机结合,为投资者提供既有战略高度又有战术灵活性的综合投资策略。你的判断沉稳、客观、有数据支撑。"},
+ {"role": "user", "content": prompt}
+ ]
+
+ analysis = self.deepseek_client.call_api(messages, max_tokens=6000)
+ print(" ✓ 首席宏观策略师综合研判完成")
+
+ return {
+ "agent_name": "首席宏观策略师",
+ "agent_icon": "👔",
+ "agent_role": "整合康波周期、美林时钟、中国政策三维分析,构建周期仪表盘,给出最终综合策略",
+ "analysis": analysis,
+ "focus_areas": ["周期仪表盘", "综合资产配置", "双指针共振", "分人群建议"],
+ "timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
+ }
+
+
+# 测试
+if __name__ == "__main__":
+ print("=" * 60)
+ print("测试宏观周期AI智能体系统")
+ print("=" * 60)
+ agents = MacroCycleAgents()
+ print(f"模型: {agents.model}")
+ print("初始化完成")
diff --git a/macro_cycle_data.py b/macro_cycle_data.py
new file mode 100644
index 0000000..6276091
--- /dev/null
+++ b/macro_cycle_data.py
@@ -0,0 +1,559 @@
+"""
+宏观周期分析 - 数据采集模块
+采集宏观经济数据(GDP、CPI/PPI、PMI、利率、M2、大宗商品等)
+用于康波周期和美林投资时钟分析
+"""
+
+import akshare as ak
+import pandas as pd
+from datetime import datetime, timedelta
+import warnings
+import time
+import logging
+import traceback
+
+warnings.filterwarnings('ignore')
+
+logger = logging.getLogger(__name__)
+
+
+class MacroCycleDataFetcher:
+ """宏观经济数据采集器"""
+
+ def __init__(self):
+ print("[宏观周期] 数据采集器初始化...")
+ self.max_retries = 3
+
+ def _safe_request(self, func, *args, **kwargs):
+ """安全请求,带重试"""
+ for i in range(self.max_retries):
+ try:
+ return func(*args, **kwargs)
+ except Exception as e:
+ if i < self.max_retries - 1:
+ time.sleep(2)
+ else:
+ logger.warning(f"请求失败: {e}")
+ return None
+
+ def get_all_macro_data(self) -> dict:
+ """
+ 获取所有宏观经济数据
+ Returns:
+ dict: 包含多维度宏观数据的字典
+ """
+ print("\n[宏观周期] 开始采集宏观经济数据...")
+ data = {
+ "success": False,
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
+ "gdp": {},
+ "cpi_ppi": {},
+ "pmi": {},
+ "money_supply": {},
+ "interest_rate": {},
+ "market_indices": {},
+ "commodities": {},
+ "real_estate": {},
+ "employment": {},
+ "news": [],
+ "errors": []
+ }
+
+ # 1. GDP
+ print(" 1/9 获取GDP数据...")
+ try:
+ gdp_data = self._get_gdp_data()
+ if gdp_data:
+ data["gdp"] = gdp_data
+ print(" ✓ GDP数据获取成功")
+ except Exception as e:
+ data["errors"].append(f"GDP: {e}")
+ print(f" ✗ GDP数据获取失败: {e}")
+
+ # 2. CPI/PPI
+ print(" 2/9 获取CPI/PPI数据...")
+ try:
+ cpi_ppi = self._get_cpi_ppi_data()
+ if cpi_ppi:
+ data["cpi_ppi"] = cpi_ppi
+ print(" ✓ CPI/PPI数据获取成功")
+ except Exception as e:
+ data["errors"].append(f"CPI/PPI: {e}")
+ print(f" ✗ CPI/PPI获取失败: {e}")
+
+ # 3. PMI
+ print(" 3/9 获取PMI数据...")
+ try:
+ pmi = self._get_pmi_data()
+ if pmi:
+ data["pmi"] = pmi
+ print(" ✓ PMI数据获取成功")
+ except Exception as e:
+ data["errors"].append(f"PMI: {e}")
+ print(f" ✗ PMI获取失败: {e}")
+
+ # 4. 货币供应量 M2
+ print(" 4/9 获取货币供应数据...")
+ try:
+ money = self._get_money_supply()
+ if money:
+ data["money_supply"] = money
+ print(" ✓ 货币供应数据获取成功")
+ except Exception as e:
+ data["errors"].append(f"货币供应: {e}")
+ print(f" ✗ 货币供应获取失败: {e}")
+
+ # 5. 利率
+ print(" 5/9 获取利率数据...")
+ try:
+ rate = self._get_interest_rate()
+ if rate:
+ data["interest_rate"] = rate
+ print(" ✓ 利率数据获取成功")
+ except Exception as e:
+ data["errors"].append(f"利率: {e}")
+ print(f" ✗ 利率获取失败: {e}")
+
+ # 6. 市场指数
+ print(" 6/9 获取市场指数...")
+ try:
+ indices = self._get_market_indices()
+ if indices:
+ data["market_indices"] = indices
+ print(" ✓ 市场指数获取成功")
+ except Exception as e:
+ data["errors"].append(f"市场指数: {e}")
+ print(f" ✗ 市场指数获取失败: {e}")
+
+ # 7. 大宗商品
+ print(" 7/9 获取大宗商品数据...")
+ try:
+ commodities = self._get_commodities_data()
+ if commodities:
+ data["commodities"] = commodities
+ print(" ✓ 大宗商品数据获取成功")
+ except Exception as e:
+ data["errors"].append(f"大宗商品: {e}")
+ print(f" ✗ 大宗商品获取失败: {e}")
+
+ # 8. 房地产
+ print(" 8/9 获取房地产数据...")
+ try:
+ real_estate = self._get_real_estate_data()
+ if real_estate:
+ data["real_estate"] = real_estate
+ print(" ✓ 房地产数据获取成功")
+ except Exception as e:
+ data["errors"].append(f"房地产: {e}")
+ print(f" ✗ 房地产获取失败: {e}")
+
+ # 9. 财经新闻
+ print(" 9/9 获取财经新闻...")
+ try:
+ news = self._get_macro_news()
+ if news:
+ data["news"] = news
+ print(f" ✓ 获取{len(news)}条新闻")
+ except Exception as e:
+ data["errors"].append(f"新闻: {e}")
+ print(f" ✗ 新闻获取失败: {e}")
+
+ # 判断是否有足够数据
+ valid_count = sum(1 for k in ["gdp", "cpi_ppi", "pmi", "money_supply",
+ "interest_rate", "market_indices", "commodities"]
+ if data.get(k))
+ if valid_count >= 3:
+ data["success"] = True
+ print(f"\n[宏观周期] 数据采集完成,成功获取 {valid_count}/7 项核心数据")
+ else:
+ print(f"\n[宏观周期] 数据不足(仅 {valid_count}/7 项),分析可能不够准确")
+ data["success"] = True # 仍允许分析
+
+ return data
+
+ def _get_gdp_data(self) -> dict:
+ """获取GDP数据"""
+ result = {}
+ try:
+ # 中国GDP年度
+ df = self._safe_request(ak.macro_china_gdp)
+ if df is not None and not df.empty:
+ recent = df.tail(8)
+ result["yearly"] = []
+ for _, row in recent.iterrows():
+ item = {}
+ for col in df.columns:
+ item[col] = str(row[col])
+ result["yearly"].append(item)
+ except Exception as e:
+ logger.warning(f"GDP年度数据获取失败: {e}")
+
+ try:
+ # 季度GDP增速
+ df = self._safe_request(ak.macro_china_gdp_yearly)
+ if df is not None and not df.empty:
+ recent = df.tail(12)
+ result["quarterly_growth"] = []
+ for _, row in recent.iterrows():
+ item = {}
+ for col in df.columns:
+ item[col] = str(row[col])
+ result["quarterly_growth"].append(item)
+ except Exception as e:
+ logger.warning(f"GDP季度数据获取失败: {e}")
+
+ return result if result else None
+
+ def _get_cpi_ppi_data(self) -> dict:
+ """获取CPI和PPI数据"""
+ result = {}
+ try:
+ # CPI月度
+ df = self._safe_request(ak.macro_china_cpi_monthly)
+ if df is not None and not df.empty:
+ recent = df.tail(12)
+ result["cpi_monthly"] = []
+ for _, row in recent.iterrows():
+ item = {}
+ for col in df.columns:
+ item[col] = str(row[col])
+ result["cpi_monthly"].append(item)
+ except Exception as e:
+ logger.warning(f"CPI数据获取失败: {e}")
+
+ try:
+ # PPI月度
+ df = self._safe_request(ak.macro_china_ppi_yearly)
+ if df is not None and not df.empty:
+ recent = df.tail(12)
+ result["ppi_monthly"] = []
+ for _, row in recent.iterrows():
+ item = {}
+ for col in df.columns:
+ item[col] = str(row[col])
+ result["ppi_monthly"].append(item)
+ except Exception as e:
+ logger.warning(f"PPI数据获取失败: {e}")
+
+ return result if result else None
+
+ def _get_pmi_data(self) -> dict:
+ """获取PMI数据"""
+ result = {}
+ try:
+ # 制造业PMI
+ df = self._safe_request(ak.macro_china_pmi_yearly)
+ if df is not None and not df.empty:
+ recent = df.tail(12)
+ result["manufacturing_pmi"] = []
+ for _, row in recent.iterrows():
+ item = {}
+ for col in df.columns:
+ item[col] = str(row[col])
+ result["manufacturing_pmi"].append(item)
+ except Exception as e:
+ logger.warning(f"制造业PMI获取失败: {e}")
+
+ try:
+ # 非制造业PMI(财新)
+ df = self._safe_request(ak.macro_china_cx_pmi_yearly)
+ if df is not None and not df.empty:
+ recent = df.tail(12)
+ result["caixin_pmi"] = []
+ for _, row in recent.iterrows():
+ item = {}
+ for col in df.columns:
+ item[col] = str(row[col])
+ result["caixin_pmi"].append(item)
+ except Exception as e:
+ logger.warning(f"财新PMI获取失败: {e}")
+
+ return result if result else None
+
+ def _get_money_supply(self) -> dict:
+ """获取货币供应量"""
+ result = {}
+ try:
+ df = self._safe_request(ak.macro_china_money_supply)
+ if df is not None and not df.empty:
+ recent = df.tail(12)
+ result["m2_data"] = []
+ for _, row in recent.iterrows():
+ item = {}
+ for col in df.columns:
+ item[col] = str(row[col])
+ result["m2_data"].append(item)
+ except Exception as e:
+ logger.warning(f"货币供应数据获取失败: {e}")
+
+ return result if result else None
+
+ def _get_interest_rate(self) -> dict:
+ """获取利率数据"""
+ result = {}
+ try:
+ # LPR利率
+ df = self._safe_request(ak.macro_china_lpr)
+ if df is not None and not df.empty:
+ recent = df.tail(12)
+ result["lpr"] = []
+ for _, row in recent.iterrows():
+ item = {}
+ for col in df.columns:
+ item[col] = str(row[col])
+ result["lpr"].append(item)
+ except Exception as e:
+ logger.warning(f"LPR利率获取失败: {e}")
+
+ return result if result else None
+
+ def _get_market_indices(self) -> dict:
+ """获取主要市场指数"""
+ result = {}
+ indices = {
+ "sh_index": "sh000001", # 上证指数
+ "sz_index": "sz399001", # 深证成指
+ "cyb_index": "sz399006", # 创业板指
+ }
+
+ for name, code in indices.items():
+ try:
+ df = self._safe_request(
+ ak.stock_zh_index_daily,
+ symbol=code
+ )
+ if df is not None and not df.empty:
+ latest = df.tail(1).iloc[0]
+ prev = df.tail(2).iloc[0] if len(df) >= 2 else latest
+
+ change_pct = 0
+ if prev["close"] > 0:
+ change_pct = (latest["close"] - prev["close"]) / prev["close"] * 100
+
+ # 计算近期涨跌
+ recent_60 = df.tail(60)
+ pct_60d = 0
+ if len(recent_60) >= 60:
+ pct_60d = (latest["close"] - recent_60.iloc[0]["close"]) / recent_60.iloc[0]["close"] * 100
+
+ result[name] = {
+ "close": round(float(latest["close"]), 2),
+ "change_pct": round(change_pct, 2),
+ "pct_60d": round(pct_60d, 2),
+ "high_52w": round(float(df.tail(250)["high"].max()), 2) if len(df) >= 250 else None,
+ "low_52w": round(float(df.tail(250)["low"].min()), 2) if len(df) >= 250 else None,
+ }
+ except Exception as e:
+ logger.warning(f"指数{name}获取失败: {e}")
+
+ return result if result else None
+
+ def _get_commodities_data(self) -> dict:
+ """获取大宗商品数据"""
+ result = {}
+
+ # 黄金
+ try:
+ df = self._safe_request(ak.futures_main_sina, symbol="AU0", start_date=(datetime.now() - timedelta(days=365)).strftime("%Y%m%d"), end_date=datetime.now().strftime("%Y%m%d"))
+ if df is not None and not df.empty:
+ latest = df.tail(1).iloc[0]
+ first = df.head(1).iloc[0]
+ ytd_pct = (float(latest["收盘价"]) - float(first["收盘价"])) / float(first["收盘价"]) * 100 if float(first["收盘价"]) > 0 else 0
+ result["gold"] = {
+ "price": round(float(latest["收盘价"]), 2),
+ "ytd_change_pct": round(ytd_pct, 2),
+ "name": "沪金主力"
+ }
+ except Exception as e:
+ logger.warning(f"黄金数据获取失败: {e}")
+
+ # 原油
+ try:
+ df = self._safe_request(ak.futures_main_sina, symbol="SC0", start_date=(datetime.now() - timedelta(days=365)).strftime("%Y%m%d"), end_date=datetime.now().strftime("%Y%m%d"))
+ if df is not None and not df.empty:
+ latest = df.tail(1).iloc[0]
+ first = df.head(1).iloc[0]
+ ytd_pct = (float(latest["收盘价"]) - float(first["收盘价"])) / float(first["收盘价"]) * 100 if float(first["收盘价"]) > 0 else 0
+ result["crude_oil"] = {
+ "price": round(float(latest["收盘价"]), 2),
+ "ytd_change_pct": round(ytd_pct, 2),
+ "name": "原油主力"
+ }
+ except Exception as e:
+ logger.warning(f"原油数据获取失败: {e}")
+
+ # 铜
+ try:
+ df = self._safe_request(ak.futures_main_sina, symbol="CU0", start_date=(datetime.now() - timedelta(days=365)).strftime("%Y%m%d"), end_date=datetime.now().strftime("%Y%m%d"))
+ if df is not None and not df.empty:
+ latest = df.tail(1).iloc[0]
+ first = df.head(1).iloc[0]
+ ytd_pct = (float(latest["收盘价"]) - float(first["收盘价"])) / float(first["收盘价"]) * 100 if float(first["收盘价"]) > 0 else 0
+ result["copper"] = {
+ "price": round(float(latest["收盘价"]), 2),
+ "ytd_change_pct": round(ytd_pct, 2),
+ "name": "沪铜主力"
+ }
+ except Exception as e:
+ logger.warning(f"铜数据获取失败: {e}")
+
+ return result if result else None
+
+ def _get_real_estate_data(self) -> dict:
+ """获取房地产相关数据"""
+ result = {}
+ try:
+ df = self._safe_request(ak.macro_china_real_estate)
+ if df is not None and not df.empty:
+ recent = df.tail(12)
+ result["data"] = []
+ for _, row in recent.iterrows():
+ item = {}
+ for col in df.columns:
+ item[col] = str(row[col])
+ result["data"].append(item)
+ except Exception as e:
+ logger.warning(f"房地产数据获取失败: {e}")
+
+ return result if result else None
+
+ def _get_macro_news(self) -> list:
+ """获取宏观经济相关新闻"""
+ news_list = []
+ try:
+ df = self._safe_request(ak.stock_info_global_em)
+ if df is not None and not df.empty:
+ for _, row in df.head(50).iterrows():
+ news_list.append({
+ "title": str(row.get("标题", "")),
+ "publish_time": str(row.get("发布时间", "")),
+ "content": str(row.get("概要", ""))[:300]
+ })
+ except Exception as e:
+ logger.warning(f"新闻获取失败: {e}")
+
+ return news_list
+
+ def format_data_for_ai(self, data: dict) -> str:
+ """将数据格式化为AI分析所需的文本"""
+ parts = []
+ parts.append(f"===== 宏观经济数据报告 =====")
+ parts.append(f"数据采集时间: {data.get('timestamp', '未知')}")
+ parts.append("")
+
+ # GDP
+ if data.get("gdp"):
+ parts.append("【一、GDP数据】")
+ gdp = data["gdp"]
+ if gdp.get("yearly"):
+ parts.append("近年GDP:")
+ for item in gdp["yearly"][-4:]:
+ parts.append(f" {item}")
+ if gdp.get("quarterly_growth"):
+ parts.append("季度GDP增速:")
+ for item in gdp["quarterly_growth"][-8:]:
+ parts.append(f" {item}")
+ parts.append("")
+
+ # CPI/PPI
+ if data.get("cpi_ppi"):
+ parts.append("【二、CPI/PPI通胀数据】")
+ cp = data["cpi_ppi"]
+ if cp.get("cpi_monthly"):
+ parts.append("近12个月CPI:")
+ for item in cp["cpi_monthly"]:
+ parts.append(f" {item}")
+ if cp.get("ppi_monthly"):
+ parts.append("近12个月PPI:")
+ for item in cp["ppi_monthly"]:
+ parts.append(f" {item}")
+ parts.append("")
+
+ # PMI
+ if data.get("pmi"):
+ parts.append("【三、PMI景气指数】")
+ pmi = data["pmi"]
+ if pmi.get("manufacturing_pmi"):
+ parts.append("制造业PMI(50为荣枯线):")
+ for item in pmi["manufacturing_pmi"]:
+ parts.append(f" {item}")
+ if pmi.get("caixin_pmi"):
+ parts.append("财新PMI:")
+ for item in pmi["caixin_pmi"]:
+ parts.append(f" {item}")
+ parts.append("")
+
+ # 货币供应
+ if data.get("money_supply"):
+ parts.append("【四、货币供应量】")
+ ms = data["money_supply"]
+ if ms.get("m2_data"):
+ parts.append("M0/M1/M2数据:")
+ for item in ms["m2_data"]:
+ parts.append(f" {item}")
+ parts.append("")
+
+ # 利率
+ if data.get("interest_rate"):
+ parts.append("【五、利率数据】")
+ ir = data["interest_rate"]
+ if ir.get("lpr"):
+ parts.append("LPR利率:")
+ for item in ir["lpr"]:
+ parts.append(f" {item}")
+ parts.append("")
+
+ # 市场指数
+ if data.get("market_indices"):
+ parts.append("【六、市场指数】")
+ mi = data["market_indices"]
+ for name, info in mi.items():
+ label = {"sh_index": "上证指数", "sz_index": "深证成指", "cyb_index": "创业板指"}.get(name, name)
+ parts.append(f" {label}: {info['close']} (日涨跌: {info['change_pct']:+.2f}%, 60日涨跌: {info.get('pct_60d', 0):+.2f}%)")
+ if info.get("high_52w"):
+ parts.append(f" 52周最高: {info['high_52w']} 52周最低: {info['low_52w']}")
+ parts.append("")
+
+ # 大宗商品
+ if data.get("commodities"):
+ parts.append("【七、大宗商品】")
+ for name, info in data["commodities"].items():
+ parts.append(f" {info['name']}: {info['price']} (年涨跌: {info['ytd_change_pct']:+.2f}%)")
+ parts.append("")
+
+ # 房地产
+ if data.get("real_estate"):
+ parts.append("【八、房地产数据】")
+ re_data = data["real_estate"]
+ if re_data.get("data"):
+ for item in re_data["data"][-4:]:
+ parts.append(f" {item}")
+ parts.append("")
+
+ # 新闻
+ if data.get("news"):
+ parts.append("【九、近期宏观经济新闻】")
+ for idx, news in enumerate(data["news"][:20], 1):
+ parts.append(f" {idx}. [{news.get('publish_time', '')}] {news.get('title', '')}")
+ if news.get('content'):
+ parts.append(f" {news['content'][:150]}")
+ parts.append("")
+
+ return "\n".join(parts)
+
+
+# 测试
+if __name__ == "__main__":
+ print("=" * 60)
+ print("测试宏观周期数据采集")
+ print("=" * 60)
+
+ fetcher = MacroCycleDataFetcher()
+ data = fetcher.get_all_macro_data()
+
+ if data.get("success"):
+ formatted = fetcher.format_data_for_ai(data)
+ print(formatted[:5000])
+ print(f"\n... (总长度: {len(formatted)} 字符)")
+ else:
+ print(f"数据采集失败: {data.get('errors')}")
diff --git a/macro_cycle_engine.py b/macro_cycle_engine.py
new file mode 100644
index 0000000..ac9e635
--- /dev/null
+++ b/macro_cycle_engine.py
@@ -0,0 +1,149 @@
+"""
+宏观周期分析 - 综合研判引擎
+协调数据采集与AI分析,生成完整的宏观周期分析报告
+"""
+
+from macro_cycle_agents import MacroCycleAgents
+from macro_cycle_data import MacroCycleDataFetcher
+from typing import Dict, Any
+import time
+import json
+import logging
+import config
+
+
+class MacroCycleEngine:
+ """宏观周期综合研判引擎"""
+
+ def __init__(self, model=None):
+ self.model = model or config.DEFAULT_MODEL_NAME
+ self.agents = MacroCycleAgents(model=self.model)
+ self.data_fetcher = MacroCycleDataFetcher()
+ self.logger = logging.getLogger(__name__)
+ if not self.logger.handlers:
+ logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(levelname)s %(name)s: %(message)s')
+ print(f"[宏观周期引擎] 初始化完成 (模型: {self.model})")
+
+ def run_full_analysis(self, progress_callback=None) -> Dict[str, Any]:
+ """
+ 运行完整的宏观周期分析流程
+
+ Args:
+ progress_callback: 进度回调函数 (progress_pct, status_text)
+
+ Returns:
+ 完整的分析结果
+ """
+ print("\n" + "=" * 60)
+ print("🧭 宏观周期分析系统启动")
+ print("=" * 60)
+
+ results = {
+ "success": False,
+ "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
+ "raw_data": {},
+ "formatted_data": "",
+ "agents_analysis": {},
+ "data_errors": []
+ }
+
+ try:
+ # 阶段1:数据采集
+ if progress_callback:
+ progress_callback(5, "📊 正在采集宏观经济数据...")
+ print("\n[阶段1] 宏观经济数据采集...")
+ print("-" * 60)
+
+ raw_data = self.data_fetcher.get_all_macro_data()
+ results["raw_data"] = raw_data
+ results["data_errors"] = raw_data.get("errors", [])
+
+ if not raw_data.get("success"):
+ print("⚠ 数据采集未完全成功,尝试继续分析...")
+
+ # 格式化数据
+ formatted_text = self.data_fetcher.format_data_for_ai(raw_data)
+ results["formatted_data"] = formatted_text
+
+ if progress_callback:
+ progress_callback(15, "✓ 数据采集完成")
+ print("✓ 数据采集和格式化完成")
+ print(f" 数据文本长度: {len(formatted_text)} 字符")
+
+ # 阶段2:AI智能体分析
+ print("\n[阶段2] AI智能体分析集群工作中...")
+ print("-" * 60)
+
+ agents_results = {}
+
+ # 2.1 康波周期分析师
+ if progress_callback:
+ progress_callback(20, "🌊 康波周期分析师正在分析...")
+ print("1/4 康波周期分析师...")
+ kondratieff_result = self.agents.kondratieff_wave_agent(formatted_text)
+ agents_results["kondratieff"] = kondratieff_result
+
+ if progress_callback:
+ progress_callback(35, "✓ 康波分析完成")
+
+ # 2.2 美林时钟分析师
+ if progress_callback:
+ progress_callback(40, "⏰ 美林时钟分析师正在分析...")
+ print("2/4 美林时钟分析师...")
+ merrill_result = self.agents.merrill_lynch_clock_agent(formatted_text)
+ agents_results["merrill"] = merrill_result
+
+ if progress_callback:
+ progress_callback(55, "✓ 美林时钟分析完成")
+
+ # 2.3 中国政策分析师
+ if progress_callback:
+ progress_callback(60, "🏛️ 中国政策分析师正在分析...")
+ print("3/4 中国政策分析师...")
+ policy_result = self.agents.china_policy_agent(formatted_text)
+ agents_results["policy"] = policy_result
+
+ if progress_callback:
+ progress_callback(75, "✓ 政策分析完成")
+
+ # 2.4 首席宏观策略师(综合三位分析师的报告)
+ if progress_callback:
+ progress_callback(80, "👔 首席宏观策略师正在综合研判...")
+ print("4/4 首席宏观策略师综合研判...")
+ chief_result = self.agents.chief_macro_strategist_agent(
+ kondratieff_report=kondratieff_result.get("analysis", ""),
+ merrill_report=merrill_result.get("analysis", ""),
+ policy_report=policy_result.get("analysis", ""),
+ macro_data_text=formatted_text
+ )
+ agents_results["chief"] = chief_result
+
+ if progress_callback:
+ progress_callback(95, "✓ 综合研判完成")
+
+ results["agents_analysis"] = agents_results
+ results["success"] = True
+
+ print("\n" + "=" * 60)
+ print("✓ 宏观周期分析完成!")
+ print("=" * 60)
+
+ if progress_callback:
+ progress_callback(100, "✅ 分析完成!")
+
+ except Exception as e:
+ print(f"\n✗ 分析过程出错: {e}")
+ import traceback
+ traceback.print_exc()
+ results["error"] = str(e)
+
+ return results
+
+
+# 测试
+if __name__ == "__main__":
+ print("=" * 60)
+ print("测试宏观周期分析引擎")
+ print("=" * 60)
+ engine = MacroCycleEngine()
+ print("引擎初始化完成")
diff --git a/macro_cycle_pdf.py b/macro_cycle_pdf.py
new file mode 100644
index 0000000..b33a742
--- /dev/null
+++ b/macro_cycle_pdf.py
@@ -0,0 +1,427 @@
+"""
+宏观周期分析 - PDF报告生成模块
+生成康波周期 × 美林投资时钟 × 中国政策分析的完整PDF报告
+"""
+
+from reportlab.lib import colors
+from reportlab.lib.pagesizes import A4
+from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
+from reportlab.lib.units import inch
+from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
+from reportlab.pdfbase import pdfmetrics
+from reportlab.pdfbase.ttfonts import TTFont
+from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
+from datetime import datetime
+import os
+import tempfile
+import re
+
+
+class MacroCyclePDFGenerator:
+ """宏观周期分析PDF报告生成器"""
+
+ def __init__(self):
+ """初始化PDF生成器"""
+ self.setup_fonts()
+
+ def setup_fonts(self):
+ """设置中文字体"""
+ try:
+ font_paths = [
+ 'C:/Windows/Fonts/msyh.ttc', # 微软雅黑
+ 'C:/Windows/Fonts/simsun.ttc', # 宋体
+ 'C:/Windows/Fonts/simhei.ttf', # 黑体
+ '/usr/share/fonts/truetype/wqy/wqy-microhei.ttc', # Linux
+ '/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf',
+ ]
+
+ for font_path in font_paths:
+ if os.path.exists(font_path):
+ try:
+ pdfmetrics.registerFont(TTFont('ChineseFont', font_path))
+ self.chinese_font = 'ChineseFont'
+ print(f"[宏观PDF] 成功加载字体: {font_path}")
+ return
+ except:
+ continue
+
+ self.chinese_font = 'Helvetica'
+ print("[宏观PDF] 警告: 未找到中文字体,使用默认字体")
+
+ except Exception as e:
+ print(f"[宏观PDF] 字体设置失败: {e}")
+ self.chinese_font = 'Helvetica'
+
+ def generate_pdf(self, result_data: dict, output_path: str = None) -> str:
+ """
+ 生成宏观周期分析PDF报告
+
+ Args:
+ result_data: 分析结果数据
+ output_path: 输出路径,如果为None则生成临时文件
+
+ Returns:
+ PDF文件路径
+ """
+ try:
+ if output_path is None:
+ temp_dir = tempfile.gettempdir()
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+ output_path = os.path.join(temp_dir, f"宏观周期报告_{timestamp}.pdf")
+
+ doc = SimpleDocTemplate(
+ output_path,
+ pagesize=A4,
+ rightMargin=0.5*inch,
+ leftMargin=0.5*inch,
+ topMargin=0.5*inch,
+ bottomMargin=0.5*inch
+ )
+
+ story = []
+
+ # 1. 标题页
+ story.extend(self._create_title_page(result_data))
+ story.append(PageBreak())
+
+ # 2. 首席宏观策略师综合研判(最重要,放最前面)
+ story.extend(self._create_chief_section(result_data))
+ story.append(PageBreak())
+
+ # 3. 康波周期分析
+ story.extend(self._create_kondratieff_section(result_data))
+ story.append(PageBreak())
+
+ # 4. 美林投资时钟分析
+ story.extend(self._create_merrill_section(result_data))
+ story.append(PageBreak())
+
+ # 5. 中国政策分析
+ story.extend(self._create_policy_section(result_data))
+
+ # 6. 结束语
+ story.extend(self._create_ending())
+
+ # 生成PDF
+ doc.build(story)
+
+ print(f"[宏观PDF] 报告生成成功: {output_path}")
+ return output_path
+
+ except Exception as e:
+ print(f"[宏观PDF] 生成失败: {e}")
+ import traceback
+ traceback.print_exc()
+ raise
+
+ def _clean_markdown(self, text: str) -> str:
+ """清理Markdown标记,转换为适合PDF的纯文本/HTML"""
+ if not text:
+ return ""
+ # 移除markdown粗体 **text** → text
+ text = re.sub(r'\*\*(.*?)\*\*', r'\1', text)
+ # 移除markdown斜体 *text* → text
+ text = re.sub(r'\*(.*?)\*', r'\1', text)
+ # 移除markdown标题 ## → 空
+ text = re.sub(r'^#{1,6}\s+', '', text, flags=re.MULTILINE)
+ # 移除markdown链接 [text](url) → text
+ text = re.sub(r'\[(.*?)\]\(.*?\)', r'\1', text)
+ # 移除markdown表格分隔线
+ text = re.sub(r'\|[-:]+\|[-:| ]+\|', '', text)
+ # 替换换行
+ text = text.replace('\n', '
')
+ return text
+
+ def _split_text_to_paragraphs(self, text: str, styles: dict, max_chars: int = 0) -> list:
+ """将长文本分段为多个Paragraph,避免单段过长溢出"""
+ elements = []
+ if not text:
+ return elements
+
+ if max_chars > 0 and len(text) > max_chars:
+ text = text[:max_chars] + "...(更多内容请查看网页版完整报告)"
+
+ # 按段落分割
+ paragraphs = text.split('\n\n')
+ for para in paragraphs:
+ para = para.strip()
+ if not para:
+ continue
+ # 清理markdown
+ cleaned = self._clean_markdown(para)
+ if cleaned:
+ try:
+ elements.append(Paragraph(cleaned, styles['Small']))
+ elements.append(Spacer(1, 0.08*inch))
+ except Exception:
+ # 如果格式化失败,用纯文本
+ plain = re.sub(r'<[^>]+>', '', cleaned)
+ elements.append(Paragraph(plain, styles['Small']))
+ elements.append(Spacer(1, 0.08*inch))
+
+ return elements
+
+ def _create_title_page(self, data: dict) -> list:
+ """创建标题页"""
+ styles = self._get_styles()
+ elements = []
+
+ elements.append(Spacer(1, 1.5*inch))
+
+ # 主标题
+ elements.append(Paragraph("宏观周期分析报告", styles['Title']))
+ elements.append(Spacer(1, 0.3*inch))
+
+ # 副标题
+ elements.append(Paragraph(
+ "康波周期 × 美林投资时钟 × 中国政策分析",
+ styles['Heading2']
+ ))
+ elements.append(Spacer(1, 0.8*inch))
+
+ # 报告信息
+ timestamp = data.get('timestamp', datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
+ info_text = f"""
+
+ 分析框架: 三维周期定位系统
+ AI分析师: 4位专业分析师协同研判
+ 分析维度: 康波长周期 · 美林中短周期 · 中国政策环境
+ 数据来源: AKShare宏观经济数据
+
+ 康波周期分析师 · 美林时钟分析师 · 中国政策分析师 · 首席宏观策略师
+
"
+ "周期理论是认知框架而非精确预测工具。投资有风险,入市需谨慎。
"
+ "本报告由宏观周期AI分析系统自动生成
"
+ "康波是罗盘,美林是航海图,政策是季风
"
+ "愿你在经济的海洋中,驶向属于自己的财富彼岸
+ 康波周期 × 美林投资时钟 × 中国政策分析 — AI驱动的宏观经济周期研判 +
++ 整合康波周期 + 美林投资时钟 + 中国政策三维分析,给出最终投资策略 +
++ 基于康德拉季耶夫长波理论,判断当前处于第五轮信息技术康波的哪个阶段 +
++ 基于经济增长+通胀+政策三维度,判断当前处于美林时钟的哪个象限 +
++ 深度分析货币政策、财政政策、产业政策、房地产政策对周期和投资的影响 +
+