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
realty_data_tools.py ADDED
@@ -0,0 +1,659 @@
1
+ """
2
+ realty_data_tools.py — 不动产市场数据层
3
+ ==========================================
4
+ 数据来源:
5
+ - AKShare — 中国房价指数/房地产投资/REITs
6
+ - FRED — 美国住房数据 (Case-Shiller/新屋开工/NAHB)
7
+ - 本地计算 — 租金收益率/物业估值/资产评级
8
+
9
+ 全部函数返回 {"success": bool, ...} 统一格式。
10
+
11
+ 安装依赖(可选):
12
+ pip install akshare # 中国数据
13
+ pip install openpyxl # Excel 导出
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import logging
19
+ import os
20
+ from datetime import datetime
21
+ from pathlib import Path
22
+ from typing import Any, Dict, List, Optional
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ try:
27
+ import akshare as ak
28
+ _HAS_AK = True
29
+ except ImportError:
30
+ _HAS_AK = False
31
+
32
+ try:
33
+ import pandas as pd
34
+ _HAS_PD = True
35
+ except ImportError:
36
+ _HAS_PD = False
37
+
38
+ try:
39
+ import numpy as np
40
+ _HAS_NP = True
41
+ except ImportError:
42
+ _HAS_NP = False
43
+
44
+ try:
45
+ import requests as _req
46
+ _HAS_REQ = True
47
+ except ImportError:
48
+ _HAS_REQ = False
49
+
50
+ # ── 70城房价指数 ─────────────────────────────────────────────────────────────
51
+
52
+ # akshare 支持的城市(70城新房价格指数覆盖范围)
53
+ CN_CITIES_TIER1 = ["北京", "上海", "广州", "深圳"]
54
+ CN_CITIES_TIER2 = [
55
+ "成都", "杭州", "武汉", "重庆", "南京", "西安", "天津", "苏州",
56
+ "长沙", "郑州", "青岛", "沈阳", "宁波", "合肥", "厦门", "济南",
57
+ "东莞", "佛山", "福州", "南宁", "昆明", "贵阳", "大连", "哈尔滨",
58
+ "长春", "石家庄", "太原", "南昌", "兰州", "银川", "西宁", "乌鲁木齐",
59
+ "海口", "三亚", "珠海", "温州", "泉州", "烟台", "洛阳", "唐山",
60
+ "南通", "常州", "徐州", "扬州", "嘉兴", "金华", "绍兴",
61
+ "湖州", "台州", "芜湖", "湘潭", "株洲", "常德", "桂林", "柳州",
62
+ "汕头", "惠州", "江门", "湛江", "中山", "保定", "廊坊",
63
+ ]
64
+ CN_CITIES_ALL = CN_CITIES_TIER1 + CN_CITIES_TIER2
65
+
66
+
67
+ def get_house_price_index(city: str = "北京", city2: str = "上海") -> dict:
68
+ """
69
+ 获取中国 70 城新房 / 二手房价格指数。
70
+
71
+ 返回同比、环比数据(最近 12 个月)。
72
+ city: 主要城市(默认北京)
73
+ city2: 对比城市(默认上海)
74
+ """
75
+ if not _HAS_AK:
76
+ return {"success": False, "error": "akshare 未安装"}
77
+ try:
78
+ df = ak.macro_china_new_house_price(city_first=city, city_second=city2)
79
+ if df is None or df.empty:
80
+ return {"success": False, "error": "无数据"}
81
+
82
+ # Keep last 24 months
83
+ df = df.tail(24).copy()
84
+ df.columns = [c.strip() for c in df.columns]
85
+ records = df.to_dict("records")
86
+
87
+ # Split by city
88
+ city1_records = [r for r in records if str(r.get("城市","")) == city][-12:]
89
+ city2_records = [r for r in records if str(r.get("城市","")) == city2][-12:]
90
+
91
+ def _latest(recs):
92
+ if not recs: return {}
93
+ r = recs[-1]
94
+ return {
95
+ "date": str(r.get("日期",""))[:7],
96
+ "new_yoy": r.get("新建商品住宅价格指数-同比"),
97
+ "new_mom": r.get("新建商品住宅价格指数-环比"),
98
+ "second_yoy": r.get("二手住宅价格指数-同比"),
99
+ "second_mom": r.get("二手住宅价格指数-环比"),
100
+ }
101
+
102
+ return {
103
+ "success": True,
104
+ "city1": city,
105
+ "city2": city2,
106
+ "latest_city1": _latest(city1_records),
107
+ "latest_city2": _latest(city2_records),
108
+ "series_city1": city1_records,
109
+ "series_city2": city2_records,
110
+ "provider": "akshare_NBS",
111
+ }
112
+ except Exception as e:
113
+ return {"success": False, "error": str(e)}
114
+
115
+
116
+ def get_re_investment() -> dict:
117
+ """
118
+ 中国房地产开发投资额(月度累计同比)。
119
+ 反映房地产行业景气度。
120
+ """
121
+ if not _HAS_AK:
122
+ return {"success": False, "error": "akshare 未安装"}
123
+ try:
124
+ df = ak.macro_china_real_estate()
125
+ if df is None or df.empty:
126
+ return {"success": False, "error": "无数据"}
127
+ df = df.tail(12)
128
+ records = df.to_dict("records")
129
+ latest = records[-1] if records else {}
130
+ return {
131
+ "success": True,
132
+ "latest": latest,
133
+ "series": records,
134
+ "provider": "akshare_NBS",
135
+ }
136
+ except Exception as e:
137
+ return {"success": False, "error": str(e)}
138
+
139
+
140
+ def get_multi_city_comparison(cities: Optional[List[str]] = None) -> dict:
141
+ """
142
+ 多城市房价指数对比(同比涨跌幅热力图数据)。
143
+ 默认对比 8 个核心城市(两两调用拼合)。
144
+ """
145
+ if not _HAS_AK:
146
+ return {"success": False, "error": "akshare 未安装"}
147
+
148
+ if cities is None:
149
+ cities = ["北京", "上海", "深圳", "广州", "成都", "杭州", "武汉", "南京",
150
+ "重庆", "西安", "长沙", "天津", "苏州", "郑州", "厦门", "青岛"]
151
+
152
+ results: List[Dict] = []
153
+ # akshare requires city_first + city_second; batch by pairs
154
+ city_pairs = list(zip(cities[::2], cities[1::2]))
155
+ if len(cities) % 2 == 1:
156
+ city_pairs.append((cities[-1], cities[0])) # pair last with first
157
+
158
+ seen: set = set()
159
+ for c1, c2 in city_pairs:
160
+ try:
161
+ df = ak.macro_china_new_house_price(city_first=c1, city_second=c2)
162
+ if df is None or df.empty:
163
+ continue
164
+ df = df.tail(4)
165
+ for city in (c1, c2):
166
+ if city in seen:
167
+ continue
168
+ sub = df[df["城市"] == city]
169
+ if sub.empty:
170
+ continue
171
+ row = sub.iloc[-1]
172
+ results.append({
173
+ "city": city,
174
+ "tier": "一线" if city in CN_CITIES_TIER1 else "二线",
175
+ "date": str(row.get("日期",""))[:7],
176
+ "new_yoy": _safe_float(row.get("新建商品住宅价格指数-同比")),
177
+ "new_mom": _safe_float(row.get("新建商品住宅价格指数-环比")),
178
+ "second_yoy": _safe_float(row.get("二手住宅价格指数-同比")),
179
+ "second_mom": _safe_float(row.get("二手住宅价格指数-环比")),
180
+ })
181
+ seen.add(city)
182
+ except Exception as e:
183
+ logger.debug("City pair (%s,%s) failed: %s", c1, c2, e)
184
+
185
+ if not results:
186
+ return {"success": False, "error": "无法获取城市数据"}
187
+
188
+ # Sort by new_yoy descending
189
+ results.sort(key=lambda x: -(x.get("new_yoy") or 0))
190
+
191
+ return {
192
+ "success": True,
193
+ "cities": results,
194
+ "top_riser": results[0]["city"] if results else "",
195
+ "top_faller":results[-1]["city"] if results else "",
196
+ "provider": "akshare_NBS",
197
+ }
198
+
199
+
200
+ # ── REITs 分析 ───────────────────────────────────────────────────────────────
201
+
202
+ def get_reits_list() -> dict:
203
+ """获取中国 REITs 实时行情列表(东方财富)。"""
204
+ if not _HAS_AK:
205
+ return {"success": False, "error": "akshare 未安装"}
206
+ try:
207
+ df = ak.reits_realtime_em()
208
+ if df is None or df.empty:
209
+ return {"success": False, "error": "无 REIT 数据"}
210
+ cols = [c for c in ["代码","名称","最新价","涨跌额","涨跌幅","昨收","成交量","成交额"] if c in df.columns]
211
+ records = df[cols].to_dict("records")
212
+ return {
213
+ "success": True,
214
+ "count": len(records),
215
+ "reits": records,
216
+ "provider": "akshare_em",
217
+ }
218
+ except Exception as e:
219
+ return {"success": False, "error": str(e)}
220
+
221
+
222
+ def get_reit_analysis(code: str) -> dict:
223
+ """
224
+ 单只 REIT 深度分析:历史行情 + 估值指标。
225
+
226
+ code: REIT 代码,如 "508603" (唯品商业)
227
+ """
228
+ if not _HAS_AK:
229
+ return {"success": False, "error": "akshare 未安装"}
230
+ try:
231
+ import yfinance as yf
232
+ _HAS_YF = True
233
+ except ImportError:
234
+ _HAS_YF = False
235
+
236
+ result: Dict[str, Any] = {"success": True, "code": code}
237
+
238
+ # Historical prices
239
+ try:
240
+ df_hist = ak.reits_hist_em(symbol=code, period="daily",
241
+ start_date="20230101",
242
+ end_date=datetime.now().strftime("%Y%m%d"),
243
+ adjust="qfq")
244
+ if df_hist is not None and not df_hist.empty:
245
+ df_hist = df_hist.tail(252) # 1 year
246
+ last = df_hist.iloc[-1]
247
+ cols = df_hist.columns.tolist()
248
+ close_col = next((c for c in cols if "收盘" in c or "close" in c.lower()), cols[-1])
249
+ prices = df_hist[close_col].astype(float)
250
+ cur_price = float(last[close_col])
251
+ result["price"] = round(cur_price, 3)
252
+
253
+ # Performance
254
+ if len(prices) >= 2:
255
+ result["return_1y"] = round((prices.iloc[-1]/prices.iloc[0] - 1)*100, 2)
256
+ if len(prices) >= 20:
257
+ result["return_1m"] = round((prices.iloc[-1]/prices.iloc[-20] - 1)*100, 2)
258
+
259
+ # Volatility
260
+ if _HAS_NP and len(prices) >= 20:
261
+ rets = prices.pct_change().dropna()
262
+ result["volatility_annual"] = round(float(rets.std() * np.sqrt(252) * 100), 2)
263
+
264
+ result["history_tail"] = df_hist.tail(5).to_dict("records")
265
+ except Exception as e:
266
+ logger.debug("REIT hist failed %s: %s", code, e)
267
+
268
+ # Realtime info from list
269
+ try:
270
+ df_rt = ak.reits_realtime_em()
271
+ if df_rt is not None and not df_rt.empty:
272
+ row = df_rt[df_rt["代码"] == code]
273
+ if not row.empty:
274
+ r = row.iloc[0]
275
+ result["name"] = str(r.get("名称",""))
276
+ result["price"] = result.get("price") or _safe_float(r.get("最新价"))
277
+ result["chg_pct"] = _safe_float(r.get("涨跌幅"))
278
+ result["prev_close"] = _safe_float(r.get("昨收"))
279
+ except Exception as e:
280
+ logger.debug("REIT realtime failed: %s", e)
281
+
282
+ return result
283
+
284
+
285
+ # ── 租金收益率计算器 ──────────────────────────────────────────────────────────
286
+
287
+ def calc_rental_yield(params: dict) -> dict:
288
+ """
289
+ 物业租金收益率计算(毛/净收益率)。
290
+
291
+ 参数:
292
+ purchase_price: 购入价格(万元)
293
+ monthly_rent: 月租金(元)
294
+ annual_costs: 年维护成本(元,默认0)
295
+ tax_rate: 租金税率(小数,默认0.05)
296
+ loan_ratio: 贷款成数(0-1,默认0=全款)
297
+ loan_rate: 贷款年利率(小数,默认0.04)
298
+ loan_years: 贷款年数(默认30)
299
+ """
300
+ price_wan = float(params.get("purchase_price", 0))
301
+ monthly_rent= float(params.get("monthly_rent", 0))
302
+ annual_costs= float(params.get("annual_costs", 0))
303
+ tax_rate = float(params.get("tax_rate", 0.05))
304
+ loan_ratio = float(params.get("loan_ratio", 0))
305
+ loan_rate = float(params.get("loan_rate", 0.04))
306
+ loan_years = int(params.get("loan_years", 30))
307
+
308
+ if price_wan <= 0 or monthly_rent <= 0:
309
+ return {"success": False, "error": "purchase_price 和 monthly_rent 均为必填项"}
310
+
311
+ price_yuan = price_wan * 10000
312
+ annual_rent = monthly_rent * 12
313
+ tax_deduction = annual_rent * tax_rate
314
+ net_rent = annual_rent - tax_deduction - annual_costs
315
+
316
+ # Gross yield
317
+ gross_yield = annual_rent / price_yuan * 100
318
+
319
+ # Net yield (without leverage)
320
+ net_yield = net_rent / price_yuan * 100
321
+
322
+ # Leveraged yield (with mortgage)
323
+ equity = price_yuan * (1 - loan_ratio)
324
+ monthly_payment = 0.0
325
+ annual_interest = 0.0
326
+ if loan_ratio > 0:
327
+ loan_amount = price_yuan * loan_ratio
328
+ monthly_rate = loan_rate / 12
329
+ n = loan_years * 12
330
+ if monthly_rate > 0:
331
+ monthly_payment = loan_amount * monthly_rate * (1 + monthly_rate)**n / ((1 + monthly_rate)**n - 1)
332
+ annual_interest = monthly_payment * 12 - loan_amount / loan_years # approximate
333
+
334
+ leveraged_net_rent = net_rent - (monthly_payment * 12 if loan_ratio > 0 else 0)
335
+ leveraged_yield = (leveraged_net_rent / equity * 100) if equity > 0 else 0
336
+
337
+ # Payback period
338
+ payback_years = price_yuan / net_rent if net_rent > 0 else 999
339
+
340
+ # Cap rate (NOI / price)
341
+ noi = annual_rent - annual_costs
342
+ cap_rate = noi / price_yuan * 100
343
+
344
+ return {
345
+ "success": True,
346
+ "purchase_price_wan": price_wan,
347
+ "monthly_rent": monthly_rent,
348
+ "annual_rent": annual_rent,
349
+ "gross_yield_pct": round(gross_yield, 2),
350
+ "net_yield_pct": round(net_yield, 2),
351
+ "cap_rate_pct": round(cap_rate, 2),
352
+ "leveraged_yield_pct": round(leveraged_yield, 2) if loan_ratio > 0 else None,
353
+ "monthly_payment": round(monthly_payment, 2) if loan_ratio > 0 else None,
354
+ "payback_years": round(payback_years, 1),
355
+ "equity_invested": round(equity / 10000, 2),
356
+ "assessment": (
357
+ "优质标的" if gross_yield >= 5
358
+ else "合理收益" if gross_yield >= 3
359
+ else "收益偏低"
360
+ ),
361
+ "benchmark": "一线城市租金收益率通常 1.5-3%,二线 2-4%,商业地产 4-7%",
362
+ }
363
+
364
+
365
+ # ── 物业估值模型 ──────────────────────────────────────────────────────────────
366
+
367
+ def property_valuation(params: dict) -> dict:
368
+ """
369
+ 物业估值三合一模型:收益法 + DCF + 市场比较法。
370
+
371
+ 必填:
372
+ area_sqm: 建筑面积(平米)
373
+ monthly_rent: 当前月租金(元)
374
+ location_tier: 区位层级("tier1"/"tier2"/"tier3")
375
+
376
+ 可选:
377
+ vacancy_rate: 空置率(默认0.05)
378
+ cap_rate: 市场资本化率(默认由区位自动推算)
379
+ annual_growth: 租金年增长率(默认0.02)
380
+ discount_rate: 折现率(默认0.06)
381
+ hold_years: 持有年数(默认10)
382
+ price_per_sqm: 参考市场单价(元/m²,不填则用租金反推)
383
+ """
384
+ area = float(params.get("area_sqm", 100))
385
+ monthly_rent = float(params.get("monthly_rent", 0))
386
+ tier = str(params.get("location_tier", "tier2")).lower()
387
+ vacancy = float(params.get("vacancy_rate", 0.05))
388
+ annual_growth= float(params.get("annual_growth", 0.02))
389
+ hold_years = int(params.get("hold_years", 10))
390
+
391
+ # Tier-based defaults
392
+ _TIER_DEFAULTS = {
393
+ "tier1": {"cap_rate": 0.025, "discount_rate": 0.055, "price_range": (80000, 200000)},
394
+ "tier2": {"cap_rate": 0.04, "discount_rate": 0.065, "price_range": (20000, 60000)},
395
+ "tier3": {"cap_rate": 0.06, "discount_rate": 0.08, "price_range": (5000, 20000)},
396
+ }
397
+ defaults = _TIER_DEFAULTS.get(tier, _TIER_DEFAULTS["tier2"])
398
+ cap_rate = float(params.get("cap_rate", defaults["cap_rate"]))
399
+ discount_rate = float(params.get("discount_rate", defaults["discount_rate"]))
400
+ price_range = defaults["price_range"]
401
+
402
+ annual_rent = monthly_rent * 12
403
+ noi = annual_rent * (1 - vacancy)
404
+
405
+ # ── 1. 收益法 (Income Approach) ────────────────────────────────────────
406
+ income_value = noi / cap_rate if cap_rate > 0 else 0
407
+
408
+ # ── 2. DCF 法 ──────────────────────────────────────────────────────────
409
+ dcf_value = 0.0
410
+ if _HAS_NP:
411
+ cf = noi
412
+ for yr in range(1, hold_years + 1):
413
+ dcf_value += cf / (1 + discount_rate) ** yr
414
+ cf *= (1 + annual_growth)
415
+ # Terminal value (Gordon growth)
416
+ terminal_growth = min(annual_growth, 0.015)
417
+ terminal_cf = noi * (1 + annual_growth) ** hold_years
418
+ terminal_val = terminal_cf / (discount_rate - terminal_growth)
419
+ dcf_value += terminal_val / (1 + discount_rate) ** hold_years
420
+ else:
421
+ dcf_value = income_value # fallback
422
+
423
+ # ── 3. 市场比较法 (Market Comparable) ─────────────────────────────────
424
+ ref_price_per_sqm = float(params.get("price_per_sqm", 0))
425
+ if ref_price_per_sqm <= 0:
426
+ # Derive from rent using typical rent-to-price ratio
427
+ rent_per_sqm_monthly = monthly_rent / area if area > 0 else 0
428
+ # Typical P/R ratio: 300-600 months for residential, 150-250 for commercial
429
+ ratio = 400 if tier == "tier1" else 300 if tier == "tier2" else 200
430
+ ref_price_per_sqm = rent_per_sqm_monthly * ratio
431
+
432
+ market_value = ref_price_per_sqm * area
433
+
434
+ # ── Weighted average ───────────────────────────────────────────────────
435
+ weights = {"income": 0.4, "dcf": 0.4, "market": 0.2}
436
+ blended = (income_value * weights["income"] +
437
+ dcf_value * weights["dcf"] +
438
+ market_value * weights["market"])
439
+
440
+ # Price range from market benchmarks
441
+ lo = price_range[0] * area
442
+ hi = price_range[1] * area
443
+
444
+ return {
445
+ "success": True,
446
+ "area_sqm": area,
447
+ "monthly_rent": monthly_rent,
448
+ "noi_annual": round(noi, 0),
449
+ "income_approach": round(income_value / 10000, 2), # 万元
450
+ "dcf_approach": round(dcf_value / 10000, 2),
451
+ "market_approach": round(market_value / 10000, 2),
452
+ "blended_value_wan": round(blended / 10000, 2),
453
+ "market_range_wan": [round(lo/10000, 0), round(hi/10000, 0)],
454
+ "price_per_sqm": round(blended / area, 0) if area > 0 else 0,
455
+ "gross_yield_pct": round(annual_rent / blended * 100, 2) if blended > 0 else 0,
456
+ "cap_rate_used": round(cap_rate * 100, 2),
457
+ "discount_rate_used": round(discount_rate * 100, 2),
458
+ "hold_years": hold_years,
459
+ "verdict": (
460
+ "价值被低估" if market_value < blended * 0.85
461
+ else "价值被高估" if market_value > blended * 1.15
462
+ else "定价合理"
463
+ ),
464
+ }
465
+
466
+
467
+ # ── 资产综合评分 ─────────────────────────────────────────────────────────────
468
+
469
+ def asset_location_score(params: dict) -> dict:
470
+ """
471
+ 资产区位 + 业态潜力综合评分(0-100)。
472
+ 结合区位因素、空间条件、业态适配度生成量化评分。
473
+
474
+ 参数:
475
+ city: 城市名称
476
+ district: 区域/商圈
477
+ area_sqm: 面积(平米)
478
+ floor: 楼层
479
+ foot_traffic: 客流量评估 ("high"/"medium"/"low")
480
+ competition: 周边竞争 ("high"/"medium"/"low")
481
+ renovation_allowed: 是否允许改造 (bool)
482
+ open_fire_allowed: 是否允许明火 (bool)
483
+ vacant_days: 空置天数
484
+ expected_rent: 期望租金(元/月)
485
+ """
486
+ city = str(params.get("city",""))
487
+ area = float(params.get("area_sqm", 100))
488
+ floor = int(params.get("floor", 1))
489
+ traffic = str(params.get("foot_traffic", "medium")).lower()
490
+ competition = str(params.get("competition", "medium")).lower()
491
+ reno_ok = bool(params.get("renovation_allowed", True))
492
+ fire_ok = bool(params.get("open_fire_allowed", False))
493
+ vacant_days = int(params.get("vacant_days", 0))
494
+ exp_rent = float(params.get("expected_rent", 0))
495
+
496
+ score = 50 # base
497
+ breakdown = {}
498
+
499
+ # 区位得分 (0-30)
500
+ city_bonus = 30 if city in CN_CITIES_TIER1 else 20 if city in CN_CITIES_TIER2 else 10
501
+ score += city_bonus - 20 # normalize around 50
502
+ breakdown["区位城市"] = f"{city_bonus - 20:+d}"
503
+
504
+ # 客流量 (0-15)
505
+ traffic_score = {"high": 15, "medium": 8, "low": 2}.get(traffic, 8)
506
+ score += traffic_score - 8
507
+ breakdown["客流量"] = f"{traffic_score - 8:+d}"
508
+
509
+ # 楼层因素 (0-10)
510
+ floor_score = 10 if floor == 1 else 6 if floor <= 3 else 2
511
+ score += floor_score - 5
512
+ breakdown["楼层"] = f"{floor_score - 5:+d}"
513
+
514
+ # 面积适配 (0-10) — 50-300m² 最优
515
+ area_score = 10 if 50 <= area <= 300 else 5 if area < 50 else 7
516
+ score += area_score - 5
517
+ breakdown["面积适配"] = f"{area_score - 5:+d}"
518
+
519
+ # 竞争强度(负分)
520
+ comp_penalty = {"high": -8, "medium": -3, "low": 0}.get(competition, -3)
521
+ score += comp_penalty
522
+ breakdown["竞争环境"] = f"{comp_penalty:+d}"
523
+
524
+ # 改造灵活性
525
+ if reno_ok:
526
+ score += 5; breakdown["可改造"] = "+5"
527
+ if fire_ok:
528
+ score += 3; breakdown["允许明火"] = "+3"
529
+
530
+ # 空置惩罚
531
+ if vacant_days > 180:
532
+ score -= 8; breakdown["空置惩罚"] = "-8"
533
+ elif vacant_days > 90:
534
+ score -= 3; breakdown["空置惩罚"] = "-3"
535
+
536
+ # 租金合理性(与城市基准对比)
537
+ if exp_rent > 0 and area > 0:
538
+ per_sqm = exp_rent / area
539
+ benchmarks = {"tier1": 300, "tier2": 120, "tier3": 50}
540
+ tier = "tier1" if city in CN_CITIES_TIER1 else "tier2" if city in CN_CITIES_TIER2 else "tier3"
541
+ bench = benchmarks[tier]
542
+ if per_sqm < bench * 0.7:
543
+ score += 5; breakdown["低于市场租金"] = "+5"
544
+ elif per_sqm > bench * 1.3:
545
+ score -= 5; breakdown["高于市场租金"] = "-5"
546
+
547
+ score = max(0, min(100, score))
548
+
549
+ if score >= 75:
550
+ rating = "A级 — 优质资产"
551
+ elif score >= 60:
552
+ rating = "B级 — 良好资产"
553
+ elif score >= 45:
554
+ rating = "C级 — 一般资产"
555
+ else:
556
+ rating = "D级 — 需改善"
557
+
558
+ # Suitable business types based on conditions
559
+ suitable = []
560
+ if fire_ok and area >= 80:
561
+ suitable.extend(["餐饮/火锅/烤肉"])
562
+ if traffic == "high" and area <= 150:
563
+ suitable.extend(["连锁零售/便利店/奶茶"])
564
+ if reno_ok and area >= 200:
565
+ suitable.extend(["健身房/儿童乐园/培训机构"])
566
+ if floor == 1 and area >= 100:
567
+ suitable.extend(["美容美发/洗车/宠物店"])
568
+ if not suitable:
569
+ suitable = ["轻餐饮", "服务类商铺"]
570
+
571
+ return {
572
+ "success": True,
573
+ "score": score,
574
+ "rating": rating,
575
+ "breakdown": breakdown,
576
+ "suitable_businesses": list(set(suitable))[:5],
577
+ "city": city,
578
+ "area_sqm": area,
579
+ }
580
+
581
+
582
+ # ── 美国住房数据 (FRED) ──────────────────────────────────────────────────────
583
+
584
+ def get_us_housing_data() -> dict:
585
+ """
586
+ 美国住房市场数据:
587
+ - 新屋开工 (HOUST)
588
+ - NAHB 建筑商信心指数
589
+ - S&P/Case-Shiller 20城房价指数 (SPCS20RSA)
590
+ - 30年固定按揭利率 (MORTGAGE30US)
591
+ """
592
+ from macro_tools import _fred_series # reuse existing FRED helper
593
+
594
+ indicators = {
595
+ "housing_starts": ("HOUST", "新屋开工(千套,季调年化)"),
596
+ "nahb_index": ("NAHB REALTORS", "NAHB建筑商信心指数"),
597
+ "case_shiller_20": ("SPCS20RSA", "Case-Shiller 20城房价指数"),
598
+ "mortgage_30y": ("MORTGAGE30US", "30年固定按揭利率(%)"),
599
+ "existing_home_sales":("EXHOSLUSM495S","成屋销售(季调年化)"),
600
+ }
601
+
602
+ # Fallback NAHB via yfinance-equivalent approximation
603
+ _FRED_MAP = {
604
+ "housing_starts": "HOUST",
605
+ "case_shiller_20": "SPCS20RSA",
606
+ "mortgage_30y": "MORTGAGE30US",
607
+ "existing_home_sales":"EXHOSLUSM495S",
608
+ }
609
+
610
+ results = {}
611
+ for key, series_id in _FRED_MAP.items():
612
+ data = _fred_series(series_id, limit=12)
613
+ if data:
614
+ results[key] = {
615
+ "label": indicators[key][1],
616
+ "latest": data[-1],
617
+ "series": data[-6:],
618
+ }
619
+
620
+ # NAHB via hardcoded label (no clean FRED key)
621
+ nahb_data = _fred_series("NHSUSSPT", limit=12)
622
+ if nahb_data:
623
+ results["nahb_index"] = {
624
+ "label": "NAHB建筑商信心指数 (>50=乐观)",
625
+ "latest": nahb_data[-1],
626
+ "series": nahb_data[-6:],
627
+ }
628
+
629
+ if not results:
630
+ return {"success": False, "error": "FRED 数据获取失败"}
631
+
632
+ # Market assessment
633
+ mortgage = (results.get("mortgage_30y", {}).get("latest") or {}).get("value") or 0
634
+ cs_yoy = None # Case-Shiller is level index, need to compute YoY separately
635
+
636
+ assessment = []
637
+ if mortgage > 7:
638
+ assessment.append("按揭利率偏高(>7%),购房负担较重")
639
+ elif mortgage < 4:
640
+ assessment.append("按揭利率较低(<4%),购房环境宽松")
641
+ else:
642
+ assessment.append(f"按揭利率 {mortgage:.2f}%,属正常区间")
643
+
644
+ return {
645
+ "success": True,
646
+ "country": "US",
647
+ "data": results,
648
+ "assessment": assessment,
649
+ "provider": "FRED",
650
+ }
651
+
652
+
653
+ # ── Helper ────────────────────────────────────────────────────────────────────
654
+
655
+ def _safe_float(val) -> Optional[float]:
656
+ try:
657
+ return float(val) if val is not None and str(val) not in ("nan","None","") else None
658
+ except (ValueError, TypeError):
659
+ return None