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
@@ -0,0 +1,1578 @@
1
+ # Auto-extracted from aria_cli.py — shared market-detection constants & helpers.
2
+ # Multiple handler modules import from here to avoid circular dependencies.
3
+
4
+ import re as _re_sym
5
+
6
+ try:
7
+ from apps.cli.market_universe import (
8
+ looks_like_unresolved_market_name as _looks_like_unresolved_market_name,
9
+ resolve_market_mentions as _resolve_market_mentions,
10
+ resolve_market_symbol as _resolve_market_symbol,
11
+ )
12
+ except Exception:
13
+ def _resolve_market_symbol(_message: str) -> str:
14
+ return ""
15
+ def _resolve_market_mentions(_message: str, limit: int = 6):
16
+ return []
17
+ def _looks_like_unresolved_market_name(_message: str) -> bool:
18
+ return False
19
+
20
+ # Regex to find stock symbols in a message: e.g. "AAPL", "苹果AAPL", "TSLA股票", "BTC"
21
+ _STOCK_PATTERN = _re_sym.compile(
22
+ r'\b([A-Z]{1,5}(?:\.(?:HK|SH|SZ))?)(?:\s*(?:股票|股价|价格|现在|今天|行情|涨跌))?\b'
23
+ r'|(?:(?:苹果|特斯拉|英伟达|谷歌|亚马逊|微软|腾讯|阿里|百度|字节)).*?(\b[A-Z]{2,5}\b)',
24
+ _re_sym.UNICODE
25
+ )
26
+ _CRYPTO_WORDS = {"比特币":"BTC","以太坊":"ETH","狗狗币":"DOGE","索拉纳":"SOL","BTC":"BTC","ETH":"ETH"}
27
+
28
+ _COMPANY_TO_TICKER = {
29
+ # ══════════════════════════════════════════════════════════════════════
30
+ # A股 — 银行
31
+ # ══════════════════════════════════════════════════════════════════════
32
+ "工商银行": "601398", "工行": "601398",
33
+ "农业银行": "601288", "农行": "601288",
34
+ "中国银行": "601988", "中行": "601988",
35
+ "建设银行": "601939", "建行": "601939",
36
+ "交通银行": "601328", "交行": "601328",
37
+ "招商银行": "600036", "招行": "600036",
38
+ "兴业银行": "601166", "兴业": "601166",
39
+ "浦发银行": "600000", "浦发": "600000",
40
+ "民生银行": "600016", "民生": "600016",
41
+ "平安银行": "000001",
42
+ "光大银行": "601818",
43
+ "华夏银行": "600015",
44
+ "北京银行": "601169",
45
+ "南京银行": "601009",
46
+ "宁波银行": "002142",
47
+ "成都银行": "601838",
48
+ "苏州银行": "002966",
49
+ "江苏银行": "600919",
50
+ "上海银行": "601229",
51
+ "杭州银行": "600926",
52
+ "长沙银行": "601577",
53
+ "重庆银行": "601963",
54
+ "贵阳银行": "601997",
55
+ "郑州银行": "002936",
56
+ "青岛银行": "002948",
57
+ "西安银行": "600928",
58
+ "邮储银行": "601658",
59
+ # ══════════════════════════════════════════════════════════════════════
60
+ # A股 — 保险
61
+ # ══════════════════════════════════════════════════════════════════════
62
+ "中国平安": "601318", "平安": "601318",
63
+ "中国人寿": "601628", "人寿": "601628",
64
+ "中国太保": "601601", "太保": "601601",
65
+ "新华保险": "601336", "新华": "601336",
66
+ "中国人保": "601319", "人保": "601319",
67
+ "中国财险": "02328.HK",
68
+ # ══════════════════════════════════════════════════════════════════════
69
+ # A股 — 证券
70
+ # ══════════════════════════════════════════════════════════════════════
71
+ "中信证券": "600030", "中信": "600030",
72
+ "华泰证券": "601688", "华泰": "601688",
73
+ "国泰君安": "601211", "君安": "601211",
74
+ "海通证券": "600837", "海通": "600837",
75
+ "广发证券": "000776", "广发": "000776",
76
+ "东方证券": "600958",
77
+ "中国银河": "601881", "银河证券": "601881",
78
+ "招商证券": "600999",
79
+ "申万宏源": "000166",
80
+ "兴业证券": "601377",
81
+ "东北证券": "000686",
82
+ "方正证券": "601901",
83
+ "光大证券": "601788",
84
+ "国信证券": "002736",
85
+ "中金公司": "601995",
86
+ "东方财富": "300059",
87
+ # ══════════════════════════════════════════════════════════════════════
88
+ # A股 — 能源 / 石油 / 煤炭 / 电力
89
+ # ══════════════════════════════════════════════════════════════════════
90
+ "中国石油": "601857", "中石油": "601857",
91
+ "中国石化": "600028", "中石化": "600028",
92
+ "中国海油": "600938", "海油": "600938",
93
+ "中国神华": "601088", "神华": "601088",
94
+ "中煤能源": "601898",
95
+ "兖矿能源": "600188",
96
+ "陕西煤业": "601225",
97
+ "山西焦煤": "000983",
98
+ "淮北矿业": "600985",
99
+ "平煤股份": "601666",
100
+ "冀中能源": "000937",
101
+ "华能国际": "600011",
102
+ "大唐发电": "601991",
103
+ "华电国际": "600027",
104
+ "国电电力": "600795",
105
+ "三峡能源": "600905",
106
+ "长江电力": "600900", "长电": "600900",
107
+ "国投电力": "600886",
108
+ "华润电力": "0836.HK",
109
+ # ══════════════════════════════════════════════════════════════════════
110
+ # A股 — 矿业 / 金属 / 有色(关键补充,修复江西铜业误识别)
111
+ # ══════════════════════════════════════════════════════════════════════
112
+ "江西铜业": "600362", "江铜": "600362", # ← 关键修复
113
+ "铜陵有色": "000630",
114
+ "云南铜业": "000878",
115
+ "中国铝业": "601600", "中铝": "601600",
116
+ "云铝股份": "000807",
117
+ "神火股份": "000933",
118
+ "洛阳钼业": "603993", "洛钼": "603993",
119
+ "紫金矿业": "601899", "紫金": "601899",
120
+ "赣锋锂业": "002460", "赣锋": "002460",
121
+ "天齐锂业": "002466", "天齐": "002466",
122
+ "藏格矿业": "000408",
123
+ "西部矿业": "601168",
124
+ "中国黄金": "600916",
125
+ "山东黄金": "600547",
126
+ "招金矿业": "1818.HK",
127
+ "华友钴业": "603799",
128
+ "格林美": "002340",
129
+ "寒锐钴业": "300618",
130
+ "盛屯矿业": "600711",
131
+ "厦门钨业": "600549",
132
+ "中国稀土": "000831",
133
+ "北方稀土": "600111",
134
+ "盛和资源": "600392",
135
+ "金钼股份": "601958",
136
+ "中国宝武": "600018", # 宝钢股份
137
+ "宝钢股份": "600018",
138
+ "鞍钢股份": "000898",
139
+ "首钢股份": "000959",
140
+ "华菱钢铁": "000932",
141
+ "方大特钢": "600507",
142
+ "马钢股份": "600808",
143
+ # ══════════════════════════════════════════════════════════════════════
144
+ # A股 — 新能源 / 光伏 / 储能
145
+ # ══════════════════════════════════════════════════════════════════════
146
+ "宁德时代": "300750", "宁德": "300750", "CATL": "300750",
147
+ "比亚迪": "002594", "BYD": "002594",
148
+ "隆基绿能": "601012", "隆基": "601012",
149
+ "阳光电源": "300274",
150
+ "通威股份": "600438", "通威": "600438",
151
+ "晶澳科技": "002459",
152
+ "天合光能": "688599",
153
+ "晶科能源": "688223",
154
+ "固德威": "688390",
155
+ "亿纬锂能": "300014",
156
+ "璞泰来": "603659",
157
+ "恩捷股份": "002812",
158
+ "星源材质": "300568",
159
+ "科达利": "002850",
160
+ "天赐材料": "002709",
161
+ "新宙邦": "300037",
162
+ "德方纳米": "300769",
163
+ "当升科技": "300073",
164
+ "容百科技": "688005",
165
+ "振德医疗": "603301",
166
+ "孚能科技": "688567",
167
+ "国轩高科": "002074",
168
+ "欣旺达": "300207",
169
+ "派能科技": "688063",
170
+ "中创新航": "3931.HK",
171
+ "蜂巢能源": "未上市",
172
+ "海辰储能": "未上市",
173
+ # ══════════════════════════════════════════════════════════════════════
174
+ # A股 — 汽车 / 零部件
175
+ # ══════════════════════════════════════════════════════════════════════
176
+ "上汽集团": "600104", "上汽": "600104",
177
+ "广汽集团": "601238", "广汽": "601238",
178
+ "长城汽车": "601633", "长城": "601633",
179
+ "长安汽车": "000625", "长安": "000625",
180
+ "东风汽车": "600006", "东风": "600006",
181
+ "江淮汽车": "600418",
182
+ "北汽蓝谷": "600733",
183
+ "宇通客车": "600066",
184
+ "中通客车": "000957",
185
+ "福田汽车": "600166",
186
+ "潍柴动力": "000338",
187
+ "汇川技术": "300124", "汇川": "300124",
188
+ "三花智控": "002050",
189
+ "拓普集团": "601689",
190
+ "伯特利": "603596",
191
+ "华域汽车": "600741",
192
+ "均胜电子": "600699",
193
+ "德赛西威": "002920",
194
+ "星宇股份": "601799",
195
+ "继峰股份": "603997",
196
+ "岱美股份": "603730",
197
+ # ══════════════════════════════════════════════════════════════════════
198
+ # A股 — 半导体 / 芯片 / 科技
199
+ # ══════════════════════════════════════════════════════════════════════
200
+ "中芯国际": "688981", "中芯": "688981",
201
+ "海光信息": "688041",
202
+ "寒武纪": "688256",
203
+ "韦尔股份": "603501",
204
+ "澜起科技": "688008",
205
+ "中科曙光": "603019",
206
+ "北方华创": "002371",
207
+ "中微公司": "688012",
208
+ "华润微": "688396",
209
+ "士兰微": "600460",
210
+ "斯达半导": "603290",
211
+ "扬杰科技": "300373",
212
+ "宏微科技": "688711",
213
+ "芯原股份": "688521",
214
+ "晶晨股份": "688099",
215
+ "卓胜微": "300782",
216
+ "国芯科技": "688262",
217
+ "瑞芯微": "603893",
218
+ "全志科技": "300458",
219
+ "兆易创新": "603986",
220
+ "北京君正": "300223",
221
+ "紫光国微": "002049",
222
+ "华大九天": "301269",
223
+ "概伦电子": "688206",
224
+ "广立微": "301095",
225
+ "沪硅产业": "688126",
226
+ "有研新材": "600206",
227
+ "神工股份": "688233",
228
+ "中环股份": "002129",
229
+ "捷捷微电": "300623",
230
+ "华峰测控": "688200",
231
+ "长川科技": "300604",
232
+ "精测电子": "300567",
233
+ "中科飞测": "688361",
234
+ "拓荆科技": "688072",
235
+ "芯源微": "688037",
236
+ "盛美上海": "688082",
237
+ "至纯科技": "603690",
238
+ "华亚智能": "003043",
239
+ "科大讯飞": "002230", "讯飞": "002230",
240
+ "海康威视": "002415", "海康": "002415",
241
+ "大华股份": "002236", "大华": "002236",
242
+ "工业富联": "601138",
243
+ "立讯精密": "002475", "立讯": "002475",
244
+ "歌尔股份": "002241", "歌尔": "002241",
245
+ "蓝思科技": "300433",
246
+ "三安光电": "600703",
247
+ "木林森": "002745",
248
+ "中国软件": "600536",
249
+ "浪潮信息": "000977", "浪潮": "000977",
250
+ "中兴通讯": "000063", "中兴": "000063",
251
+ "烽火通信": "600498",
252
+ "中国联通": "600050", "联通": "600050",
253
+ "中国电信": "601728", "电信": "601728",
254
+ "中国移动": "600941", "移动": "600941",
255
+ "京东方": "000725", "BOE": "000725",
256
+ "TCL科技": "000100", "TCL": "000100",
257
+ "视源股份": "002841",
258
+ "闻泰科技": "600745",
259
+ "鸿海精密": "2317.TW",
260
+ # ══════════════════════════════════════════════════════════════════════
261
+ # A股 — 消费 / 食品饮料 / 白酒
262
+ # ══════════════════════════════════════════════════════════════════════
263
+ "贵州茅台": "600519", "茅台": "600519",
264
+ "五粮液": "000858",
265
+ "泸州老窖": "000568", "老窖": "000568",
266
+ "洋河股份": "002304", "洋河": "002304",
267
+ "山西汾酒": "600809", "汾酒": "600809",
268
+ "古井贡酒": "000596", "古井": "000596",
269
+ "今世缘": "603369",
270
+ "酒鬼酒": "000799",
271
+ "迎驾贡酒": "603198",
272
+ "舍得酒业": "600702",
273
+ "岩石股份": "600696",
274
+ "牧原食品": "002714", "牧原": "002714",
275
+ "温氏股份": "300498", "温氏": "300498",
276
+ "新希望": "000876",
277
+ "天邦食品": "002124",
278
+ "双汇发展": "000895", "双汇": "000895",
279
+ "海天味业": "603288", "海天": "603288",
280
+ "中炬高新": "600872",
281
+ "恒顺醋业": "600305",
282
+ "绝味食品": "603517",
283
+ "安井食品": "603345",
284
+ "千味央厨": "001215",
285
+ "桃李面包": "603866",
286
+ "伊利股份": "600887", "伊利": "600887",
287
+ "蒙牛乳业": "2319.HK", "蒙牛": "2319.HK",
288
+ "光明乳业": "600597",
289
+ "三元股份": "600429",
290
+ "味知香": "605089",
291
+ "安琪酵母": "600298",
292
+ "中粮糖业": "600737",
293
+ "东鹏饮料": "605499",
294
+ "农夫山泉": "9633.HK",
295
+ "百润股份": "002568",
296
+ "华润啤酒": "0291.HK",
297
+ "青岛啤酒": "600600", "青啤": "600600",
298
+ "燕京啤酒": "000729",
299
+ "重庆啤酒": "600132",
300
+ "海底捞": "6862.HK",
301
+ "九毛九": "9922.HK",
302
+ "百胜中国": "9987.HK",
303
+ "瑞幸咖啡": "LKNCY",
304
+ "锅圈食品": "2517.HK",
305
+ # ══════════════════════════════════════════════════════════════════════
306
+ # A股 — 医疗 / 医药 / 生物
307
+ # ══════════════════════════════════════════════════════════════════════
308
+ "恒瑞医药": "600276", "恒瑞": "600276",
309
+ "迈瑞医疗": "300760", "迈瑞": "300760",
310
+ "爱尔眼科": "300015", "爱尔": "300015",
311
+ "药明康德": "603259", "药明": "603259",
312
+ "片仔癀": "600436",
313
+ "云南白药": "000538",
314
+ "同仁堂": "600085",
315
+ "华润三九": "000999",
316
+ "东阿阿胶": "000423",
317
+ "华东医药": "000963",
318
+ "复星医药": "600196", "复星": "600196",
319
+ "上海医药": "601607",
320
+ "国药股份": "600511",
321
+ "国药一致": "000028",
322
+ "康龙化成": "300759",
323
+ "昭衍新药": "603127",
324
+ "泰格医药": "300347",
325
+ "凯莱英": "002821",
326
+ "九洲药业": "603456",
327
+ "博腾股份": "300363",
328
+ "美迪西": "688202",
329
+ "阳光诺和": "688621",
330
+ "方达控股": "1521.HK",
331
+ "普蕊斯": "301257",
332
+ "迪安诊断": "300244",
333
+ "金域医学": "603882",
334
+ "艾德生物": "300685",
335
+ "贝瑞基因": "000710",
336
+ "华大基因": "300676",
337
+ "华大智造": "688114",
338
+ "诺唯赞": "688105",
339
+ "奥泰生物": "688606",
340
+ "乐普医疗": "300003",
341
+ "心脉医疗": "688016",
342
+ "微创医疗": "0853.HK",
343
+ "联影医疗": "688271",
344
+ "惠泰医疗": "688617",
345
+ "博迈科": "300404",
346
+ "振德医疗": "603301",
347
+ "蓝帆医疗": "002382",
348
+ "康泰生物": "300601",
349
+ "智飞生物": "300122",
350
+ "万泰生物": "603392",
351
+ "沃森生物": "300142",
352
+ "华兰生物": "002007",
353
+ "天坛生物": "600161",
354
+ "博雅生物": "300294",
355
+ "信立泰": "002294",
356
+ "科伦药业": "002422",
357
+ "扬子江药业": "未上市",
358
+ # ══════════════════════════════════════════════════════════════════════
359
+ # A股 — 房地产
360
+ # ══════════════════════════════════════════════════════════════════════
361
+ "万科A": "000002", "万科": "000002",
362
+ "保利发展": "600048", "保利": "600048",
363
+ "金地集团": "600383", "金地": "600383",
364
+ "招商蛇口": "001979", "招蛇": "001979",
365
+ "华发股份": "600325",
366
+ "绿地控股": "600606", "绿地": "600606",
367
+ "中南建设": "000961",
368
+ "荣盛发展": "002146",
369
+ "新城控股": "601155",
370
+ "滨江集团": "002244",
371
+ "龙湖集团": "0960.HK", "龙湖": "0960.HK",
372
+ "碧桂园": "2007.HK",
373
+ "华润置地": "1109.HK",
374
+ "恒大集团": "3333.HK", "恒大": "3333.HK",
375
+ "旭辉控股": "0884.HK",
376
+ "融创中国": "1918.HK",
377
+ "美的置业": "3990.HK",
378
+ # ══════════════════════════════════════════════════════════════════════
379
+ # A股 — 化工 / 材料
380
+ # ══════════════════════════════════════════════════════════════════════
381
+ "万华化学": "600309", "万华": "600309",
382
+ "华鲁恒升": "600426",
383
+ "鲁西化工": "000830",
384
+ "扬农化工": "600486",
385
+ "沿海能源": "001213",
386
+ "恒逸石化": "000703",
387
+ "荣盛石化": "002493",
388
+ "东方盛虹": "000301",
389
+ "桐昆股份": "601233",
390
+ "新凤鸣": "603225",
391
+ "恒力石化": "600346",
392
+ "卫星化学": "002648",
393
+ "龙佰集团": "002601",
394
+ "中国巨石": "600176",
395
+ "中材科技": "002080",
396
+ "科顺股份": "300737",
397
+ "东方雨虹": "002271", "雨虹": "002271",
398
+ "三棵树": "603737",
399
+ "回天新材": "300041",
400
+ # ══════════════════════════════════════════════════════════════════════
401
+ # A股 — 机械 / 工业
402
+ # ══════════════════════════════════════════════════════════════════════
403
+ "三一重工": "600031", "三一": "600031",
404
+ "中联重科": "000157", "中联": "000157",
405
+ "徐工机械": "000425", "徐工": "000425",
406
+ "柳工": "000528",
407
+ "上海机电": "600835",
408
+ "海天精工": "601882",
409
+ "科德数控": "688305",
410
+ "创世纪": "300083",
411
+ "华中数控": "300161",
412
+ "秦川机床": "000837",
413
+ "欣旺达": "300207",
414
+ "宏发股份": "600885",
415
+ "正泰电器": "601877",
416
+ "良信股份": "002706",
417
+ "天正电气": "605066",
418
+ "思源电气": "002028",
419
+ "许继电气": "000400",
420
+ "平高电气": "600312",
421
+ # ══════════════════════════════════════════════════════════════════════
422
+ # A股 — 零售 / 商业
423
+ # ══════════════════════════════════════════════════════════════════════
424
+ "中国中免": "601888", "中免": "601888",
425
+ "王府井": "600859",
426
+ "百联股份": "600827",
427
+ "永辉超市": "601933",
428
+ "家家悦": "603708",
429
+ "步步高": "002251",
430
+ "名创优品": "MNSO",
431
+ "泡泡玛特": "9992.HK",
432
+ # ══════════════════════════════════════════════════════════════════════
433
+ # A股 — 物流 / 快递
434
+ # ══════════════════════════════════════════════════════════════════════
435
+ "顺丰控股": "002352", "顺丰": "002352",
436
+ "圆通速递": "600233", "圆通": "600233",
437
+ "申通快递": "002468", "申通": "002468",
438
+ "韵达股份": "002120", "韵达": "002120",
439
+ "中通快递": "ZTO",
440
+ "中远海控": "601919",
441
+ "招商轮船": "601872",
442
+ "中国外运": "601598",
443
+ "白云机场": "600004",
444
+ "上海机场": "600009",
445
+ "首都机场": "0694.HK",
446
+ "深圳机场": "000089",
447
+ "厦门空港": "600897",
448
+ # ══════════════════════════════════════════════════════════════════════
449
+ # A股 — 传媒 / 游戏 / 教育
450
+ # ══════════════════════════════════════════════════════════════════════
451
+ "三七互娱": "002555",
452
+ "完美世界": "002624",
453
+ "游族网络": "002174",
454
+ "吉比特": "603444",
455
+ "恺英网络": "002517",
456
+ "中青宝": "300052",
457
+ "芒果超媒": "300413", "芒果": "300413",
458
+ "华策影视": "300133",
459
+ "光线传媒": "300251",
460
+ "中文传媒": "600373",
461
+ "分众传媒": "002027",
462
+ "中公教育": "002607",
463
+ # ══════════════════════════════════════════════════════════════════════
464
+ # 港股 — 主要蓝筹
465
+ # ══════════════════════════════════════════════════════════════════════
466
+ "腾讯控股": "0700.HK", "腾讯": "0700.HK",
467
+ "阿里巴巴港股": "9988.HK", "阿里港股": "9988.HK",
468
+ "小米集团": "1810.HK", "小米": "1810.HK",
469
+ "美团": "3690.HK", "美团点评": "3690.HK",
470
+ "快手": "1024.HK",
471
+ "百度港股": "9888.HK",
472
+ "京东港股": "9618.HK",
473
+ "网易港股": "9999.HK",
474
+ "哔哩哔哩港股": "9626.HK", "B站港股": "9626.HK",
475
+ "携程港股": "9961.HK",
476
+ "汇丰控股": "0005.HK", "汇丰": "0005.HK", "HSBC港股": "0005.HK",
477
+ "友邦保险": "1299.HK", "友邦": "1299.HK",
478
+ "中国人寿港股": "2628.HK",
479
+ "中国平安港股": "2318.HK",
480
+ "中国太平": "0966.HK",
481
+ "太古地产": "1972.HK",
482
+ "新鸿基地产": "0016.HK", "新鸿基": "0016.HK",
483
+ "长和": "0001.HK",
484
+ "长实集团": "1113.HK",
485
+ "恒基地产": "0012.HK",
486
+ "九龙仓集团": "0004.HK",
487
+ "李宁": "2331.HK",
488
+ "安踏体育": "2020.HK", "安踏": "2020.HK",
489
+ "申洲国际": "2313.HK",
490
+ "华润啤酒港股": "0291.HK",
491
+ "中国移动港股": "0941.HK",
492
+ "中国联通港股": "0762.HK",
493
+ "中国电信港股": "0728.HK",
494
+ "中国铁塔": "0788.HK",
495
+ "中国建筑": "3311.HK",
496
+ "中国中铁": "0390.HK",
497
+ "中国交建": "1800.HK",
498
+ "吉利汽车": "0175.HK", "吉利": "0175.HK",
499
+ "比亚迪港股": "1211.HK",
500
+ "长城汽车港股": "2333.HK",
501
+ "广汽集团港股": "2238.HK",
502
+ "东风集团": "0489.HK",
503
+ "药明生物": "2269.HK",
504
+ "信达生物": "1801.HK",
505
+ "百济神州港股": "6160.HK",
506
+ "君实生物": "1877.HK",
507
+ "荣昌生物": "9995.HK",
508
+ "康方生物": "9926.HK",
509
+ "绿叶制药": "2186.HK",
510
+ "中生制药": "1177.HK",
511
+ "石药集团": "1093.HK",
512
+ "联想集团": "0992.HK", "联想": "0992.HK",
513
+ "瑞声科技": "2018.HK",
514
+ "舜宇光学": "2382.HK",
515
+ "中芯国际港股": "0981.HK",
516
+ "滔搏": "6110.HK",
517
+ "泡泡玛特港股": "9992.HK",
518
+ "名创优品港股": "9896.HK",
519
+ "周大福": "1929.HK",
520
+ "六福集团": "0590.HK",
521
+ "周生生": "0116.HK",
522
+ "莎莎国际": "0178.HK",
523
+ "海丰国际": "1308.HK",
524
+ # ══════════════════════════════════════════════════════════════════════
525
+ # 中概股(美股上市)
526
+ # ══════════════════════════════════════════════════════════════════════
527
+ "阿里巴巴": "BABA", "阿里": "BABA",
528
+ "百度": "BIDU",
529
+ "京东": "JD",
530
+ "拼多多": "PDD",
531
+ "网易": "NTES",
532
+ "哔哩哔哩": "BILI", "B站": "BILI",
533
+ "携程": "TCOM",
534
+ "微博": "WB", "新浪微博": "WB",
535
+ "虎牙直播": "HUYA",
536
+ "斗鱼": "DOYU",
537
+ "爱奇艺": "IQ",
538
+ "好未来": "TAL",
539
+ "新东方": "EDU",
540
+ "高途": "GOTU",
541
+ "中通快递美股": "ZTO",
542
+ "唯品会": "VIPS",
543
+ "满帮": "YMM",
544
+ "贝壳": "BEKE",
545
+ "富途控股": "FUTU",
546
+ "老虎证券": "TIGR",
547
+ "小鹏汽车": "XPEV",
548
+ "蔚来汽车": "NIO", "蔚来": "NIO",
549
+ "理想汽车": "LI", "理想": "LI",
550
+ "极氪": "ZK",
551
+ "零跑汽车": "9863.HK",
552
+ "滴滴": "DIDIY",
553
+ "字节跳动": "PRIVATE:ByteDance",
554
+ "瑞幸咖啡美股": "LKNCY",
555
+ # ══════════════════════════════════════════════════════════════════════
556
+ # 美股 — 科技 / 互联网
557
+ # ══════════════════════════════════════════════════════════════════════
558
+ "Apple": "AAPL", "苹果": "AAPL", "苹果公司": "AAPL",
559
+ "Microsoft": "MSFT", "微软": "MSFT",
560
+ "Alphabet": "GOOGL", "Google": "GOOGL", "谷歌": "GOOGL",
561
+ "Amazon": "AMZN", "亚马逊": "AMZN",
562
+ "Meta": "META", "Facebook": "META", "脸书": "META",
563
+ "Netflix": "NFLX", "奈飞": "NFLX", "网飞": "NFLX",
564
+ "Tesla": "TSLA", "特斯拉": "TSLA", "特斯拉公司": "TSLA",
565
+ "NVIDIA": "NVDA", "英伟达": "NVDA",
566
+ "AMD": "AMD",
567
+ "Intel": "INTC", "英特尔": "INTC",
568
+ "Qualcomm": "QCOM", "高通": "QCOM",
569
+ "Broadcom": "AVGO", "博通": "AVGO",
570
+ "Texas Instruments": "TXN", "德州仪器": "TXN",
571
+ "Micron": "MU", "美光": "MU",
572
+ "Western Digital": "WDC", "西部数据": "WDC",
573
+ "Seagate": "STX", "希捷": "STX",
574
+ "Cisco": "CSCO", "思科": "CSCO",
575
+ "Oracle": "ORCL", "甲骨文": "ORCL",
576
+ "Salesforce": "CRM",
577
+ "Adobe": "ADBE", "奥多比": "ADBE",
578
+ "ServiceNow": "NOW",
579
+ "Workday": "WDAY",
580
+ "Snowflake": "SNOW",
581
+ "Databricks": "未上市",
582
+ "Palantir": "PLTR", "帕兰提尔": "PLTR",
583
+ "Cloudflare": "NET",
584
+ "CrowdStrike": "CRWD",
585
+ "Palo Alto": "PANW",
586
+ "Fortinet": "FTNT",
587
+ "Zscaler": "ZS",
588
+ "Okta": "OKTA",
589
+ "Datadog": "DDOG",
590
+ "MongoDB": "MDB",
591
+ "Twilio": "TWLO",
592
+ "Shopify": "SHOP",
593
+ "Uber": "UBER", "优步": "UBER",
594
+ "Lyft": "LYFT",
595
+ "Airbnb": "ABNB", "爱彼迎": "ABNB",
596
+ "DoorDash": "DASH",
597
+ "Instacart": "CART",
598
+ "Spotify": "SPOT",
599
+ "Pinterest": "PINS",
600
+ "Snap": "SNAP", "Snapchat": "SNAP",
601
+ "Twitter": "X", "推特": "X",
602
+ "Reddit": "RDDT",
603
+ "Roblox": "RBLX",
604
+ "Unity": "U",
605
+ "Zoom": "ZM",
606
+ "Slack": "未独立",
607
+ "OpenAI": "PRIVATE:OpenAI",
608
+ "Anthropic": "PRIVATE:Anthropic",
609
+ "xAI": "PRIVATE:xAI",
610
+ "SpaceX": "SPCX", "太空探索技术": "SPCX", "太空探索": "SPCX", "星链": "SPCX",
611
+ "Starlink": "SPCX", "星链卫星": "SPCX",
612
+ "ByteDance": "PRIVATE:ByteDance",
613
+ "Stripe": "PRIVATE:Stripe",
614
+ "Databricks": "PRIVATE:Databricks",
615
+ "Canva": "PRIVATE:Canva",
616
+ "Waymo": "PRIVATE:Waymo",
617
+ "Klarna": "PRIVATE:Klarna",
618
+ "Revolut": "PRIVATE:Revolut",
619
+ "Shein": "PRIVATE:Shein", "希音": "PRIVATE:Shein",
620
+ "SHEIN": "PRIVATE:Shein",
621
+ "华为": "PRIVATE:Huawei", "Huawei": "PRIVATE:Huawei",
622
+ "大疆": "PRIVATE:DJI", "DJI": "PRIVATE:DJI",
623
+ "蚂蚁集团": "PRIVATE:AntGroup", "支付宝": "PRIVATE:AntGroup",
624
+ "京东物流": "PRIVATE:JDL",
625
+ "Arm": "ARM",
626
+ "TSMC": "TSM", "台积电": "TSM",
627
+ "Samsung": "005930.KS",
628
+ # ══════════════════════════════════════════════════════════════════════
629
+ # 美股 — 金融
630
+ # ══════════════════════════════════════════════════════════════════════
631
+ "JPMorgan": "JPM", "摩根大通": "JPM",
632
+ "Goldman Sachs": "GS", "高盛": "GS",
633
+ "Morgan Stanley": "MS", "摩根士丹利": "MS",
634
+ "Bank of America": "BAC", "美国银行": "BAC",
635
+ "Citigroup": "C", "花旗": "C",
636
+ "Wells Fargo": "WFC", "富国银行": "WFC",
637
+ "Berkshire": "BRK-B", "伯克希尔": "BRK-B", "巴菲特": "BRK-B",
638
+ "BlackRock": "BLK", "贝莱德": "BLK",
639
+ "Visa": "V",
640
+ "Mastercard": "MA",
641
+ "American Express": "AXP", "美国运通": "AXP",
642
+ "PayPal": "PYPL",
643
+ "Block": "SQ", "Square": "SQ",
644
+ "Coinbase": "COIN",
645
+ "Robinhood": "HOOD",
646
+ "Charles Schwab": "SCHW",
647
+ "Interactive Brokers": "IBKR",
648
+ # ══════════════════════════════════════════════════════════════════════
649
+ # 美股 — 消费 / 零售
650
+ # ══════════════════════════════════════════════════════════════════════
651
+ "Walmart": "WMT", "沃尔玛": "WMT",
652
+ "Costco": "COST", "好市多": "COST",
653
+ "Target": "TGT",
654
+ "Home Depot": "HD", "家得宝": "HD",
655
+ "Lowe's": "LOW",
656
+ "Nike": "NKE", "耐克": "NKE",
657
+ "Adidas": "ADDYY", "阿迪达斯": "ADDYY",
658
+ "Lululemon": "LULU",
659
+ "Tapestry": "TPR",
660
+ "Coca-Cola": "KO", "可口可乐": "KO",
661
+ "PepsiCo": "PEP", "百事可乐": "PEP",
662
+ "McDonald's": "MCD", "麦当劳": "MCD",
663
+ "Starbucks": "SBUX", "星巴克": "SBUX",
664
+ "Yum Brands": "YUM",
665
+ "Chipotle": "CMG",
666
+ "Procter & Gamble": "PG", "宝洁": "PG",
667
+ "Unilever": "UL", "联合利华": "UL",
668
+ "Colgate": "CL",
669
+ "Estee Lauder": "EL", "雅诗兰黛": "EL",
670
+ "L'Oreal": "LRLCY", "欧莱雅": "LRLCY",
671
+ # ══════════════════════════════════════════════════════════════════════
672
+ # 美股 — 医疗 / 制药
673
+ # ══════════════════════════════════════════════════════════════════════
674
+ "Johnson & Johnson": "JNJ", "强生": "JNJ",
675
+ "Pfizer": "PFE", "辉瑞": "PFE",
676
+ "Merck": "MRK", "默沙东": "MRK",
677
+ "Eli Lilly": "LLY", "礼来": "LLY",
678
+ "AbbVie": "ABBV",
679
+ "Bristol-Myers": "BMY",
680
+ "Amgen": "AMGN",
681
+ "Gilead": "GILD",
682
+ "Biogen": "BIIB",
683
+ "Moderna": "MRNA", "莫德纳": "MRNA",
684
+ "BioNTech": "BNTX", "拜恩泰科": "BNTX",
685
+ "Regeneron": "REGN",
686
+ "Vertex": "VRTX",
687
+ "Illumina": "ILMN",
688
+ "Thermo Fisher": "TMO", "赛默飞": "TMO",
689
+ "Abbott": "ABT",
690
+ "Medtronic": "MDT",
691
+ "Boston Scientific": "BSX",
692
+ "UnitedHealth": "UNH",
693
+ "CVS Health": "CVS",
694
+ # ══════════════════════════════════════════════════════════════════════
695
+ # 美股 — 能源
696
+ # ══════════════════════════════════════════════════════════════════════
697
+ "ExxonMobil": "XOM", "埃克森美孚": "XOM", "埃克森": "XOM",
698
+ "Chevron": "CVX", "雪佛龙": "CVX",
699
+ "ConocoPhillips": "COP",
700
+ "Shell": "SHEL", "壳牌": "SHEL",
701
+ "BP": "BP", "英国石油": "BP",
702
+ "TotalEnergies": "TTE", "道达尔": "TTE",
703
+ "Schlumberger": "SLB",
704
+ "Halliburton": "HAL",
705
+ # ══════════════════════════════════════════════════════════════════════
706
+ # 美股 — 工业 / 航空 / 国防
707
+ # ══════════════════════════════════════════════════════════════════════
708
+ "Boeing": "BA", "波音": "BA",
709
+ "Lockheed": "LMT", "洛克希德": "LMT",
710
+ "Raytheon": "RTX",
711
+ "Northrop": "NOC",
712
+ "General Dynamics": "GD",
713
+ "Caterpillar": "CAT",
714
+ "Deere": "DE", "约翰迪尔": "DE",
715
+ "Honeywell": "HON", "霍尼韦尔": "HON",
716
+ "3M": "MMM",
717
+ "GE": "GE", "通用电气": "GE",
718
+ "Siemens": "SIEGY", "西门子美股": "SIEGY",
719
+ "ABB": "ABB",
720
+ "Emerson": "EMR",
721
+ "Parker Hannifin": "PH",
722
+ "United Airlines": "UAL", "美联航": "UAL",
723
+ "Delta": "DAL", "达美航空": "DAL",
724
+ "American Airlines": "AAL", "美国航空": "AAL",
725
+ "Southwest": "LUV", "西南航空": "LUV",
726
+ "FedEx": "FDX", "联邦快递": "FDX",
727
+ "UPS": "UPS",
728
+ # ══════════════════════════════════════════════════════════════════════
729
+ # 欧洲股票
730
+ # ══════════════════════════════════════════════════════════════════════
731
+ "LVMH": "MC.PA", "路易威登": "MC.PA", "路易斯威登": "MC.PA",
732
+ "爱马仕": "RMS.PA",
733
+ "开云集团": "KER.PA", "古驰": "KER.PA",
734
+ "香奈儿": "CHNLF",
735
+ "标致雪铁龙": "STLAM.MI", "Stellantis": "STLAM.MI",
736
+ "奔驰": "MBG.DE", "梅赛德斯": "MBG.DE", "梅赛德斯奔驰": "MBG.DE",
737
+ "宝马": "BMW.DE",
738
+ "大众": "VOW3.DE", "大众汽车": "VOW3.DE", "Volkswagen": "VOW3.DE",
739
+ "保时捷": "P911.DE",
740
+ "奥迪": "NSU.DE",
741
+ "西门子": "SIE.DE",
742
+ "SAP": "SAP",
743
+ "ASML": "ASML", "阿斯麦": "ASML",
744
+ "英飞凌": "IFNNY",
745
+ "意法半导体": "STM",
746
+ "诺基亚": "NOK",
747
+ "爱立信": "ERIC",
748
+ "汇丰": "HSBC", "汇丰银行": "HSBC",
749
+ "巴克莱": "BCS",
750
+ "德意志银行": "DB", "德银": "DB",
751
+ "法国巴黎银行": "BNPQY", "法巴": "BNPQY",
752
+ "瑞银": "UBS",
753
+ "安联": "ALIZY",
754
+ "安盛": "AXAHY",
755
+ "飞利浦": "PHG",
756
+ "雀巢": "NSRGY", "Nestle": "NSRGY",
757
+ "诺华": "NVS",
758
+ "罗氏": "RHHBY",
759
+ "阿斯利康": "AZN",
760
+ "GSK": "GSK", "葛兰素史克": "GSK",
761
+ "优尼利华": "UL",
762
+ "帝亚吉欧": "DEO",
763
+ "AB InBev": "BUD", "百威": "BUD",
764
+ "空客": "EADSY",
765
+ "赛诺菲": "SNY",
766
+ "道达尔能源": "TTE",
767
+ # ══════════════════════════════════════════════════════════════════════
768
+ # 日本股票
769
+ # ══════════════════════════════════════════════════════════════════════
770
+ "丰田": "TM", "丰田汽车": "TM", "Toyota": "TM",
771
+ "本田": "HMC", "本田汽车": "HMC", "Honda": "HMC",
772
+ "日产": "NSANY", "Nissan": "NSANY",
773
+ "马自达": "MZDAY",
774
+ "斯巴鲁": "FUJHY",
775
+ "三菱": "MMTOF",
776
+ "索尼": "SONY", "索尼集团": "SONY", "Sony": "SONY",
777
+ "松下": "PCRFY", "Panasonic": "PCRFY",
778
+ "日立": "HTHIY", "Hitachi": "HTHIY",
779
+ "东芝": "TOSYY", "Toshiba": "TOSYY",
780
+ "富士通": "FJTSY",
781
+ "NEC": "NIPNF",
782
+ "京瓷": "KYCCF",
783
+ "村田制作所": "MURAY",
784
+ "TDK": "TTDKY",
785
+ "日东电工": "NTTDF",
786
+ "信越化学": "SHECY",
787
+ "JSR": "JSRCF",
788
+ "东京电子": "TOELY",
789
+ "佳能": "CAJ", "Canon": "CAJ",
790
+ "尼康": "NINOY",
791
+ "软银": "SFTBY", "SoftBank": "SFTBY",
792
+ "任天堂": "NTDOY", "Nintendo": "NTDOY",
793
+ "乐天": "RKUNY",
794
+ "迅销": "FRCOF", "优衣库": "FRCOF",
795
+ "资生堂": "SSDOY",
796
+ "花王": "KAOOY",
797
+ "武田制药": "TAK",
798
+ "安斯泰来": "ALPMY",
799
+ "第一三共": "DSNKY",
800
+ "大塚制药": "OTSKY",
801
+ "瑞穗金融": "MFG",
802
+ "三菱日联": "MUFG",
803
+ "三井住友": "SMFG",
804
+ # ══════════════════════════════════════════════════════════════════════
805
+ # 韩国股票
806
+ # ══════════════════════════════════════════════════════════════════════
807
+ "三星电子": "005930.KS", "三星": "005930.KS",
808
+ "SK海力士": "000660.KS", "海力士": "000660.KS",
809
+ "LG电子": "066570.KS",
810
+ "LG化学": "051910.KS",
811
+ "LG能源": "373220.KS",
812
+ "现代汽车": "005380.KS", "现代": "005380.KS",
813
+ "起亚": "000270.KS",
814
+ "现代摩比斯": "012330.KS",
815
+ "POSCO": "005490.KS", "浦项制铁": "005490.KS",
816
+ "SK电讯": "017670.KS",
817
+ "KT": "030200.KS",
818
+ "Kakao": "035720.KS",
819
+ "Naver": "035420.KS",
820
+ "Celltrion": "068270.KS",
821
+ # ══════════════════════════════════════════════════════════════════════
822
+ # 台湾股票
823
+ # ══════════════════════════════════════════════════════════════════════
824
+ "台积电美股": "TSM", "台积": "TSM",
825
+ "台积电台股": "2330.TW",
826
+ "联发科": "2454.TW", "联发科技": "2454.TW",
827
+ "鸿海": "2317.TW", "鸿海精密": "2317.TW",
828
+ "台达电": "2308.TW",
829
+ "日月光": "3711.TW",
830
+ "联华电子": "2303.TW", "联电": "2303.TW",
831
+ "华硕": "2357.TW",
832
+ "宏碁": "2353.TW",
833
+ "中华电信": "2412.TW",
834
+ # ══════════════════════════════════════════════════════════════════════
835
+ # 指数 / ETF
836
+ # ══════════════════════════════════════════════════════════════════════
837
+ "纳斯达克100": "QQQ", "纳斯达克": "QQQ", "纳指": "QQQ",
838
+ "标普500": "SPY", "标普": "SPY", "S&P500": "SPY", "S&P 500": "SPY",
839
+ "道琼斯": "DIA", "道指": "DIA", "道琼": "DIA",
840
+ "罗素2000": "IWM",
841
+ "标普科技": "XLK",
842
+ "半导体ETF": "SOXX",
843
+ "沪深300": "000300.SS", "沪深": "000300.SS",
844
+ "上证指数": "000001.SS", "上证": "000001.SS",
845
+ "深证成指": "399001.SZ", "深证": "399001.SZ",
846
+ "创业板指": "399006.SZ", "创业板": "399006.SZ",
847
+ "科创50": "000688.SS", "科创板": "000688.SS",
848
+ "中证500": "000905.SS",
849
+ "中证1000": "000852.SS",
850
+ "恒生指数": "^HSI", "恒生": "^HSI", "恒指": "^HSI",
851
+ "恒生科技": "^HSTECH",
852
+ "国企指数": "^HSCE", "国企": "^HSCE",
853
+ "日经225": "^N225", "日经": "^N225",
854
+ "韩国综合": "^KS11", "KOSPI": "^KS11",
855
+ "台湾加权": "^TWII",
856
+ "富时100": "^FTSE", "英国富时": "^FTSE",
857
+ "德国DAX": "^GDAXI", "DAX": "^GDAXI",
858
+ "法国CAC": "^FCHI", "CAC40": "^FCHI",
859
+ "欧洲斯托克50": "^STOXX50E",
860
+ "澳大利亚": "^AXJO",
861
+ # ══════════════════════════════════════════════════════════════════════
862
+ # 加密货币
863
+ # ══════════════════════════════════════════════════════════════════════
864
+ "比特币": "BTC-USD", "Bitcoin": "BTC-USD",
865
+ "以太坊": "ETH-USD", "Ethereum": "ETH-USD",
866
+ "狗狗币": "DOGE-USD", "Dogecoin": "DOGE-USD",
867
+ "索拉纳": "SOL-USD", "Solana": "SOL-USD",
868
+ "瑞波币": "XRP-USD", "XRP": "XRP-USD",
869
+ "Cardano": "ADA-USD", "艾达币": "ADA-USD",
870
+ "Avalanche": "AVAX-USD",
871
+ "Polygon": "MATIC-USD",
872
+ "Chainlink": "LINK-USD",
873
+ "Uniswap": "UNI-USD",
874
+ "莱特币": "LTC-USD", "Litecoin": "LTC-USD",
875
+ "币安币": "BNB-USD", "BNB": "BNB-USD",
876
+ "USDT": "USDT-USD",
877
+ "USDC": "USDC-USD",
878
+ # ══════════════════════════════════════════════════════════════════════
879
+ # 大宗商品期货
880
+ # ══════════════════════════════════════════════════════════════════════
881
+ "黄金": "GC=F", "Gold": "GC=F",
882
+ "白银": "SI=F", "Silver": "SI=F",
883
+ "铂金": "PL=F",
884
+ "钯金": "PA=F",
885
+ "WTI原油": "CL=F", "原油": "CL=F", "WTI": "CL=F",
886
+ "布伦特原油": "BZ=F", "布油": "BZ=F",
887
+ "天然气": "NG=F",
888
+ "铜期货": "HG=F", "COMEX铜": "HG=F", # 注意:是期货不是公司
889
+ "玉米": "ZC=F",
890
+ "大豆": "ZS=F",
891
+ "小麦": "ZW=F",
892
+ "棉花": "CT=F",
893
+ "咖啡": "KC=F",
894
+ "可可": "CC=F",
895
+ "糖": "SB=F",
896
+ }
897
+
898
+ # ══════════════════════════════════════════════════════════════════════════════
899
+ # Private company profiles — updated as of 2025-Q2
900
+ # Fields: valuation_usd (latest known, $B), rev_est (annual revenue est, $B),
901
+ # rev_growth (YoY est %), founded, hq, employees (k),
902
+ # segments (list), comparables (public tickers for peer analysis),
903
+ # ipo_status, highlights (list of key facts)
904
+ # ══════════════════════════════════════════════════════════════════════════════
905
+ _PRIVATE_COMPANY_PROFILES: dict[str, dict] = {
906
+ "OpenAI": {
907
+ "name": "OpenAI",
908
+ "valuation_usd": 157,
909
+ "rev_est": 3.7,
910
+ "rev_growth": 200,
911
+ "founded": 2015,
912
+ "founder": "Sam Altman, Greg Brockman, Elon Musk (已离开)",
913
+ "hq": "San Francisco, California, USA",
914
+ "employees": 3.5,
915
+ "segments": ["ChatGPT 订阅 (Plus/Pro/Team/Enterprise)", "API 服务 (GPT-4o, o3等)", "Sora 视频生成", "企业定制部署"],
916
+ "comparables": ["MSFT", "GOOGL", "META", "AMZN"],
917
+ "ipo_status": "2025年完成公司结构从非营利转为营利,IPO 预计 2026-2027",
918
+ "last_funding": "2025-Q1 $40B 融资,SoftBank 领投,估值 $157B",
919
+ "highlights": [
920
+ "ChatGPT 月活超 5 亿,企业客户超 100 万",
921
+ "2024 年 ARR 超 $3.7B,增速 >200% YoY",
922
+ "与微软 $13B 战略合作,Azure OpenAI 是主要分发渠道",
923
+ "o3/o4 系列推理模型引领 AI 能力竞争",
924
+ ],
925
+ "risks": [
926
+ "持续巨额亏损(计算成本极高,2024 运营亏损 ~$5B)",
927
+ "Google Gemini、Anthropic Claude、Meta LLaMA 激烈竞争",
928
+ "人才流失风险(联创及核心研究员多次离职)",
929
+ "监管压力:全球 AI 监管法规趋严",
930
+ ],
931
+ },
932
+ "ByteDance": {
933
+ "name": "ByteDance (字节跳动)",
934
+ "valuation_usd": 300,
935
+ "rev_est": 120,
936
+ "rev_growth": 30,
937
+ "founded": 2012,
938
+ "founder": "张一鸣 (Zhang Yiming)",
939
+ "hq": "北京,中国 / 新加坡(国际总部)",
940
+ "employees": 110,
941
+ "segments": ["抖音/TikTok 广告", "今日头条/西瓜视频", "飞书企业办公", "游戏/教育/电商"],
942
+ "comparables": ["META", "GOOGL", "SNAP", "PINS"],
943
+ "ipo_status": "屡次推迟;TikTok 美国封禁风险悬而未决;整体 IPO 无明确时间表",
944
+ "last_funding": "二级市场估值 ~$300B;上一轮融资 2021 年 $5B",
945
+ "highlights": [
946
+ "TikTok 月活超 17 亿,广告收入超 $200 亿",
947
+ "抖音国内广告收入 2024 超 $500 亿人民币",
948
+ "飞书企业服务增速快,但仍处亏损扩张期",
949
+ "整体集团收入约 $1200 亿(2024 est.),接近 Meta",
950
+ ],
951
+ "risks": [
952
+ "TikTok 美国强制出售/封禁立法风险",
953
+ "中国数据安全监管,出海受限",
954
+ "广告市场周期性,高度依赖广告营收",
955
+ ],
956
+ },
957
+ "Anthropic": {
958
+ "name": "Anthropic",
959
+ "valuation_usd": 61,
960
+ "rev_est": 1.5,
961
+ "rev_growth": 300,
962
+ "founded": 2021,
963
+ "founder": "Dario Amodei, Daniela Amodei (原 OpenAI)",
964
+ "hq": "San Francisco, California, USA",
965
+ "employees": 1.5,
966
+ "segments": ["Claude API (Haiku/Sonnet/Opus)", "Claude.ai 订阅", "企业/政府部署"],
967
+ "comparables": ["MSFT", "GOOGL", "META"],
968
+ "ipo_status": "无公开 IPO 计划,处于高速成长期",
969
+ "last_funding": "2025 Google $3B + Amazon $4B 战略投资,估值 $61B",
970
+ "highlights": [
971
+ "Claude 3.5 系列被认为是最强代码/推理模型之一",
972
+ "AWS Bedrock 和 Google Cloud 均为分发合作伙伴",
973
+ "2024 ARR 增速超 300%,约 $1.5B",
974
+ ],
975
+ "risks": [
976
+ "巨额算力成本,仍处亏损",
977
+ "OpenAI GPT-4o 和 Google Gemini Ultra 直接竞争",
978
+ "单一产品线风险",
979
+ ],
980
+ },
981
+ "Huawei": {
982
+ "name": "华为技术有限公司",
983
+ "valuation_usd": 220,
984
+ "rev_est": 99,
985
+ "rev_growth": 22,
986
+ "founded": 1987,
987
+ "founder": "任正非 (Ren Zhengfei)",
988
+ "hq": "深圳,广东,中国",
989
+ "employees": 207,
990
+ "segments": ["运营商业务(5G 设备)", "企业业务(云/存储/AI)", "终端业务(手机/PC/汽车)", "海思半导体(内部)"],
991
+ "comparables": ["ERIC", "NOK", "CSCO", "INTC"],
992
+ "ipo_status": "任正非多次公开表示不计划上市",
993
+ "last_funding": "员工持股计划,无外部融资",
994
+ "highlights": [
995
+ "2023 年营收 ~7042 亿人民币(约 $990 亿),同增 9.6%;2024 年超 $990 亿",
996
+ "麒麟 9000S 芯片突破 5G 封锁,Mate 60 系列引发广泛关注",
997
+ "鸿蒙 OS 生态持续扩大,2024 设备用户超 9 亿",
998
+ "全球 5G 专利数量第一,技术护城河深厚",
999
+ ],
1000
+ "risks": [
1001
+ "美国实体清单:高端芯片封锁持续",
1002
+ "西方市场 5G 合同被排除(欧盟多国)",
1003
+ "依赖自研替代进口,成本高企",
1004
+ ],
1005
+ },
1006
+ "DJI": {
1007
+ "name": "大疆创新 (DJI)",
1008
+ "valuation_usd": 16,
1009
+ "rev_est": 7,
1010
+ "rev_growth": 15,
1011
+ "founded": 2006,
1012
+ "founder": "汪滔 (Frank Wang)",
1013
+ "hq": "深圳,广东,中国",
1014
+ "employees": 14,
1015
+ "segments": ["消费级无人机(Mavic/Mini系列)", "行业级无人机(农业/测绘/影视)", "稳定器/相机云台(如影)", "农业植保机"],
1016
+ "comparables": ["AVAV", "KTOS", "PRNT"],
1017
+ "ipo_status": "2023 年港股 IPO 计划因美国制裁风险搁置",
1018
+ "last_funding": "2021 年融资 $500M,估值 $16B",
1019
+ "highlights": [
1020
+ "全球消费无人机市场份额 >70%",
1021
+ "Agras 农业无人机在中国市占率超 80%",
1022
+ "2021 年被列入美国实体清单,仍是全球领导者",
1023
+ ],
1024
+ "risks": [
1025
+ "美国国防部黑名单影响企业客户",
1026
+ "本土竞争者(如道通、极飞)快速追赶",
1027
+ "各国无人机监管趋严",
1028
+ ],
1029
+ },
1030
+ "Stripe": {
1031
+ "name": "Stripe",
1032
+ "valuation_usd": 70,
1033
+ "rev_est": 15,
1034
+ "rev_growth": 25,
1035
+ "founded": 2010,
1036
+ "founder": "Patrick Collison, John Collison",
1037
+ "hq": "San Francisco / Dublin",
1038
+ "employees": 8,
1039
+ "segments": ["支付处理", "Stripe Billing 订阅管理", "Stripe Capital 金融服务", "Stripe Atlas 公司注册"],
1040
+ "comparables": ["V", "MA", "SQ", "PYPL", "ADYEN.AS"],
1041
+ "ipo_status": "多次推迟;2024 年员工流动性计划估值 $65-70B;IPO 预计 2025-2026",
1042
+ "last_funding": "2023 年 $6.5B 融资用于员工纳税,估值从峰值 $95B 降至 $50B 后回升",
1043
+ "highlights": [
1044
+ "2023 年处理支付额 ~$1 万亿,收入约 $15B",
1045
+ "美国支付 API 市场开发者首选,生态极强",
1046
+ "客户包括 Amazon、Shopify、Salesforce 等",
1047
+ ],
1048
+ "risks": [
1049
+ "Adyen、PayPal、Square 直接竞争",
1050
+ "利率上升增加支付成本",
1051
+ "大客户谈判能力强,费率受压",
1052
+ ],
1053
+ },
1054
+ "xAI": {
1055
+ "name": "xAI (Elon Musk's AI company)",
1056
+ "valuation_usd": 50,
1057
+ "rev_est": 0.5,
1058
+ "rev_growth": 500,
1059
+ "founded": 2023,
1060
+ "founder": "Elon Musk",
1061
+ "hq": "Memphis, Tennessee, USA",
1062
+ "employees": 1,
1063
+ "segments": ["Grok AI 助手 (X 平台集成)", "Grok API", "超级计算机 Colossus(100k H100)"],
1064
+ "comparables": ["MSFT", "GOOGL", "META"],
1065
+ "ipo_status": "无上市计划",
1066
+ "last_funding": "2025 年 $6B 融资,估值 $50B",
1067
+ "highlights": [
1068
+ "Grok 3 系列在多项 benchmark 上排名前列",
1069
+ "Memphis Colossus 超级计算机为目前最大 AI 训练集群之一",
1070
+ "与 X(Twitter)深度集成",
1071
+ ],
1072
+ "risks": [
1073
+ "Elon Musk 精力分散(同时管理 Tesla/SpaceX/X/xAI)",
1074
+ "起步晚,OpenAI/Google/Anthropic/Meta 先发优势显著",
1075
+ ],
1076
+ },
1077
+ }
1078
+
1079
+ # Keywords that indicate the user is asking about their own portfolio/account
1080
+ _BROKER_INTENT_KW = (
1081
+ "我的持仓", "我的仓位", "我持有", "我买了", "我的股票", "我的账户",
1082
+ "账户余额", "可用余额", "我有多少钱", "我的资金", "我还有多少",
1083
+ "持仓盈亏", "我的盈亏", "赚了多少", "亏了多少", "总盈亏", "当日盈亏",
1084
+ "委托", "我的订单", "挂单", "我下的单",
1085
+ "my portfolio", "my positions", "my holdings", "my account",
1086
+ "my balance", "my orders", "how much cash", "what do i own",
1087
+ )
1088
+
1089
+
1090
+ def _is_broker_intent(message: str) -> bool:
1091
+ """Return True if the message is asking about the user's own account/positions."""
1092
+ low = message.lower()
1093
+ return any(kw.lower() in low for kw in _BROKER_INTENT_KW)
1094
+
1095
+
1096
+ # ── Broker setup / configuration intent ──────────────────────────────────────
1097
+ # Maps broker type keys to their display names and recognition keywords.
1098
+ _BROKER_SETUP_NAMES: dict[str, tuple[str, tuple]] = {
1099
+ "tiger": ("老虎证券", ("老虎", "tiger", "tigeropen", "tigerop")),
1100
+ "futu": ("富途牛牛", ("富途", "futu", "futuooo", "牛牛", "futuo")),
1101
+ "longbridge": ("长桥证券", ("长桥", "longbridge", "longport")),
1102
+ "ibkr": ("盈透证券", ("盈透", "ibkr", "ib ", "interactive broker", "ib_insync")),
1103
+ "alpaca": ("Alpaca", ("alpaca",)),
1104
+ "webull": ("Webull", ("webull", "微牛")),
1105
+ "xtquant": ("迅投XTQuant", ("迅投", "xtquant", "qmt", "中信建投", "华鑫", "浙商")),
1106
+ "easytrader": ("EasyTrader", ("easytrader", "通达信", "同花顺", "ths", "tdx", "华泰", "国君")),
1107
+ }
1108
+
1109
+ _BROKER_SETUP_VERBS = (
1110
+ "配置", "连接", "接入", "安装", "设置", "开通", "使用", "启用", "绑定", "接口",
1111
+ "添加券商", "添加交易", "怎么用", "如何用", "怎样用", "怎么接", "如何接",
1112
+ "帮我", "帮忙", "教我", "指导", "setup", "connect", "configure", "install",
1113
+ "add broker", "use broker", "how to use", "how to connect", "how to setup",
1114
+ "guide", "tutorial", "凭证", "api key", "申请", "开发者",
1115
+ )
1116
+
1117
+ _BROKER_GUIDE_INTENT_KW = (
1118
+ "支持哪些券商", "有哪些券商", "券商列表", "券商能力", "能力矩阵",
1119
+ "所有券商", "各个券商", "券商服务", "券商联动", "怎么连接各个券商",
1120
+ "如何连接各个券商", "如何和各个券商连接", "怎么和各个券商连接",
1121
+ "分析如何和各个券商连接", "使用这个项目的各个服务", "项目的各个服务",
1122
+ "broker guide", "broker matrix", "supported brokers", "broker capabilities",
1123
+ "broker services",
1124
+ )
1125
+
1126
+
1127
+ def _is_broker_guide_intent(message: str) -> bool:
1128
+ """Return True for broad broker/service discovery requests.
1129
+
1130
+ These should show a capability guide instead of jumping straight into an
1131
+ interactive broker-add wizard.
1132
+ """
1133
+ low = message.lower()
1134
+ compact = low.replace(" ", "")
1135
+ if any(kw in low or kw.replace(" ", "") in compact for kw in _BROKER_GUIDE_INTENT_KW):
1136
+ return True
1137
+ has_broker_word = any(k in low for k in ("券商", "证券", "broker"))
1138
+ wants_overview = any(k in low for k in ("哪些", "列表", "支持", "能力", "矩阵", "各个", "所有", "服务"))
1139
+ asks_connection = any(k in low for k in ("怎么", "如何", "怎样", "连接", "接入", "使用"))
1140
+ return bool(has_broker_word and wants_overview and asks_connection)
1141
+
1142
+
1143
+ def _is_broker_setup_intent(message: str) -> bool:
1144
+ """Return True if the user is asking to set up / configure a broker."""
1145
+ low = message.lower()
1146
+ has_verb = any(v in low for v in _BROKER_SETUP_VERBS)
1147
+ if not has_verb:
1148
+ return False
1149
+ # Check if any broker name/keyword is mentioned
1150
+ for _broker_type, (display, kws) in _BROKER_SETUP_NAMES.items():
1151
+ if any(k in low for k in kws):
1152
+ return True
1153
+ # Generic broker-setup phrases without a specific broker named
1154
+ generic_kws = ("券商", "证券", "broker", "交易所", "量化交易", "quant", "开户")
1155
+ return any(k in low for k in generic_kws)
1156
+
1157
+
1158
+ def _detect_broker_type(message: str) -> str:
1159
+ """Return the broker type key if a specific broker is mentioned, else ''."""
1160
+ low = message.lower()
1161
+ for broker_type, (_display, kws) in _BROKER_SETUP_NAMES.items():
1162
+ if any(k in low for k in kws):
1163
+ return broker_type
1164
+ return ""
1165
+
1166
+
1167
+ _FINANCIAL_TERMS_BLOCKLIST: frozenset = frozenset({
1168
+ "DCF", "EPS", "ROE", "ROA", "ROI", "PEG", "NAV", "AUM", "NMV", "IRR",
1169
+ "NPV", "FCF", "OCF", "ICF", "EBIT", "WACC", "CAGR", "YTD", "QoQ",
1170
+ "YoY", "MoM", "GDP", "CPI", "PPI", "PMI", "NFP", "ISM", "PCE",
1171
+ "FED", "ECB", "PBC", "IMF", "BIS", "WTO", "SEC", "FSB",
1172
+ "QE", "QT", "VIX", "RSI", "MACD", "EMA", "SMA", "ATR", "ADX",
1173
+ "ATH", "ATL", "IPO", "SPO", "M&A", "LBO", "MBO",
1174
+ "NYSE", "NASDAQ", "AMEX", "LSE", "TSX", "ASX", "SGX", "HKE",
1175
+ "ETF", "CEF", "REIT", "MLP", "ADR", "GDR",
1176
+ "USD", "CNY", "EUR", "GBP", "JPY", "HKD", "SGD", "AUD",
1177
+ "BTC", "ETH", "XRP", # crypto are handled by _CRYPTO_WORDS
1178
+ "AI", "AR", "VR", "ML", "NLP", "API", "SaaS", "B2B", "B2C",
1179
+ })
1180
+ _FINANCIAL_TERMS_SYMBOL_BLOCKLIST: frozenset = frozenset(
1181
+ str(item).upper() for item in _FINANCIAL_TERMS_BLOCKLIST
1182
+ )
1183
+
1184
+ _MARKET_SYMBOL_WORD_BLOCKLIST: frozenset = frozenset({
1185
+ "ANALYZE", "ANALYSIS", "DATA", "QUOTE", "QUOTES", "PRICE", "PRICES",
1186
+ "MARKET", "MARKETS", "VOLUME", "VOLUMES", "CHART", "CHARTS", "PLOT",
1187
+ "YAHOO", "STOOQ", "YFINANCE", "AKSHARE", "FINNHUB", "SOURCE",
1188
+ "SOURCES", "PROVIDER", "PROVIDERS", "HISTORY", "HISTORICAL",
1189
+ })
1190
+
1191
+
1192
+ def _company_alias_symbol_blocklist() -> set[str]:
1193
+ """Words like APPLE/TESLA are company aliases, not Yahoo ticker symbols."""
1194
+ blocked: set[str] = set()
1195
+ for name, ticker in _COMPANY_TO_TICKER.items():
1196
+ alias = str(name or "").strip().upper()
1197
+ resolved = str(ticker or "").strip().upper()
1198
+ if (
1199
+ 1 < len(alias) <= 5
1200
+ and alias.isalpha()
1201
+ and resolved
1202
+ and resolved not in ("未上市", "未独立")
1203
+ and not resolved.startswith("PRIVATE:")
1204
+ and alias != resolved
1205
+ ):
1206
+ blocked.add(alias)
1207
+ return blocked
1208
+
1209
+
1210
+ _COMPANY_ALIAS_SYMBOL_BLOCKLIST: frozenset = frozenset(_company_alias_symbol_blocklist())
1211
+
1212
+
1213
+ def _is_blocked_market_symbol_candidate(symbol: str) -> bool:
1214
+ """Return True for uppercase words that are context terms, not tickers."""
1215
+ normalized = str(symbol or "").strip().upper()
1216
+ if not normalized:
1217
+ return True
1218
+ return (
1219
+ normalized in _FINANCIAL_TERMS_SYMBOL_BLOCKLIST
1220
+ or normalized in _MARKET_SYMBOL_WORD_BLOCKLIST
1221
+ or normalized in _COMPANY_ALIAS_SYMBOL_BLOCKLIST
1222
+ )
1223
+
1224
+
1225
+ def _extract_market_symbol(message: str) -> str:
1226
+ """Extract a likely market symbol from Chinese company names or tickers."""
1227
+ resolved = _resolve_market_symbol(message)
1228
+ if resolved:
1229
+ return resolved
1230
+ # 最长名称优先(避免"美的"抢先匹配"美的集团")
1231
+ for cn, tick in sorted(_COMPANY_TO_TICKER.items(), key=lambda x: -len(x[0])):
1232
+ if cn in message:
1233
+ return tick
1234
+ # A股裸6位代码(600519 / sh600519 / 688256.SH)
1235
+ m = _re_sym.search(r'(?<!\d)(?:[sS][hHzZ])?([036]\d{5}|68\d{4})(?:\.(?:SH|SZ|SS))?(?!\d)', message)
1236
+ if m:
1237
+ return m.group(1)
1238
+ # Yahoo-compatible index / futures / FX tickers: ^GSPC, GC=F, EURUSD=X
1239
+ m = _re_sym.search(r'(?<![A-Za-z0-9])(\^[A-Z0-9]{2,10}|[A-Z]{2,8}=F|[A-Z]{6}=X|DX-Y\.NYB)(?![A-Za-z0-9])', message, _re_sym.I)
1240
+ if m:
1241
+ return m.group(1).upper()
1242
+ m = _re_sym.search(r'\b([A-Z]{1,5}(?:\.(?:HK|SH|SZ))?)\b', message)
1243
+ if m and not _is_blocked_market_symbol_candidate(m.group(1)):
1244
+ return m.group(1)
1245
+ # Chinese text immediately after a ticker ("AAPL的市场") prevents \b from
1246
+ # matching because Unicode word-boundary rules treat 的 as a word char.
1247
+ m = _re_sym.search(r'(?<![A-Za-z])([A-Z]{1,5}(?:\.(?:HK|SH|SZ))?)(?![A-Za-z])', message)
1248
+ if m and not _is_blocked_market_symbol_candidate(m.group(1)):
1249
+ return m.group(1)
1250
+ return ""
1251
+
1252
+ def _extract_market_symbols(message: str, limit: int = 6) -> list[str]:
1253
+ """Extract all likely market symbols, preserving mention order."""
1254
+ hits: list[tuple[int, str]] = []
1255
+
1256
+ def add_hit(start: int, symbol: str) -> None:
1257
+ if start < 0 or not symbol:
1258
+ return
1259
+ # Higher-priority resolvers win at the same text position. This avoids
1260
+ # old aliases like "纳斯达克" -> QQQ duplicating the universe resolver's
1261
+ # "纳斯达克" -> ^IXIC result.
1262
+ if any(existing_start == start for existing_start, _ in hits):
1263
+ return
1264
+ hits.append((start, symbol))
1265
+
1266
+ for start, item in _resolve_market_mentions(message, limit=limit):
1267
+ add_hit(start, item.symbol)
1268
+
1269
+ for name, ticker in sorted(_COMPANY_TO_TICKER.items(), key=lambda x: -len(x[0])):
1270
+ if ticker.startswith("PRIVATE:") or ticker in ("未上市", "未独立"):
1271
+ continue
1272
+ start = message.find(name)
1273
+ add_hit(start, ticker)
1274
+
1275
+ for match in _re_sym.finditer(r'(?<!\d)(?:[sS][hHzZ])?([036]\d{5}|68\d{4})(?:\.(?:SH|SZ|SS))?(?!\d)', message):
1276
+ add_hit(match.start(), match.group(1))
1277
+
1278
+ for match in _re_sym.finditer(r'(?<![A-Za-z0-9])(\^[A-Z0-9]{2,10}|[A-Z]{2,8}=F|[A-Z]{6}=X|DX-Y\.NYB)(?![A-Za-z0-9])', message, _re_sym.I):
1279
+ add_hit(match.start(), match.group(1).upper())
1280
+
1281
+ for match in _re_sym.finditer(r'\b([A-Z]{1,5}(?:\.(?:HK|SH|SZ))?)\b', message):
1282
+ symbol = match.group(1)
1283
+ if not _is_blocked_market_symbol_candidate(symbol):
1284
+ add_hit(match.start(), symbol)
1285
+
1286
+ for match in _re_sym.finditer(r'(?<![A-Za-z])([A-Z]{1,5}(?:\.(?:HK|SH|SZ))?)(?![A-Za-z])', message):
1287
+ symbol = match.group(1)
1288
+ if not _is_blocked_market_symbol_candidate(symbol):
1289
+ add_hit(match.start(), symbol)
1290
+
1291
+ ordered: list[str] = []
1292
+ for _, symbol in sorted(hits, key=lambda item: item[0]):
1293
+ symbol = str(symbol).upper()
1294
+ if symbol not in ordered:
1295
+ ordered.append(symbol)
1296
+ if len(ordered) >= limit:
1297
+ break
1298
+ return ordered
1299
+
1300
+
1301
+ def _extract_symbol_from_history(history: list, max_lookback: int = 8) -> str:
1302
+ """从最近的会话历史中提取标的(跟进问题继承上下文,如"现在的股票和趋势呢")。
1303
+
1304
+ 倒序扫描最近 max_lookback 条消息,返回最先命中的标的代码。
1305
+ user 消息优先于 assistant 消息(用户提到的标的意图最明确)。
1306
+ """
1307
+ if not history:
1308
+ return ""
1309
+ recent = history[-max_lookback:]
1310
+ # 先扫 user 消息(倒序)
1311
+ for msg in reversed(recent):
1312
+ if msg.get("role") != "user":
1313
+ continue
1314
+ content = msg.get("content", "")
1315
+ if isinstance(content, list): # 多模态消息取 text 部分
1316
+ content = " ".join(p.get("text", "") for p in content
1317
+ if isinstance(p, dict) and p.get("type") == "text")
1318
+ sym = _extract_market_symbol(content)
1319
+ if sym:
1320
+ return sym
1321
+ # 再扫 assistant 消息(倒序)
1322
+ for msg in reversed(recent):
1323
+ if msg.get("role") != "assistant":
1324
+ continue
1325
+ sym = _extract_market_symbol(str(msg.get("content", "")))
1326
+ if sym:
1327
+ return sym
1328
+ return ""
1329
+
1330
+ def _is_stock_chart_analysis_request(message: str) -> bool:
1331
+ """Return True for clear stock chart requests on stock-like symbols.
1332
+
1333
+ This intentionally catches both:
1334
+ - "分析 AAPL 的 K 线图"
1335
+ - "生成 Apple 公司近一年的股票图表"
1336
+ so callers can route chart requests before generic market snapshot logic.
1337
+ """
1338
+ low = message.lower()
1339
+ has_chart = any(k in low for k in (
1340
+ "图表", "走势图", "k线", "k线图", "k-line", "kline", "chart", "plot",
1341
+ "蜡烛图", "candlestick", "生成", "绘制", "画", "制作", "生成一个", "生成近",
1342
+ ))
1343
+ has_stock = any(k in low for k in ("股票", "stock", "股价")) or bool(_extract_market_symbol(message))
1344
+ return has_chart and has_stock
1345
+
1346
+
1347
+ def _is_visual_market_artifact_request(message: str) -> bool:
1348
+ """Return True for chart/dashboard/report requests tied to market data."""
1349
+ low = message.lower()
1350
+ has_visual = any(k in low for k in (
1351
+ "图表", "走势图", "k线", "k线图", "k-line", "kline", "candlestick",
1352
+ "chart", "plot", "dashboard", "看板", "晨报", "日报", "周报", "月报",
1353
+ "report", "热力图", "heatmap",
1354
+ ))
1355
+ if not has_visual:
1356
+ return False
1357
+ has_market = any(k in low for k in (
1358
+ "股票", "股价", "行情", "市场", "美股", "港股", "a股", "指数",
1359
+ "持仓", "portfolio", "回测", "财报", "earnings", "基金", "etf",
1360
+ "资产", "组合", "市场数据", "market data",
1361
+ ))
1362
+ return has_market or bool(_extract_market_symbol(message))
1363
+
1364
+
1365
+ _UNRESOLVED_CO_INDICATORS = (
1366
+ # 中文公司后缀
1367
+ "公司", "集团", "股份", "有限", "控股", "科技", "电子", "能源", "银行",
1368
+ "汽车", "制药", "医疗", "传媒", "文化", "地产", "金融", "基金", "证券",
1369
+ # 外国品牌/公司名常见关键词(未进入字典的)
1370
+ "路易", "奔驰", "宝马", "大众", "爱马仕", "香奈儿", "耐克", "阿迪",
1371
+ "三星", "索尼", "丰田", "本田", "日产", "空客", "波音", "壳牌", "埃克森",
1372
+ "高盛", "摩根", "花旗", "巴克莱", "德银", "瑞信", "瑞银",
1373
+ "奈飞", "推特", "脸书", "优步", "滴滴", "字节", "快手", "拼多多",
1374
+ )
1375
+
1376
+
1377
+ def _has_unresolved_company_mention(message: str) -> bool:
1378
+ """Return True if message names a company/brand that _extract_market_symbol couldn't resolve.
1379
+
1380
+ Used to block silent history inheritance: if user says "路易斯威登怎么看" and we
1381
+ can't resolve it, we should NOT fall back to the last queried ticker (e.g. 300124).
1382
+ """
1383
+ if _extract_market_symbol(message):
1384
+ return False # already resolved — no problem
1385
+ return any(kw in message for kw in _UNRESOLVED_CO_INDICATORS) or _looks_like_unresolved_market_name(message)
1386
+
1387
+
1388
+ # These keywords signal the user is asking about property markets, NOT stocks.
1389
+ # Any message matching here must NOT be routed to the stock market snapshot path
1390
+ # and must NOT silently inherit a stock ticker from session history.
1391
+ _REALTY_QUERY_KEYWORDS = (
1392
+ "房价", "楼市", "楼价", "房产", "房地产", "地产", "租金", "租房",
1393
+ "二手房", "新房", "住宅", "商品房", "开发商", "楼盘", "物业",
1394
+ "house price", "property price", "real estate market", "housing market",
1395
+ "home price", "apartment rent",
1396
+ )
1397
+ # Major Chinese cities — when paired with real-estate context words, confirms realty query.
1398
+ _CN_CITIES = (
1399
+ # 一线
1400
+ "北京", "上海", "深圳", "广州",
1401
+ # 新一线
1402
+ "成都", "杭州", "武汉", "南京", "重庆", "西安", "长沙", "天津",
1403
+ "苏州", "郑州", "东莞", "厦门", "宁波", "青岛", "无锡", "合肥",
1404
+ "佛山", "福州",
1405
+ # 二线
1406
+ "南宁", "昆明", "贵阳", "沈阳", "大连", "哈尔滨", "长春", "济南",
1407
+ "石家庄", "太原", "呼和浩特", "南昌", "兰州", "银川", "西宁",
1408
+ "乌鲁木齐", "海口", "三亚", "珠海", "中山", "惠州", "温州", "南通",
1409
+ "嘉兴", "金华", "绍兴", "泉州", "漳州", "赣州", "九江",
1410
+ "烟台", "潍坊", "济宁", "临沂", "洛阳", "南阳", "开封",
1411
+ "湘潭", "株洲", "常德", "常州", "徐州", "扬州", "盐城",
1412
+ "唐山", "保定", "邯郸", "秦皇岛", "廊坊",
1413
+ "湛江", "江门", "茂名", "汕头", "汕尾",
1414
+ "桂林", "柳州", "芜湖", "马鞍山", "蚌埠",
1415
+ )
1416
+
1417
+ # International city keywords (for realty query detection — no akshare data but LLM context)
1418
+ _INTL_CITIES = (
1419
+ # English names
1420
+ "new york", "los angeles", "san francisco", "chicago", "miami",
1421
+ "london", "paris", "berlin", "amsterdam", "zurich", "geneva",
1422
+ "tokyo", "osaka", "seoul", "singapore", "sydney", "melbourne",
1423
+ "hong kong", "taipei", "bangkok", "dubai", "toronto", "vancouver",
1424
+ # Chinese transliterations / common references
1425
+ "纽约", "洛杉矶", "旧金山", "芝加哥", "迈阿密",
1426
+ "伦敦", "巴黎", "柏林", "阿姆斯特丹", "苏黎世",
1427
+ "东京", "大阪", "首尔", "新加坡", "悉尼", "墨尔本",
1428
+ "香港", "台北", "曼谷", "迪拜", "多伦多", "温哥华",
1429
+ )
1430
+ # Market/analysis words that only count as "stock" context — not real-estate context
1431
+ _STOCK_ONLY_MARKET_WORDS = (
1432
+ "股票", "股价", "涨跌", "涨幅", "股市", "A股", "港股", "美股",
1433
+ "指数", "大盘", "个股", "板块", "开盘", "收盘", "换手率",
1434
+ "stock", "share", "equity", "ticker",
1435
+ )
1436
+
1437
+
1438
+ def _is_realty_query(message: str) -> bool:
1439
+ """Return True when the message is about real-estate / housing, not stocks.
1440
+
1441
+ Three patterns count:
1442
+ (a) explicit realty keywords (房价, 楼市, 租金 …)
1443
+ (b) CN city name + generic market/price/trend words with no stock-specific keywords
1444
+ (c) international city name + realty keyword
1445
+ """
1446
+ low = message.lower()
1447
+ # (a) Explicit realty keyword present
1448
+ if any(k in low for k in _REALTY_QUERY_KEYWORDS):
1449
+ return True
1450
+ # (b) CN city + market/trend word but NO stock-specific word
1451
+ has_cn_city = any(c in message for c in _CN_CITIES)
1452
+ has_market_word = any(k in low for k in ("市场", "价格", "走势", "趋势", "现在", "最新"))
1453
+ has_stock_word = any(k in low for k in _STOCK_ONLY_MARKET_WORDS)
1454
+ if has_cn_city and has_market_word and not has_stock_word:
1455
+ return True
1456
+ # (c) International city + realty/housing word
1457
+ has_intl_city = any(c in low for c in _INTL_CITIES)
1458
+ if has_intl_city and has_market_word and not has_stock_word:
1459
+ return True
1460
+ return False
1461
+
1462
+
1463
+ def _detect_market_overview(message: str):
1464
+ """Detect a whole-market overview request → 'cn' | 'us' | 'hk' | None.
1465
+
1466
+ Critical disambiguation: "分析A股市场行情" means the A-share MARKET (indices),
1467
+ NOT a stock with ticker 'A' (Agilent). This must run before any ticker
1468
+ extraction so a market name never collapses into a wrong single stock.
1469
+ Returns None for coding/chart/realty requests or specific-stock queries.
1470
+ """
1471
+ if _is_realty_query(message):
1472
+ return None
1473
+ low = message.lower().strip()
1474
+ _coding_kw = ("write", "generate", "script", "code", "plot", "backtest",
1475
+ "策略", "代码", "回测", "编写", "k线", "kline", "python", "dashboard")
1476
+ if any(k in low for k in _coding_kw):
1477
+ return None
1478
+ if _is_stock_chart_analysis_request(message) or _is_visual_market_artifact_request(message):
1479
+ return None
1480
+
1481
+ # Market-level intent words (overview, not a single stock)
1482
+ _overview_kw = (
1483
+ "市场", "大盘", "行情", "今天怎么样", "今日", "现在怎么样", "怎么样",
1484
+ "收盘", "开盘", "走势", "整体", "表现", "概况", "overview", "market",
1485
+ )
1486
+ if not any(k in low for k in _overview_kw):
1487
+ return None
1488
+
1489
+ # Market identifiers (ordered: check before generic '大盘' defaults to CN)
1490
+ _us = ("美股", "美国股市", "美国市场", "华尔街", "纳斯达克", "纳指", "标普",
1491
+ "道琼斯", "道指", "us market", "wall street", "nasdaq", "s&p", "dow")
1492
+ _hk = ("港股", "恒生", "香港股市", "香港市场", "hk market", "hang seng", "hsi")
1493
+ _cn = ("a股", "a 股", "沪深", "上证", "深证", "创业板", "沪市", "深市",
1494
+ "科创板", "a-share", "ashare", "china market", "中国股市", "大盘")
1495
+
1496
+ if any(k in low for k in _us):
1497
+ return "us"
1498
+ if any(k in low for k in _hk):
1499
+ return "hk"
1500
+ if any(k in low for k in _cn):
1501
+ return "cn"
1502
+ return None
1503
+
1504
+
1505
+ def _is_market_snapshot_request(message: str, history: list = None) -> bool:
1506
+ """Return True for simple quote / market snapshot analysis requests.
1507
+
1508
+ 跟进问题("现在的股票和趋势呢"):当前消息无标的时回溯会话历史继承。
1509
+ 但如果消息中包含未能识别的公司名,则不视为跟进问题(不继承历史标的)。
1510
+ 房价/楼市类问题直接拒绝,防止继承上一个股票代码。
1511
+ """
1512
+ # Real-estate questions must never be routed to the stock snapshot path
1513
+ if _is_realty_query(message):
1514
+ return False
1515
+ # Pre-built prompts from cmd_analyze contain specific footer text — bypass snapshot
1516
+ if ("Please provide a comprehensive analysis of" in message
1517
+ or "请对以上" in message and "进行综合分析" in message):
1518
+ return False
1519
+ # Inline check (avoids circular import with aria_cli.py)
1520
+ _coding_kw = ("write", "generate", "create", "script", "code", "plot",
1521
+ "backtest", "策略", "代码", "回测", "编写", "生成", "k线",
1522
+ "k-line", "kline", "python", "dashboard", "写一个", "写代码")
1523
+ _is_coding = any(k in message.lower() for k in _coding_kw)
1524
+ if _is_coding or _is_stock_chart_analysis_request(message) or _is_visual_market_artifact_request(message):
1525
+ return False
1526
+ low = message.lower()
1527
+ has_market_word = any(k in low for k in (
1528
+ "市场", "行情", "股价", "价格", "涨跌", "涨幅", "现价", "今天",
1529
+ "现在", "最新", "分析", "走势", "趋势", "market", "quote", "price",
1530
+ ))
1531
+ if not has_market_word:
1532
+ return False
1533
+ if _extract_market_symbol(message):
1534
+ return True
1535
+ # Only inherit history if the message doesn't name an unresolvable company
1536
+ if _has_unresolved_company_mention(message):
1537
+ return False
1538
+ return bool(history and _extract_symbol_from_history(history))
1539
+
1540
+
1541
+ def _format_compact_market_cap(value, currency: str = "USD") -> str:
1542
+ try:
1543
+ cap = float(value)
1544
+ if cap <= 0:
1545
+ return "—"
1546
+ if cap >= 1e12:
1547
+ return f"{currency} {cap / 1e12:.2f}T"
1548
+ if cap >= 1e9:
1549
+ return f"{currency} {cap / 1e9:.1f}B"
1550
+ if cap >= 1e6:
1551
+ return f"{currency} {cap / 1e6:.0f}M"
1552
+ return f"{currency} {cap:,.0f}"
1553
+ except Exception:
1554
+ return "—"
1555
+
1556
+
1557
+ def _market_snapshot_trend(price, high, low, change_pct) -> str:
1558
+ try:
1559
+ p = float(price)
1560
+ h = float(high) if high not in (None, "", "—") else None
1561
+ l = float(low) if low not in (None, "", "—") else None
1562
+ c = float(change_pct) if change_pct not in (None, "", "—") else 0.0
1563
+ parts = []
1564
+ if h is not None and l is not None and h > l:
1565
+ pos = int((p - l) / (h - l) * 100)
1566
+ if pos >= 75:
1567
+ parts.append("near day high")
1568
+ elif pos <= 25:
1569
+ parts.append("near day low")
1570
+ else:
1571
+ parts.append("mid-range")
1572
+ if abs(c) >= 2:
1573
+ parts.append("strong up" if c > 0 else "strong down")
1574
+ elif c != 0:
1575
+ parts.append("up" if c > 0 else "down")
1576
+ return ", ".join(parts) or "flat"
1577
+ except Exception:
1578
+ return "—"