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