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,258 @@
1
+ """
2
+ agents/financial/technical.py — 技术分析 Agent
3
+ ===============================================
4
+ 分析 K线形态、均线结构、MACD/RSI、布林带、关键价位。
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ from typing import Any, Dict, List
11
+
12
+ from ..base import BaseAgent, AgentResult
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class TechnicalAgent(BaseAgent):
18
+
19
+ name = "technical"
20
+ description = "技术分析:图形形态、动量指标、关键价位"
21
+
22
+ _SYSTEM = (
23
+ "You are a quantitative technical analyst. Analyze price action, "
24
+ "chart patterns, momentum indicators (RSI, MACD), moving averages, "
25
+ "and Bollinger Bands. Provide concise, data-driven insights. "
26
+ "Conclude with a clear directional bias: BULLISH / NEUTRAL / BEARISH."
27
+ )
28
+
29
+ async def fetch_data(self, symbol: str) -> Dict[str, Any]:
30
+ data = await super().fetch_data(symbol)
31
+ if self.data:
32
+ try:
33
+ h = self.data.history(symbol, days=120)
34
+ if h and h.data is not None:
35
+ df = h.data
36
+ data["history"] = _compute_indicators(df)
37
+ except Exception as e:
38
+ logger.debug(f"[technical] fetch history {symbol}: {e}")
39
+ return data
40
+
41
+ async def analyze(self, symbol: str, data: Dict[str, Any]) -> AgentResult:
42
+ quote = data.get("quote", {})
43
+ history = data.get("history", {})
44
+ price = quote.get("price", 0)
45
+
46
+ # 提取指标用于 prompt
47
+ indicators = _format_indicators(history)
48
+ pattern = history.get("pattern", "无特殊形态")
49
+
50
+ prompt = (
51
+ f"Stock: {symbol} Current Price: {price}\n"
52
+ f"Technical Indicators:\n{indicators}\n"
53
+ f"Pattern: {pattern}\n\n"
54
+ "Provide: 1) Trend assessment 2) Key support/resistance levels "
55
+ "3) Signal strength 4) Short-term outlook (3-10 days) "
56
+ "5) Conclusion: BULLISH / NEUTRAL / BEARISH"
57
+ )
58
+
59
+ analysis = await self._call_llm(self._SYSTEM, prompt, max_tokens=600, quote=quote)
60
+ if not analysis:
61
+ analysis = _template_analysis(symbol, price, history)
62
+
63
+ signal = _extract_signal(analysis, history)
64
+ confidence = history.get("signal_strength", 0.5)
65
+ key_points = _extract_key_points(history, price)
66
+
67
+ return AgentResult(
68
+ agent=self.name, symbol=symbol,
69
+ analysis=analysis, confidence=confidence,
70
+ signal=signal, key_points=key_points,
71
+ data_used={"price": price, "indicators": history},
72
+ )
73
+
74
+
75
+ # ── 技术指标计算 ──────────────────────────────────────────────────────────────
76
+
77
+ def _compute_indicators(df) -> Dict[str, Any]:
78
+ """从 OHLCV DataFrame 计算常用指标"""
79
+ try:
80
+ import numpy as np
81
+ close = df["close"].values if "close" in df.columns else df.iloc[:, -1].values
82
+
83
+ # MA
84
+ ma5 = float(np.mean(close[-5:])) if len(close) >= 5 else 0
85
+ ma20 = float(np.mean(close[-20:])) if len(close) >= 20 else 0
86
+ ma60 = float(np.mean(close[-60:])) if len(close) >= 60 else 0
87
+
88
+ # RSI(14)
89
+ delta = np.diff(close[-15:])
90
+ gains = np.where(delta > 0, delta, 0)
91
+ losses= np.where(delta < 0, -delta, 0)
92
+ rs = np.mean(gains[-14:]) / (np.mean(losses[-14:]) + 1e-9)
93
+ rsi = round(100 - 100 / (1 + rs), 1)
94
+
95
+ # MACD
96
+ ema12 = _ema(close, 12)
97
+ ema26 = _ema(close, 26)
98
+ macd = ema12 - ema26
99
+ signal= _ema_arr(macd[-50:] if len(macd) >= 50 else macd, 9)
100
+ macd_val = round(float(macd[-1]), 4)
101
+ signal_val= round(float(signal[-1]), 4)
102
+ hist_val = round(macd_val - signal_val, 4)
103
+
104
+ # 布林带
105
+ std20 = float(np.std(close[-20:])) if len(close) >= 20 else 0
106
+ bb_up = round(ma20 + 2 * std20, 2)
107
+ bb_lo = round(ma20 - 2 * std20, 2)
108
+
109
+ # 信号强度
110
+ price = float(close[-1])
111
+ ma_bull = price > ma5 > ma20
112
+ strength = 0.5
113
+ if ma_bull and macd_val > signal_val:
114
+ strength = 0.75
115
+ elif not ma_bull and macd_val < signal_val:
116
+ strength = 0.25
117
+
118
+ # 形态检测(简单)
119
+ pattern = _detect_simple_pattern(close)
120
+
121
+ return {
122
+ "price": round(price, 2),
123
+ "ma5": round(ma5, 2),
124
+ "ma20": round(ma20, 2),
125
+ "ma60": round(ma60, 2),
126
+ "rsi": rsi,
127
+ "macd": macd_val,
128
+ "macd_signal": signal_val,
129
+ "macd_hist": hist_val,
130
+ "bb_upper": bb_up,
131
+ "bb_lower": bb_lo,
132
+ "signal_strength": strength,
133
+ "pattern": pattern,
134
+ }
135
+ except Exception as e:
136
+ logger.debug(f"compute_indicators 失败: {e}")
137
+ return {}
138
+
139
+
140
+ def _ema(arr, period):
141
+ import numpy as np
142
+ k = 2 / (period + 1)
143
+ ema = np.zeros(len(arr))
144
+ ema[0] = arr[0]
145
+ for i in range(1, len(arr)):
146
+ ema[i] = arr[i] * k + ema[i-1] * (1 - k)
147
+ return ema
148
+
149
+ def _ema_arr(arr, period):
150
+ return _ema(arr, period)
151
+
152
+ def _detect_simple_pattern(close) -> str:
153
+ if len(close) < 3:
154
+ return "数据不足"
155
+ o, h, c = close[-3], close[-2], close[-1]
156
+ if c > o and c > h and (c - h) > abs(c - o) * 2:
157
+ return "锤子线"
158
+ if c > o and o < close[-4] and c > close[-4]:
159
+ return "阳线吞噬"
160
+ if abs(c - o) / (max(close[-3:]) - min(close[-3:]) + 1e-9) < 0.1:
161
+ return "十字星"
162
+ return "无特殊形态"
163
+
164
+
165
+ def _format_indicators(history: Dict) -> str:
166
+ if not history:
167
+ return " (无指标数据)"
168
+ return (
169
+ f" MA5={history.get('ma5',0):.2f} MA20={history.get('ma20',0):.2f} "
170
+ f"MA60={history.get('ma60',0):.2f}\n"
171
+ f" RSI={history.get('rsi',50):.1f} "
172
+ f"MACD={history.get('macd',0):.4f} Signal={history.get('macd_signal',0):.4f}\n"
173
+ f" BB Upper={history.get('bb_upper',0):.2f} BB Lower={history.get('bb_lower',0):.2f}"
174
+ )
175
+
176
+
177
+ def _extract_signal(analysis: str, history: Dict) -> str:
178
+ import re as _re
179
+ text = analysis.upper()
180
+
181
+ # Unambiguous strong signals always win
182
+ if "STRONG_BUY" in text or "强烈买入" in text:
183
+ return "STRONG_BUY"
184
+ if "STRONG_SELL" in text or "强烈卖出" in text:
185
+ return "STRONG_SELL"
186
+
187
+ # The LLM prompt asks the model to conclude with BULLISH/NEUTRAL/BEARISH.
188
+ # Use the LAST occurrence — it's the conclusion, not mid-text context like
189
+ # "a potential BULLISH reversal" appearing before "currently BEARISH".
190
+ matches = _re.findall(r'\b(BULLISH|BEARISH|NEUTRAL)\b', text)
191
+ if matches:
192
+ last = matches[-1]
193
+ if last == "BULLISH":
194
+ return "BUY"
195
+ if last == "BEARISH":
196
+ return "SELL"
197
+ # NEUTRAL → fall through to quantitative check below
198
+
199
+ # Explicit Chinese directional signals
200
+ if "看多" in analysis:
201
+ return "BUY"
202
+ if "看空" in analysis:
203
+ return "SELL"
204
+
205
+ # Quantitative fallback: score RSI + MACD crossover
206
+ rsi = history.get("rsi", 50)
207
+ macd = history.get("macd", 0)
208
+ msig = history.get("macd_signal", 0)
209
+ score = 0
210
+ if rsi < 30: score += 2 # oversold — strong mean-reversion signal
211
+ elif rsi < 45: score -= 1
212
+ elif rsi > 70: score -= 2 # overbought
213
+ elif rsi > 55: score += 1
214
+ if macd > msig: score += 1
215
+ elif macd < msig: score -= 1
216
+ if score >= 2: return "BUY"
217
+ if score <= -2: return "SELL"
218
+ return "HOLD"
219
+
220
+
221
+ def _extract_key_points(history: Dict, price: float) -> List[str]:
222
+ points = []
223
+ rsi = history.get("rsi", 50)
224
+ ma20 = history.get("ma20", 0)
225
+ ma60 = history.get("ma60", 0)
226
+ if rsi < 35:
227
+ points.append(f"RSI超卖({rsi:.0f}),有反弹机会")
228
+ elif rsi > 70:
229
+ points.append(f"RSI超买({rsi:.0f}),注意回调风险")
230
+ if ma20 > 0:
231
+ diff_pct = (price - ma20) / ma20 * 100
232
+ points.append(f"距MA20 {diff_pct:+.1f}%")
233
+ macd = history.get("macd", 0)
234
+ sig = history.get("macd_signal", 0)
235
+ if macd > sig and history.get("macd_hist", 0) > 0:
236
+ points.append("MACD金叉,多头动能")
237
+ elif macd < sig:
238
+ points.append("MACD死叉,空头压力")
239
+ pattern = history.get("pattern", "")
240
+ if pattern and pattern != "无特殊形态":
241
+ points.append(f"K线形态: {pattern}")
242
+ return points
243
+
244
+
245
+ def _template_analysis(symbol: str, price: float, history: Dict) -> str:
246
+ rsi = history.get("rsi", 50)
247
+ ma20 = history.get("ma20", 0)
248
+ macd = history.get("macd", 0)
249
+ sig = history.get("macd_signal", 0)
250
+ trend = "上升趋势" if price > ma20 else "下降趋势"
251
+ momentum = "偏多" if macd > sig else "偏空"
252
+ return (
253
+ f"{symbol} 技术面分析(模板):\n"
254
+ f"• 当前价格 {price},处于 {trend}(MA20={ma20:.2f})\n"
255
+ f"• RSI={rsi:.0f},{'超卖' if rsi<35 else '超买' if rsi>70 else '正常区间'}\n"
256
+ f"• MACD 动能{momentum}\n"
257
+ f"• 整体技术面{'BULLISH' if momentum=='偏多' and trend=='上升趋势' else 'NEUTRAL'}"
258
+ )
@@ -0,0 +1,44 @@
1
+ from typing import Any
2
+ import json
3
+ from .base import BaseAgent
4
+ from .registry import get_registry
5
+
6
+ class OrchestratorAgent(BaseAgent):
7
+ name = "orchestrator"
8
+ description = "动态分析用户意图,从注册中心挑选最合适的专业智能体进行流转和编排。"
9
+
10
+ def get_prompt(self, context: Any) -> str:
11
+ registry = get_registry()
12
+ agents = registry.list()
13
+ # Remove orchestrator itself from the list to avoid recursive loop
14
+ agents = [a for a in agents if a["name"] != "orchestrator"]
15
+
16
+ agents_info = "\n".join([f"- {a['name']}: {a['description']}" for a in agents])
17
+
18
+ prompt = (
19
+ "你是一个高级架构师智能体 (Orchestrator)。\n"
20
+ "你的任务是分析用户的请求,并从以下可用的专业智能体库中,挑选出最合适的一个或多个智能体来解决问题。\n"
21
+ "可用智能体库:\n"
22
+ f"{agents_info}\n\n"
23
+ "请直接输出 JSON 格式的结果,包含两项:\n"
24
+ "1. 'plan': 简要的一句话解释为什么选择这些智能体。\n"
25
+ "2. 'agents': 一个字符串列表,包含需要调用的智能体名称(最多选 3 个)。\n"
26
+ "注意: 必须且只能输出合法的 JSON,不要有任何代码块包裹或前言后语。\n"
27
+ )
28
+ return prompt
29
+
30
+ def get_tools(self) -> list:
31
+ return []
32
+
33
+ async def execute(self, provider, model_name: str, **kwargs) -> Any:
34
+ context = kwargs.get("context", "")
35
+ # Because Orchestrator doesn't have its own tools, it just calls the LLM with a basic generate_text
36
+ # We need a simple non-streaming call or accumulate the stream.
37
+ from aria_cli import send_message
38
+ prompt = self.get_prompt(context)
39
+
40
+ # Or we can just use the provider directly if we have a simple function.
41
+ # But BaseAgent usually operates via `AgentTeam` which handles the loops.
42
+ # Since Orchestrator is special, it should probably be used by the CLI directly.
43
+ pass
44
+
@@ -0,0 +1,363 @@
1
+ """
2
+ agents/portfolio_agent.py — 组合级分析 Agent
3
+ =============================================
4
+ 跨标的分析:相关性矩阵、波动率、集中度风险、再平衡建议。
5
+ 数据源:yfinance 1年日线(免费,无需 key)
6
+ 触发:/portfolio analyze [symbols…]
7
+ /portfolio rebalance [symbols…]
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ from datetime import datetime
14
+ from typing import Any, Dict, List, Optional
15
+
16
+ from .base import BaseAgent, AgentResult
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class PortfolioAgent(BaseAgent):
22
+
23
+ name = "portfolio"
24
+ description = "组合级分析 — 相关性、分散度、整体风险、再平衡建议"
25
+
26
+ _SYSTEM = (
27
+ "You are a portfolio risk analyst. Given price history and statistics for "
28
+ "a portfolio of stocks, your job is to:\n"
29
+ "1. Evaluate overall portfolio risk (Low / Medium / High)\n"
30
+ "2. Identify diversification gaps — highly correlated pairs, sector crowding\n"
31
+ "3. Flag concentration risk if any single position dominates\n"
32
+ "4. Give 2-3 actionable rebalancing suggestions (be specific: which stocks to "
33
+ "trim / add / replace and why)\n"
34
+ "5. End with a one-line verdict: HEALTHY / NEEDS_ATTENTION / HIGH_RISK\n"
35
+ "Be concrete. Avoid boilerplate."
36
+ )
37
+
38
+ # ── BaseAgent compatibility ───────────────────────────────────────────────
39
+
40
+ async def fetch_data(self, symbol: str) -> Dict[str, Any]:
41
+ symbols = [s.strip().upper() for s in symbol.split(",") if s.strip()]
42
+ return await self.fetch_portfolio_data(symbols)
43
+
44
+ async def analyze(self, symbol: str, data: Dict[str, Any]) -> AgentResult:
45
+ symbols = data.get("symbols") or [s.strip().upper() for s in symbol.split(",") if s.strip()]
46
+ return await self.analyze_portfolio(symbols, data)
47
+
48
+ # ── Multi-symbol interface ────────────────────────────────────────────────
49
+
50
+ async def run_portfolio(
51
+ self, symbols: List[str], weights: Optional[Dict[str, float]] = None
52
+ ) -> AgentResult:
53
+ """Primary entry point for multi-symbol analysis.
54
+
55
+ weights: optional {symbol: dollar_cost_basis} from the user's real
56
+ holdings (portfolio_ledger.PortfolioLedger.get_positions()). Without
57
+ it, every position is silently treated as equal-weight — which used
58
+ to be the ONLY mode this agent supported, so a user who actually
59
+ holds 80% NVDA and 20% everything else got a risk verdict computed
60
+ on a fictional 1/n-weighted portfolio instead of their real exposure.
61
+ """
62
+ data = await self.fetch_portfolio_data(symbols, weights=weights)
63
+ return await self.analyze_portfolio(symbols, data)
64
+
65
+ async def fetch_portfolio_data(
66
+ self, symbols: List[str], weights: Optional[Dict[str, float]] = None
67
+ ) -> Dict[str, Any]:
68
+ data: Dict[str, Any] = {"symbols": symbols}
69
+ if len(symbols) < 2:
70
+ return data
71
+
72
+ price_series: Dict[str, Any] = {}
73
+ latest_prices: Dict[str, float] = {}
74
+ sector_map: Dict[str, str] = {}
75
+
76
+ for sym in symbols:
77
+ try:
78
+ import yfinance as yf
79
+ ticker = yf.Ticker(sym)
80
+ hist = ticker.history(period="1y")
81
+ if not hist.empty and len(hist) > 20:
82
+ price_series[sym] = hist["Close"]
83
+ latest_prices[sym] = float(hist["Close"].iloc[-1])
84
+ # Try to get sector info
85
+ try:
86
+ info = ticker.info or {}
87
+ sector = info.get("sector") or info.get("industry", "")
88
+ if sector:
89
+ sector_map[sym] = sector
90
+ except Exception:
91
+ pass
92
+ except Exception as e:
93
+ logger.debug("[portfolio] yfinance %s: %s", sym, e)
94
+
95
+ if len(price_series) < 2:
96
+ data["error"] = "insufficient_data"
97
+ return data
98
+
99
+ try:
100
+ import pandas as pd
101
+ import numpy as np
102
+
103
+ df = pd.DataFrame(price_series).dropna()
104
+ returns = df.pct_change().dropna()
105
+
106
+ corr = returns.corr()
107
+ ann_returns = (returns.mean() * 252).round(4)
108
+ ann_vols = (returns.std() * np.sqrt(252)).round(4)
109
+ cov_matrix = returns.cov() * 252
110
+ valid_syms = list(df.columns)
111
+ n = len(valid_syms)
112
+ if weights and any(weights.get(s, 0) > 0 for s in valid_syms):
113
+ raw = np.array([max(weights.get(s, 0.0), 0.0) for s in valid_syms])
114
+ w_vec = raw / raw.sum() if raw.sum() > 0 else np.ones(n) / n
115
+ weight_source = "ledger"
116
+ else:
117
+ w_vec = np.ones(n) / n
118
+ weight_source = "equal"
119
+ port_vol = float(np.sqrt(w_vec @ cov_matrix.values @ w_vec))
120
+ position_weights = {s: round(float(w_vec[i]), 4) for i, s in enumerate(valid_syms)}
121
+
122
+ # High correlation pairs (|r| > 0.70)
123
+ high_corr: List[Dict] = []
124
+ for i in range(len(valid_syms)):
125
+ for j in range(i + 1, len(valid_syms)):
126
+ c = float(corr.iloc[i, j])
127
+ if abs(c) > 0.70:
128
+ high_corr.append({
129
+ "sym1": valid_syms[i],
130
+ "sym2": valid_syms[j],
131
+ "corr": round(c, 3),
132
+ })
133
+
134
+ # Diversification ratio: weighted avg individual vol / portfolio vol.
135
+ # Was np.mean() (unweighted) despite the comment already claiming
136
+ # "weighted" — with real ledger weights now available, actually weight it.
137
+ avg_vol = float(sum(w_vec[i] * float(ann_vols.get(s, 0)) for i, s in enumerate(valid_syms)))
138
+ div_ratio = round(avg_vol / port_vol, 2) if port_vol > 0 else 1.0
139
+
140
+ # 52-week return per symbol
141
+ returns_1y: Dict[str, float] = {}
142
+ for sym in valid_syms:
143
+ first = float(df[sym].iloc[0])
144
+ last = float(df[sym].iloc[-1])
145
+ if first > 0:
146
+ returns_1y[sym] = round((last - first) / first, 4)
147
+
148
+ # Best/worst performer
149
+ sorted_ret = sorted(returns_1y.items(), key=lambda x: x[1], reverse=True)
150
+
151
+ data.update({
152
+ "valid_symbols": valid_syms,
153
+ "latest_prices": latest_prices,
154
+ "ann_returns": {s: float(v) for s, v in ann_returns.items()},
155
+ "ann_vols": {s: float(v) for s, v in ann_vols.items()},
156
+ "port_vol_ann": round(port_vol, 4),
157
+ "div_ratio": div_ratio,
158
+ "corr_matrix": corr.round(3).to_dict(),
159
+ "high_corr": high_corr,
160
+ "returns_1y": returns_1y,
161
+ "best_performer": sorted_ret[0] if sorted_ret else None,
162
+ "worst_performer": sorted_ret[-1] if sorted_ret else None,
163
+ "sector_map": sector_map,
164
+ "position_weights": position_weights,
165
+ "weight_source": weight_source,
166
+ })
167
+
168
+ except ImportError:
169
+ data["error"] = "pandas/numpy not available"
170
+ except Exception as e:
171
+ logger.warning("[portfolio] stats calculation: %s", e)
172
+ data["error"] = str(e)
173
+
174
+ return data
175
+
176
+ async def analyze_portfolio(
177
+ self, symbols: List[str], data: Dict[str, Any]
178
+ ) -> AgentResult:
179
+ if len(symbols) < 2:
180
+ return AgentResult(
181
+ agent=self.name, symbol=",".join(symbols),
182
+ analysis="组合分析需要至少 2 个标的。",
183
+ confidence=0.0, signal="HOLD",
184
+ key_points=["标的数量不足"],
185
+ )
186
+
187
+ if data.get("error") == "insufficient_data":
188
+ return AgentResult(
189
+ agent=self.name, symbol=",".join(symbols),
190
+ analysis="无法获取足够的历史价格数据进行组合分析。",
191
+ confidence=0.3, signal="HOLD",
192
+ key_points=["历史数据不足(需要 >20 个交易日)"],
193
+ )
194
+
195
+ valid_syms = data.get("valid_symbols", symbols)
196
+ port_block = _format_portfolio_stats(data)
197
+ weight_desc = ("your actual position sizes" if data.get("weight_source") == "ledger"
198
+ else "equal weight — no real position sizes available")
199
+
200
+ prompt = (
201
+ f"Portfolio: {', '.join(valid_syms)} ({len(valid_syms)} positions, weighted by {weight_desc})\n\n"
202
+ f"{port_block}\n\n"
203
+ "Analyze this portfolio:\n"
204
+ "1. Overall risk level (Low / Medium / High)\n"
205
+ "2. Diversification quality — any dangerous correlations or sector crowding?\n"
206
+ "3. Top 2-3 concerns (be specific about which symbols)\n"
207
+ "4. Concrete rebalancing suggestions\n"
208
+ "5. End with: HEALTHY / NEEDS_ATTENTION / HIGH_RISK"
209
+ )
210
+
211
+ analysis = await self._call_llm(self._SYSTEM, prompt, max_tokens=650)
212
+ if not analysis:
213
+ analysis = _template_analysis(valid_syms, data)
214
+
215
+ verdict = _extract_verdict(analysis)
216
+ signal = _verdict_to_signal(verdict)
217
+ confidence = _estimate_confidence(data)
218
+ key_points = _build_key_points(data, verdict)
219
+
220
+ return AgentResult(
221
+ agent=self.name,
222
+ symbol=",".join(valid_syms),
223
+ analysis=analysis,
224
+ confidence=confidence,
225
+ signal=signal,
226
+ key_points=key_points,
227
+ data_used={
228
+ "n": len(valid_syms),
229
+ "port_vol_ann": data.get("port_vol_ann"),
230
+ "div_ratio": data.get("div_ratio"),
231
+ "high_corr_count": len(data.get("high_corr", [])),
232
+ },
233
+ )
234
+
235
+
236
+ # ── Helpers ───────────────────────────────────────────────────────────────────
237
+
238
+ def _format_portfolio_stats(d: Dict) -> str:
239
+ lines = []
240
+
241
+ # Returns & volatility table
242
+ vols = d.get("ann_vols", {})
243
+ rets = d.get("ann_returns", {})
244
+ rets_1y = d.get("returns_1y", {})
245
+ pweights = d.get("position_weights", {})
246
+ if vols:
247
+ lines.append("Symbol │ Weight │ Ann.Vol │ Ann.Ret │ 1Y Return")
248
+ lines.append("─" * 52)
249
+ for sym in d.get("valid_symbols", list(vols)):
250
+ v = vols.get(sym, 0)
251
+ r = rets.get(sym, 0)
252
+ y = rets_1y.get(sym, 0)
253
+ w = pweights.get(sym, 0)
254
+ lines.append(
255
+ f"{sym:<6} │ {w*100:>5.1f}% │ {v*100:>6.1f}% │ {r*100:>6.1f}% │ {y*100:>+7.1f}%"
256
+ )
257
+
258
+ port_vol = d.get("port_vol_ann", 0)
259
+ div_r = d.get("div_ratio", 1)
260
+ if port_vol:
261
+ lines.append(f"\nPortfolio Ann.Vol: {port_vol*100:.1f}%")
262
+ lines.append(f"Diversification Ratio: {div_r:.2f}x "
263
+ f"({'good' if div_r >= 1.3 else 'poor'})")
264
+
265
+ high_corr = d.get("high_corr", [])
266
+ if high_corr:
267
+ lines.append("\nHigh-Correlation Pairs (|r|>0.70):")
268
+ for p in high_corr[:5]:
269
+ lines.append(f" {p['sym1']} ↔ {p['sym2']}: {p['corr']:+.2f}")
270
+
271
+ sectors = d.get("sector_map", {})
272
+ if sectors:
273
+ from collections import Counter
274
+ sector_counts = Counter(sectors.values())
275
+ dominant = [(s, c) for s, c in sector_counts.items() if c >= 2]
276
+ if dominant:
277
+ lines.append("\nSector Concentration:")
278
+ for sec, cnt in dominant:
279
+ lines.append(f" {sec}: {cnt} positions")
280
+
281
+ best = d.get("best_performer")
282
+ worst = d.get("worst_performer")
283
+ if best and worst:
284
+ lines.append(f"\nBest 1Y: {best[0]} ({best[1]*100:+.1f}%)")
285
+ lines.append(f"Worst 1Y: {worst[0]} ({worst[1]*100:+.1f}%)")
286
+
287
+ return "\n".join(lines)
288
+
289
+
290
+ def _extract_verdict(analysis: str) -> str:
291
+ text = analysis.upper()
292
+ if "HIGH_RISK" in text or "HIGH RISK" in text:
293
+ return "HIGH_RISK"
294
+ if "NEEDS_ATTENTION" in text or "NEEDS ATTENTION" in text:
295
+ return "NEEDS_ATTENTION"
296
+ if "HEALTHY" in text:
297
+ return "HEALTHY"
298
+ return "NEEDS_ATTENTION"
299
+
300
+
301
+ def _verdict_to_signal(verdict: str) -> str:
302
+ return {"HEALTHY": "HOLD", "NEEDS_ATTENTION": "SELL", "HIGH_RISK": "SELL"}.get(verdict, "HOLD")
303
+
304
+
305
+ def _estimate_confidence(d: Dict) -> float:
306
+ base = 0.60
307
+ n = len(d.get("valid_symbols", []))
308
+ if n >= 5:
309
+ base += 0.05
310
+ if d.get("div_ratio", 1) < 1.1:
311
+ base += 0.05
312
+ return min(round(base, 2), 0.75)
313
+
314
+
315
+ def _build_key_points(d: Dict, verdict: str) -> List[str]:
316
+ pts = []
317
+ n = len(d.get("valid_symbols", []))
318
+ if d.get("weight_source") == "ledger":
319
+ pts.append(f"{n} 个标的,按真实持仓成本加权")
320
+ else:
321
+ pts.append(f"{n} 个标的,等权配置(无真实持仓数据,非实际风险暴露)")
322
+
323
+ port_vol = d.get("port_vol_ann", 0)
324
+ if port_vol:
325
+ risk_lv = "高风险" if port_vol > 0.30 else ("中等" if port_vol > 0.18 else "低风险")
326
+ pts.append(f"组合年化波动 {port_vol*100:.1f}%({risk_lv})")
327
+
328
+ div_r = d.get("div_ratio", 1)
329
+ if div_r < 1.1:
330
+ pts.append(f"分散度不足(ratio {div_r:.2f}x,标的相关性高)")
331
+ else:
332
+ pts.append(f"分散度合理(ratio {div_r:.2f}x)")
333
+
334
+ high_corr = d.get("high_corr", [])
335
+ if high_corr:
336
+ top = high_corr[0]
337
+ pts.append(f"最高相关对: {top['sym1']}↔{top['sym2']} ({top['corr']:+.2f})")
338
+
339
+ pts.append(f"整体评级: {verdict}")
340
+ return pts[:6]
341
+
342
+
343
+ def _template_analysis(symbols: List[str], d: Dict) -> str:
344
+ high_corr = d.get("high_corr", [])
345
+ port_vol = d.get("port_vol_ann", 0)
346
+ div_r = d.get("div_ratio", 1)
347
+
348
+ verdict = "NEEDS_ATTENTION"
349
+ if not high_corr and port_vol < 0.20 and div_r >= 1.2:
350
+ verdict = "HEALTHY"
351
+ elif len(high_corr) >= 3 or port_vol > 0.30:
352
+ verdict = "HIGH_RISK"
353
+
354
+ lines = [f"{','.join(symbols)} 组合分析报告(模板)"]
355
+ lines.append(f"年化波动率: {port_vol*100:.1f}%")
356
+ lines.append(f"分散度系数: {div_r:.2f}x")
357
+ if high_corr:
358
+ lines.append(f"高相关对 {len(high_corr)} 组,最高: "
359
+ f"{high_corr[0]['sym1']}↔{high_corr[0]['sym2']} {high_corr[0]['corr']:+.2f}")
360
+ else:
361
+ lines.append("无显著高相关对,分散良好。")
362
+ lines.append(f"\n{verdict}")
363
+ return "\n".join(lines)