aria-code 0.52.0__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 (478) hide show
  1. aria_code/__init__.py +42 -0
  2. aria_code/_version.py +20 -0
  3. aria_code/adk_apps/__init__.py +1 -0
  4. aria_code/adk_apps/aria_code_review/__init__.py +1 -0
  5. aria_code/adk_apps/aria_code_review/agent.py +32 -0
  6. aria_code/adk_apps/aria_research/__init__.py +1 -0
  7. aria_code/adk_apps/aria_research/agent.py +31 -0
  8. aria_code/agents/__init__.py +34 -0
  9. aria_code/agents/base.py +318 -0
  10. aria_code/agents/code_review.py +200 -0
  11. aria_code/agents/deep/__init__.py +37 -0
  12. aria_code/agents/deep/calibration_loop.py +144 -0
  13. aria_code/agents/deep/critic.py +125 -0
  14. aria_code/agents/deep/deepen.py +193 -0
  15. aria_code/agents/deep/models.py +149 -0
  16. aria_code/agents/deep/pipeline.py +164 -0
  17. aria_code/agents/deep/quant_fusion.py +192 -0
  18. aria_code/agents/deep/themes.py +95 -0
  19. aria_code/agents/deep/tiers.py +106 -0
  20. aria_code/agents/engineering/__init__.py +14 -0
  21. aria_code/agents/engineering/coder.py +222 -0
  22. aria_code/agents/engineering/tester.py +145 -0
  23. aria_code/agents/financial/__init__.py +21 -0
  24. aria_code/agents/financial/cashflow_burn.py +119 -0
  25. aria_code/agents/financial/catalyst.py +279 -0
  26. aria_code/agents/financial/corporate_finance.py +175 -0
  27. aria_code/agents/financial/debate.py +224 -0
  28. aria_code/agents/financial/earnings.py +303 -0
  29. aria_code/agents/financial/fundamental.py +191 -0
  30. aria_code/agents/financial/macro.py +99 -0
  31. aria_code/agents/financial/news.py +207 -0
  32. aria_code/agents/financial/risk.py +165 -0
  33. aria_code/agents/financial/sector.py +279 -0
  34. aria_code/agents/financial/strategist.py +121 -0
  35. aria_code/agents/financial/stripe_revenue.py +127 -0
  36. aria_code/agents/financial/synthesis.py +274 -0
  37. aria_code/agents/financial/technical.py +258 -0
  38. aria_code/agents/orchestrator.py +44 -0
  39. aria_code/agents/portfolio_agent.py +363 -0
  40. aria_code/agents/realty/__init__.py +62 -0
  41. aria_code/agents/realty/asset_diagnosis.py +152 -0
  42. aria_code/agents/realty/business_match.py +166 -0
  43. aria_code/agents/realty/cashflow_verify.py +209 -0
  44. aria_code/agents/realty/contract_rules.py +210 -0
  45. aria_code/agents/realty/energy_anomaly.py +189 -0
  46. aria_code/agents/realty/exit_settlement.py +208 -0
  47. aria_code/agents/realty/fulfillment_risk.py +206 -0
  48. aria_code/agents/realty/ops_optimize.py +160 -0
  49. aria_code/agents/realty/revenue_share.py +215 -0
  50. aria_code/agents/registry.py +158 -0
  51. aria_code/agents/signal_scheme.py +111 -0
  52. aria_code/agents/sports/__init__.py +0 -0
  53. aria_code/agents/sports/football_agent.py +197 -0
  54. aria_code/agents/supervisor.py +113 -0
  55. aria_code/agents/team.py +492 -0
  56. aria_code/agents/warehouse/__init__.py +25 -0
  57. aria_code/agents/warehouse/contracts.py +26 -0
  58. aria_code/agents/warehouse/dashboard.py +155 -0
  59. aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
  60. aria_code/agents/warehouse/inbound_exceptions.py +25 -0
  61. aria_code/agents/warehouse/inventory_health.py +24 -0
  62. aria_code/agents/warehouse/local_run.py +71 -0
  63. aria_code/agents/warehouse/local_snapshot.py +132 -0
  64. aria_code/agents/warehouse/logistics_cost.py +84 -0
  65. aria_code/agents/warehouse/logistics_sync.py +21 -0
  66. aria_code/agents/warehouse/workflow.py +31 -0
  67. aria_code/aliyun_data_client.py +4 -0
  68. aria_code/apps/README.md +12 -0
  69. aria_code/apps/__init__.py +2 -0
  70. aria_code/apps/channels/README.md +15 -0
  71. aria_code/apps/channels/__init__.py +17 -0
  72. aria_code/apps/channels/intake.py +92 -0
  73. aria_code/apps/channels/registry.py +95 -0
  74. aria_code/apps/channels/tradingview.py +94 -0
  75. aria_code/apps/cli/README.md +13 -0
  76. aria_code/apps/cli/__init__.py +2 -0
  77. aria_code/apps/cli/_optional.py +120 -0
  78. aria_code/apps/cli/bootstrap.py +145 -0
  79. aria_code/apps/cli/broker_render.py +206 -0
  80. aria_code/apps/cli/codegen_paths.py +29 -0
  81. aria_code/apps/cli/commands/__init__.py +16 -0
  82. aria_code/apps/cli/commands/_business_render.py +78 -0
  83. aria_code/apps/cli/commands/_ui.py +71 -0
  84. aria_code/apps/cli/commands/analysis_cmds.py +369 -0
  85. aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
  86. aria_code/apps/cli/commands/auth_cmds.py +239 -0
  87. aria_code/apps/cli/commands/backtest_cmds.py +2543 -0
  88. aria_code/apps/cli/commands/broker_cmds.py +1234 -0
  89. aria_code/apps/cli/commands/business_workflow_cmds.py +319 -0
  90. aria_code/apps/cli/commands/canvas_cmds.py +77 -0
  91. aria_code/apps/cli/commands/catalog.py +87 -0
  92. aria_code/apps/cli/commands/core_cmds.py +2520 -0
  93. aria_code/apps/cli/commands/data_cmds.py +457 -0
  94. aria_code/apps/cli/commands/diagnostic_cmds.py +230 -0
  95. aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
  96. aria_code/apps/cli/commands/file_cmds.py +312 -0
  97. aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
  98. aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
  99. aria_code/apps/cli/commands/market.py +434 -0
  100. aria_code/apps/cli/commands/market_cmds.py +1381 -0
  101. aria_code/apps/cli/commands/market_context.py +425 -0
  102. aria_code/apps/cli/commands/market_render.py +7 -0
  103. aria_code/apps/cli/commands/model_cmds.py +2153 -0
  104. aria_code/apps/cli/commands/ops_cmds.py +721 -0
  105. aria_code/apps/cli/commands/orchestrator_cmds.py +179 -0
  106. aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
  107. aria_code/apps/cli/commands/portfolio_cmds.py +1278 -0
  108. aria_code/apps/cli/commands/report.py +437 -0
  109. aria_code/apps/cli/commands/scaffold_templates.py +617 -0
  110. aria_code/apps/cli/commands/session_cmds.py +236 -0
  111. aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
  112. aria_code/apps/cli/commands/team.py +669 -0
  113. aria_code/apps/cli/commands/team_render.py +8 -0
  114. aria_code/apps/cli/commands/ui_cmds.py +401 -0
  115. aria_code/apps/cli/commands/warehouse_cmds.py +79 -0
  116. aria_code/apps/cli/commands/workflow_cmds.py +480 -0
  117. aria_code/apps/cli/commands/workspace_cmds.py +1494 -0
  118. aria_code/apps/cli/config_paths.py +73 -0
  119. aria_code/apps/cli/config_store.py +78 -0
  120. aria_code/apps/cli/constants.py +88 -0
  121. aria_code/apps/cli/context.py +34 -0
  122. aria_code/apps/cli/deterministic.py +191 -0
  123. aria_code/apps/cli/direct.py +48 -0
  124. aria_code/apps/cli/finance_service_catalog.py +111 -0
  125. aria_code/apps/cli/football_reports.py +231 -0
  126. aria_code/apps/cli/github_app_auth.py +135 -0
  127. aria_code/apps/cli/google_login.py +196 -0
  128. aria_code/apps/cli/handlers/__init__.py +11 -0
  129. aria_code/apps/cli/handlers/broker_handlers.py +122 -0
  130. aria_code/apps/cli/handlers/chart_handlers.py +1309 -0
  131. aria_code/apps/cli/handlers/market_handlers.py +2513 -0
  132. aria_code/apps/cli/handlers/realty_handlers.py +114 -0
  133. aria_code/apps/cli/handlers/sports_handlers.py +22 -0
  134. aria_code/apps/cli/handlers/strategy_advice.py +82 -0
  135. aria_code/apps/cli/helpers.py +302 -0
  136. aria_code/apps/cli/hooks.py +180 -0
  137. aria_code/apps/cli/i18n.py +297 -0
  138. aria_code/apps/cli/intent.py +136 -0
  139. aria_code/apps/cli/intent_router.py +276 -0
  140. aria_code/apps/cli/intent_signals.py +344 -0
  141. aria_code/apps/cli/lifecycle_hooks.py +48 -0
  142. aria_code/apps/cli/local_tool_schemas.py +536 -0
  143. aria_code/apps/cli/main.py +29 -0
  144. aria_code/apps/cli/market_metadata.py +135 -0
  145. aria_code/apps/cli/market_universe.py +487 -0
  146. aria_code/apps/cli/message_processing.py +310 -0
  147. aria_code/apps/cli/model_catalog.py +620 -0
  148. aria_code/apps/cli/pdf_report.py +977 -0
  149. aria_code/apps/cli/plan_mode.py +139 -0
  150. aria_code/apps/cli/plotly_html.py +15 -0
  151. aria_code/apps/cli/prediction_feedback.py +202 -0
  152. aria_code/apps/cli/preflight.py +502 -0
  153. aria_code/apps/cli/project_aria.py +60 -0
  154. aria_code/apps/cli/prompt_assembly.py +59 -0
  155. aria_code/apps/cli/prompts/__init__.py +0 -0
  156. aria_code/apps/cli/prompts/coding.py +53 -0
  157. aria_code/apps/cli/prompts/select.py +142 -0
  158. aria_code/apps/cli/prompts/system_prompts.py +552 -0
  159. aria_code/apps/cli/prompts/ui.py +436 -0
  160. aria_code/apps/cli/provider_endpoints.py +237 -0
  161. aria_code/apps/cli/provider_keys.py +73 -0
  162. aria_code/apps/cli/providers/__init__.py +1 -0
  163. aria_code/apps/cli/providers/base.py +521 -0
  164. aria_code/apps/cli/providers/chat_routing.py +128 -0
  165. aria_code/apps/cli/providers/collaboration.py +98 -0
  166. aria_code/apps/cli/providers/llm/__init__.py +1 -0
  167. aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
  168. aria_code/apps/cli/providers/llm/ollama_stream.py +1594 -0
  169. aria_code/apps/cli/providers/llm/response_cache.py +60 -0
  170. aria_code/apps/cli/providers/llm/sse_stream.py +257 -0
  171. aria_code/apps/cli/providers/runtime_bridge.py +353 -0
  172. aria_code/apps/cli/providers/vertexai_stream.py +316 -0
  173. aria_code/apps/cli/runtime_consumer.py +657 -0
  174. aria_code/apps/cli/sandbox.py +32 -0
  175. aria_code/apps/cli/session_export.py +120 -0
  176. aria_code/apps/cli/session_jsonl.py +208 -0
  177. aria_code/apps/cli/session_store.py +112 -0
  178. aria_code/apps/cli/skills_catalog.py +273 -0
  179. aria_code/apps/cli/todo_tracker.py +190 -0
  180. aria_code/apps/cli/tool_executor.py +823 -0
  181. aria_code/apps/cli/tool_registry.py +34 -0
  182. aria_code/apps/cli/tool_scope.py +106 -0
  183. aria_code/apps/cli/tools/__init__.py +40 -0
  184. aria_code/apps/cli/tools/context.py +46 -0
  185. aria_code/apps/cli/tools/file_tools.py +180 -0
  186. aria_code/apps/cli/tools/market_tools.py +620 -0
  187. aria_code/apps/cli/tools/notebook_tools.py +111 -0
  188. aria_code/apps/cli/tools/system_tools.py +726 -0
  189. aria_code/apps/cli/tools/write_tools.py +845 -0
  190. aria_code/apps/cli/tradingview_bridge.py +529 -0
  191. aria_code/apps/cli/turn_planning.py +137 -0
  192. aria_code/apps/cli/update_check.py +153 -0
  193. aria_code/apps/cli/utils/__init__.py +0 -0
  194. aria_code/apps/cli/utils/market_detect.py +1597 -0
  195. aria_code/apps/daemon/README.md +14 -0
  196. aria_code/apps/vscode/README.md +115 -0
  197. aria_code/apps/vscode/package.json +70 -0
  198. aria_code/aria_cli.py +6743 -0
  199. aria_code/aria_daemon.py +1398 -0
  200. aria_code/aria_feishu_bot.py +1423 -0
  201. aria_code/aria_mcp_server.py +7 -0
  202. aria_code/aria_relay_client.py +4 -0
  203. aria_code/aria_relay_server.py +490 -0
  204. aria_code/aria_telegram_bot.py +202 -0
  205. aria_code/ariarc.py +385 -0
  206. aria_code/artifacts.py +581 -0
  207. aria_code/backtest_engine.py +4 -0
  208. aria_code/backtest_report.py +4 -0
  209. aria_code/brokers/__init__.py +78 -0
  210. aria_code/brokers/automation.py +193 -0
  211. aria_code/brokers/base.py +231 -0
  212. aria_code/brokers/capabilities.py +264 -0
  213. aria_code/brokers/cn/__init__.py +10 -0
  214. aria_code/brokers/cn/easytrader_broker.py +193 -0
  215. aria_code/brokers/cn/futu_broker.py +194 -0
  216. aria_code/brokers/cn/longbridge_broker.py +190 -0
  217. aria_code/brokers/cn/tiger_broker.py +196 -0
  218. aria_code/brokers/cn/xtquant_broker.py +225 -0
  219. aria_code/brokers/config.py +414 -0
  220. aria_code/brokers/intl/__init__.py +5 -0
  221. aria_code/brokers/intl/alpaca_broker.py +183 -0
  222. aria_code/brokers/intl/ibkr_broker.py +215 -0
  223. aria_code/brokers/intl/trading212_broker.py +183 -0
  224. aria_code/brokers/intl/webull_broker.py +156 -0
  225. aria_code/brokers/paper_broker.py +266 -0
  226. aria_code/brokers/planning.py +296 -0
  227. aria_code/brokers/registry.py +213 -0
  228. aria_code/brokers/trading.py +267 -0
  229. aria_code/canva_client.py +4 -0
  230. aria_code/change_store.py +127 -0
  231. aria_code/clients/__init__.py +0 -0
  232. aria_code/clients/aliyun_data_client.py +660 -0
  233. aria_code/clients/aria_relay_client.py +182 -0
  234. aria_code/clients/canva_client.py +284 -0
  235. aria_code/clients/figma_client.py +100 -0
  236. aria_code/clients/football_data_client.py +1671 -0
  237. aria_code/clients/kling_video_client.py +182 -0
  238. aria_code/clients/market_data_client.py +1976 -0
  239. aria_code/clients/openai_image_client.py +242 -0
  240. aria_code/clients/runway_video_client.py +157 -0
  241. aria_code/clients/warehouse_erp_client.py +105 -0
  242. aria_code/cloud_memory.py +181 -0
  243. aria_code/command_safety.py +19 -0
  244. aria_code/computer_use_tools.py +4 -0
  245. aria_code/dashboard_generator.py +579 -0
  246. aria_code/data_analysis_tools.py +4 -0
  247. aria_code/data_cleaner.py +483 -0
  248. aria_code/data_service.py +513 -0
  249. aria_code/datasources/__init__.py +23 -0
  250. aria_code/datasources/base.py +166 -0
  251. aria_code/datasources/router.py +253 -0
  252. aria_code/datasources/sources/__init__.py +15 -0
  253. aria_code/datasources/sources/akshare_source.py +269 -0
  254. aria_code/datasources/sources/alpha_vantage_source.py +203 -0
  255. aria_code/datasources/sources/edgar_source.py +219 -0
  256. aria_code/datasources/sources/finnhub_source.py +198 -0
  257. aria_code/datasources/sources/fred_source.py +223 -0
  258. aria_code/datasources/sources/tushare_source.py +143 -0
  259. aria_code/datasources/sources/web_scraper_source.py +278 -0
  260. aria_code/datasources/sources/world_bank_source.py +206 -0
  261. aria_code/datasources/sources/yfinance_source.py +152 -0
  262. aria_code/demo_player.py +207 -0
  263. aria_code/doctor.py +813 -0
  264. aria_code/domain/__init__.py +0 -0
  265. aria_code/domain/backtest_engine.py +354 -0
  266. aria_code/domain/backtest_report.py +472 -0
  267. aria_code/domain/finance_formulas.py +389 -0
  268. aria_code/domain/portfolio_ledger.py +307 -0
  269. aria_code/domain/strategy_vault.py +421 -0
  270. aria_code/evals/__init__.py +37 -0
  271. aria_code/evals/harness.py +598 -0
  272. aria_code/evals/runner.py +238 -0
  273. aria_code/external_agent_runner.py +115 -0
  274. aria_code/figma_client.py +4 -0
  275. aria_code/file_analysis_tools.py +4 -0
  276. aria_code/finance_formulas.py +4 -0
  277. aria_code/football_data_client.py +4 -0
  278. aria_code/image_gen_tools.py +4 -0
  279. aria_code/intent_classifier.py +386 -0
  280. aria_code/kling_video_client.py +4 -0
  281. aria_code/licensing.py +151 -0
  282. aria_code/local_finance_tools.py +4 -0
  283. aria_code/local_image_provider.py +4 -0
  284. aria_code/local_llm_provider.py +4 -0
  285. aria_code/macro_tools.py +4 -0
  286. aria_code/markdown_pdf.py +289 -0
  287. aria_code/market_data_client.py +4 -0
  288. aria_code/mcp_client.py +639 -0
  289. aria_code/memory_manager.py +246 -0
  290. aria_code/model_capability.py +486 -0
  291. aria_code/notification_tools.py +4 -0
  292. aria_code/openai_image_client.py +4 -0
  293. aria_code/packages/__init__.py +23 -0
  294. aria_code/packages/adk_bridge/__init__.py +11 -0
  295. aria_code/packages/adk_bridge/code_review_tools.py +49 -0
  296. aria_code/packages/adk_bridge/market_tools.py +125 -0
  297. aria_code/packages/aria_agents/__init__.py +5 -0
  298. aria_code/packages/aria_agents/manifest.py +78 -0
  299. aria_code/packages/aria_core/__init__.py +34 -0
  300. aria_code/packages/aria_core/architecture.py +269 -0
  301. aria_code/packages/aria_core/export.py +147 -0
  302. aria_code/packages/aria_core/manifest.py +65 -0
  303. aria_code/packages/aria_core/paths.py +57 -0
  304. aria_code/packages/aria_core/secure_file.py +68 -0
  305. aria_code/packages/aria_infra/__init__.py +15 -0
  306. aria_code/packages/aria_infra/arthera.py +52 -0
  307. aria_code/packages/aria_infra/doctor.py +246 -0
  308. aria_code/packages/aria_infra/product.py +40 -0
  309. aria_code/packages/aria_mcp/__init__.py +25 -0
  310. aria_code/packages/aria_mcp/bridge.py +201 -0
  311. aria_code/packages/aria_mcp/circuit.py +82 -0
  312. aria_code/packages/aria_mcp/config.py +111 -0
  313. aria_code/packages/aria_mcp/installer.py +95 -0
  314. aria_code/packages/aria_mcp/server.py +1550 -0
  315. aria_code/packages/aria_mcp/tools.py +81 -0
  316. aria_code/packages/aria_sdk/__init__.py +19 -0
  317. aria_code/packages/aria_sdk/client.py +396 -0
  318. aria_code/packages/aria_sdk/providers.py +70 -0
  319. aria_code/packages/aria_sdk/streaming.py +79 -0
  320. aria_code/packages/aria_sdk/types.py +86 -0
  321. aria_code/packages/aria_services/__init__.py +92 -0
  322. aria_code/packages/aria_services/cache.py +60 -0
  323. aria_code/packages/aria_services/context.py +356 -0
  324. aria_code/packages/aria_services/context_checkpoints.py +157 -0
  325. aria_code/packages/aria_services/data.py +11 -0
  326. aria_code/packages/aria_services/provider_health.py +268 -0
  327. aria_code/packages/aria_services/references.py +247 -0
  328. aria_code/packages/aria_services/registry.py +249 -0
  329. aria_code/packages/aria_services/research_protocol.py +57 -0
  330. aria_code/packages/aria_services/research_quality.py +186 -0
  331. aria_code/packages/aria_services/settings.py +157 -0
  332. aria_code/packages/aria_services/usage.py +151 -0
  333. aria_code/packages/aria_skills/__init__.py +46 -0
  334. aria_code/packages/aria_skills/catalog.py +149 -0
  335. aria_code/packages/aria_skills/loader.py +455 -0
  336. aria_code/packages/aria_skills/registry.py +84 -0
  337. aria_code/packages/aria_tools/__init__.py +5 -0
  338. aria_code/packages/aria_tools/financial/__init__.py +8 -0
  339. aria_code/packages/aria_tools/financial/_paths.py +130 -0
  340. aria_code/packages/aria_tools/financial/compliance.py +31 -0
  341. aria_code/packages/aria_tools/financial/factors.py +33 -0
  342. aria_code/packages/aria_tools/financial/risk_tools.py +33 -0
  343. aria_code/packages/aria_tools/financial/strategy.py +31 -0
  344. aria_code/packages/aria_tools/registry.py +128 -0
  345. aria_code/packages/quant_engine/__init__.py +34 -0
  346. aria_code/packages/quant_engine/_namespace.py +62 -0
  347. aria_code/packages/quant_engine/backtest/__init__.py +14 -0
  348. aria_code/packages/quant_engine/backtest/core.py +53 -0
  349. aria_code/packages/quant_engine/backtest/engine.py +96 -0
  350. aria_code/packages/quant_engine/portfolio/__init__.py +13 -0
  351. aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
  352. aria_code/packages/quant_engine/sports/__init__.py +80 -0
  353. aria_code/packages/quant_engine/sports/calibrator.py +354 -0
  354. aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
  355. aria_code/packages/quant_engine/sports/elo.py +300 -0
  356. aria_code/packages/quant_engine/sports/form.py +188 -0
  357. aria_code/packages/quant_engine/sports/h2h.py +195 -0
  358. aria_code/packages/quant_engine/sports/ml_model.py +377 -0
  359. aria_code/packages/quant_engine/sports/predictor.py +312 -0
  360. aria_code/packages/quant_engine/sports/tracker.py +665 -0
  361. aria_code/packages/quant_engine/stochastic/__init__.py +35 -0
  362. aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  363. aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
  364. aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  365. aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  366. aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
  367. aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  368. aria_code/packs/__init__.py +85 -0
  369. aria_code/packs/base.py +168 -0
  370. aria_code/packs/finance/__init__.py +250 -0
  371. aria_code/packs/logistics/__init__.py +169 -0
  372. aria_code/packs/payments/__init__.py +139 -0
  373. aria_code/packs/realty/__init__.py +190 -0
  374. aria_code/packs/registry.py +202 -0
  375. aria_code/packs/rules.py +90 -0
  376. aria_code/plan_utils.py +194 -0
  377. aria_code/plugin_loader.py +328 -0
  378. aria_code/portfolio_ledger.py +4 -0
  379. aria_code/preview_server.py +449 -0
  380. aria_code/privacy/__init__.py +5 -0
  381. aria_code/privacy/feedback.py +123 -0
  382. aria_code/project_review.py +278 -0
  383. aria_code/project_review_api.py +235 -0
  384. aria_code/project_review_server.py +18 -0
  385. aria_code/project_tools.py +4 -0
  386. aria_code/providers/__init__.py +30 -0
  387. aria_code/providers/llm/__init__.py +19 -0
  388. aria_code/providers/llm/anthropic.py +184 -0
  389. aria_code/providers/llm/autoconfig.py +213 -0
  390. aria_code/providers/llm/base.py +139 -0
  391. aria_code/providers/llm/custom.py +158 -0
  392. aria_code/providers/llm/ollama.py +129 -0
  393. aria_code/providers/llm/openai_compat.py +313 -0
  394. aria_code/providers/llm/registry.py +444 -0
  395. aria_code/providers/local_image_provider.py +202 -0
  396. aria_code/providers/local_llm_provider.py +589 -0
  397. aria_code/realty_data_tools.py +4 -0
  398. aria_code/report_exporters.py +150 -0
  399. aria_code/report_generator.py +1764 -0
  400. aria_code/runtime/__init__.py +169 -0
  401. aria_code/runtime/acceptance.py +481 -0
  402. aria_code/runtime/agent_loop.py +1520 -0
  403. aria_code/runtime/approval.py +57 -0
  404. aria_code/runtime/budget.py +232 -0
  405. aria_code/runtime/checkpoints.py +432 -0
  406. aria_code/runtime/crash_report.py +174 -0
  407. aria_code/runtime/events.py +113 -0
  408. aria_code/runtime/gateway.py +150 -0
  409. aria_code/runtime/lsp.py +484 -0
  410. aria_code/runtime/orchestrator.py +55 -0
  411. aria_code/runtime/repo_map.py +700 -0
  412. aria_code/runtime/run_state.py +127 -0
  413. aria_code/runtime/run_store.py +450 -0
  414. aria_code/runtime/self_healing.py +422 -0
  415. aria_code/runtime/subagent.py +547 -0
  416. aria_code/runtime/task_ledger.py +86 -0
  417. aria_code/runtime/task_orchestrator.py +90 -0
  418. aria_code/runtime/tool_executor.py +217 -0
  419. aria_code/runtime/tool_policy.py +107 -0
  420. aria_code/runtime/worktrees.py +215 -0
  421. aria_code/runway_video_client.py +4 -0
  422. aria_code/safety/__init__.py +23 -0
  423. aria_code/safety/permissions.py +306 -0
  424. aria_code/safety/service.py +102 -0
  425. aria_code/setup_wizard.py +673 -0
  426. aria_code/spreadsheet_tools.py +4 -0
  427. aria_code/strategy_vault.py +4 -0
  428. aria_code/tools/__init__.py +0 -0
  429. aria_code/tools/artifact_tools.py +252 -0
  430. aria_code/tools/broker_tools.py +92 -0
  431. aria_code/tools/build_quant_engine.py +97 -0
  432. aria_code/tools/code_audit_tools.py +192 -0
  433. aria_code/tools/computer_use_tools.py +504 -0
  434. aria_code/tools/data_analysis_tools.py +809 -0
  435. aria_code/tools/enterprise_finance_tools.py +156 -0
  436. aria_code/tools/extended_tools.py +240 -0
  437. aria_code/tools/file_analysis_tools.py +885 -0
  438. aria_code/tools/image_gen_tools.py +114 -0
  439. aria_code/tools/local_finance_tools.py +3417 -0
  440. aria_code/tools/logistics_tools.py +135 -0
  441. aria_code/tools/macro_tools.py +369 -0
  442. aria_code/tools/notification_tools.py +249 -0
  443. aria_code/tools/project_tools.py +525 -0
  444. aria_code/tools/realty_data_tools.py +659 -0
  445. aria_code/tools/spreadsheet_tools.py +388 -0
  446. aria_code/tools/stripe_tools.py +115 -0
  447. aria_code/ui/__init__.py +105 -0
  448. aria_code/ui/banner.py +508 -0
  449. aria_code/ui/completer.py +501 -0
  450. aria_code/ui/console.py +281 -0
  451. aria_code/ui/image_render.py +243 -0
  452. aria_code/ui/input_box.py +488 -0
  453. aria_code/ui/picker.py +201 -0
  454. aria_code/ui/render/__init__.py +11 -0
  455. aria_code/ui/render/finance.py +1705 -0
  456. aria_code/ui/render/market.py +225 -0
  457. aria_code/ui/render/output.py +775 -0
  458. aria_code/ui/render/responsive.py +75 -0
  459. aria_code/ui/render/team.py +354 -0
  460. aria_code/ui/robot.py +235 -0
  461. aria_code/ui/startup_dashboard.py +109 -0
  462. aria_code/utils/vscode_uri.py +37 -0
  463. aria_code/video_analysis.py +147 -0
  464. aria_code/video_editor.py +271 -0
  465. aria_code/workspace/__init__.py +6 -0
  466. aria_code/workspace/files.py +193 -0
  467. aria_code/workspace/verify.py +113 -0
  468. aria_code/workspace_changes.py +199 -0
  469. aria_code/workspace_context.py +233 -0
  470. aria_code/workspace_service.py +196 -0
  471. aria_code/workspace_tools.py +150 -0
  472. aria_code-0.52.0.dist-info/METADATA +237 -0
  473. aria_code-0.52.0.dist-info/RECORD +478 -0
  474. aria_code-0.52.0.dist-info/WHEEL +5 -0
  475. aria_code-0.52.0.dist-info/entry_points.txt +3 -0
  476. aria_code-0.52.0.dist-info/licenses/LICENSE +201 -0
  477. aria_code-0.52.0.dist-info/licenses/NOTICE +49 -0
  478. aria_code-0.52.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,207 @@
1
+ """
2
+ agents/financial/news.py — 新闻与舆情分析 Agent
3
+ ================================================
4
+ 分析近期新闻标题、公告和媒体情绪,识别关键事件类型。
5
+ 数据源:yfinance.news(美股/港股)、akshare公告(A股,可选)
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import re
12
+ from datetime import datetime, timezone
13
+ from typing import Any, Dict, List
14
+
15
+ from ..base import BaseAgent, AgentResult
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ _EVENT_KEYWORDS = {
20
+ "earnings": ["earnings", "revenue", "profit", "EPS", "业绩", "净利润", "营收"],
21
+ "upgrade": ["upgrade", "outperform", "buy", "overweight", "上调", "买入"],
22
+ "downgrade": ["downgrade", "underperform", "sell", "underweight", "下调", "卖出"],
23
+ "insider": ["insider", "director", "CEO", "增持", "减持", "大股东"],
24
+ "regulatory": ["SEC", "CSRC", "证监会", "regulatory", "investigation", "调查"],
25
+ "dividend": ["dividend", "分红", "派息", "股息"],
26
+ "merger": ["merger", "acquisition", "takeover", "合并", "收购"],
27
+ }
28
+
29
+
30
+ class NewsAgent(BaseAgent):
31
+
32
+ name = "news"
33
+ description = "新闻与舆情分析 — 近期重大事件、公告、媒体情绪"
34
+
35
+ _SYSTEM = (
36
+ "You are a financial news analyst. Analyze the provided news headlines "
37
+ "and summaries for a stock. Identify the most impactful recent events, "
38
+ "classify sentiment (POSITIVE / NEUTRAL / NEGATIVE), and assess likely "
39
+ "price impact. Focus on material events: earnings, analyst changes, "
40
+ "regulatory actions, M&A, and insider activity. "
41
+ "Conclude with: POSITIVE / NEUTRAL / NEGATIVE"
42
+ )
43
+
44
+ async def fetch_data(self, symbol: str) -> Dict[str, Any]:
45
+ data = await super().fetch_data(symbol)
46
+ news_items: List[Dict] = []
47
+
48
+ # 1. yfinance news (works for US / HK stocks)
49
+ try:
50
+ import yfinance as yf
51
+ ticker = yf.Ticker(symbol)
52
+ raw = ticker.news or []
53
+ now = datetime.now(timezone.utc).timestamp()
54
+ for item in raw[:12]:
55
+ pub = item.get("providerPublishTime", 0)
56
+ age_days = (now - pub) / 86400 if pub else 999
57
+ news_items.append({
58
+ "title": item.get("title", ""),
59
+ "summary": item.get("summary", "") or item.get("description", ""),
60
+ "source": item.get("publisher", ""),
61
+ "age_days": round(age_days, 1),
62
+ })
63
+ except Exception as e:
64
+ logger.debug("[news] yfinance fetch failed for %s: %s", symbol, e)
65
+
66
+ # 2. akshare A-share announcements (optional)
67
+ if not news_items and re.match(r"^[036]\d{5}$", symbol):
68
+ try:
69
+ import akshare as ak
70
+ df = ak.stock_news_em(symbol=symbol)
71
+ if df is not None and not df.empty:
72
+ for _, row in df.head(10).iterrows():
73
+ news_items.append({
74
+ "title": str(row.get("新闻标题", "")),
75
+ "summary": str(row.get("新闻内容", ""))[:200],
76
+ "source": str(row.get("新闻来源", "")),
77
+ "age_days": 0,
78
+ })
79
+ except Exception as e:
80
+ logger.debug("[news] akshare fetch failed for %s: %s", symbol, e)
81
+
82
+ data["news"] = news_items
83
+ return data
84
+
85
+ async def analyze(self, symbol: str, data: Dict[str, Any]) -> AgentResult:
86
+ news = data.get("news", [])
87
+ quote = data.get("quote", {})
88
+ price = quote.get("price", 0)
89
+
90
+ if not news:
91
+ return AgentResult(
92
+ agent=self.name, symbol=symbol,
93
+ analysis=f"{symbol}: 未获取到近期新闻数据。",
94
+ confidence=0.3, signal="HOLD",
95
+ key_points=["无近期新闻数据"],
96
+ )
97
+
98
+ events = _classify_events(news)
99
+ news_block = _format_news(news[:8])
100
+
101
+ prompt = (
102
+ f"Stock: {symbol} Price: {price}\n\n"
103
+ f"Recent News ({len(news)} items):\n{news_block}\n\n"
104
+ "Tasks:\n"
105
+ "1. Identify the 2-3 most impactful recent events\n"
106
+ "2. Classify overall media sentiment\n"
107
+ "3. Assess potential price catalyst (short-term, 1-5 days)\n"
108
+ "4. Conclude with: POSITIVE / NEUTRAL / NEGATIVE"
109
+ )
110
+
111
+ analysis = await self._call_llm(self._SYSTEM, prompt, max_tokens=500)
112
+ if not analysis:
113
+ analysis = _template_analysis(symbol, news, events)
114
+
115
+ signal = _extract_signal(analysis, events)
116
+ confidence = _estimate_confidence(news, events)
117
+ key_points = _build_key_points(news, events)
118
+
119
+ return AgentResult(
120
+ agent=self.name, symbol=symbol,
121
+ analysis=analysis,
122
+ confidence=confidence,
123
+ signal=signal,
124
+ key_points=key_points,
125
+ data_used={"news_count": len(news), "events": list(events.keys())},
126
+ )
127
+
128
+
129
+ # ── Helpers ───────────────────────────────────────────────────────────────────
130
+
131
+ def _classify_events(news: List[Dict]) -> Dict[str, int]:
132
+ counts: Dict[str, int] = {}
133
+ for item in news:
134
+ text = (item.get("title", "") + " " + item.get("summary", "")).lower()
135
+ for event_type, keywords in _EVENT_KEYWORDS.items():
136
+ if any(kw.lower() in text for kw in keywords):
137
+ counts[event_type] = counts.get(event_type, 0) + 1
138
+ return counts
139
+
140
+
141
+ def _format_news(news: List[Dict]) -> str:
142
+ lines = []
143
+ for i, item in enumerate(news, 1):
144
+ age = f"{item['age_days']:.0f}d ago" if item.get("age_days", 999) < 30 else ""
145
+ src = item.get("source", "")
146
+ title = item.get("title", "").strip()
147
+ lines.append(f"{i}. [{src}] {title} {age}".strip())
148
+ return "\n".join(lines)
149
+
150
+
151
+ def _extract_signal(analysis: str, events: Dict[str, int]) -> str:
152
+ text = analysis.upper()
153
+ if "POSITIVE" in text:
154
+ if events.get("upgrade", 0) > 0 or "STRONG" in text:
155
+ return "BUY"
156
+ return "BUY"
157
+ if "NEGATIVE" in text:
158
+ if events.get("downgrade", 0) > 0 or events.get("regulatory", 0) > 0:
159
+ return "SELL"
160
+ return "SELL"
161
+ return "HOLD"
162
+
163
+
164
+ def _estimate_confidence(news: List[Dict], events: Dict[str, int]) -> float:
165
+ base = 0.45
166
+ recent = sum(1 for n in news if n.get("age_days", 999) <= 3)
167
+ base += min(recent * 0.05, 0.15)
168
+ if events.get("earnings") or events.get("upgrade") or events.get("downgrade"):
169
+ base += 0.1
170
+ return min(round(base, 2), 0.75)
171
+
172
+
173
+ def _build_key_points(news: List[Dict], events: Dict[str, int]) -> List[str]:
174
+ points = []
175
+ recent = [n for n in news if n.get("age_days", 999) <= 3]
176
+ if recent:
177
+ points.append(f"近3日 {len(recent)} 条新鲜新闻")
178
+ for etype, count in events.items():
179
+ label = {
180
+ "earnings": "业绩/财报相关",
181
+ "upgrade": "分析师升级评级",
182
+ "downgrade": "分析师降级评级",
183
+ "insider": "内部人士交易",
184
+ "regulatory": "监管/调查事件",
185
+ "dividend": "股息/分红公告",
186
+ "merger": "并购/重组消息",
187
+ }.get(etype, etype)
188
+ points.append(f"{label} × {count}")
189
+ return points[:5]
190
+
191
+
192
+ def _template_analysis(symbol: str, news: List[Dict], events: Dict[str, int]) -> str:
193
+ recent = [n for n in news if n.get("age_days", 999) <= 7]
194
+ sentiment = "中性"
195
+ if events.get("upgrade", 0) > events.get("downgrade", 0):
196
+ sentiment = "偏正面"
197
+ elif events.get("downgrade", 0) > 0 or events.get("regulatory", 0) > 0:
198
+ sentiment = "偏负面"
199
+
200
+ titles = "\n".join(f" • {n['title'][:60]}" for n in recent[:3])
201
+ return (
202
+ f"{symbol} 近期新闻情绪:{sentiment}\n"
203
+ f"近7日 {len(recent)} 条相关新闻\n"
204
+ f"主要标题:\n{titles or ' (无最新标题)'}\n"
205
+ f"事件分类:{', '.join(events.keys()) or '无特定事件'}\n"
206
+ f"结论:{'POSITIVE' if sentiment == '偏正面' else ('NEGATIVE' if sentiment == '偏负面' else 'NEUTRAL')}"
207
+ )
@@ -0,0 +1,165 @@
1
+ """
2
+ agents/financial/risk.py — 风险评估 Agent
3
+ ==========================================
4
+ 分析:波动率、最大回撤、与大盘相关性、仓位建议。
5
+ """
6
+ from __future__ import annotations
7
+ from typing import Any, Dict, List
8
+ from ..base import BaseAgent, AgentResult
9
+
10
+ try:
11
+ import numpy as np
12
+ _HAS_NP = True
13
+ except ImportError:
14
+ _HAS_NP = False
15
+ np = None # type: ignore[assignment]
16
+
17
+ import os
18
+ import sys
19
+ from pathlib import Path
20
+ import asyncio
21
+
22
+ # 动态加载 aria_tools 中的 MCP 工具
23
+ # 相对本文件定位同仓库的 packages/,不写死开发者本机路径
24
+ _PKGS = str(Path(__file__).resolve().parents[2] / "packages")
25
+ if _PKGS not in sys.path:
26
+ sys.path.append(_PKGS)
27
+ try:
28
+ from aria_tools.financial.risk_tools import run_risk_profile
29
+ except ImportError:
30
+ pass
31
+
32
+
33
+ class RiskAgent(BaseAgent):
34
+ name = "risk"
35
+ description = "风险评估:波动率/回撤/相关性/仓位建议"
36
+
37
+ _SYSTEM = (
38
+ "You are a risk manager. Assess: historical volatility, max drawdown, "
39
+ "liquidity risk, correlation to market, and position sizing recommendation. "
40
+ "Output: risk score 1-10 (1=very low, 10=very high) and "
41
+ "POSITION: <percentage> (e.g. POSITION: 10%)."
42
+ )
43
+
44
+ async def _execute_tool(self, tool_name: str, tool_args: Dict[str, Any]) -> str:
45
+ """拦截 risk_profile 的调用"""
46
+ if tool_name == "run_risk_profile":
47
+ if self.on_tool_start:
48
+ self.on_tool_start(tool_name, tool_args)
49
+ res = await run_risk_profile(**tool_args)
50
+ if self.on_tool_end:
51
+ self.on_tool_end(tool_name, res)
52
+ import json
53
+ return json.dumps({"result": res}, ensure_ascii=False)
54
+
55
+ return await super()._execute_tool(tool_name, tool_args)
56
+
57
+ async def fetch_data(self, symbol: str) -> Dict[str, Any]:
58
+ data = await super().fetch_data(symbol)
59
+ if self.data:
60
+ h = self.data.history(symbol, days=252) # 1年
61
+ if h and h.data is not None:
62
+ data["risk_metrics"] = _compute_risk(h.data)
63
+ return data
64
+
65
+ async def analyze(self, symbol: str, data: Dict[str, Any]) -> AgentResult:
66
+ quote = data.get("quote", {})
67
+ metrics = data.get("risk_metrics", {})
68
+ price = quote.get("price", 0)
69
+
70
+ risk_str = (
71
+ f" Ann. Volatility: {metrics.get('ann_vol',0):.1f}%\n"
72
+ f" Max Drawdown: {metrics.get('max_dd',0):.1f}%\n"
73
+ f" Sharpe (1Y): {metrics.get('sharpe',0):.2f}\n"
74
+ f" Beta est.: {metrics.get('beta',1.0):.2f}\n"
75
+ f" Avg Daily Vol: {metrics.get('avg_vol_usd',0):,.0f}"
76
+ ) if metrics else " (risk metrics unavailable)"
77
+
78
+ prompt = (
79
+ f"Stock: {symbol} Price: {price}\n"
80
+ f"Risk Metrics:\n{risk_str}\n\n"
81
+ "Assess:\n"
82
+ "1. Volatility profile (high/medium/low)\n"
83
+ "2. Drawdown history and recovery\n"
84
+ "3. Liquidity adequacy\n"
85
+ "4. Tail risk and black swan exposure\n"
86
+ "5. Recommended position size for a diversified portfolio\n"
87
+ "Output format: Risk Score: X/10\nPOSITION: Y%\n\n"
88
+ "## Available Tools\n"
89
+ "You have access to a tool named `run_risk_profile` to run stress tests.\n"
90
+ "If you need to evaluate tail risk or run a stress test, output exactly in this format:\n"
91
+ "{\"type\": \"tool_call\", \"name\": \"run_risk_profile\", \"args\": {\"symbol\": \"AAPL\"}}\n"
92
+ "Wait for the tool result before concluding."
93
+ )
94
+
95
+ analysis = await self._call_llm(self._SYSTEM, prompt, max_tokens=400, quote=quote)
96
+ if not analysis:
97
+ analysis = _template_risk(symbol, metrics)
98
+
99
+ signal = _risk_to_signal(metrics)
100
+ confidence = 0.70
101
+ key_points = [
102
+ f"年化波动率 {metrics.get('ann_vol',0):.1f}%",
103
+ f"最大回撤 {metrics.get('max_dd',0):.1f}%",
104
+ f"夏普比率 {metrics.get('sharpe',0):.2f}",
105
+ ] if metrics else ["风险数据不可用"]
106
+
107
+ return AgentResult(
108
+ agent=self.name, symbol=symbol,
109
+ analysis=analysis, confidence=confidence,
110
+ signal=signal, key_points=key_points,
111
+ data_used=metrics,
112
+ )
113
+
114
+
115
+ def _compute_risk(df) -> Dict:
116
+ if not _HAS_NP:
117
+ return {}
118
+ try:
119
+ close = df["close"].values if "close" in df.columns else df.iloc[:, -1].values
120
+ vol = df["volume"].values if "volume" in df.columns else np.ones(len(close))
121
+ rets = np.diff(close) / close[:-1]
122
+
123
+ ann_vol = float(np.std(rets) * np.sqrt(252) * 100)
124
+ ann_ret = float(np.mean(rets) * 252 * 100)
125
+ sharpe = ann_ret / max(ann_vol, 0.01)
126
+ cum = np.cumprod(1 + rets)
127
+ peaks = np.maximum.accumulate(cum)
128
+ drawdowns = (cum - peaks) / peaks
129
+ max_dd = float(drawdowns.min() * 100)
130
+ avg_vol = float(np.mean(vol[-20:]) * close[-1]) if len(close) > 20 else 0
131
+
132
+ return {
133
+ "ann_vol": round(ann_vol, 1),
134
+ "ann_ret": round(ann_ret, 1),
135
+ "sharpe": round(sharpe, 2),
136
+ "max_dd": round(max_dd, 1),
137
+ "beta": 1.0, # 需要指数数据对比
138
+ "avg_vol_usd": round(avg_vol, 0),
139
+ }
140
+ except Exception:
141
+ return {}
142
+
143
+
144
+ def _risk_to_signal(metrics: Dict) -> str:
145
+ vol = metrics.get("ann_vol", 30)
146
+ dd = metrics.get("max_dd", -20)
147
+ if vol < 20 and dd > -15:
148
+ return "BUY"
149
+ if vol > 50 or dd < -40:
150
+ return "REDUCE"
151
+ return "HOLD"
152
+
153
+
154
+ def _template_risk(symbol: str, m: Dict) -> str:
155
+ vol = m.get("ann_vol", 0)
156
+ dd = m.get("max_dd", 0)
157
+ risk_level = "高" if vol > 40 else ("中" if vol > 20 else "低")
158
+ return (
159
+ f"{symbol} 风险分析(模板):\n"
160
+ f"• 年化波动率: {vol:.1f}%(风险水平: {risk_level})\n"
161
+ f"• 最大回撤: {dd:.1f}%\n"
162
+ f"• 夏普比率: {m.get('sharpe',0):.2f}\n"
163
+ f"• 建议仓位: {'5-10%' if vol>40 else '10-15%' if vol>20 else '15-20%'}\n"
164
+ f"• Risk Score: {min(10, int(vol/5))}/10"
165
+ )
@@ -0,0 +1,279 @@
1
+ """
2
+ agents/financial/sector.py — 行业轮动分析 Agent
3
+ ================================================
4
+ 分析标的所属行业的相对强弱:行业指数表现、资金流入/流出趋势、
5
+ 板块轮动阶段,以判断标的是否处于顺风还是逆风环境。
6
+ 数据源:yfinance 行业 ETF(XLK/XLF/XLE/…)+ akshare 行业指数(A股)
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ from ..base import BaseAgent, AgentResult
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # 美股行业 ETF 映射 (GICS Sectors)
19
+ _US_SECTOR_ETFS: Dict[str, str] = {
20
+ "Technology": "XLK",
21
+ "Health Care": "XLV",
22
+ "Financials": "XLF",
23
+ "Consumer Discretionary": "XLY",
24
+ "Consumer Staples": "XLP",
25
+ "Industrials": "XLI",
26
+ "Energy": "XLE",
27
+ "Materials": "XLB",
28
+ "Real Estate": "XLRE",
29
+ "Utilities": "XLU",
30
+ "Communication Services": "XLC",
31
+ }
32
+
33
+ # 默认参考基准
34
+ _BENCHMARK = "SPY"
35
+
36
+
37
+ class SectorAgent(BaseAgent):
38
+
39
+ name = "sector"
40
+ description = "行业轮动 — 所属板块相对强弱、资金流入/流出趋势"
41
+
42
+ _SYSTEM = (
43
+ "You are a sector rotation analyst. Given a stock and its sector's "
44
+ "recent performance relative to the broader market, assess:\n"
45
+ "1. Is the sector in an uptrend or downtrend vs the S&P 500?\n"
46
+ "2. Is money flowing INTO or OUT OF this sector recently?\n"
47
+ "3. What stage of the sector rotation cycle is this sector in "
48
+ "(early/mid/late recovery, contraction)?\n"
49
+ "4. Does the sector tailwind/headwind support BUY/HOLD/SELL on the stock?\n"
50
+ "Conclude: TAILWIND (sector supports bullish) / NEUTRAL / HEADWIND (sector drags bearish)"
51
+ )
52
+
53
+ async def fetch_data(self, symbol: str) -> Dict[str, Any]:
54
+ data = await super().fetch_data(symbol)
55
+ sector_data: Dict[str, Any] = {}
56
+
57
+ try:
58
+ import yfinance as yf
59
+ ticker = yf.Ticker(symbol)
60
+
61
+ # 1. Identify sector from stock info
62
+ info = ticker.info or {}
63
+ sector = info.get("sector", "")
64
+ industry = info.get("industry", "")
65
+ sector_data["sector"] = sector
66
+ sector_data["industry"] = industry
67
+
68
+ # 2. Get sector ETF for US stocks
69
+ etf_sym = _US_SECTOR_ETFS.get(sector)
70
+ if not etf_sym:
71
+ # Try to guess from ticker pattern for A-shares
72
+ if _is_a_share(symbol):
73
+ sector_data["market"] = "CN"
74
+ sector_data["etf_sym"] = None
75
+ else:
76
+ sector_data["market"] = "US"
77
+ sector_data["etf_sym"] = None
78
+ else:
79
+ sector_data["etf_sym"] = etf_sym
80
+ sector_data["market"] = "US"
81
+
82
+ # 3. Fetch returns for sector ETF and benchmark
83
+ comparisons: Dict[str, Dict] = {}
84
+ for periods in [("1mo", 21), ("3mo", 63), ("6mo", 126)]:
85
+ period_key, _ = periods[0], periods[1]
86
+ comparisons[period_key] = {}
87
+
88
+ syms_to_fetch = [_BENCHMARK]
89
+ if etf_sym:
90
+ syms_to_fetch.append(etf_sym)
91
+
92
+ for fetch_sym in syms_to_fetch:
93
+ try:
94
+ hist = yf.Ticker(fetch_sym).history(period="6mo")
95
+ if hist.empty:
96
+ continue
97
+ close = hist["Close"]
98
+ for period_key, days in [("1mo", 21), ("3mo", 63), ("6mo", len(close))]:
99
+ if len(close) >= days:
100
+ ret = (float(close.iloc[-1]) - float(close.iloc[-days])) / float(close.iloc[-days])
101
+ comparisons[period_key][fetch_sym] = round(ret * 100, 2)
102
+ except Exception as e:
103
+ logger.debug("[sector] fetch %s: %s", fetch_sym, e)
104
+
105
+ sector_data["comparisons"] = comparisons
106
+
107
+ # 4. Stock's own recent performance vs sector
108
+ try:
109
+ hist = ticker.history(period="3mo")
110
+ if not hist.empty and len(hist) >= 21:
111
+ close = hist["Close"]
112
+ stock_1mo = (float(close.iloc[-1]) - float(close.iloc[-21])) / float(close.iloc[-21]) * 100
113
+ sector_data["stock_1mo_return"] = round(stock_1mo, 2)
114
+ except Exception as e:
115
+ logger.debug("[sector] stock return %s: %s", symbol, e)
116
+
117
+ # 5. A-share sector data via akshare
118
+ if _is_a_share(symbol):
119
+ try:
120
+ import akshare as ak
121
+ sector_df = ak.stock_board_industry_name_em()
122
+ if sector_df is not None and not sector_df.empty:
123
+ sector_data["cn_sectors_available"] = True
124
+ except Exception:
125
+ pass
126
+
127
+ except Exception as e:
128
+ logger.debug("[sector] yfinance init %s: %s", symbol, e)
129
+
130
+ data["sector_data"] = sector_data
131
+ return data
132
+
133
+ async def analyze(self, symbol: str, data: Dict[str, Any]) -> AgentResult:
134
+ sd = data.get("sector_data", {})
135
+ quote = data.get("quote", {})
136
+ price = quote.get("price", 0)
137
+
138
+ if not sd:
139
+ return AgentResult(
140
+ agent=self.name, symbol=symbol,
141
+ analysis=f"{symbol}: 未获取到行业数据。",
142
+ confidence=0.3, signal="HOLD",
143
+ key_points=["无行业数据"],
144
+ )
145
+
146
+ sector_block = _format_sector_stats(sd)
147
+ rel_strength = _compute_relative_strength(sd)
148
+
149
+ prompt = (
150
+ f"Stock: {symbol} Sector: {sd.get('sector', 'Unknown')} "
151
+ f"Industry: {sd.get('industry', 'Unknown')}\n\n"
152
+ f"Sector Performance vs Market:\n{sector_block}\n\n"
153
+ f"Relative Strength Assessment: {rel_strength}\n\n"
154
+ "Analyze sector rotation dynamics and conclude:\n"
155
+ "TAILWIND / NEUTRAL / HEADWIND"
156
+ )
157
+
158
+ analysis = await self._call_llm(self._SYSTEM, prompt, max_tokens=450)
159
+ if not analysis:
160
+ analysis = _template_analysis(symbol, sd, rel_strength)
161
+
162
+ signal, confidence = _derive_signal(analysis, sd, rel_strength)
163
+ key_points = _build_key_points(sd, rel_strength, signal)
164
+
165
+ return AgentResult(
166
+ agent=self.name, symbol=symbol,
167
+ analysis=analysis,
168
+ confidence=confidence,
169
+ signal=signal,
170
+ key_points=key_points,
171
+ data_used={
172
+ "sector": sd.get("sector"),
173
+ "etf": sd.get("etf_sym"),
174
+ "rel_strength": rel_strength,
175
+ },
176
+ )
177
+
178
+
179
+ # ── Helpers ───────────────────────────────────────────────────────────────────
180
+
181
+ def _is_a_share(symbol: str) -> bool:
182
+ import re
183
+ return bool(re.match(r"^[036]\d{5}$", symbol))
184
+
185
+
186
+ def _format_sector_stats(sd: Dict) -> str:
187
+ lines = []
188
+ sector = sd.get("sector", "Unknown")
189
+ etf_sym = sd.get("etf_sym", "")
190
+ comps = sd.get("comparisons", {})
191
+ lines.append(f"Sector: {sector}{f' ETF: {etf_sym}' if etf_sym else ''}")
192
+
193
+ if comps:
194
+ lines.append(f"\n{'Period':<8} {'SPY':>8} {etf_sym or 'SECTOR':>8} Rel.Strength")
195
+ for period in ("1mo", "3mo", "6mo"):
196
+ c = comps.get(period, {})
197
+ spy = c.get(_BENCHMARK)
198
+ etf = c.get(etf_sym) if etf_sym else None
199
+ spy_str = f"{spy:+.1f}%" if spy is not None else " N/A"
200
+ etf_str = f"{etf:+.1f}%" if etf is not None else " N/A"
201
+ rel = ""
202
+ if spy is not None and etf is not None:
203
+ diff = etf - spy
204
+ rel = f"{diff:+.1f}% vs SPY"
205
+ lines.append(f"{period:<8} {spy_str:>8} {etf_str:>8} {rel}")
206
+
207
+ stock_1mo = sd.get("stock_1mo_return")
208
+ if stock_1mo is not None:
209
+ lines.append(f"\nStock 1-month: {stock_1mo:+.1f}%")
210
+
211
+ return "\n".join(lines)
212
+
213
+
214
+ def _compute_relative_strength(sd: Dict) -> str:
215
+ etf_sym = sd.get("etf_sym")
216
+ comps = sd.get("comparisons", {})
217
+ if not etf_sym or not comps:
218
+ return "无法计算(无行业 ETF 数据)"
219
+
220
+ scores = []
221
+ for period in ("1mo", "3mo"):
222
+ c = comps.get(period, {})
223
+ spy = c.get(_BENCHMARK)
224
+ etf = c.get(etf_sym)
225
+ if spy is not None and etf is not None:
226
+ scores.append(etf - spy)
227
+
228
+ if not scores:
229
+ return "数据不足"
230
+ avg_rs = sum(scores) / len(scores)
231
+ if avg_rs > 3:
232
+ return f"行业强于大盘 +{avg_rs:.1f}%(顺风)"
233
+ if avg_rs > 0:
234
+ return f"行业小幅跑赢 +{avg_rs:.1f}%(中性偏正)"
235
+ if avg_rs > -3:
236
+ return f"行业小幅落后 {avg_rs:.1f}%(中性偏负)"
237
+ return f"行业明显弱于大盘 {avg_rs:.1f}%(逆风)"
238
+
239
+
240
+ def _derive_signal(analysis: str, sd: Dict, rel_str: str) -> tuple[str, float]:
241
+ text = analysis.upper()
242
+ if "TAILWIND" in text:
243
+ return "BUY", 0.55
244
+ if "HEADWIND" in text:
245
+ return "SELL", 0.55
246
+
247
+ # Fallback: use relative strength
248
+ if "顺风" in rel_str or "跑赢" in rel_str:
249
+ return "BUY", 0.45
250
+ if "逆风" in rel_str or "落后" in rel_str:
251
+ return "SELL", 0.45
252
+ return "HOLD", 0.40
253
+
254
+
255
+ def _build_key_points(sd: Dict, rel_str: str, signal: str) -> List[str]:
256
+ pts = []
257
+ sec = sd.get("sector", "")
258
+ if sec:
259
+ pts.append(f"行业: {sec}")
260
+ etf = sd.get("etf_sym")
261
+ if etf:
262
+ pts.append(f"对应 ETF: {etf}")
263
+ pts.append(f"板块强弱: {rel_str[:40]}")
264
+ stock_1mo = sd.get("stock_1mo_return")
265
+ if stock_1mo is not None:
266
+ pts.append(f"个股1月涨跌: {stock_1mo:+.1f}%")
267
+ pts.append(f"行业信号: {signal}")
268
+ return pts[:5]
269
+
270
+
271
+ def _template_analysis(symbol: str, sd: Dict, rel_str: str) -> str:
272
+ sector = sd.get("sector", "Unknown")
273
+ verdict = "TAILWIND" if "顺风" in rel_str else ("HEADWIND" if "逆风" in rel_str else "NEUTRAL")
274
+ return (
275
+ f"{symbol} 行业分析(模板)\n"
276
+ f"所属行业: {sector}\n"
277
+ f"行业强弱: {rel_str}\n"
278
+ f"结论: {verdict}"
279
+ )