aria-code 0.167.0__py3-none-win_amd64.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 (601) hide show
  1. aria_bots/__init__.py +7 -0
  2. aria_bots/channels/README.md +97 -0
  3. aria_bots/channels/__init__.py +1 -0
  4. aria_bots/channels/approvals.py +263 -0
  5. aria_bots/channels/conversation.py +280 -0
  6. aria_bots/channels/digest.py +186 -0
  7. aria_bots/channels/registry.py +95 -0
  8. aria_bots/engine.py +144 -0
  9. aria_bots/feishu_bot.py +1385 -0
  10. aria_bots/logistics/__init__.py +1 -0
  11. aria_bots/logistics/carriers.py +253 -0
  12. aria_bots/logistics/freight.py +154 -0
  13. aria_bots/logistics/inventory.py +242 -0
  14. aria_bots/logistics/mcp_server.py +156 -0
  15. aria_bots/logistics/records.py +119 -0
  16. aria_bots/logistics/tenancy.py +152 -0
  17. aria_bots/market.py +91 -0
  18. aria_bots/relay_client.py +326 -0
  19. aria_bots/telegram_bot.py +299 -0
  20. aria_code/__init__.py +111 -0
  21. aria_code/_bin/aria-native.exe +0 -0
  22. aria_code/_bin/aria-runtime.exe +0 -0
  23. aria_code/_version.py +20 -0
  24. aria_code/account_memory.py +296 -0
  25. aria_code/account_session.py +166 -0
  26. aria_code/adk_apps/__init__.py +1 -0
  27. aria_code/adk_apps/aria_code_review/__init__.py +1 -0
  28. aria_code/adk_apps/aria_code_review/agent.py +32 -0
  29. aria_code/adk_apps/aria_research/__init__.py +1 -0
  30. aria_code/adk_apps/aria_research/agent.py +31 -0
  31. aria_code/agents/__init__.py +49 -0
  32. aria_code/agents/base.py +329 -0
  33. aria_code/agents/code_review.py +200 -0
  34. aria_code/agents/deep/__init__.py +37 -0
  35. aria_code/agents/deep/calibration_loop.py +144 -0
  36. aria_code/agents/deep/critic.py +125 -0
  37. aria_code/agents/deep/deepen.py +193 -0
  38. aria_code/agents/deep/models.py +149 -0
  39. aria_code/agents/deep/pipeline.py +164 -0
  40. aria_code/agents/deep/quant_fusion.py +192 -0
  41. aria_code/agents/deep/themes.py +95 -0
  42. aria_code/agents/deep/tiers.py +106 -0
  43. aria_code/agents/engineering/__init__.py +14 -0
  44. aria_code/agents/engineering/coder.py +222 -0
  45. aria_code/agents/engineering/tester.py +145 -0
  46. aria_code/agents/financial/__init__.py +21 -0
  47. aria_code/agents/financial/cashflow_burn.py +119 -0
  48. aria_code/agents/financial/catalyst.py +279 -0
  49. aria_code/agents/financial/corporate_finance.py +175 -0
  50. aria_code/agents/financial/debate.py +224 -0
  51. aria_code/agents/financial/earnings.py +315 -0
  52. aria_code/agents/financial/fundamental.py +178 -0
  53. aria_code/agents/financial/macro.py +111 -0
  54. aria_code/agents/financial/news.py +231 -0
  55. aria_code/agents/financial/risk.py +146 -0
  56. aria_code/agents/financial/sector.py +279 -0
  57. aria_code/agents/financial/strategist.py +121 -0
  58. aria_code/agents/financial/stripe_revenue.py +127 -0
  59. aria_code/agents/financial/synthesis.py +274 -0
  60. aria_code/agents/financial/technical.py +277 -0
  61. aria_code/agents/orchestrator.py +44 -0
  62. aria_code/agents/portfolio_agent.py +392 -0
  63. aria_code/agents/realty/__init__.py +62 -0
  64. aria_code/agents/realty/asset_diagnosis.py +152 -0
  65. aria_code/agents/realty/business_match.py +166 -0
  66. aria_code/agents/realty/cashflow_verify.py +209 -0
  67. aria_code/agents/realty/contract_rules.py +210 -0
  68. aria_code/agents/realty/energy_anomaly.py +189 -0
  69. aria_code/agents/realty/exit_settlement.py +208 -0
  70. aria_code/agents/realty/fulfillment_risk.py +206 -0
  71. aria_code/agents/realty/ops_optimize.py +160 -0
  72. aria_code/agents/realty/revenue_share.py +215 -0
  73. aria_code/agents/registry.py +168 -0
  74. aria_code/agents/signal_scheme.py +111 -0
  75. aria_code/agents/sports/__init__.py +0 -0
  76. aria_code/agents/sports/football_agent.py +197 -0
  77. aria_code/agents/supervisor.py +113 -0
  78. aria_code/agents/team.py +500 -0
  79. aria_code/agents/video/__init__.py +16 -0
  80. aria_code/agents/video/director.py +376 -0
  81. aria_code/agents/video/qa_agent.py +143 -0
  82. aria_code/agents/video/script_doctor.py +112 -0
  83. aria_code/agents/warehouse/__init__.py +25 -0
  84. aria_code/agents/warehouse/contracts.py +26 -0
  85. aria_code/agents/warehouse/dashboard.py +155 -0
  86. aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
  87. aria_code/agents/warehouse/inbound_exceptions.py +25 -0
  88. aria_code/agents/warehouse/inventory_health.py +24 -0
  89. aria_code/agents/warehouse/local_run.py +71 -0
  90. aria_code/agents/warehouse/local_snapshot.py +132 -0
  91. aria_code/agents/warehouse/logistics_cost.py +84 -0
  92. aria_code/agents/warehouse/logistics_sync.py +21 -0
  93. aria_code/agents/warehouse/navigation.schema.json +110 -0
  94. aria_code/agents/warehouse/task_agent.py +172 -0
  95. aria_code/agents/warehouse/workflow.py +31 -0
  96. aria_code/aliyun_data_client.py +4 -0
  97. aria_code/apps/README.md +12 -0
  98. aria_code/apps/__init__.py +2 -0
  99. aria_code/apps/channels/README.md +9 -0
  100. aria_code/apps/channels/__init__.py +17 -0
  101. aria_code/apps/channels/approvals.py +11 -0
  102. aria_code/apps/channels/conversation.py +11 -0
  103. aria_code/apps/channels/digest.py +11 -0
  104. aria_code/apps/channels/intake.py +92 -0
  105. aria_code/apps/channels/registry.py +11 -0
  106. aria_code/apps/channels/tradingview.py +94 -0
  107. aria_code/apps/cli/README.md +13 -0
  108. aria_code/apps/cli/__init__.py +2 -0
  109. aria_code/apps/cli/_optional.py +120 -0
  110. aria_code/apps/cli/app_server.py +656 -0
  111. aria_code/apps/cli/auto_update.py +125 -0
  112. aria_code/apps/cli/bootstrap.py +206 -0
  113. aria_code/apps/cli/broker_render.py +206 -0
  114. aria_code/apps/cli/chat_turn.py +1327 -0
  115. aria_code/apps/cli/cloud_models.py +231 -0
  116. aria_code/apps/cli/codegen_paths.py +29 -0
  117. aria_code/apps/cli/commands/__init__.py +34 -0
  118. aria_code/apps/cli/commands/_business_render.py +78 -0
  119. aria_code/apps/cli/commands/_ui.py +71 -0
  120. aria_code/apps/cli/commands/analysis_cmds.py +378 -0
  121. aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
  122. aria_code/apps/cli/commands/auth_cmds.py +215 -0
  123. aria_code/apps/cli/commands/backtest_cmds.py +2518 -0
  124. aria_code/apps/cli/commands/broker_cmds.py +1236 -0
  125. aria_code/apps/cli/commands/business_workflow_cmds.py +379 -0
  126. aria_code/apps/cli/commands/canvas_cmds.py +77 -0
  127. aria_code/apps/cli/commands/catalog.py +189 -0
  128. aria_code/apps/cli/commands/core_cmds.py +2748 -0
  129. aria_code/apps/cli/commands/data_cmds.py +521 -0
  130. aria_code/apps/cli/commands/diagnostic_cmds.py +285 -0
  131. aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
  132. aria_code/apps/cli/commands/engineering_cmds.py +399 -0
  133. aria_code/apps/cli/commands/file_cmds.py +312 -0
  134. aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
  135. aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
  136. aria_code/apps/cli/commands/market.py +544 -0
  137. aria_code/apps/cli/commands/market_cmds.py +1419 -0
  138. aria_code/apps/cli/commands/market_context.py +432 -0
  139. aria_code/apps/cli/commands/market_render.py +7 -0
  140. aria_code/apps/cli/commands/model_cmds.py +2374 -0
  141. aria_code/apps/cli/commands/ops_cmds.py +721 -0
  142. aria_code/apps/cli/commands/orchestrator_cmds.py +181 -0
  143. aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
  144. aria_code/apps/cli/commands/portfolio_cmds.py +1316 -0
  145. aria_code/apps/cli/commands/report.py +476 -0
  146. aria_code/apps/cli/commands/scaffold_templates.py +617 -0
  147. aria_code/apps/cli/commands/session_cmds.py +236 -0
  148. aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
  149. aria_code/apps/cli/commands/team.py +723 -0
  150. aria_code/apps/cli/commands/team_render.py +8 -0
  151. aria_code/apps/cli/commands/ui_cmds.py +401 -0
  152. aria_code/apps/cli/commands/warehouse_cmds.py +297 -0
  153. aria_code/apps/cli/commands/workflow_cmds.py +629 -0
  154. aria_code/apps/cli/commands/workspace_cmds.py +1543 -0
  155. aria_code/apps/cli/config_paths.py +73 -0
  156. aria_code/apps/cli/config_store.py +158 -0
  157. aria_code/apps/cli/constants.py +88 -0
  158. aria_code/apps/cli/context.py +34 -0
  159. aria_code/apps/cli/conversation_policy.py +53 -0
  160. aria_code/apps/cli/deterministic.py +200 -0
  161. aria_code/apps/cli/direct.py +48 -0
  162. aria_code/apps/cli/earnings_view.py +172 -0
  163. aria_code/apps/cli/endpoint_migration.py +64 -0
  164. aria_code/apps/cli/exec_events.py +255 -0
  165. aria_code/apps/cli/file_references.py +155 -0
  166. aria_code/apps/cli/finance_service_catalog.py +111 -0
  167. aria_code/apps/cli/football_reports.py +231 -0
  168. aria_code/apps/cli/github_app_auth.py +135 -0
  169. aria_code/apps/cli/google_login.py +239 -0
  170. aria_code/apps/cli/handlers/__init__.py +11 -0
  171. aria_code/apps/cli/handlers/broker_handlers.py +122 -0
  172. aria_code/apps/cli/handlers/chart_handlers.py +1314 -0
  173. aria_code/apps/cli/handlers/market_handlers.py +2570 -0
  174. aria_code/apps/cli/handlers/realty_handlers.py +114 -0
  175. aria_code/apps/cli/handlers/sports_handlers.py +22 -0
  176. aria_code/apps/cli/handlers/strategy_advice.py +82 -0
  177. aria_code/apps/cli/headless.py +352 -0
  178. aria_code/apps/cli/helpers.py +326 -0
  179. aria_code/apps/cli/hooks.py +180 -0
  180. aria_code/apps/cli/i18n.py +310 -0
  181. aria_code/apps/cli/intent.py +136 -0
  182. aria_code/apps/cli/intent_router.py +305 -0
  183. aria_code/apps/cli/intent_shadow.py +72 -0
  184. aria_code/apps/cli/intent_signals.py +347 -0
  185. aria_code/apps/cli/lifecycle_hooks.py +48 -0
  186. aria_code/apps/cli/local_tool_schemas.py +550 -0
  187. aria_code/apps/cli/main.py +39 -0
  188. aria_code/apps/cli/market_hours.py +100 -0
  189. aria_code/apps/cli/market_metadata.py +135 -0
  190. aria_code/apps/cli/market_universe.py +487 -0
  191. aria_code/apps/cli/message_processing.py +310 -0
  192. aria_code/apps/cli/mixin_binding.py +32 -0
  193. aria_code/apps/cli/model_catalog.py +652 -0
  194. aria_code/apps/cli/model_probe.py +169 -0
  195. aria_code/apps/cli/model_retirement.py +65 -0
  196. aria_code/apps/cli/native_branding.py +12 -0
  197. aria_code/apps/cli/native_bridge.py +118 -0
  198. aria_code/apps/cli/native_entry.py +58 -0
  199. aria_code/apps/cli/pdf_report.py +977 -0
  200. aria_code/apps/cli/plan_mode.py +139 -0
  201. aria_code/apps/cli/plotly_html.py +15 -0
  202. aria_code/apps/cli/prediction_feedback.py +202 -0
  203. aria_code/apps/cli/preflight.py +502 -0
  204. aria_code/apps/cli/project_aria.py +60 -0
  205. aria_code/apps/cli/prompt_assembly.py +78 -0
  206. aria_code/apps/cli/prompts/__init__.py +0 -0
  207. aria_code/apps/cli/prompts/coding.py +71 -0
  208. aria_code/apps/cli/prompts/select.py +193 -0
  209. aria_code/apps/cli/prompts/system_prompts.py +562 -0
  210. aria_code/apps/cli/prompts/ui.py +436 -0
  211. aria_code/apps/cli/provider_endpoints.py +237 -0
  212. aria_code/apps/cli/provider_keys.py +73 -0
  213. aria_code/apps/cli/providers/__init__.py +1 -0
  214. aria_code/apps/cli/providers/base.py +775 -0
  215. aria_code/apps/cli/providers/chat_routing.py +139 -0
  216. aria_code/apps/cli/providers/collaboration.py +98 -0
  217. aria_code/apps/cli/providers/llm/__init__.py +1 -0
  218. aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
  219. aria_code/apps/cli/providers/llm/ollama_stream.py +1574 -0
  220. aria_code/apps/cli/providers/llm/response_cache.py +60 -0
  221. aria_code/apps/cli/providers/llm/sse_stream.py +298 -0
  222. aria_code/apps/cli/providers/runtime_bridge.py +586 -0
  223. aria_code/apps/cli/providers/vertexai_stream.py +592 -0
  224. aria_code/apps/cli/release_download.py +159 -0
  225. aria_code/apps/cli/review_runner.py +111 -0
  226. aria_code/apps/cli/runtime_consumer.py +769 -0
  227. aria_code/apps/cli/sandbox.py +32 -0
  228. aria_code/apps/cli/session_export.py +120 -0
  229. aria_code/apps/cli/session_jsonl.py +208 -0
  230. aria_code/apps/cli/session_refresh.py +123 -0
  231. aria_code/apps/cli/session_store.py +125 -0
  232. aria_code/apps/cli/skills_catalog.py +273 -0
  233. aria_code/apps/cli/task_isolation.py +211 -0
  234. aria_code/apps/cli/terminal_chrome.py +412 -0
  235. aria_code/apps/cli/todo_tracker.py +190 -0
  236. aria_code/apps/cli/tool_executor.py +932 -0
  237. aria_code/apps/cli/tool_registry.py +34 -0
  238. aria_code/apps/cli/tool_scope.py +113 -0
  239. aria_code/apps/cli/tools/__init__.py +40 -0
  240. aria_code/apps/cli/tools/context.py +46 -0
  241. aria_code/apps/cli/tools/file_tools.py +181 -0
  242. aria_code/apps/cli/tools/market_tools.py +626 -0
  243. aria_code/apps/cli/tools/notebook_tools.py +111 -0
  244. aria_code/apps/cli/tools/system_tools.py +656 -0
  245. aria_code/apps/cli/tools/write_tools.py +961 -0
  246. aria_code/apps/cli/tradingview_bridge.py +529 -0
  247. aria_code/apps/cli/transactions.py +269 -0
  248. aria_code/apps/cli/turn_planning.py +137 -0
  249. aria_code/apps/cli/update_check.py +257 -0
  250. aria_code/apps/cli/updater.py +373 -0
  251. aria_code/apps/cli/utils/__init__.py +0 -0
  252. aria_code/apps/cli/utils/market_detect.py +1663 -0
  253. aria_code/apps/cli/web_login.py +136 -0
  254. aria_code/apps/cli/workflow_runner.py +127 -0
  255. aria_code/apps/cli/workspace_route.py +121 -0
  256. aria_code/apps/daemon/README.md +14 -0
  257. aria_code/apps/vscode/README.md +115 -0
  258. aria_code/apps/vscode/package.json +70 -0
  259. aria_code/aria_cli.py +5244 -0
  260. aria_code/aria_daemon.py +1372 -0
  261. aria_code/aria_feishu_bot.py +16 -0
  262. aria_code/aria_mcp_server.py +61 -0
  263. aria_code/aria_relay_client.py +4 -0
  264. aria_code/aria_telegram_bot.py +11 -0
  265. aria_code/ariarc.py +385 -0
  266. aria_code/artifacts.py +581 -0
  267. aria_code/backtest_engine.py +4 -0
  268. aria_code/backtest_report.py +4 -0
  269. aria_code/brokers/__init__.py +78 -0
  270. aria_code/brokers/automation.py +193 -0
  271. aria_code/brokers/base.py +231 -0
  272. aria_code/brokers/capabilities.py +264 -0
  273. aria_code/brokers/cn/__init__.py +10 -0
  274. aria_code/brokers/cn/easytrader_broker.py +193 -0
  275. aria_code/brokers/cn/futu_broker.py +194 -0
  276. aria_code/brokers/cn/longbridge_broker.py +190 -0
  277. aria_code/brokers/cn/tiger_broker.py +196 -0
  278. aria_code/brokers/cn/xtquant_broker.py +225 -0
  279. aria_code/brokers/config.py +414 -0
  280. aria_code/brokers/intl/__init__.py +5 -0
  281. aria_code/brokers/intl/alpaca_broker.py +183 -0
  282. aria_code/brokers/intl/ibkr_broker.py +215 -0
  283. aria_code/brokers/intl/trading212_broker.py +183 -0
  284. aria_code/brokers/intl/webull_broker.py +156 -0
  285. aria_code/brokers/paper_broker.py +266 -0
  286. aria_code/brokers/planning.py +296 -0
  287. aria_code/brokers/registry.py +213 -0
  288. aria_code/brokers/trading.py +267 -0
  289. aria_code/canva_client.py +4 -0
  290. aria_code/change_store.py +127 -0
  291. aria_code/clients/__init__.py +0 -0
  292. aria_code/clients/aliyun_data_client.py +660 -0
  293. aria_code/clients/aria_relay_client.py +16 -0
  294. aria_code/clients/canva_client.py +284 -0
  295. aria_code/clients/figma_client.py +100 -0
  296. aria_code/clients/football_data_client.py +1671 -0
  297. aria_code/clients/kling_video_client.py +182 -0
  298. aria_code/clients/market_data_client.py +1985 -0
  299. aria_code/clients/openai_image_client.py +242 -0
  300. aria_code/clients/runway_video_client.py +157 -0
  301. aria_code/clients/warehouse_erp_client.py +105 -0
  302. aria_code/cloud_memory.py +190 -0
  303. aria_code/command_safety.py +19 -0
  304. aria_code/computer_use_server.py +276 -0
  305. aria_code/computer_use_tools.py +4 -0
  306. aria_code/dashboard_generator.py +579 -0
  307. aria_code/data_analysis_tools.py +4 -0
  308. aria_code/data_cleaner.py +483 -0
  309. aria_code/data_service.py +513 -0
  310. aria_code/datasources/__init__.py +23 -0
  311. aria_code/datasources/base.py +166 -0
  312. aria_code/datasources/router.py +253 -0
  313. aria_code/datasources/sec_statements.py +217 -0
  314. aria_code/datasources/sources/__init__.py +15 -0
  315. aria_code/datasources/sources/akshare_source.py +269 -0
  316. aria_code/datasources/sources/alpha_vantage_source.py +203 -0
  317. aria_code/datasources/sources/edgar_source.py +219 -0
  318. aria_code/datasources/sources/finnhub_source.py +198 -0
  319. aria_code/datasources/sources/fred_source.py +223 -0
  320. aria_code/datasources/sources/tushare_source.py +143 -0
  321. aria_code/datasources/sources/web_scraper_source.py +278 -0
  322. aria_code/datasources/sources/world_bank_source.py +206 -0
  323. aria_code/datasources/sources/yfinance_source.py +152 -0
  324. aria_code/doctor.py +860 -0
  325. aria_code/domain/__init__.py +0 -0
  326. aria_code/domain/_finance_core/__init__.py +5 -0
  327. aria_code/domain/_finance_core/backtest_engine.py +325 -0
  328. aria_code/domain/_finance_core/performance_stats.py +101 -0
  329. aria_code/domain/backtest_engine.py +75 -0
  330. aria_code/domain/backtest_report.py +488 -0
  331. aria_code/domain/finance_formulas.py +389 -0
  332. aria_code/domain/performance_stats.py +6 -0
  333. aria_code/domain/portfolio_ledger.py +307 -0
  334. aria_code/domain/strategy_vault.py +421 -0
  335. aria_code/evals/__init__.py +37 -0
  336. aria_code/evals/behavior.py +113 -0
  337. aria_code/evals/gap.py +93 -0
  338. aria_code/evals/harness.py +949 -0
  339. aria_code/evals/review_probe.py +132 -0
  340. aria_code/evals/runner.py +425 -0
  341. aria_code/evals/scorecard.py +322 -0
  342. aria_code/evals/summary.py +125 -0
  343. aria_code/external_agent_runner.py +115 -0
  344. aria_code/figma_client.py +4 -0
  345. aria_code/file_analysis_tools.py +4 -0
  346. aria_code/finance_formulas.py +4 -0
  347. aria_code/football_data_client.py +4 -0
  348. aria_code/image_gen_tools.py +4 -0
  349. aria_code/intent_classifier.py +459 -0
  350. aria_code/kling_video_client.py +4 -0
  351. aria_code/licensing.py +151 -0
  352. aria_code/local_finance_tools.py +4 -0
  353. aria_code/local_image_provider.py +4 -0
  354. aria_code/local_llm_provider.py +4 -0
  355. aria_code/macro_tools.py +4 -0
  356. aria_code/markdown_pdf.py +289 -0
  357. aria_code/market_data_client.py +4 -0
  358. aria_code/mcp_client.py +744 -0
  359. aria_code/memory_manager.py +273 -0
  360. aria_code/model_capability.py +512 -0
  361. aria_code/notification_tools.py +4 -0
  362. aria_code/openai_image_client.py +4 -0
  363. aria_code/packages/__init__.py +26 -0
  364. aria_code/packages/adk_bridge/__init__.py +11 -0
  365. aria_code/packages/adk_bridge/code_review_tools.py +49 -0
  366. aria_code/packages/adk_bridge/market_tools.py +125 -0
  367. aria_code/packages/aria_agents/__init__.py +5 -0
  368. aria_code/packages/aria_agents/manifest.py +78 -0
  369. aria_code/packages/aria_core/__init__.py +34 -0
  370. aria_code/packages/aria_core/architecture.py +269 -0
  371. aria_code/packages/aria_core/export.py +147 -0
  372. aria_code/packages/aria_core/manifest.py +65 -0
  373. aria_code/packages/aria_core/paths.py +57 -0
  374. aria_code/packages/aria_core/secure_file.py +68 -0
  375. aria_code/packages/aria_infra/__init__.py +15 -0
  376. aria_code/packages/aria_infra/arthera.py +58 -0
  377. aria_code/packages/aria_infra/doctor.py +246 -0
  378. aria_code/packages/aria_infra/product.py +40 -0
  379. aria_code/packages/aria_mcp/__init__.py +25 -0
  380. aria_code/packages/aria_mcp/bridge.py +232 -0
  381. aria_code/packages/aria_mcp/circuit.py +82 -0
  382. aria_code/packages/aria_mcp/config.py +162 -0
  383. aria_code/packages/aria_mcp/installer.py +95 -0
  384. aria_code/packages/aria_mcp/server.py +1705 -0
  385. aria_code/packages/aria_mcp/tools.py +81 -0
  386. aria_code/packages/aria_sdk/__init__.py +19 -0
  387. aria_code/packages/aria_sdk/client.py +393 -0
  388. aria_code/packages/aria_sdk/providers.py +70 -0
  389. aria_code/packages/aria_sdk/streaming.py +75 -0
  390. aria_code/packages/aria_sdk/types.py +86 -0
  391. aria_code/packages/aria_services/__init__.py +92 -0
  392. aria_code/packages/aria_services/cache.py +60 -0
  393. aria_code/packages/aria_services/context.py +356 -0
  394. aria_code/packages/aria_services/context_checkpoints.py +157 -0
  395. aria_code/packages/aria_services/data.py +11 -0
  396. aria_code/packages/aria_services/provider_health.py +304 -0
  397. aria_code/packages/aria_services/references.py +247 -0
  398. aria_code/packages/aria_services/registry.py +249 -0
  399. aria_code/packages/aria_services/research_protocol.py +137 -0
  400. aria_code/packages/aria_services/research_quality.py +186 -0
  401. aria_code/packages/aria_services/settings.py +160 -0
  402. aria_code/packages/aria_services/usage.py +151 -0
  403. aria_code/packages/aria_skills/__init__.py +46 -0
  404. aria_code/packages/aria_skills/catalog.py +149 -0
  405. aria_code/packages/aria_skills/loader.py +455 -0
  406. aria_code/packages/aria_skills/registry.py +84 -0
  407. aria_code/packages/aria_tools/__init__.py +5 -0
  408. aria_code/packages/aria_tools/financial/__init__.py +5 -0
  409. aria_code/packages/aria_tools/financial/_paths.py +92 -0
  410. aria_code/packages/aria_tools/registry.py +129 -0
  411. aria_code/packages/quant_engine/__init__.py +33 -0
  412. aria_code/packages/quant_engine/_namespace.py +24 -0
  413. aria_code/packages/quant_engine/backtest/__init__.py +10 -0
  414. aria_code/packages/quant_engine/backtest/core.py +53 -0
  415. aria_code/packages/quant_engine/backtest/engine.py +102 -0
  416. aria_code/packages/quant_engine/portfolio/__init__.py +9 -0
  417. aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
  418. aria_code/packages/quant_engine/sports/__init__.py +76 -0
  419. aria_code/packages/quant_engine/sports/calibrator.py +354 -0
  420. aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
  421. aria_code/packages/quant_engine/sports/elo.py +300 -0
  422. aria_code/packages/quant_engine/sports/form.py +188 -0
  423. aria_code/packages/quant_engine/sports/h2h.py +195 -0
  424. aria_code/packages/quant_engine/sports/predictor.py +312 -0
  425. aria_code/packages/quant_engine/sports/tracker.py +665 -0
  426. aria_code/packages/quant_engine/stochastic/__init__.py +31 -0
  427. aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  428. aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
  429. aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  430. aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  431. aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
  432. aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  433. aria_code/packs/__init__.py +85 -0
  434. aria_code/packs/base.py +168 -0
  435. aria_code/packs/finance/__init__.py +250 -0
  436. aria_code/packs/healthcare/__init__.py +40 -0
  437. aria_code/packs/logistics/__init__.py +171 -0
  438. aria_code/packs/payments/__init__.py +139 -0
  439. aria_code/packs/realty/__init__.py +190 -0
  440. aria_code/packs/registry.py +202 -0
  441. aria_code/packs/rules.py +90 -0
  442. aria_code/plan_utils.py +194 -0
  443. aria_code/plugin_loader.py +328 -0
  444. aria_code/portfolio_ledger.py +4 -0
  445. aria_code/preview_server.py +449 -0
  446. aria_code/privacy/__init__.py +5 -0
  447. aria_code/privacy/feedback.py +123 -0
  448. aria_code/project_review.py +278 -0
  449. aria_code/project_review_api.py +235 -0
  450. aria_code/project_review_server.py +18 -0
  451. aria_code/project_tools.py +4 -0
  452. aria_code/providers/__init__.py +30 -0
  453. aria_code/providers/google_video_provider.py +256 -0
  454. aria_code/providers/llm/__init__.py +19 -0
  455. aria_code/providers/llm/anthropic.py +184 -0
  456. aria_code/providers/llm/arthera_gateway.py +90 -0
  457. aria_code/providers/llm/autoconfig.py +213 -0
  458. aria_code/providers/llm/base.py +139 -0
  459. aria_code/providers/llm/custom.py +158 -0
  460. aria_code/providers/llm/ollama.py +137 -0
  461. aria_code/providers/llm/openai_compat.py +350 -0
  462. aria_code/providers/llm/registry.py +457 -0
  463. aria_code/providers/local_image_provider.py +202 -0
  464. aria_code/providers/local_llm_provider.py +549 -0
  465. aria_code/providers/ollama_models.py +79 -0
  466. aria_code/providers/openai_params.py +32 -0
  467. aria_code/providers/tool_messages.py +100 -0
  468. aria_code/realty_data_tools.py +4 -0
  469. aria_code/report_exporters.py +150 -0
  470. aria_code/report_generator.py +1764 -0
  471. aria_code/review_service.py +497 -0
  472. aria_code/runtime/__init__.py +176 -0
  473. aria_code/runtime/acceptance.py +632 -0
  474. aria_code/runtime/agent_loop.py +1851 -0
  475. aria_code/runtime/agent_profiles.py +75 -0
  476. aria_code/runtime/approval.py +71 -0
  477. aria_code/runtime/baseline.py +219 -0
  478. aria_code/runtime/budget.py +232 -0
  479. aria_code/runtime/checkpoints.py +481 -0
  480. aria_code/runtime/consult.py +65 -0
  481. aria_code/runtime/contract.py +282 -0
  482. aria_code/runtime/delivery.py +432 -0
  483. aria_code/runtime/events.py +113 -0
  484. aria_code/runtime/gateway.py +167 -0
  485. aria_code/runtime/lsp.py +484 -0
  486. aria_code/runtime/native_index.py +153 -0
  487. aria_code/runtime/orchestrator.py +55 -0
  488. aria_code/runtime/processes.py +343 -0
  489. aria_code/runtime/project_graph.py +614 -0
  490. aria_code/runtime/repo_map.py +712 -0
  491. aria_code/runtime/research_bundle.py +105 -0
  492. aria_code/runtime/review.py +269 -0
  493. aria_code/runtime/run_state.py +127 -0
  494. aria_code/runtime/run_store.py +522 -0
  495. aria_code/runtime/scoped_runs.py +113 -0
  496. aria_code/runtime/self_healing.py +422 -0
  497. aria_code/runtime/semantic_diff.py +171 -0
  498. aria_code/runtime/subagent.py +721 -0
  499. aria_code/runtime/symbol_edit.py +117 -0
  500. aria_code/runtime/task_brief.py +46 -0
  501. aria_code/runtime/task_context.py +145 -0
  502. aria_code/runtime/task_ledger.py +108 -0
  503. aria_code/runtime/task_orchestrator.py +90 -0
  504. aria_code/runtime/task_outcome.py +33 -0
  505. aria_code/runtime/task_verification.py +266 -0
  506. aria_code/runtime/task_worktree.py +448 -0
  507. aria_code/runtime/tool_executor.py +329 -0
  508. aria_code/runtime/tool_policy.py +107 -0
  509. aria_code/runtime/transactions.py +216 -0
  510. aria_code/runtime/windows_job.py +115 -0
  511. aria_code/runtime/workflows.py +222 -0
  512. aria_code/runtime/worktrees.py +215 -0
  513. aria_code/runway_video_client.py +4 -0
  514. aria_code/safety/__init__.py +27 -0
  515. aria_code/safety/client_isolation.py +178 -0
  516. aria_code/safety/permissions.py +339 -0
  517. aria_code/safety/risk.py +699 -0
  518. aria_code/safety/sandbox.py +148 -0
  519. aria_code/safety/service.py +102 -0
  520. aria_code/service_endpoints.py +14 -0
  521. aria_code/setup_wizard.py +684 -0
  522. aria_code/spreadsheet_tools.py +4 -0
  523. aria_code/strategy_vault.py +4 -0
  524. aria_code/tools/__init__.py +0 -0
  525. aria_code/tools/artifact_tools.py +252 -0
  526. aria_code/tools/broker_tools.py +92 -0
  527. aria_code/tools/build_quant_engine.py +97 -0
  528. aria_code/tools/code_audit_tools.py +192 -0
  529. aria_code/tools/computer_use_tools.py +509 -0
  530. aria_code/tools/data_analysis_tools.py +831 -0
  531. aria_code/tools/enterprise_finance_tools.py +202 -0
  532. aria_code/tools/extended_tools.py +240 -0
  533. aria_code/tools/file_analysis_tools.py +880 -0
  534. aria_code/tools/image_gen_tools.py +114 -0
  535. aria_code/tools/local_finance_tools.py +3488 -0
  536. aria_code/tools/logistics_carriers.py +11 -0
  537. aria_code/tools/logistics_inventory.py +11 -0
  538. aria_code/tools/logistics_io.py +11 -0
  539. aria_code/tools/logistics_tenancy.py +11 -0
  540. aria_code/tools/logistics_tools.py +11 -0
  541. aria_code/tools/macro_tools.py +412 -0
  542. aria_code/tools/notification_tools.py +249 -0
  543. aria_code/tools/project_tools.py +525 -0
  544. aria_code/tools/realty_data_tools.py +662 -0
  545. aria_code/tools/spreadsheet_tools.py +399 -0
  546. aria_code/tools/stripe_tools.py +115 -0
  547. aria_code/tools/video_tools.py +500 -0
  548. aria_code/training/__init__.py +1 -0
  549. aria_code/training/compare.py +117 -0
  550. aria_code/training/prelude_commands.py +111 -0
  551. aria_code/training/prelude_eval.py +390 -0
  552. aria_code/training/prelude_real.py +183 -0
  553. aria_code/training/prelude_train.py +248 -0
  554. aria_code/training/sft_dataset.py +230 -0
  555. aria_code/training/trajectory_dataset.py +263 -0
  556. aria_code/ui/__init__.py +105 -0
  557. aria_code/ui/banner.py +404 -0
  558. aria_code/ui/completer.py +571 -0
  559. aria_code/ui/console.py +285 -0
  560. aria_code/ui/input_box.py +505 -0
  561. aria_code/ui/mentions.py +206 -0
  562. aria_code/ui/picker.py +248 -0
  563. aria_code/ui/render/__init__.py +11 -0
  564. aria_code/ui/render/actions.py +297 -0
  565. aria_code/ui/render/finance.py +1732 -0
  566. aria_code/ui/render/market.py +253 -0
  567. aria_code/ui/render/output.py +954 -0
  568. aria_code/ui/render/responsive.py +75 -0
  569. aria_code/ui/render/team.py +367 -0
  570. aria_code/ui/robot.py +239 -0
  571. aria_code/ui/startup_dashboard.py +110 -0
  572. aria_code/video_analysis.py +147 -0
  573. aria_code/video_editor.py +518 -0
  574. aria_code/video_workflow/__init__.py +8 -0
  575. aria_code/video_workflow/__main__.py +61 -0
  576. aria_code/video_workflow/engines/__init__.py +18 -0
  577. aria_code/video_workflow/engines/base.py +69 -0
  578. aria_code/video_workflow/engines/custom_model.py +204 -0
  579. aria_code/video_workflow/engines/google_veo.py +94 -0
  580. aria_code/video_workflow/engines/local_engine.py +71 -0
  581. aria_code/video_workflow/engines/registry.py +98 -0
  582. aria_code/video_workflow/google_models.py +278 -0
  583. aria_code/video_workflow/music/__init__.py +5 -0
  584. aria_code/video_workflow/music/foley_service.py +132 -0
  585. aria_code/video_workflow/music/service.py +212 -0
  586. aria_code/video_workflow/pipeline.py +620 -0
  587. aria_code/video_workflow/plan.py +202 -0
  588. aria_code/video_workflow/session.py +297 -0
  589. aria_code/workspace/__init__.py +6 -0
  590. aria_code/workspace/files.py +219 -0
  591. aria_code/workspace/verify.py +167 -0
  592. aria_code/workspace_changes.py +199 -0
  593. aria_code/workspace_service.py +196 -0
  594. aria_code/workspace_tools.py +150 -0
  595. aria_code-0.167.0.dist-info/METADATA +330 -0
  596. aria_code-0.167.0.dist-info/RECORD +601 -0
  597. aria_code-0.167.0.dist-info/WHEEL +5 -0
  598. aria_code-0.167.0.dist-info/entry_points.txt +5 -0
  599. aria_code-0.167.0.dist-info/licenses/LICENSE +201 -0
  600. aria_code-0.167.0.dist-info/licenses/NOTICE +49 -0
  601. aria_code-0.167.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,1236 @@
1
+ """BrokerCommandsMixin — /broker /account /positions /orders commands.
2
+
3
+ Extracted from aria_cli.py. Module globals (self.context.has_rich, self.context.console, etc.) are
4
+ imported lazily inside each method body to avoid circular imports at load time.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from aria_code.apps.cli.i18n import ui_text
10
+
11
+ from ._ui import Panel, print_error, rich_box
12
+ from aria_code.packages.aria_core.paths import aria_home
13
+
14
+
15
+ import json
16
+ import asyncio
17
+ import datetime
18
+ import time
19
+ import shlex
20
+ import sys
21
+ import os
22
+ from typing import Dict, Any, Optional
23
+
24
+
25
+ class BrokerCommandsMixin:
26
+ """Mixin providing broker/account/positions/orders commands."""
27
+
28
+ async def cmd_broker(self, args: str):
29
+ """券商账户管理: /broker list | guide | doctor | services | connect <id> | add <type>"""
30
+ from aria_code.apps.cli._optional import HAS_BROKERS as _HAS_BROKERS
31
+ if not _HAS_BROKERS:
32
+ print_error(self.context, "brokers 模块未加载", "请确认 brokers/ 目录存在")
33
+ return
34
+
35
+ parts = args.strip().split(maxsplit=1)
36
+ sub = parts[0].lower() if parts else "list"
37
+ rest = parts[1].strip() if len(parts) > 1 else ""
38
+
39
+ if sub == "list":
40
+ await self._cmd_broker_list()
41
+ elif sub == "status":
42
+ await self._cmd_broker_status()
43
+ elif sub in ("guide", "matrix", "capabilities", "capability", "help"):
44
+ await self._cmd_broker_guide(rest)
45
+ elif sub in ("doctor", "check", "preflight"):
46
+ await self._cmd_broker_doctor(rest)
47
+ elif sub in ("services", "service", "usage"):
48
+ await self._cmd_broker_services()
49
+ elif sub == "connect":
50
+ await self._cmd_broker_connect(rest)
51
+ elif sub == "disconnect":
52
+ await self._cmd_broker_disconnect(rest)
53
+ elif sub in ("add", "new"):
54
+ await self._cmd_broker_add(rest)
55
+ elif sub == "remove":
56
+ await self._cmd_broker_remove(rest)
57
+ elif sub in ("default", "use"):
58
+ await self._cmd_broker_default(rest)
59
+ elif sub == "init":
60
+ await self._cmd_broker_init()
61
+ else:
62
+ if self.context.has_rich:
63
+ self.context.console.print(Panel(
64
+ "[dim]用法:[/dim]\n"
65
+ " [bold]/broker list[/bold] — 显示所有已配置券商\n"
66
+ " [bold]/broker guide[/bold] [type] — 查看券商能力矩阵和连接步骤\n"
67
+ " [bold]/broker doctor[/bold] — 检查配置字段、SDK 和连接状态\n"
68
+ " [bold]/broker services[/bold] — 查看券商数据如何进入分析/报告/交易计划\n"
69
+ " [bold]/broker connect[/bold] [id] — 连接券商\n"
70
+ " [bold]/broker disconnect[/bold] [id] — 断开连接\n"
71
+ " [bold]/broker status[/bold] — 查看连接状态\n"
72
+ " [bold]/broker add[/bold] <type> — 添加新券商配置\n"
73
+ " [bold]/broker remove[/bold] <id> — 删除券商配置\n"
74
+ " [bold]/broker default[/bold] <id> — 设置默认账户\n"
75
+ " [bold]/broker init[/bold] — 输出所有类型的配置模板",
76
+ title="[bold]/broker[/bold]",
77
+ border_style="dim", box=rich_box.ROUNDED, padding=(0, 1),
78
+ ))
79
+
80
+ async def _cmd_broker_list(self):
81
+ from aria_code.apps.cli._optional import list_broker_configs as _list_broker_configs, BROKERS_CONFIG_PATH as _BROKERS_CONFIG_PATH, get_registry as _get_broker_registry
82
+ cfgs = _list_broker_configs()
83
+ if not cfgs:
84
+ await self._prompt_no_broker_action()
85
+ return
86
+
87
+ reg = _get_broker_registry()
88
+ if self.context.has_rich:
89
+ from rich.table import Table
90
+ tbl = Table(title="[bold]已配置券商[/bold]", show_header=True, header_style="bold")
91
+ tbl.add_column("ID", style="bold")
92
+ tbl.add_column("类型", style="dim")
93
+ tbl.add_column("名称")
94
+ tbl.add_column("市场", style="dim")
95
+ tbl.add_column("状态")
96
+ tbl.add_column("默认", justify="center")
97
+ for c in cfgs:
98
+ bid = c.get("id", "")
99
+ btype = c.get("type", "")
100
+ label = c.get("label", bid)
101
+ b = reg.get(bid) if reg else None
102
+ is_active = reg and reg.active() and reg.active().broker_id == bid
103
+ if b and b.is_connected:
104
+ status = "[green]● 已连接[/green]"
105
+ else:
106
+ status = "[dim]○ 未连接[/dim]"
107
+ market_map = {
108
+ "xtquant":"A股","easytrader":"A股","futu":"港/美/A",
109
+ "tiger":"美/港/A","longbridge":"港/美/A",
110
+ "ibkr":"美股","alpaca":"美股","webull":"美股",
111
+ }
112
+ mkt = c.get("market", market_map.get(btype, "—"))
113
+ default_mark = "[green]✓[/green]" if c.get("default") or is_active else ""
114
+ tbl.add_row(bid, btype, label, mkt, status, default_mark)
115
+ self.context.console.print(tbl)
116
+ else:
117
+ for c in cfgs:
118
+ print(f" {c.get('id',''):<20} {c.get('type',''):<12} {c.get('label','')}")
119
+
120
+ async def _cmd_broker_status(self):
121
+ from aria_code.apps.cli._optional import get_registry as _get_broker_registry
122
+ from aria_code.safety import SafetyService
123
+
124
+ # Risk-off banner: make a global trading freeze impossible to miss.
125
+ if SafetyService(self.terminal.config).trading_dry_run():
126
+ if self.context.has_rich:
127
+ self.context.console.print("[black on yellow] DRY-RUN 全局只读 (ARIA_DRY_RUN) — 所有下单已冻结 [/black on yellow]")
128
+ else:
129
+ print("[DRY-RUN] ARIA_DRY_RUN 已启用 — 所有下单已冻结")
130
+
131
+ reg = _get_broker_registry()
132
+ connected = reg.list_connected() if reg else []
133
+ if not connected:
134
+ if self.context.has_rich:
135
+ self.context.console.print("[dim]当前无已连接券商。运行 [bold]/broker connect[/bold] 建立连接。[/dim]")
136
+ else:
137
+ print("无已连接券商")
138
+ return
139
+ for b in connected:
140
+ # Heartbeat: green = live probe ok, yellow = flag set but probe failed.
141
+ try:
142
+ healthy = bool(b.is_connected and b.ping())
143
+ except Exception:
144
+ healthy = False
145
+ dot = "[green]●[/green]" if healthy else "[yellow]○[/yellow]"
146
+ try:
147
+ acct = b.account_info()
148
+ line = (
149
+ f"{dot} [bold]{b.label}[/bold] ({b.broker_type})"
150
+ f" 账户: {acct.masked_account}"
151
+ f" 总资产: [bold]{acct.currency} {acct.total_assets:,.2f}[/bold]"
152
+ f" 可用: {acct.cash:,.2f}"
153
+ )
154
+ if not healthy:
155
+ line += " [dim](心跳异常)[/dim]"
156
+ except Exception as e:
157
+ line = f"[yellow]○[/yellow] [bold]{b.label}[/bold] ({b.broker_type}) [dim]查询失败: {e}[/dim]"
158
+ if self.context.has_rich:
159
+ self.context.console.print(line)
160
+ else:
161
+ print(line)
162
+
163
+ async def _cmd_broker_guide(self, broker_type: str = ""):
164
+ """Show broker capability matrix or a single broker setup plan."""
165
+
166
+ from brokers.capabilities import (
167
+ broker_connection_plan, broker_dependency_state,
168
+ get_broker_capability, list_broker_capabilities,
169
+ )
170
+ from brokers.config import get_broker_config
171
+
172
+ query = (broker_type or "").strip().split(maxsplit=1)[0].lower()
173
+ spec = get_broker_capability(query) if query else None
174
+ if query and not spec:
175
+ cfg = get_broker_config(query)
176
+ if cfg:
177
+ spec = get_broker_capability(str(cfg.get("type", "")))
178
+ if query and not spec:
179
+ print_error(self.context, f"未知券商类型或配置 id: {query}", "运行 /broker guide 查看支持列表")
180
+ return
181
+
182
+ if spec:
183
+ dep = broker_dependency_state(spec)
184
+ lines = [
185
+ f"类型: {spec.broker_type}",
186
+ f"市场: {', '.join(spec.markets)}",
187
+ f"读取能力: {', '.join(spec.read_capabilities)}",
188
+ f"交易能力: {spec.trade_capability}",
189
+ f"SDK: {spec.sdk_module} / {spec.pip_package} "
190
+ f"({'已安装' if dep['installed'] else '未安装'})",
191
+ f"本地运行时: {spec.local_runtime}",
192
+ f"配置字段: {', '.join(spec.credential_fields)}",
193
+ "",
194
+ "接入步骤:",
195
+ *[f" {idx}. {step}" for idx, step in enumerate(broker_connection_plan(spec.broker_type), 1)],
196
+ "",
197
+ "安全边界:",
198
+ *[f" - {note}" for note in spec.safety_notes],
199
+ ]
200
+ if self.context.has_rich:
201
+ self.context.console.print(Panel(
202
+ "\n".join(lines),
203
+ title=f"[bold]{spec.display_name}[/bold] 连接指南",
204
+ border_style="blue",
205
+ box=rich_box.ROUNDED,
206
+ padding=(0, 1),
207
+ ))
208
+ else:
209
+ print("\n".join(lines))
210
+ return
211
+
212
+ specs = list_broker_capabilities()
213
+ if self.context.has_rich:
214
+ from rich.table import Table
215
+ tbl = Table(
216
+ title="[bold]券商能力矩阵[/bold]",
217
+ show_header=True,
218
+ header_style="bold dim",
219
+ box=rich_box.ROUNDED,
220
+ border_style="dim",
221
+ )
222
+ tbl.add_column("Type", style="bold")
223
+ tbl.add_column("券商")
224
+ tbl.add_column("市场")
225
+ tbl.add_column("SDK")
226
+ tbl.add_column("交易")
227
+ tbl.add_column("运行时 / 凭证")
228
+ tbl.add_column("下一步")
229
+ for item in specs:
230
+ dep = broker_dependency_state(item)
231
+ sdk = f"{item.sdk_module} " + ("[green]✓[/green]" if dep["installed"] else "[yellow]未装[/yellow]")
232
+ trade = "[green]可交易[/green]" if item.can_trade else "[dim]只读[/dim]"
233
+ runtime = item.local_runtime.split(";", 1)[0]
234
+ tbl.add_row(
235
+ item.broker_type,
236
+ item.display_name,
237
+ "/".join(item.markets),
238
+ sdk,
239
+ trade,
240
+ runtime,
241
+ f"/broker guide {item.broker_type}",
242
+ )
243
+ self.context.console.print(tbl)
244
+ self.context.console.print("[dim]配置路径: brokers.json;添加: /broker add <type>;体检: /broker doctor[/dim]")
245
+ else:
246
+ for item in specs:
247
+ print(
248
+ f"{item.broker_type:<12} {item.display_name:<28} "
249
+ f"{'/'.join(item.markets):<12} {item.pip_package:<14} {item.trade_capability}"
250
+ )
251
+
252
+ async def _cmd_broker_doctor(self, args: str = ""):
253
+ """Check configured broker fields, SDK availability, and connection state."""
254
+
255
+ from brokers.capabilities import broker_dependency_state, get_broker_capability
256
+ from brokers.config import BROKERS_CONFIG_PATH, list_broker_configs, validate_broker_config
257
+ from aria_code.apps.cli._optional import get_registry as _get_broker_registry
258
+
259
+ cfgs = list_broker_configs()
260
+ reg = _get_broker_registry()
261
+ if not cfgs:
262
+ if self.context.has_rich:
263
+ self.context.console.print(Panel(
264
+ "尚未配置券商。\n\n"
265
+ "先运行 /broker guide 选择券商类型,再运行 /broker add <type>。\n"
266
+ f"配置文件: {BROKERS_CONFIG_PATH}",
267
+ title="[bold]Broker Doctor[/bold]",
268
+ border_style="yellow",
269
+ box=rich_box.ROUNDED,
270
+ padding=(0, 1),
271
+ ))
272
+ else:
273
+ print(f"尚未配置券商。配置文件: {BROKERS_CONFIG_PATH}")
274
+ return
275
+
276
+ rows = []
277
+ for cfg in cfgs:
278
+ broker_id = str(cfg.get("id", ""))
279
+ btype = str(cfg.get("type", ""))
280
+ spec = get_broker_capability(btype)
281
+ errors = validate_broker_config(cfg)
282
+ fatal = [err for err in errors if not str(err).startswith("⚠")]
283
+ warnings = [err for err in errors if str(err).startswith("⚠")]
284
+ dep = broker_dependency_state(spec) if spec else {
285
+ "installed": False,
286
+ "install_hint": "",
287
+ "module": "",
288
+ "package": "",
289
+ }
290
+ broker = reg.get(broker_id) if reg and broker_id else None
291
+ connected = bool(broker and broker.is_connected)
292
+ if fatal:
293
+ status = "fail"
294
+ next_step = fatal[0]
295
+ elif not dep["installed"]:
296
+ status = "warn"
297
+ next_step = str(dep["install_hint"] or f"安装 {dep['package']}")
298
+ elif warnings:
299
+ status = "warn"
300
+ next_step = warnings[0]
301
+ elif not connected:
302
+ status = "ready"
303
+ next_step = f"/broker connect {broker_id}"
304
+ else:
305
+ status = "ok"
306
+ next_step = "/account /positions"
307
+ rows.append({
308
+ "id": broker_id,
309
+ "type": btype,
310
+ "label": str(cfg.get("label", broker_id)),
311
+ "sdk": str(dep.get("module") or "—"),
312
+ "sdk_ok": bool(dep.get("installed")),
313
+ "connected": connected,
314
+ "status": status,
315
+ "next": next_step,
316
+ })
317
+
318
+ if self.context.has_rich:
319
+ from rich.table import Table
320
+ tbl = Table(
321
+ title="[bold]Broker Doctor[/bold]",
322
+ show_header=True,
323
+ header_style="bold dim",
324
+ box=rich_box.ROUNDED,
325
+ border_style="dim",
326
+ )
327
+ tbl.add_column("ID", style="bold")
328
+ tbl.add_column("Type")
329
+ tbl.add_column("SDK")
330
+ tbl.add_column("连接")
331
+ tbl.add_column("状态")
332
+ tbl.add_column("下一步")
333
+ colors = {"ok": "green", "ready": "cyan", "warn": "yellow", "fail": "red"}
334
+ for row in rows:
335
+ color = colors.get(row["status"], "dim")
336
+ tbl.add_row(
337
+ row["id"],
338
+ row["type"],
339
+ f"{row['sdk']} {'✓' if row['sdk_ok'] else '未装'}",
340
+ "已连接" if row["connected"] else "未连接",
341
+ f"[{color}]{row['status']}[/{color}]",
342
+ row["next"],
343
+ )
344
+ self.context.console.print(tbl)
345
+ self.context.console.print(f"[dim]配置文件: {BROKERS_CONFIG_PATH}[/dim]")
346
+ else:
347
+ for row in rows:
348
+ print(f"{row['id']} {row['type']} {row['status']} next={row['next']}")
349
+
350
+ async def _cmd_broker_services(self):
351
+ """Show how broker data flows into Aria services."""
352
+
353
+ from brokers.capabilities import broker_service_playbook
354
+
355
+ rows = broker_service_playbook()
356
+ if self.context.has_rich:
357
+ from rich.table import Table
358
+ tbl = Table(
359
+ title="[bold]券商数据与项目服务联动[/bold]",
360
+ show_header=True,
361
+ header_style="bold dim",
362
+ box=rich_box.ROUNDED,
363
+ border_style="dim",
364
+ )
365
+ tbl.add_column("服务", width=18)
366
+ tbl.add_column("命令 / 流程", width=42)
367
+ tbl.add_column("被哪些能力使用", width=34)
368
+ tbl.add_column("安全边界")
369
+ for row in rows:
370
+ tbl.add_row(row["service"], row["commands"], row["used_by"], row["guardrail"])
371
+ self.context.console.print(tbl)
372
+ else:
373
+ for row in rows:
374
+ print(f"{row['service']}: {row['commands']} | {row['guardrail']}")
375
+
376
+ async def _cmd_broker_connect(self, broker_id: str):
377
+ from aria_code.apps.cli._optional import list_broker_configs as _list_broker_configs, BROKERS_CONFIG_PATH as _BROKERS_CONFIG_PATH, get_registry as _get_broker_registry
378
+ cfgs = _list_broker_configs()
379
+ if not cfgs:
380
+ print_error(self.context, "尚未配置任何券商", f"请先编辑 {_BROKERS_CONFIG_PATH}")
381
+ return
382
+ if not broker_id:
383
+ from brokers.config import get_default_broker_config
384
+ cfg = get_default_broker_config()
385
+ if not cfg:
386
+ print_error(self.context, "未设置默认券商", "请用 /broker connect <id> 指定")
387
+ return
388
+ broker_id = cfg["id"]
389
+
390
+ reg = _get_broker_registry()
391
+ label = broker_id
392
+ try:
393
+ if self.context.has_rich:
394
+ with self.context.console.status(f"[dim]{ui_text(self, f'正在连接 {broker_id}', f'Connecting to {broker_id}')}...[/dim]", spinner="dots"):
395
+ import asyncio as _aio
396
+ loop = _aio.get_event_loop()
397
+ broker = await loop.run_in_executor(None, reg.connect, broker_id)
398
+ else:
399
+ broker = reg.connect(broker_id)
400
+ label = broker.label
401
+ msg = f"[green]✓[/green] 已连接 [bold]{label}[/bold] ({broker.broker_type})"
402
+ if self.context.has_rich:
403
+ self.context.console.print(msg)
404
+ else:
405
+ print(f"已连接 {label}")
406
+ except Exception as e:
407
+ # If the failure is a missing broker SDK, give actionable guidance
408
+ # that ties into the dependency system (/install <pkg>).
409
+ _err = str(e)
410
+ _btype = ""
411
+ try:
412
+ _btype = next((c.get("type", "") for c in cfgs
413
+ if c.get("id") == broker_id), "")
414
+ except Exception:
415
+ pass
416
+ _SDK_PKG = {
417
+ "longbridge": "longbridge", "ibkr": "ib_insync", "futu": "futu-api",
418
+ "tiger": "tigeropen", "alpaca": "alpaca-py", "webull": "webull",
419
+ "easytrader": "easytrader",
420
+ }
421
+ _pkg = _SDK_PKG.get(_btype, "")
422
+ # Primary signal: can we import the SDK module? If not, it's a
423
+ # missing-dependency failure regardless of the error message
424
+ # language (the SDK raises a localized "未安装" ImportError).
425
+ _is_missing_sdk = False
426
+ if _pkg:
427
+ _mod = {"longbridge": "longbridge", "ibkr": "ib_insync",
428
+ "futu": "futu", "tiger": "tigeropen", "alpaca": "alpaca",
429
+ "webull": "webull", "easytrader": "easytrader"}.get(_btype, _pkg)
430
+ try:
431
+ import importlib as _il
432
+ _il.import_module(_mod)
433
+ except Exception:
434
+ _is_missing_sdk = True
435
+ if _is_missing_sdk:
436
+ # Put the actionable guidance in msg — _print_error's `context`
437
+ # arg is a category keyword, not free-text, so it won't render.
438
+ print_error(self.context,
439
+ f"连接失败: {label} — {_btype} SDK 未安装。"
440
+ f"运行 /install {_pkg} 安装后重试(会先确认)。",
441
+ )
442
+ else:
443
+ print_error(self.context, f"连接失败: {label}", _err or "未知错误(检查账户配置与网络)")
444
+
445
+ async def _cmd_broker_disconnect(self, broker_id: str):
446
+ from aria_code.apps.cli._optional import get_registry as _get_broker_registry
447
+ reg = _get_broker_registry()
448
+ if not broker_id:
449
+ b = reg.active() if reg else None
450
+ if not b:
451
+ print_error(self.context, "无活跃券商", "请指定 id:/broker disconnect <id>")
452
+ return
453
+ broker_id = b.broker_id
454
+ if reg:
455
+ reg.disconnect(broker_id)
456
+ msg = f"[dim]已断开连接: {broker_id}[/dim]"
457
+ if self.context.has_rich:
458
+ self.context.console.print(msg)
459
+ else:
460
+ print(f"已断开: {broker_id}")
461
+
462
+ async def _cmd_broker_add(self, broker_type: str):
463
+ from aria_code.ui.picker import arrow_select as _arrow_select
464
+ from aria_code.apps.cli._optional import supported_broker_types as _supported_broker_types, get_config_template as _get_broker_template, add_broker_config as _add_broker_cfg, BROKERS_CONFIG_PATH as _BROKERS_CONFIG_PATH
465
+ from ui.picker import arrow_select
466
+
467
+ supported = _supported_broker_types()
468
+
469
+ # ── 按市场分组、固定宽度对齐 ─────────────────────────────────────────
470
+ # 每组: (分组标签, [broker_key, ...])
471
+ _GROUPS = [
472
+ ("仿盘", ["paper"]),
473
+ ("A 股", ["xtquant", "easytrader"]),
474
+ ("港股 / 美股", ["futu", "tiger", "longbridge"]),
475
+ ("美股 / 国际", ["ibkr", "alpaca", "webull"]),
476
+ ]
477
+ _CAT = {
478
+ "paper": "仿盘",
479
+ "xtquant": "A股", "easytrader": "A股",
480
+ "futu": "港/美/A", "tiger": "港/美/A", "longbridge": "港/美/A",
481
+ "ibkr": "全球", "alpaca": "美股", "webull": "美股",
482
+ }
483
+
484
+ # ── 选择券商类型 ─────────────────────────────────────────────────────
485
+ if not broker_type or broker_type not in supported:
486
+ # Build ordered list with separators for display grouping.
487
+ # Separator entries have key=None and won't be assigned.
488
+ all_items = [] # (display_label, desc_str, key_or_None)
489
+ for g_label, g_keys in _GROUPS:
490
+ all_items.append((f"─── {g_label} ", "", None))
491
+ for k in g_keys:
492
+ if k in supported:
493
+ all_items.append((f" {k:<12}", supported[k], k))
494
+
495
+ picker_options = [(label, desc) for label, desc, _ in all_items]
496
+ sep_indices = {i for i, (_, _, key) in enumerate(all_items) if key is None}
497
+ key_at = {i: key for i, (_, _, key) in enumerate(all_items) if key}
498
+
499
+ # Start cursor on first real entry (skip leading separator)
500
+ first_real = next((i for i in range(len(all_items)) if i not in sep_indices), 0)
501
+
502
+ if self.context.has_rich:
503
+ self.context.console.print(
504
+ "[bold]选择要添加的券商[/bold] "
505
+ "[dim]↑↓ 移动 Enter 确认 q 取消[/dim]"
506
+ )
507
+
508
+ # If user lands on a separator, nudge to the next real entry.
509
+ while True:
510
+ idx = _arrow_select(picker_options, selected=first_real,
511
+ title="", max_visible=12)
512
+ if idx < 0:
513
+ return
514
+ if idx in sep_indices:
515
+ # Find next real entry below; wrap to first if none
516
+ nxt = next((i for i in range(idx + 1, len(all_items))
517
+ if i not in sep_indices), first_real)
518
+ first_real = nxt
519
+ continue
520
+ broker_type = key_at[idx]
521
+ break
522
+
523
+ tmpl = _get_broker_template(broker_type)
524
+ if not tmpl:
525
+ print_error(self.context, f"无法获取 {broker_type} 模板", "")
526
+ return
527
+
528
+ # ── 开户 & 凭证获取指南 ───────────────────────────────────────────
529
+ _GUIDE: dict[str, str] = {
530
+ "paper": (
531
+ "Aria 本地仿盘账户 — 无需真实券商\n\n"
532
+ "使用步骤:\n"
533
+ " 1. 运行 /paper start 100000 USD 创建本地仿盘账户\n"
534
+ " 2. 运行 /trade preview AAPL buy 10 190 生成订单预览\n"
535
+ " 3. 运行 /trade confirm <preview_id> 执行仿盘成交\n\n"
536
+ "数据位置:~/.arthera/paper_ledger.json"
537
+ ),
538
+ "alpaca": (
539
+ "Alpaca Markets — 免费美股/加密货币 API(支持模拟盘)\n\n"
540
+ "获取 API Key 步骤:\n"
541
+ " 1. 注册账号: https://app.alpaca.markets/signup\n"
542
+ " 2. 登录后进入 Paper Trading → API Keys\n"
543
+ " 3. 点击 [Generate New Key],复制 Key 和 Secret\n"
544
+ " 4. 模拟盘(paper=true)无需入金即可使用\n"
545
+ " 5. 实盘:修改 paper=false 并完成入金认证\n\n"
546
+ "依赖:pip install alpaca-py"
547
+ ),
548
+ "tiger": (
549
+ "老虎证券 OpenAPI — 港股 / 美股\n\n"
550
+ "获取凭证步骤:\n"
551
+ " 1. 在老虎证券 App 开户并完成实名认证\n"
552
+ " 2. 访问开发者平台: https://quant.tigeropen.com\n"
553
+ " 3. 创建应用,获取 Tiger ID 和 RSA 密钥对\n"
554
+ " 4. 将私钥文件保存到 ~/.arthera/tiger_rsa.pem\n\n"
555
+ "依赖:pip install tigeropen"
556
+ ),
557
+ "longbridge": (
558
+ "长桥证券 OpenAPI — 港股 / 美股 / A股\n\n"
559
+ "获取凭证步骤:\n"
560
+ " 1. 在长桥 App 开户并完成入金\n"
561
+ " 2. 开发者中心: https://open.longportapp.com\n"
562
+ " 3. 创建应用获取 App Key、App Secret、Access Token\n\n"
563
+ "依赖:pip install longbridge"
564
+ ),
565
+ "ibkr": (
566
+ "Interactive Brokers TWS/Gateway — 全球市场\n\n"
567
+ "连接步骤:\n"
568
+ " 1. 开户: https://www.interactivebrokers.com\n"
569
+ " 2. 下载并启动 TWS 或 IB Gateway(保持后台运行)\n"
570
+ " 3. TWS → 配置 → API → 启用 Socket Client\n"
571
+ " 实盘端口 7496,模拟端口 7497\n"
572
+ " 4. Gateway 端口:实盘 4001,模拟 4002\n\n"
573
+ "依赖:pip install ib_insync"
574
+ ),
575
+ "futu": (
576
+ "富途牛牛 OpenAPI — 港股 / 美股\n\n"
577
+ "连接步骤:\n"
578
+ " 1. 在富途牛牛 App 开户\n"
579
+ " 2. 下载并启动 FutuOpenD\n"
580
+ " (牛牛客户端 → 更多 → OpenD)\n"
581
+ " 3. OpenD 默认监听 127.0.0.1:11111,保持运行\n"
582
+ " 4. 开发者文档: https://openapi.futunn.com\n\n"
583
+ "依赖:pip install futu-api"
584
+ ),
585
+ "webull": (
586
+ "Webull — 美股(非官方 API,行情查询为主)\n\n"
587
+ "获取凭证步骤:\n"
588
+ " 1. 注册: https://www.webull.com\n"
589
+ " 2. 使用注册邮箱/手机号 + 密码即可\n"
590
+ " 3. device_id 首次留空,登录后自动填充\n"
591
+ " 4. 建议仅用于行情查询,下单功能稳定性有限\n\n"
592
+ "依赖:pip install webull"
593
+ ),
594
+ "xtquant": (
595
+ "迅投 XTQuant — A股(中信/华鑫/浙商等券商)\n\n"
596
+ "获取凭证步骤:\n"
597
+ " 1. 在支持的券商(中信/华鑫/浙商等)开户\n"
598
+ " 2. 从券商获取并安装 QMT 量化交易终端\n"
599
+ " 3. 登录 QMT 后保持运行\n"
600
+ " 4. account_id 即你的券商账号\n"
601
+ " 5. 仅支持 Windows / Linux (Wine)\n\n"
602
+ "依赖:pip install xtquant (安装包需从券商获取)"
603
+ ),
604
+ "easytrader": (
605
+ "EasyTrader — A股(同花顺/通达信/华泰/国君等)\n\n"
606
+ "配置步骤:\n"
607
+ " 1. 安装对应券商的交易客户端\n"
608
+ " 2. broker_name 可选值:\n"
609
+ " huatai / guojun / ths / tdx / yh / zszq / xq\n"
610
+ " 3. exe_path 填写客户端完整路径\n"
611
+ " 4. 使用时需保持客户端登录运行\n"
612
+ " 5. 仅支持 Windows\n\n"
613
+ "依赖:pip install easytrader"
614
+ ),
615
+ }
616
+
617
+ guide = _GUIDE.get(broker_type, "")
618
+ cat = _CAT.get(broker_type, "")
619
+ if guide and self.context.has_rich:
620
+ self.context.console.print(Panel(
621
+ guide,
622
+ title=f"[bold]{supported[broker_type]}[/bold]"
623
+ + (f" [dim]{cat}[/dim]" if cat else "")
624
+ + " — 开户 & 凭证获取指南",
625
+ border_style="blue", box=rich_box.ROUNDED, padding=(0, 2),
626
+ ))
627
+ try:
628
+ input("\n [Enter] 继续填写配置 Ctrl+C 取消 › ")
629
+ except (EOFError, KeyboardInterrupt):
630
+ if self.context.has_rich:
631
+ self.context.console.print("[dim]已取消[/dim]")
632
+ return
633
+
634
+ # ── 对话式配置向导 ─────────────────────────────────────────────────
635
+ # 字段元组: (key, 说明, 默认值, 是否隐藏输入, 是否可选)
636
+ _WIZARD: dict[str, list[tuple]] = {
637
+ "alpaca": [
638
+ ("id", "配置 ID (用于 /broker connect)", "alpaca_paper", False, False),
639
+ ("label", "显示名称", "Alpaca 模拟盘", False, False),
640
+ ("api_key", "API Key", "", False, False),
641
+ ("api_secret", "API Secret", "", True, False),
642
+ ("paper", "模拟盘 (true=模拟 / false=实盘)", "true", False, False),
643
+ ],
644
+ "paper": [
645
+ ("id", "配置 ID", "paper_main", False, False),
646
+ ("label", "显示名称", "Aria 仿盘账户", False, False),
647
+ ("starting_cash", "初始资金", "100000", False, False),
648
+ ("currency", "币种", "USD", False, False),
649
+ ],
650
+ "tiger": [
651
+ ("id", "配置 ID", "tiger_us", False, False),
652
+ ("label", "显示名称", "老虎", False, False),
653
+ ("tiger_id", "Tiger ID", "", False, False),
654
+ ("account", "账户号", "", False, False),
655
+ ("private_key_path", "RSA 私钥路径", "~/.arthera/tiger_rsa.pem", False, True),
656
+ ],
657
+ "longbridge": [
658
+ ("id", "配置 ID", "lb_main", False, False),
659
+ ("label", "显示名称", "长桥", False, False),
660
+ ("app_key", "App Key", "", False, False),
661
+ ("app_secret", "App Secret", "", True, False),
662
+ ("access_token", "Access Token", "", True, False),
663
+ ],
664
+ "ibkr": [
665
+ ("id", "配置 ID", "ibkr_main", False, False),
666
+ ("label", "显示名称", "盈透", False, False),
667
+ ("host", "TWS/Gateway 主机", "127.0.0.1", False, False),
668
+ ("port", "端口 (TWS实盘=7496 模拟=7497 Gateway=4001)", "7496", False, False),
669
+ ("client_id", "Client ID (每个连接唯一,整数)", "1", False, True),
670
+ ],
671
+ "futu": [
672
+ ("id", "配置 ID", "futu_main", False, False),
673
+ ("label", "显示名称", "富途", False, False),
674
+ ("host", "OpenD 主机", "127.0.0.1", False, False),
675
+ ("port", "OpenD 端口", "11111", False, False),
676
+ ("market", "市场 (HK / US / CN)", "HK", False, True),
677
+ ],
678
+ "webull": [
679
+ ("id", "配置 ID", "webull_main", False, False),
680
+ ("label", "显示名称", "Webull", False, False),
681
+ ("username", "邮箱或手机号", "", False, False),
682
+ ("password", "密码", "", True, False),
683
+ ("device_id", "设备 ID", "", False, True),
684
+ ],
685
+ "xtquant": [
686
+ ("id", "配置 ID", "xt_main", False, False),
687
+ ("label", "显示名称", "XTQuant", False, False),
688
+ ("account_id", "账户号", "", False, False),
689
+ ],
690
+ "easytrader": [
691
+ ("id", "配置 ID", "et_main", False, False),
692
+ ("label", "显示名称", "EasyTrader", False, False),
693
+ ("broker_name", "券商名", "huatai", False, False),
694
+ ("exe_path", "客户端路径", "C:\\华泰证券\\xiadan.exe", False, True),
695
+ ],
696
+ }
697
+
698
+ fields = _WIZARD.get(broker_type, [])
699
+ total = len(fields)
700
+
701
+ import getpass as _getpass
702
+
703
+ if self.context.has_rich:
704
+ self.context.console.print(Panel(
705
+ f"[bold]{supported[broker_type]}[/bold] 配置向导 "
706
+ f"[dim]共 {total} 项[/dim]\n"
707
+ f"[dim]Enter = 使用括号内默认值 / 标注 (可选) 的字段可直接跳过[/dim]",
708
+ border_style="dim", box=rich_box.ROUNDED, padding=(0, 1),
709
+ ))
710
+ self.context.console.print()
711
+
712
+ filled = dict(tmpl)
713
+ for step, (key, label, default, secret, optional) in enumerate(fields, 1):
714
+ # Progress prefix: [1/5]
715
+ progress = f"[{step}/{total}]"
716
+ opt_tag = " (可选)" if optional else ""
717
+
718
+ if default:
719
+ prompt_str = f" {progress} {label}{opt_tag} [{default}]: "
720
+ else:
721
+ prompt_str = f" {progress} {label}{opt_tag}: "
722
+
723
+ try:
724
+ if secret:
725
+ val = _getpass.getpass(prompt_str) or default
726
+ else:
727
+ val = input(prompt_str).strip() or default
728
+ except (EOFError, KeyboardInterrupt):
729
+ if self.context.has_rich:
730
+ self.context.console.print("\n[dim]已取消[/dim]")
731
+ else:
732
+ print("\n已取消")
733
+ return
734
+
735
+ # Type coercion
736
+ if key == "paper":
737
+ filled[key] = val.lower() not in ("false", "0", "no", "f")
738
+ elif key == "starting_cash" and val:
739
+ try:
740
+ filled[key] = float(val)
741
+ except ValueError:
742
+ filled[key] = val
743
+ elif key in ("port", "client_id") and val:
744
+ try:
745
+ filled[key] = int(val)
746
+ except ValueError:
747
+ filled[key] = val
748
+ elif val:
749
+ filled[key] = val
750
+
751
+ filled.pop("_comment", None)
752
+
753
+ # ── 保存配置 ──────────────────────────────────────────────────────
754
+ try:
755
+ _add_broker_cfg(filled)
756
+ broker_id = filled.get("id", broker_type)
757
+ if self.context.has_rich:
758
+ self.context.console.print()
759
+ self.context.console.print(Panel(
760
+ f"[green]✓ 已保存[/green] {broker_id} [dim]→ {_BROKERS_CONFIG_PATH}[/dim]",
761
+ border_style="green", box=rich_box.ROUNDED, padding=(0, 1),
762
+ ))
763
+ else:
764
+ print(f"✓ 已保存 {broker_id}")
765
+ except Exception as exc:
766
+ print_error(self.context, f"保存失败: {exc}", f"请手动编辑 {_BROKERS_CONFIG_PATH}")
767
+ return
768
+
769
+ # ── 保存后即刻连接 ────────────────────────────────────────────────
770
+ try:
771
+ ans = input(f"\n 是否立即尝试连接 {broker_id}? (y/N) › ").strip().lower()
772
+ except (EOFError, KeyboardInterrupt):
773
+ ans = ""
774
+ if ans in ("y", "yes", "是"):
775
+ await self._cmd_broker_connect(broker_id)
776
+ else:
777
+ if self.context.has_rich:
778
+ self.context.console.print(
779
+ f"[dim]稍后可运行 [bold]/broker connect {broker_id}[/bold] 建立连接[/dim]"
780
+ )
781
+
782
+ async def _cmd_broker_remove(self, broker_id: str):
783
+ from aria_code.apps.cli._optional import remove_broker_config as _remove_broker_cfg
784
+ if not broker_id:
785
+ print_error(self.context, "请指定要删除的券商 id", "/broker remove <id>")
786
+ return
787
+ removed = _remove_broker_cfg(broker_id)
788
+ if removed:
789
+ self.context.console.print(f"[dim]已删除券商配置: {broker_id}[/dim]") if self.context.has_rich else print(f"已删除: {broker_id}")
790
+ else:
791
+ print_error(self.context, f"未找到券商: {broker_id}", "")
792
+
793
+ async def _cmd_broker_default(self, broker_id: str):
794
+ from aria_code.apps.cli._optional import set_default_broker as _set_default_broker, get_registry as _get_broker_registry
795
+ if not broker_id:
796
+ print_error(self.context, "请指定 id", "/broker default <id>")
797
+ return
798
+ ok = _set_default_broker(broker_id)
799
+ if ok:
800
+ reg = _get_broker_registry()
801
+ if reg:
802
+ reg.set_active(broker_id)
803
+ msg = f"[green]✓[/green] 默认账户已设为: [bold]{broker_id}[/bold]"
804
+ if self.context.has_rich:
805
+ self.context.console.print(msg)
806
+ else:
807
+ print(f"默认账户: {broker_id}")
808
+ else:
809
+ print_error(self.context, f"未找到券商: {broker_id}", "请先用 /broker add 添加")
810
+
811
+ async def _cmd_broker_init(self):
812
+ from aria_code.apps.cli._optional import BROKERS_CONFIG_PATH as _BROKERS_CONFIG_PATH
813
+ from brokers.config import print_all_templates
814
+ if self.context.has_rich:
815
+ self.context.console.print(Panel(
816
+ f"[dim]将以下内容保存到[/dim] [bold]{_BROKERS_CONFIG_PATH}[/bold] [dim],填写实际凭证后运行 /broker connect 连接。[/dim]\n\n"
817
+ f"[dim](仅保留你需要的券商,删除不用的)[/dim]",
818
+ title="[bold]所有券商配置模板[/bold]",
819
+ border_style="dim", box=rich_box.ROUNDED, padding=(0, 1),
820
+ ))
821
+ from rich.syntax import Syntax
822
+ self.context.console.print(Syntax(print_all_templates(), "json", theme="monokai", line_numbers=False))
823
+ else:
824
+ print(print_all_templates())
825
+
826
+ async def cmd_account(self, args: str):
827
+ """显示账户资金汇总。"""
828
+ from aria_cli import _print_broker_account
829
+ from aria_code.apps.cli._optional import HAS_BROKERS as _HAS_BROKERS, get_registry as _get_broker_registry
830
+ if not _HAS_BROKERS:
831
+ print_error(self.context, "brokers 模块未加载", "")
832
+ return
833
+ broker_id = args.strip()
834
+ reg = _get_broker_registry()
835
+ try:
836
+ broker = reg.get(broker_id) if broker_id else reg.active()
837
+ if not broker:
838
+ broker = await self._auto_connect_broker(broker_id)
839
+ if not broker:
840
+ return
841
+ import asyncio as _aio
842
+ acct = await _aio.get_event_loop().run_in_executor(None, broker.account_info)
843
+ _print_broker_account(acct)
844
+ except Exception as e:
845
+ print_error(self.context, f"账户查询失败: {e}", "请检查券商连接状态 (/broker status)")
846
+
847
+ async def cmd_positions(self, args: str):
848
+ """显示当前持仓。"""
849
+ from aria_cli import _print_broker_positions
850
+ from aria_code.apps.cli._optional import HAS_BROKERS as _HAS_BROKERS, get_registry as _get_broker_registry
851
+ from ._ui import null_ctx as _null_ctx
852
+ if not _HAS_BROKERS:
853
+ print_error(self.context, "brokers 模块未加载", "")
854
+ return
855
+ broker_id = args.strip()
856
+ reg = _get_broker_registry()
857
+ try:
858
+ broker = reg.get(broker_id) if broker_id else reg.active()
859
+ if not broker:
860
+ broker = await self._auto_connect_broker(broker_id)
861
+ if not broker:
862
+ return
863
+ import asyncio as _aio
864
+ with self.context.console.status(f"[dim]{ui_text(self, '获取持仓', 'Fetching positions')}...[/dim]", spinner="dots") if self.context.has_rich else _null_ctx():
865
+ pos = await _aio.get_event_loop().run_in_executor(None, broker.positions)
866
+ _print_broker_positions(pos, broker.label, broker.config.get("currency","CNY"))
867
+ except Exception as e:
868
+ print_error(self.context, f"持仓查询失败: {e}", "请检查券商连接状态 (/broker status)")
869
+
870
+ async def cmd_orders(self, args: str):
871
+ """显示订单记录。"""
872
+ from aria_cli import _print_broker_orders
873
+ from aria_code.apps.cli._optional import HAS_BROKERS as _HAS_BROKERS, get_registry as _get_broker_registry
874
+ from ._ui import null_ctx as _null_ctx
875
+ if not _HAS_BROKERS:
876
+ print_error(self.context, "brokers 模块未加载", "")
877
+ return
878
+ parts = args.strip().split()
879
+ status = "all"
880
+ broker_id = ""
881
+ for p in parts:
882
+ if p in ("open", "filled", "cancelled", "all"):
883
+ status = p
884
+ else:
885
+ broker_id = p
886
+ reg = _get_broker_registry()
887
+ try:
888
+ broker = reg.get(broker_id) if broker_id else reg.active()
889
+ if not broker:
890
+ broker = await self._auto_connect_broker(broker_id)
891
+ if not broker:
892
+ return
893
+ import asyncio as _aio
894
+ with self.context.console.status(f"[dim]{ui_text(self, '获取订单', 'Fetching orders')}...[/dim]", spinner="dots") if self.context.has_rich else _null_ctx():
895
+ orders = await _aio.get_event_loop().run_in_executor(
896
+ None, lambda: broker.orders(status=status, limit=30)
897
+ )
898
+ _print_broker_orders(orders, broker.label, status)
899
+ except Exception as e:
900
+ print_error(self.context, f"订单查询失败: {e}", "请检查券商连接状态 (/broker status)")
901
+
902
+ async def cmd_paper(self, args: str):
903
+ """本地仿盘账户: /paper start [cash] [currency] | account | positions | orders | reset."""
904
+
905
+ from brokers.config import add_broker_config, get_broker_config, set_default_broker
906
+ from brokers.paper_broker import PAPER_LEDGER_PATH, PaperBroker
907
+
908
+ parts = args.strip().split()
909
+ sub = parts[0].lower() if parts else "account"
910
+ rest = parts[1:]
911
+ broker_id = "paper_main"
912
+ cash = 100000.0
913
+ currency = "USD"
914
+ if rest:
915
+ try:
916
+ cash = float(rest[0])
917
+ except Exception:
918
+ pass
919
+ if len(rest) > 1:
920
+ currency = rest[1].upper()
921
+
922
+ if sub in ("start", "init", "reset"):
923
+ cfg = {
924
+ "id": broker_id,
925
+ "type": "paper",
926
+ "label": "Aria 仿盘账户",
927
+ "mode": "paper",
928
+ "starting_cash": cash,
929
+ "currency": currency,
930
+ "default": True,
931
+ }
932
+ add_broker_config(cfg)
933
+ set_default_broker(broker_id)
934
+ broker = PaperBroker(broker_id, cfg)
935
+ broker.reset(starting_cash=cash, currency=currency)
936
+ if self.context.has_rich:
937
+ self.context.console.print(
938
+ f"[green]✓[/green] 仿盘账户已初始化: [bold]{currency} {cash:,.2f}[/bold] "
939
+ f"[dim]{PAPER_LEDGER_PATH}[/dim]"
940
+ )
941
+ else:
942
+ print(f"仿盘账户已初始化: {currency} {cash:,.2f} {PAPER_LEDGER_PATH}")
943
+ return
944
+
945
+ cfg = get_broker_config(broker_id)
946
+ if not cfg:
947
+ print_error(self.context, "尚未创建仿盘账户", "运行 /paper start 100000 USD")
948
+ return
949
+ broker = PaperBroker(broker_id, cfg)
950
+ broker.connect()
951
+ if sub in ("account", "status"):
952
+ from aria_cli import _print_broker_account
953
+ _print_broker_account(broker.account_info())
954
+ elif sub in ("positions", "pos"):
955
+ from aria_cli import _print_broker_positions
956
+ _print_broker_positions(broker.positions(), broker.label, broker.currency)
957
+ elif sub in ("orders", "order"):
958
+ from aria_cli import _print_broker_orders
959
+ _print_broker_orders(broker.orders(limit=30), broker.label, "all")
960
+ else:
961
+ if self.context.has_rich:
962
+ self.context.console.print("[dim]用法: /paper start [cash] [currency] | account | positions | orders | reset[/dim]")
963
+ else:
964
+ print("Usage: /paper start [cash] [currency] | account | positions | orders | reset")
965
+
966
+ async def cmd_trade(self, args: str):
967
+ """两阶段交易: /trade mode | preview SYMBOL buy|sell QTY PRICE | confirm PREVIEW_ID | previews
968
+ | allow-chat-confirm [broker_id] | disallow-chat-confirm [broker_id]."""
969
+ from aria_code.apps.cli._optional import get_registry as _get_broker_registry
970
+ from brokers import (
971
+ OrderIntent, build_order_preview, execute_order_preview,
972
+ list_order_previews,
973
+ )
974
+ from aria_code.safety import SafetyService
975
+
976
+ parts = args.strip().split()
977
+ sub = parts[0].lower() if parts else "mode"
978
+ reg = _get_broker_registry()
979
+ broker = reg.active() if reg else None
980
+ if not broker:
981
+ try:
982
+ broker = reg.connect_default() if reg else None
983
+ except Exception as exc:
984
+ print_error(self.context, f"无法连接默认账户: {exc}", "先运行 /paper start 或 /broker connect <id>")
985
+ return
986
+
987
+ if sub == "mode":
988
+ if not broker:
989
+ print_error(self.context, "无活跃账户", "先运行 /paper start 或 /broker connect <id>")
990
+ return
991
+ # The broker's own config, not the terminal's — trading policy is
992
+ # per-account (paper vs live lives on the account, not the session).
993
+ policy = SafetyService(getattr(broker, "config", {}) or {}).trading_policy(
994
+ getattr(broker, "broker_type", "")
995
+ )
996
+ msg = (
997
+ f"账户: {broker.label} ({broker.broker_type})\n"
998
+ f"模式: {policy.mode}\n"
999
+ f"实盘允许: {policy.allow_live_trade}\n"
1000
+ f"确认要求: {policy.require_confirm}\n"
1001
+ f"单笔上限: {policy.max_order_value_weight:.1%} 单票仓位上限: {policy.max_single_position_weight:.1%}"
1002
+ )
1003
+ if self.context.has_rich:
1004
+
1005
+ color = "red" if policy.mode == "live" else "green" if policy.mode == "paper" else "yellow"
1006
+ self.context.console.print(Panel(msg, title="[bold]Trade Mode[/bold]", border_style=color, box=rich_box.ROUNDED))
1007
+ else:
1008
+ print(msg)
1009
+ return
1010
+
1011
+ if sub in ("allow-chat-confirm", "disallow-chat-confirm"):
1012
+ from brokers.config import get_broker_config, set_chat_confirm_enabled
1013
+
1014
+ target_id = parts[1] if len(parts) > 1 else (broker.broker_id if broker else "")
1015
+ if not target_id:
1016
+ print_error(self.context, "请指定券商 id", "/trade allow-chat-confirm <broker_id>")
1017
+ return
1018
+ cfg = get_broker_config(target_id)
1019
+ if not cfg:
1020
+ print_error(self.context, f"未找到券商配置: {target_id}", "先用 /broker connect 配置好")
1021
+ return
1022
+
1023
+ if sub == "disallow-chat-confirm":
1024
+ set_chat_confirm_enabled(target_id, False)
1025
+ msg = f"✓ 已关闭 {target_id} 的聊天内确认下单"
1026
+ self.context.console.print(f"[green]{msg}[/green]") if self.context.has_rich else print(msg)
1027
+ return
1028
+
1029
+ # 这是唯一能打开这道门的地方——必须是真人坐在终端前,MCP/聊天
1030
+ # 没有任何路径能碰到这个函数。要求打完整的 broker_id,不是
1031
+ # 简单敲个 y,降低误触概率。
1032
+ warn = (
1033
+ f"这会允许通过 MCP/聊天(比如 Claude Code 连着这个项目的时候)"
1034
+ f"对 {target_id}({cfg.get('label', target_id)}) 发出的下单确认"
1035
+ f"真正执行下单,不再要求你本人在这个终端里手动敲 /trade confirm。\n\n"
1036
+ f"确认开启的话,完整输入券商 id「{target_id}」(不是 y/yes):"
1037
+ )
1038
+ self.context.console.print(f"[bold red]⚠ 高风险操作[/bold red]\n{warn}") if self.context.has_rich else print(warn)
1039
+ try:
1040
+ answer = self.context.console.input("> ") if self.context.has_rich else input("> ")
1041
+ except (EOFError, KeyboardInterrupt):
1042
+ answer = ""
1043
+ if answer.strip() != target_id:
1044
+ msg = "未确认,聊天内确认下单保持关闭"
1045
+ self.context.console.print(f"[yellow]{msg}[/yellow]") if self.context.has_rich else print(msg)
1046
+ return
1047
+ set_chat_confirm_enabled(target_id, True)
1048
+ msg = f"✓ 已为 {target_id} 开启聊天内确认下单——之后 aria.broker.confirm_order 这个 MCP 工具能真正执行这个账户的订单了"
1049
+ self.context.console.print(f"[green]{msg}[/green]") if self.context.has_rich else print(msg)
1050
+ return
1051
+
1052
+ if sub in ("previews", "list"):
1053
+ rows = list_order_previews(limit=10)
1054
+ if self.context.has_rich:
1055
+ from rich.table import Table
1056
+
1057
+ tbl = Table(title="[bold]Trade Previews[/bold]", box=rich_box.ROUNDED, border_style="dim")
1058
+ tbl.add_column("ID")
1059
+ tbl.add_column("Mode")
1060
+ tbl.add_column("Broker")
1061
+ tbl.add_column("Order")
1062
+ tbl.add_column("Status")
1063
+ tbl.add_column("Can Exec")
1064
+ for row in rows:
1065
+ intent = row.get("intent") or {}
1066
+ tbl.add_row(
1067
+ row.get("preview_id", ""),
1068
+ row.get("mode", ""),
1069
+ row.get("broker_label", ""),
1070
+ f"{intent.get('side','')} {intent.get('symbol','')} x {intent.get('quantity','')}",
1071
+ row.get("status", ""),
1072
+ "yes" if row.get("can_execute") else "no",
1073
+ )
1074
+ self.context.console.print(tbl)
1075
+ else:
1076
+ for row in rows:
1077
+ print(row)
1078
+ return
1079
+
1080
+ if sub == "confirm":
1081
+ if not broker:
1082
+ print_error(self.context, "无活跃账户", "先运行 /paper start 或 /broker connect <id>")
1083
+ return
1084
+ preview_id = parts[1] if len(parts) > 1 else ""
1085
+ if not preview_id:
1086
+ print_error(self.context, "请提供 preview_id", "/trade confirm <preview_id>")
1087
+ return
1088
+ result = execute_order_preview(broker, preview_id, confirmed=True)
1089
+ if result.get("success"):
1090
+ if self.context.has_rich:
1091
+ self.context.console.print(
1092
+ f"[green]✓[/green] 订单已执行 [{result.get('mode')}] "
1093
+ f"{result.get('side')} {result.get('symbol')} x {result.get('qty')} "
1094
+ f"[dim]#{result.get('order_id')}[/dim]"
1095
+ )
1096
+ else:
1097
+ print(result)
1098
+ else:
1099
+ print_error(self.context,
1100
+ "订单未执行",
1101
+ "; ".join(result.get("execution_blockers") or [result.get("error", "unknown")]),
1102
+ )
1103
+ return
1104
+
1105
+ if sub == "preview":
1106
+ order_parts = parts[1:]
1107
+ else:
1108
+ order_parts = parts
1109
+ if len(order_parts) < 4:
1110
+ print_error(self.context, "用法: /trade preview SYMBOL buy|sell QTY PRICE", "/trade preview AAPL buy 10 190")
1111
+ return
1112
+ if not broker:
1113
+ print_error(self.context, "无活跃账户", "先运行 /paper start 或 /broker connect <id>")
1114
+ return
1115
+ symbol, side, qty_raw, price_raw = order_parts[:4]
1116
+ try:
1117
+ qty = float(qty_raw)
1118
+ price = float(price_raw)
1119
+ except Exception:
1120
+ print_error(self.context, "数量和价格必须是数字", "/trade preview AAPL buy 10 190")
1121
+ return
1122
+ if side.lower() not in ("buy", "sell"):
1123
+ print_error(self.context, "side 必须是 buy 或 sell", "/trade preview AAPL buy 10 190")
1124
+ return
1125
+ preview = build_order_preview(
1126
+ broker,
1127
+ OrderIntent(
1128
+ symbol=symbol,
1129
+ side=side,
1130
+ quantity=qty,
1131
+ price=price,
1132
+ order_type="limit",
1133
+ source="slash_trade",
1134
+ ),
1135
+ )
1136
+ blockers = preview.get("execution_blockers") or []
1137
+ if self.context.has_rich:
1138
+
1139
+ status = "可执行" if preview.get("can_execute") else "不可执行"
1140
+ body = (
1141
+ f"preview_id: [bold]{preview.get('preview_id')}[/bold]\n"
1142
+ f"模式: {preview.get('mode')} 账户: {preview.get('broker_label')}\n"
1143
+ f"订单: {side.upper()} {symbol.upper()} x {qty:g} @ {price:g}\n"
1144
+ f"状态: {status}\n"
1145
+ )
1146
+ if blockers:
1147
+ body += "\n执行限制:\n" + "\n".join(f" - {b}" for b in blockers)
1148
+ body += "\n\n确认执行: /trade confirm " + str(preview.get("preview_id"))
1149
+ self.context.console.print(Panel(body, title="[bold]Trade Preview[/bold]", border_style="yellow", box=rich_box.ROUNDED))
1150
+ else:
1151
+ print(preview)
1152
+
1153
+ async def _prompt_no_broker_action(self) -> None:
1154
+ """未配置券商时显示可导航的操作菜单,选择后直接路由到对应功能。"""
1155
+ from aria_code.ui.picker import arrow_select as _arrow_select
1156
+ from aria_code.apps.cli._optional import BROKERS_CONFIG_PATH as _BROKERS_CONFIG_PATH
1157
+ from ui.picker import arrow_select
1158
+ import subprocess
1159
+ import sys as _sys
1160
+
1161
+ if self.context.has_rich:
1162
+ self.context.console.print(Panel(
1163
+ "[yellow]尚未配置任何券商[/yellow] — 请选择下一步操作:",
1164
+ border_style="yellow", box=rich_box.ROUNDED, padding=(0, 1),
1165
+ ))
1166
+
1167
+ actions = [
1168
+ (" 添加新券商", "交互式向导:选择券商 → 开户指引 → 填写凭证 → 一键连接"),
1169
+ (" 手动编辑配置文件", f"用系统编辑器打开 {_BROKERS_CONFIG_PATH}"),
1170
+ (" 查看所有配置模板", "输出全部券商的 JSON 模板供参考"),
1171
+ (" 暂时跳过", "关闭此菜单,稍后再配置"),
1172
+ ]
1173
+ idx = _arrow_select(actions, selected=0, title="", max_visible=6)
1174
+
1175
+ if idx == 0:
1176
+ await self._cmd_broker_add("")
1177
+ elif idx == 1:
1178
+ try:
1179
+ import pathlib as _pl
1180
+ import json as _json
1181
+ from brokers.config import print_all_templates
1182
+ path = _pl.Path(str(_BROKERS_CONFIG_PATH or
1183
+ aria_home() / "brokers.json"))
1184
+ path.parent.mkdir(parents=True, exist_ok=True)
1185
+
1186
+ # Pre-populate with full commented template if file is empty/missing
1187
+ needs_template = (
1188
+ not path.exists()
1189
+ or path.stat().st_size < 20
1190
+ or path.read_text(encoding="utf-8").strip() in ('', '{"brokers": []}')
1191
+ )
1192
+ if needs_template:
1193
+ path.write_text(print_all_templates(), encoding="utf-8")
1194
+
1195
+ subprocess.Popen(["open", str(path)])
1196
+
1197
+ if self.context.has_rich:
1198
+ from rich.syntax import Syntax
1199
+ from ui.render.output import display_path as _display_path
1200
+ self.context.console.print()
1201
+ self.context.console.print(Panel(
1202
+ f"[bold]已在编辑器中打开:[/bold] {_display_path(path, fallback='config')}\n\n"
1203
+ f"[dim]文件已预填所有券商模板。\n"
1204
+ f"删除不需要的券商块,填写你的实际凭证后保存。[/dim]\n\n"
1205
+ f"[dim]保存后回到此终端,运行:[/dim]\n"
1206
+ f" [bold]/broker connect <id>[/bold] 建立连接\n"
1207
+ f" [bold]/broker list[/bold] 查看配置状态",
1208
+ title="[bold]手动编辑配置[/bold]",
1209
+ border_style="dim", box=rich_box.ROUNDED, padding=(0, 1),
1210
+ ))
1211
+ except Exception as exc:
1212
+ if self.context.has_rich:
1213
+ self.context.console.print(f"[dim]配置文件路径: {_BROKERS_CONFIG_PATH}[/dim]")
1214
+ self.context.console.print(f"[red]打开失败: {exc}[/red]")
1215
+ elif idx == 2:
1216
+ await self._cmd_broker_init()
1217
+ # idx == 3 or -1 → do nothing (skip)
1218
+
1219
+ async def _auto_connect_broker(self, broker_id: str):
1220
+ """尝试自动连接;无配置时弹出操作菜单。"""
1221
+ from aria_code.apps.cli._optional import get_registry as _get_broker_registry, list_broker_configs as _list_broker_configs
1222
+ reg = _get_broker_registry()
1223
+ cfgs = _list_broker_configs()
1224
+ if not cfgs:
1225
+ await self._prompt_no_broker_action()
1226
+ return None
1227
+ target_id = broker_id or (cfgs[0].get("id", "") if cfgs else "")
1228
+ if not target_id:
1229
+ return None
1230
+ try:
1231
+ import asyncio as _aio
1232
+ return await _aio.get_event_loop().run_in_executor(None, reg.connect, target_id)
1233
+ except Exception as e:
1234
+ print_error(self.context, f"自动连接 {target_id} 失败: {e}",
1235
+ "请运行 /broker connect <id> 手动连接")
1236
+ return None