""" 宏观周期分析 - 数据采集模块 采集宏观经济数据(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')}")