aria-code 4.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (284) hide show
  1. agents/__init__.py +32 -0
  2. agents/base.py +190 -0
  3. agents/deep/__init__.py +37 -0
  4. agents/deep/calibration_loop.py +144 -0
  5. agents/deep/critic.py +125 -0
  6. agents/deep/deepen.py +193 -0
  7. agents/deep/models.py +149 -0
  8. agents/deep/pipeline.py +164 -0
  9. agents/deep/quant_fusion.py +192 -0
  10. agents/deep/themes.py +95 -0
  11. agents/deep/tiers.py +106 -0
  12. agents/financial/__init__.py +10 -0
  13. agents/financial/catalyst.py +279 -0
  14. agents/financial/debate.py +145 -0
  15. agents/financial/earnings.py +303 -0
  16. agents/financial/fundamental.py +159 -0
  17. agents/financial/macro.py +99 -0
  18. agents/financial/news.py +207 -0
  19. agents/financial/risk.py +132 -0
  20. agents/financial/sector.py +279 -0
  21. agents/financial/synthesis.py +274 -0
  22. agents/financial/technical.py +258 -0
  23. agents/portfolio_agent.py +333 -0
  24. agents/realty/__init__.py +62 -0
  25. agents/realty/asset_diagnosis.py +150 -0
  26. agents/realty/business_match.py +165 -0
  27. agents/realty/cashflow_verify.py +208 -0
  28. agents/realty/contract_rules.py +209 -0
  29. agents/realty/energy_anomaly.py +188 -0
  30. agents/realty/exit_settlement.py +207 -0
  31. agents/realty/fulfillment_risk.py +205 -0
  32. agents/realty/ops_optimize.py +159 -0
  33. agents/realty/revenue_share.py +214 -0
  34. agents/registry.py +144 -0
  35. agents/sports/__init__.py +0 -0
  36. agents/sports/football_agent.py +169 -0
  37. agents/team.py +289 -0
  38. aliyun_data_client.py +660 -0
  39. apps/README.md +12 -0
  40. apps/__init__.py +2 -0
  41. apps/channels/README.md +15 -0
  42. apps/cli/README.md +13 -0
  43. apps/cli/__init__.py +2 -0
  44. apps/cli/bootstrap.py +99 -0
  45. apps/cli/codegen_paths.py +29 -0
  46. apps/cli/commands/__init__.py +16 -0
  47. apps/cli/commands/analysis_cmds.py +288 -0
  48. apps/cli/commands/backtest_cmds.py +1887 -0
  49. apps/cli/commands/broker_cmds.py +1154 -0
  50. apps/cli/commands/business_workflow_cmds.py +289 -0
  51. apps/cli/commands/catalog.py +84 -0
  52. apps/cli/commands/data_cmds.py +405 -0
  53. apps/cli/commands/diagnostic_cmds.py +179 -0
  54. apps/cli/commands/diagnostic_ops_cmds.py +696 -0
  55. apps/cli/commands/finance_render.py +12 -0
  56. apps/cli/commands/market.py +399 -0
  57. apps/cli/commands/market_cmds.py +1276 -0
  58. apps/cli/commands/market_context.py +425 -0
  59. apps/cli/commands/market_render.py +7 -0
  60. apps/cli/commands/model_cmds.py +1579 -0
  61. apps/cli/commands/ops_cmds.py +668 -0
  62. apps/cli/commands/portfolio_cmds.py +962 -0
  63. apps/cli/commands/report.py +377 -0
  64. apps/cli/commands/scaffold_templates.py +617 -0
  65. apps/cli/commands/session_cmds.py +179 -0
  66. apps/cli/commands/session_ux_cmds.py +280 -0
  67. apps/cli/commands/team.py +588 -0
  68. apps/cli/commands/team_render.py +8 -0
  69. apps/cli/commands/ui_cmds.py +358 -0
  70. apps/cli/commands/workflow_cmds.py +279 -0
  71. apps/cli/commands/workspace_cmds.py +1414 -0
  72. apps/cli/config_paths.py +70 -0
  73. apps/cli/config_store.py +61 -0
  74. apps/cli/deterministic.py +122 -0
  75. apps/cli/direct.py +48 -0
  76. apps/cli/github_app_auth.py +135 -0
  77. apps/cli/handlers/__init__.py +11 -0
  78. apps/cli/handlers/broker_handlers.py +122 -0
  79. apps/cli/handlers/chart_handlers.py +1309 -0
  80. apps/cli/handlers/market_handlers.py +2509 -0
  81. apps/cli/handlers/realty_handlers.py +114 -0
  82. apps/cli/handlers/strategy_advice.py +82 -0
  83. apps/cli/hooks.py +180 -0
  84. apps/cli/i18n.py +284 -0
  85. apps/cli/intent.py +136 -0
  86. apps/cli/intent_router.py +217 -0
  87. apps/cli/lifecycle_hooks.py +48 -0
  88. apps/cli/main.py +29 -0
  89. apps/cli/market_metadata.py +135 -0
  90. apps/cli/market_universe.py +265 -0
  91. apps/cli/message_processing.py +257 -0
  92. apps/cli/plan_mode.py +139 -0
  93. apps/cli/plotly_html.py +15 -0
  94. apps/cli/prediction_feedback.py +202 -0
  95. apps/cli/preflight.py +497 -0
  96. apps/cli/project_aria.py +60 -0
  97. apps/cli/prompts/__init__.py +0 -0
  98. apps/cli/prompts/coding.py +658 -0
  99. apps/cli/prompts/system_prompts.py +531 -0
  100. apps/cli/prompts/ui.py +434 -0
  101. apps/cli/providers/__init__.py +1 -0
  102. apps/cli/providers/base.py +271 -0
  103. apps/cli/providers/chat_routing.py +80 -0
  104. apps/cli/providers/llm/__init__.py +1 -0
  105. apps/cli/providers/llm/ollama_stream.py +1170 -0
  106. apps/cli/providers/llm/sse_stream.py +216 -0
  107. apps/cli/providers/runtime_bridge.py +185 -0
  108. apps/cli/runtime_consumer.py +489 -0
  109. apps/cli/session_export.py +87 -0
  110. apps/cli/session_jsonl.py +207 -0
  111. apps/cli/session_store.py +112 -0
  112. apps/cli/todo_tracker.py +190 -0
  113. apps/cli/tools/__init__.py +40 -0
  114. apps/cli/tools/context.py +46 -0
  115. apps/cli/tools/file_tools.py +112 -0
  116. apps/cli/tools/market_tools.py +549 -0
  117. apps/cli/tools/notebook_tools.py +111 -0
  118. apps/cli/tools/system_tools.py +669 -0
  119. apps/cli/tools/write_tools.py +715 -0
  120. apps/cli/tradingview_bridge.py +434 -0
  121. apps/cli/update_check.py +152 -0
  122. apps/cli/utils/__init__.py +0 -0
  123. apps/cli/utils/market_detect.py +1578 -0
  124. apps/daemon/README.md +14 -0
  125. apps/vscode/README.md +115 -0
  126. apps/vscode/package.json +70 -0
  127. aria_cli.py +11636 -0
  128. aria_code-4.1.3.dist-info/METADATA +952 -0
  129. aria_code-4.1.3.dist-info/RECORD +284 -0
  130. aria_code-4.1.3.dist-info/WHEEL +5 -0
  131. aria_code-4.1.3.dist-info/entry_points.txt +2 -0
  132. aria_code-4.1.3.dist-info/licenses/LICENSE +121 -0
  133. aria_code-4.1.3.dist-info/top_level.txt +50 -0
  134. aria_daemon.py +1295 -0
  135. aria_feishu_bot.py +1359 -0
  136. aria_relay_client.py +182 -0
  137. aria_relay_server.py +405 -0
  138. aria_telegram_bot.py +202 -0
  139. ariarc.py +328 -0
  140. artifacts.py +491 -0
  141. backtest_report.py +472 -0
  142. brokers/__init__.py +72 -0
  143. brokers/base.py +207 -0
  144. brokers/capabilities.py +264 -0
  145. brokers/cn/__init__.py +10 -0
  146. brokers/cn/easytrader_broker.py +193 -0
  147. brokers/cn/futu_broker.py +194 -0
  148. brokers/cn/longbridge_broker.py +190 -0
  149. brokers/cn/tiger_broker.py +196 -0
  150. brokers/cn/xtquant_broker.py +175 -0
  151. brokers/config.py +364 -0
  152. brokers/intl/__init__.py +5 -0
  153. brokers/intl/alpaca_broker.py +183 -0
  154. brokers/intl/ibkr_broker.py +215 -0
  155. brokers/intl/webull_broker.py +156 -0
  156. brokers/paper_broker.py +259 -0
  157. brokers/planning.py +296 -0
  158. brokers/registry.py +181 -0
  159. brokers/trading.py +237 -0
  160. change_store.py +127 -0
  161. command_safety.py +19 -0
  162. computer_use_tools.py +504 -0
  163. dashboard_generator.py +578 -0
  164. data_analysis_tools.py +808 -0
  165. data_cleaner.py +483 -0
  166. data_service.py +481 -0
  167. datasources/__init__.py +23 -0
  168. datasources/base.py +166 -0
  169. datasources/router.py +221 -0
  170. datasources/sources/__init__.py +15 -0
  171. datasources/sources/akshare_source.py +269 -0
  172. datasources/sources/alpha_vantage_source.py +202 -0
  173. datasources/sources/edgar_source.py +218 -0
  174. datasources/sources/finnhub_source.py +197 -0
  175. datasources/sources/fred_source.py +219 -0
  176. datasources/sources/tushare_source.py +141 -0
  177. datasources/sources/web_scraper_source.py +278 -0
  178. datasources/sources/world_bank_source.py +205 -0
  179. datasources/sources/yfinance_source.py +152 -0
  180. demo_player.py +204 -0
  181. doctor.py +508 -0
  182. file_analysis_tools.py +734 -0
  183. finance_formulas.py +389 -0
  184. football_data_client.py +1670 -0
  185. intent_classifier.py +358 -0
  186. local_finance_tools.py +3221 -0
  187. local_llm_provider.py +552 -0
  188. macro_tools.py +368 -0
  189. market_data_client.py +1899 -0
  190. mcp_client.py +506 -0
  191. memory_manager.py +245 -0
  192. model_capability.py +416 -0
  193. notification_tools.py +248 -0
  194. packages/__init__.py +23 -0
  195. packages/aria_agents/__init__.py +5 -0
  196. packages/aria_agents/manifest.py +69 -0
  197. packages/aria_core/__init__.py +34 -0
  198. packages/aria_core/architecture.py +192 -0
  199. packages/aria_core/export.py +124 -0
  200. packages/aria_core/manifest.py +65 -0
  201. packages/aria_infra/__init__.py +15 -0
  202. packages/aria_infra/arthera.py +52 -0
  203. packages/aria_infra/doctor.py +246 -0
  204. packages/aria_infra/product.py +37 -0
  205. packages/aria_mcp/__init__.py +25 -0
  206. packages/aria_mcp/bridge.py +38 -0
  207. packages/aria_mcp/config.py +97 -0
  208. packages/aria_mcp/tools.py +61 -0
  209. packages/aria_sdk/__init__.py +19 -0
  210. packages/aria_sdk/client.py +396 -0
  211. packages/aria_sdk/providers.py +70 -0
  212. packages/aria_sdk/streaming.py +73 -0
  213. packages/aria_sdk/types.py +86 -0
  214. packages/aria_services/__init__.py +55 -0
  215. packages/aria_services/context.py +258 -0
  216. packages/aria_services/data.py +11 -0
  217. packages/aria_services/provider_health.py +189 -0
  218. packages/aria_services/registry.py +213 -0
  219. packages/aria_services/usage.py +138 -0
  220. packages/aria_skills/__init__.py +5 -0
  221. packages/aria_skills/registry.py +59 -0
  222. packages/aria_tools/__init__.py +5 -0
  223. packages/aria_tools/registry.py +128 -0
  224. packages/quant_engine/__init__.py +6 -0
  225. packages/quant_engine/sports/__init__.py +72 -0
  226. packages/quant_engine/sports/calibrator.py +353 -0
  227. packages/quant_engine/sports/dixon_coles.py +234 -0
  228. packages/quant_engine/sports/elo.py +299 -0
  229. packages/quant_engine/sports/form.py +188 -0
  230. packages/quant_engine/sports/h2h.py +195 -0
  231. packages/quant_engine/sports/ml_model.py +354 -0
  232. packages/quant_engine/sports/predictor.py +311 -0
  233. packages/quant_engine/sports/tracker.py +664 -0
  234. packages/quant_engine/stochastic/__init__.py +27 -0
  235. packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  236. packages/quant_engine/stochastic/ito_calculus.py +477 -0
  237. packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  238. packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  239. packages/quant_engine/stochastic/options_pricing.py +573 -0
  240. packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  241. plan_utils.py +194 -0
  242. plugin_loader.py +328 -0
  243. portfolio_ledger.py +262 -0
  244. privacy/__init__.py +5 -0
  245. privacy/feedback.py +123 -0
  246. project_tools.py +525 -0
  247. providers/__init__.py +30 -0
  248. providers/llm/__init__.py +19 -0
  249. providers/llm/anthropic.py +184 -0
  250. providers/llm/base.py +139 -0
  251. providers/llm/ollama.py +128 -0
  252. providers/llm/openai_compat.py +282 -0
  253. providers/llm/registry.py +358 -0
  254. realty_data_tools.py +659 -0
  255. report_generator.py +1314 -0
  256. runtime/__init__.py +103 -0
  257. runtime/agent_loop.py +1183 -0
  258. runtime/approval.py +51 -0
  259. runtime/events.py +102 -0
  260. runtime/gateway.py +128 -0
  261. runtime/lsp.py +346 -0
  262. runtime/subagent.py +258 -0
  263. runtime/tool_executor.py +104 -0
  264. runtime/tool_policy.py +106 -0
  265. safety/__init__.py +21 -0
  266. safety/permissions.py +275 -0
  267. setup_wizard.py +653 -0
  268. strategy_vault.py +420 -0
  269. ui/__init__.py +100 -0
  270. ui/banner.py +310 -0
  271. ui/completer.py +391 -0
  272. ui/console.py +271 -0
  273. ui/image_render.py +243 -0
  274. ui/input_box.py +376 -0
  275. ui/picker.py +195 -0
  276. ui/render/__init__.py +11 -0
  277. ui/render/finance.py +1480 -0
  278. ui/render/market.py +225 -0
  279. ui/render/output.py +681 -0
  280. ui/render/team.py +346 -0
  281. ui/robot.py +235 -0
  282. workspace/__init__.py +6 -0
  283. workspace/files.py +170 -0
  284. workspace/verify.py +113 -0
macro_tools.py ADDED
@@ -0,0 +1,368 @@
1
+ """
2
+ macro_tools.py — 宏观经济数据层
3
+ =================================
4
+ 来源:
5
+ - FRED (美联储 St. Louis) — GDP, CPI, 联邦基金利率, 失业率, M2
6
+ - AKShare — 中国 CPI/PPI/PMI/GDP/LPR/社融
7
+ - yfinance — 全球央行政策利率(备用)
8
+
9
+ 全部函数返回 {"success": bool, "data": [...], ...} 格式,
10
+ 与 local_finance_tools 保持一致。
11
+
12
+ 依赖安装(可选):
13
+ pip install requests # FRED REST API
14
+ pip install akshare # 中国数据
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import logging
20
+ import os
21
+ from datetime import datetime, timedelta
22
+ from pathlib import Path
23
+ from typing import Any, Dict, List, Optional
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ # ── Optional imports ──────────────────────────────────────────────────────────
28
+
29
+ try:
30
+ import requests as _req
31
+ _HAS_REQUESTS = True
32
+ except ImportError:
33
+ _HAS_REQUESTS = False
34
+
35
+ try:
36
+ import akshare as ak
37
+ _HAS_AK = True
38
+ except ImportError:
39
+ _HAS_AK = False
40
+
41
+ try:
42
+ import pandas as pd
43
+ _HAS_PD = True
44
+ except ImportError:
45
+ _HAS_PD = False
46
+
47
+ # ── FRED API helper ──────────────────────────────────────────────────────────
48
+
49
+ FRED_BASE = "https://fred.stlouisfed.org/graph/fredgraph.csv"
50
+ FRED_API_BASE = "https://api.stlouisfed.org/fred"
51
+
52
+ def _get_fred_key() -> str:
53
+ """Read FRED API key from env or ~/.arthera/providers.json."""
54
+ key = os.getenv("FRED_API_KEY", "")
55
+ if not key:
56
+ try:
57
+ import json
58
+ p = Path.home() / ".arthera" / "providers.json"
59
+ if p.exists():
60
+ d = json.loads(p.read_text())
61
+ key = d.get("fred", {}).get("api_key", "") or d.get("fred_api_key", "")
62
+ except Exception:
63
+ pass
64
+ return key
65
+
66
+
67
+ def _fred_series(series_id: str, limit: int = 24, units: str = "") -> List[Dict]:
68
+ """Fetch a FRED data series. Returns list of {date, value} dicts.
69
+
70
+ ``units`` maps to FRED's units transformation, e.g. "pc1" = percent change
71
+ from a year ago (used for YoY rates like CPI inflation, so we don't show
72
+ the raw index level mislabelled as a percentage).
73
+ """
74
+ if not _HAS_REQUESTS:
75
+ return []
76
+ key = _get_fred_key()
77
+ try:
78
+ params: Dict[str, Any] = {
79
+ "series_id": series_id,
80
+ "limit": limit,
81
+ "sort_order": "desc",
82
+ "file_type": "json",
83
+ }
84
+ if units:
85
+ params["units"] = units
86
+ if key:
87
+ params["api_key"] = key
88
+ url = f"{FRED_API_BASE}/series/observations"
89
+ r = _req.get(url, params=params, timeout=8)
90
+ r.raise_for_status()
91
+ obs = r.json().get("observations", [])
92
+ return [
93
+ {"date": o["date"], "value": float(o["value"]) if o["value"] != "." else None}
94
+ for o in reversed(obs)
95
+ ]
96
+ else:
97
+ # Public CSV endpoint (no key needed, slower). fredgraph supports
98
+ # transformation codes via the `transformation` query param.
99
+ obs_url = f"https://fred.stlouisfed.org/graph/fredgraph.csv?id={series_id}"
100
+ if units:
101
+ obs_url += f"&transformation={units}"
102
+ r = _req.get(obs_url, timeout=10)
103
+ r.raise_for_status()
104
+ lines = r.text.strip().split("\n")[1:] # skip header
105
+ result = []
106
+ for line in lines[-limit:]:
107
+ parts = line.split(",")
108
+ if len(parts) == 2:
109
+ try:
110
+ result.append({"date": parts[0], "value": float(parts[1])})
111
+ except ValueError:
112
+ pass
113
+ return result
114
+ except Exception as e:
115
+ logger.debug("FRED fetch %s failed: %s", series_id, e)
116
+ return []
117
+
118
+
119
+ # ── US Macro ──────────────────────────────────────────────────────────────────
120
+
121
+ _FRED_SERIES_MAP = {
122
+ "gdp": ("GDP", "GDP 实际值 (十亿美元, 季度)", "B"),
123
+ "gdp_growth": ("A191RL1Q225SBEA", "GDP 同比增速 (%)", "%"),
124
+ "cpi": ("CPIAUCSL", "CPI 城市消费者价格指数", "idx"),
125
+ "cpi_yoy": ("CPIAUCSL", "CPI 同比 (%)", "%", "pc1"), # pc1 = YoY %
126
+ "core_cpi_yoy": ("CPILFESL", "核心 CPI 同比 (%)", "%", "pc1"),
127
+ "core_cpi": ("CPILFESL", "核心 CPI (剔除食品能源)", "idx"),
128
+ "pce": ("PCEPI", "PCE 通胀指数", "idx"),
129
+ "fed_rate": ("FEDFUNDS", "联邦基金利率 (%)", "%"),
130
+ "unemployment": ("UNRATE", "美国失业率 (%)", "%"),
131
+ "m2": ("M2SL", "M2 货币供给 (十亿美元)", "B"),
132
+ "10y_yield": ("DGS10", "10年期国债收益率 (%)", "%"),
133
+ "2y_yield": ("DGS2", "2年期国债收益率 (%)", "%"),
134
+ "vix": ("VIXCLS", "VIX 恐慌指数", ""),
135
+ "retail_sales": ("RSAFS", "零售销售额 (百万美元, 季调)", "M"),
136
+ "industrial": ("INDPRO", "工业产出指数", "idx"),
137
+ "housing": ("HOUST", "新屋开工 (千套, 季调年化)", "K"),
138
+ "ppi": ("PPIACO", "PPI 生产者价格指数", "idx"),
139
+ }
140
+
141
+
142
+ def get_us_macro(indicator: str = "all", periods: int = 12) -> dict:
143
+ """
144
+ 获取美国宏观经济数据。
145
+
146
+ indicator: "gdp" | "cpi" | "fed_rate" | "unemployment" | "m2" |
147
+ "10y_yield" | "retail_sales" | "all"
148
+ periods: 返回最近 N 期数据
149
+ """
150
+ if indicator == "all":
151
+ keys = ["gdp_growth", "cpi_yoy", "fed_rate", "unemployment", "10y_yield",
152
+ "2y_yield", "m2", "ppi"]
153
+ else:
154
+ keys = [indicator] if indicator in _FRED_SERIES_MAP else []
155
+ if not keys:
156
+ return {"success": False, "error": f"未知指标: {indicator}。可选: {', '.join(_FRED_SERIES_MAP)}"}
157
+
158
+ results = {}
159
+ for key in keys:
160
+ _spec = _FRED_SERIES_MAP[key]
161
+ series_id, label, unit = _spec[0], _spec[1], _spec[2]
162
+ _units = _spec[3] if len(_spec) > 3 else ""
163
+ # pc1 (YoY) needs ~13 months of monthly data to compute the first point
164
+ _lim = max(periods, 14) if _units == "pc1" else periods
165
+ data = _fred_series(series_id, limit=_lim, units=_units)
166
+ if data:
167
+ latest = data[-1]
168
+ prev = data[-2] if len(data) >= 2 else None
169
+ change = None
170
+ if latest["value"] is not None and prev and prev["value"] is not None:
171
+ change = round(latest["value"] - prev["value"], 3)
172
+ results[key] = {
173
+ "label": label,
174
+ "unit": unit,
175
+ "latest": latest,
176
+ "prev": prev,
177
+ "change": change,
178
+ "series": data[-6:], # last 6 periods for sparkline
179
+ }
180
+
181
+ if not results:
182
+ return {"success": False, "error": "FRED 数据获取失败(无 API Key 时使用公共 CSV 端点,速度较慢)"}
183
+
184
+ # Yield curve shape
185
+ if "10y_yield" in results and "2y_yield" in results:
186
+ v10 = results["10y_yield"]["latest"]["value"] or 0
187
+ v2 = results["2y_yield"]["latest"]["value"] or 0
188
+ spread = round(v10 - v2, 3)
189
+ results["_yield_curve"] = {
190
+ "spread_10y_2y": spread,
191
+ "shape": "正常" if spread > 0.2 else "倒挂" if spread < 0 else "平坦",
192
+ }
193
+
194
+ return {"success": True, "country": "US", "indicator": indicator,
195
+ "data": results, "provider": "FRED"}
196
+
197
+
198
+ # ── China Macro ───────────────────────────────────────────────────────────────
199
+
200
+ def get_cn_macro(indicator: str = "all") -> dict:
201
+ """
202
+ 获取中国宏观经济数据 (via akshare)。
203
+
204
+ indicator: "cpi" | "ppi" | "pmi" | "gdp" | "lpr" | "m2" | "all"
205
+ """
206
+ if not _HAS_AK:
207
+ return {"success": False, "error": "akshare 未安装,请运行: pip install akshare"}
208
+
209
+ results = {}
210
+
211
+ def _fetch(fn, key, label):
212
+ try:
213
+ df = fn()
214
+ if df is None or (hasattr(df, "empty") and df.empty):
215
+ return
216
+ # Keep last 12 rows
217
+ df = df.tail(12)
218
+ records = df.to_dict("records") if _HAS_PD else []
219
+ latest = records[-1] if records else {}
220
+ results[key] = {"label": label, "latest": latest, "series": records}
221
+ except Exception as e:
222
+ logger.debug("akshare %s failed: %s", key, e)
223
+
224
+ if indicator in ("cpi", "all"):
225
+ _fetch(ak.macro_china_cpi_yearly, "cpi", "中国 CPI 同比 (%)")
226
+
227
+ if indicator in ("ppi", "all"):
228
+ _fetch(ak.macro_china_ppi_yearly, "ppi", "中国 PPI 同比 (%)")
229
+
230
+ if indicator in ("pmi", "all"):
231
+ _fetch(ak.macro_china_pmi_yearly, "pmi_manufacturing",
232
+ "制造业 PMI")
233
+ try:
234
+ df_s = ak.macro_china_non_man_pmi()
235
+ if df_s is not None and not df_s.empty:
236
+ records_s = df_s.tail(12).to_dict("records")
237
+ results["pmi_service"] = {
238
+ "label": "非制造业 PMI",
239
+ "latest": records_s[-1] if records_s else {},
240
+ "series": records_s,
241
+ }
242
+ except Exception:
243
+ pass
244
+
245
+ if indicator in ("gdp", "all"):
246
+ _fetch(ak.macro_china_gdp_yearly, "gdp", "中国 GDP 同比增速 (%)")
247
+
248
+ if indicator in ("lpr", "all"):
249
+ try:
250
+ df_lpr = ak.macro_china_lpr()
251
+ if df_lpr is not None and not df_lpr.empty:
252
+ records_l = df_lpr.tail(6).to_dict("records")
253
+ results["lpr"] = {"label": "LPR 利率", "series": records_l,
254
+ "latest": records_l[-1] if records_l else {}}
255
+ except Exception as e:
256
+ logger.debug("LPR fetch failed: %s", e)
257
+
258
+ if indicator in ("m2", "all"):
259
+ _fetch(ak.macro_china_money_supply, "m2", "中国 M2 同比增速 (%)")
260
+
261
+ if not results:
262
+ return {"success": False, "error": "未能获取任何中国宏观数据"}
263
+
264
+ return {"success": True, "country": "CN", "indicator": indicator,
265
+ "data": results, "provider": "akshare"}
266
+
267
+
268
+ # ── Economic Calendar ─────────────────────────────────────────────────────────
269
+
270
+ def get_economic_calendar(days_ahead: int = 7) -> dict:
271
+ """
272
+ 获取未来 N 天的重大经济事件日历。
273
+ 数据来源:投资道/东方财富 (akshare),备用: Finnhub
274
+ """
275
+ results = []
276
+
277
+ # Try akshare economic calendar (东方财富)
278
+ if _HAS_AK:
279
+ try:
280
+ df = ak.news_economic_baidu(date=datetime.now().strftime("%Y%m%d"))
281
+ if df is not None and not df.empty:
282
+ cols = [c for c in ["time","event","actual","forecast","previous","importance"]
283
+ if c in df.columns]
284
+ results = df[cols].head(30).to_dict("records") if cols else []
285
+ except Exception as e:
286
+ logger.debug("akshare calendar failed: %s", e)
287
+
288
+ # Fallback: Finnhub economic calendar
289
+ if not results and _HAS_REQUESTS:
290
+ try:
291
+ key = _get_finnhub_key()
292
+ if key:
293
+ from_dt = datetime.now().strftime("%Y-%m-%d")
294
+ to_dt = (datetime.now() + timedelta(days=days_ahead)).strftime("%Y-%m-%d")
295
+ r = _req.get(
296
+ "https://finnhub.io/api/v1/calendar/economic",
297
+ params={"from": from_dt, "to": to_dt, "token": key},
298
+ timeout=8,
299
+ )
300
+ r.raise_for_status()
301
+ results = r.json().get("economicCalendar", [])
302
+ except Exception as e:
303
+ logger.debug("Finnhub calendar failed: %s", e)
304
+
305
+ if not results:
306
+ # Static upcoming FOMC/PBOC dates as fallback
307
+ results = [
308
+ {"event": "FOMC Meeting", "importance": "HIGH",
309
+ "note": "具体日期请查阅 federalreserve.gov/monetarypolicy"},
310
+ {"event": "PBOC LPR Announcement", "importance": "HIGH",
311
+ "note": "每月 20 日前后公布"},
312
+ {"event": "US CPI Release", "importance": "HIGH",
313
+ "note": "每月第二周(周二或周三)"},
314
+ {"event": "China PMI Release", "importance": "MEDIUM",
315
+ "note": "每月最后一天(官方)/ 次月第一工作日(财新)"},
316
+ ]
317
+
318
+ return {"success": True, "events": results, "days_ahead": days_ahead,
319
+ "provider": "akshare/finnhub"}
320
+
321
+
322
+ def _get_finnhub_key() -> str:
323
+ """Read Finnhub key from env or providers.json."""
324
+ k = os.getenv("FINNHUB_API_KEY", "")
325
+ if not k:
326
+ try:
327
+ import json
328
+ p = Path.home() / ".arthera" / "providers.json"
329
+ if p.exists():
330
+ d = json.loads(p.read_text())
331
+ k = d.get("finnhub", {}).get("api_key", "") or d.get("finnhub_api_key", "")
332
+ except Exception:
333
+ pass
334
+ return k
335
+
336
+
337
+ # ── Central Bank Policy Rates ─────────────────────────────────────────────────
338
+
339
+ def get_central_bank_rates() -> dict:
340
+ """主要央行政策利率快照。"""
341
+ CB_TICKERS = {
342
+ "美联储 (Fed Funds)": "FEDFUNDS",
343
+ "欧央行 (ECB Refi)": "ECBDFR",
344
+ "英央行 (BoE Rate)": "BOERUKM",
345
+ "日央行 (BoJ Rate)": "IRSTCI01JPM156N",
346
+ }
347
+ rates = {}
348
+ for name, series in CB_TICKERS.items():
349
+ data = _fred_series(series, limit=3)
350
+ if data:
351
+ latest = data[-1]
352
+ rates[name] = latest["value"]
353
+
354
+ # PBOC LPR via akshare
355
+ if _HAS_AK:
356
+ try:
357
+ df = ak.macro_china_lpr()
358
+ if df is not None and not df.empty:
359
+ row = df.iloc[-1]
360
+ rates["中国人民银行 LPR 1Y"] = float(row.get("1年期贷款市场报价利率", row.iloc[1]))
361
+ rates["中国人民银行 LPR 5Y"] = float(row.get("5年期贷款市场报价利率", row.iloc[2]))
362
+ except Exception:
363
+ pass
364
+
365
+ if not rates:
366
+ return {"success": False, "error": "无法获取央行利率数据"}
367
+
368
+ return {"success": True, "rates": rates, "provider": "FRED+akshare"}