aria-code 4.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (284) hide show
  1. agents/__init__.py +32 -0
  2. agents/base.py +190 -0
  3. agents/deep/__init__.py +37 -0
  4. agents/deep/calibration_loop.py +144 -0
  5. agents/deep/critic.py +125 -0
  6. agents/deep/deepen.py +193 -0
  7. agents/deep/models.py +149 -0
  8. agents/deep/pipeline.py +164 -0
  9. agents/deep/quant_fusion.py +192 -0
  10. agents/deep/themes.py +95 -0
  11. agents/deep/tiers.py +106 -0
  12. agents/financial/__init__.py +10 -0
  13. agents/financial/catalyst.py +279 -0
  14. agents/financial/debate.py +145 -0
  15. agents/financial/earnings.py +303 -0
  16. agents/financial/fundamental.py +159 -0
  17. agents/financial/macro.py +99 -0
  18. agents/financial/news.py +207 -0
  19. agents/financial/risk.py +132 -0
  20. agents/financial/sector.py +279 -0
  21. agents/financial/synthesis.py +274 -0
  22. agents/financial/technical.py +258 -0
  23. agents/portfolio_agent.py +333 -0
  24. agents/realty/__init__.py +62 -0
  25. agents/realty/asset_diagnosis.py +150 -0
  26. agents/realty/business_match.py +165 -0
  27. agents/realty/cashflow_verify.py +208 -0
  28. agents/realty/contract_rules.py +209 -0
  29. agents/realty/energy_anomaly.py +188 -0
  30. agents/realty/exit_settlement.py +207 -0
  31. agents/realty/fulfillment_risk.py +205 -0
  32. agents/realty/ops_optimize.py +159 -0
  33. agents/realty/revenue_share.py +214 -0
  34. agents/registry.py +144 -0
  35. agents/sports/__init__.py +0 -0
  36. agents/sports/football_agent.py +169 -0
  37. agents/team.py +289 -0
  38. aliyun_data_client.py +660 -0
  39. apps/README.md +12 -0
  40. apps/__init__.py +2 -0
  41. apps/channels/README.md +15 -0
  42. apps/cli/README.md +13 -0
  43. apps/cli/__init__.py +2 -0
  44. apps/cli/bootstrap.py +99 -0
  45. apps/cli/codegen_paths.py +29 -0
  46. apps/cli/commands/__init__.py +16 -0
  47. apps/cli/commands/analysis_cmds.py +288 -0
  48. apps/cli/commands/backtest_cmds.py +1887 -0
  49. apps/cli/commands/broker_cmds.py +1154 -0
  50. apps/cli/commands/business_workflow_cmds.py +289 -0
  51. apps/cli/commands/catalog.py +84 -0
  52. apps/cli/commands/data_cmds.py +405 -0
  53. apps/cli/commands/diagnostic_cmds.py +179 -0
  54. apps/cli/commands/diagnostic_ops_cmds.py +696 -0
  55. apps/cli/commands/finance_render.py +12 -0
  56. apps/cli/commands/market.py +399 -0
  57. apps/cli/commands/market_cmds.py +1276 -0
  58. apps/cli/commands/market_context.py +425 -0
  59. apps/cli/commands/market_render.py +7 -0
  60. apps/cli/commands/model_cmds.py +1579 -0
  61. apps/cli/commands/ops_cmds.py +668 -0
  62. apps/cli/commands/portfolio_cmds.py +962 -0
  63. apps/cli/commands/report.py +377 -0
  64. apps/cli/commands/scaffold_templates.py +617 -0
  65. apps/cli/commands/session_cmds.py +179 -0
  66. apps/cli/commands/session_ux_cmds.py +280 -0
  67. apps/cli/commands/team.py +588 -0
  68. apps/cli/commands/team_render.py +8 -0
  69. apps/cli/commands/ui_cmds.py +358 -0
  70. apps/cli/commands/workflow_cmds.py +279 -0
  71. apps/cli/commands/workspace_cmds.py +1414 -0
  72. apps/cli/config_paths.py +70 -0
  73. apps/cli/config_store.py +61 -0
  74. apps/cli/deterministic.py +122 -0
  75. apps/cli/direct.py +48 -0
  76. apps/cli/github_app_auth.py +135 -0
  77. apps/cli/handlers/__init__.py +11 -0
  78. apps/cli/handlers/broker_handlers.py +122 -0
  79. apps/cli/handlers/chart_handlers.py +1309 -0
  80. apps/cli/handlers/market_handlers.py +2509 -0
  81. apps/cli/handlers/realty_handlers.py +114 -0
  82. apps/cli/handlers/strategy_advice.py +82 -0
  83. apps/cli/hooks.py +180 -0
  84. apps/cli/i18n.py +284 -0
  85. apps/cli/intent.py +136 -0
  86. apps/cli/intent_router.py +217 -0
  87. apps/cli/lifecycle_hooks.py +48 -0
  88. apps/cli/main.py +29 -0
  89. apps/cli/market_metadata.py +135 -0
  90. apps/cli/market_universe.py +265 -0
  91. apps/cli/message_processing.py +257 -0
  92. apps/cli/plan_mode.py +139 -0
  93. apps/cli/plotly_html.py +15 -0
  94. apps/cli/prediction_feedback.py +202 -0
  95. apps/cli/preflight.py +497 -0
  96. apps/cli/project_aria.py +60 -0
  97. apps/cli/prompts/__init__.py +0 -0
  98. apps/cli/prompts/coding.py +658 -0
  99. apps/cli/prompts/system_prompts.py +531 -0
  100. apps/cli/prompts/ui.py +434 -0
  101. apps/cli/providers/__init__.py +1 -0
  102. apps/cli/providers/base.py +271 -0
  103. apps/cli/providers/chat_routing.py +80 -0
  104. apps/cli/providers/llm/__init__.py +1 -0
  105. apps/cli/providers/llm/ollama_stream.py +1170 -0
  106. apps/cli/providers/llm/sse_stream.py +216 -0
  107. apps/cli/providers/runtime_bridge.py +185 -0
  108. apps/cli/runtime_consumer.py +489 -0
  109. apps/cli/session_export.py +87 -0
  110. apps/cli/session_jsonl.py +207 -0
  111. apps/cli/session_store.py +112 -0
  112. apps/cli/todo_tracker.py +190 -0
  113. apps/cli/tools/__init__.py +40 -0
  114. apps/cli/tools/context.py +46 -0
  115. apps/cli/tools/file_tools.py +112 -0
  116. apps/cli/tools/market_tools.py +549 -0
  117. apps/cli/tools/notebook_tools.py +111 -0
  118. apps/cli/tools/system_tools.py +669 -0
  119. apps/cli/tools/write_tools.py +715 -0
  120. apps/cli/tradingview_bridge.py +434 -0
  121. apps/cli/update_check.py +152 -0
  122. apps/cli/utils/__init__.py +0 -0
  123. apps/cli/utils/market_detect.py +1578 -0
  124. apps/daemon/README.md +14 -0
  125. apps/vscode/README.md +115 -0
  126. apps/vscode/package.json +70 -0
  127. aria_cli.py +11636 -0
  128. aria_code-4.1.3.dist-info/METADATA +952 -0
  129. aria_code-4.1.3.dist-info/RECORD +284 -0
  130. aria_code-4.1.3.dist-info/WHEEL +5 -0
  131. aria_code-4.1.3.dist-info/entry_points.txt +2 -0
  132. aria_code-4.1.3.dist-info/licenses/LICENSE +121 -0
  133. aria_code-4.1.3.dist-info/top_level.txt +50 -0
  134. aria_daemon.py +1295 -0
  135. aria_feishu_bot.py +1359 -0
  136. aria_relay_client.py +182 -0
  137. aria_relay_server.py +405 -0
  138. aria_telegram_bot.py +202 -0
  139. ariarc.py +328 -0
  140. artifacts.py +491 -0
  141. backtest_report.py +472 -0
  142. brokers/__init__.py +72 -0
  143. brokers/base.py +207 -0
  144. brokers/capabilities.py +264 -0
  145. brokers/cn/__init__.py +10 -0
  146. brokers/cn/easytrader_broker.py +193 -0
  147. brokers/cn/futu_broker.py +194 -0
  148. brokers/cn/longbridge_broker.py +190 -0
  149. brokers/cn/tiger_broker.py +196 -0
  150. brokers/cn/xtquant_broker.py +175 -0
  151. brokers/config.py +364 -0
  152. brokers/intl/__init__.py +5 -0
  153. brokers/intl/alpaca_broker.py +183 -0
  154. brokers/intl/ibkr_broker.py +215 -0
  155. brokers/intl/webull_broker.py +156 -0
  156. brokers/paper_broker.py +259 -0
  157. brokers/planning.py +296 -0
  158. brokers/registry.py +181 -0
  159. brokers/trading.py +237 -0
  160. change_store.py +127 -0
  161. command_safety.py +19 -0
  162. computer_use_tools.py +504 -0
  163. dashboard_generator.py +578 -0
  164. data_analysis_tools.py +808 -0
  165. data_cleaner.py +483 -0
  166. data_service.py +481 -0
  167. datasources/__init__.py +23 -0
  168. datasources/base.py +166 -0
  169. datasources/router.py +221 -0
  170. datasources/sources/__init__.py +15 -0
  171. datasources/sources/akshare_source.py +269 -0
  172. datasources/sources/alpha_vantage_source.py +202 -0
  173. datasources/sources/edgar_source.py +218 -0
  174. datasources/sources/finnhub_source.py +197 -0
  175. datasources/sources/fred_source.py +219 -0
  176. datasources/sources/tushare_source.py +141 -0
  177. datasources/sources/web_scraper_source.py +278 -0
  178. datasources/sources/world_bank_source.py +205 -0
  179. datasources/sources/yfinance_source.py +152 -0
  180. demo_player.py +204 -0
  181. doctor.py +508 -0
  182. file_analysis_tools.py +734 -0
  183. finance_formulas.py +389 -0
  184. football_data_client.py +1670 -0
  185. intent_classifier.py +358 -0
  186. local_finance_tools.py +3221 -0
  187. local_llm_provider.py +552 -0
  188. macro_tools.py +368 -0
  189. market_data_client.py +1899 -0
  190. mcp_client.py +506 -0
  191. memory_manager.py +245 -0
  192. model_capability.py +416 -0
  193. notification_tools.py +248 -0
  194. packages/__init__.py +23 -0
  195. packages/aria_agents/__init__.py +5 -0
  196. packages/aria_agents/manifest.py +69 -0
  197. packages/aria_core/__init__.py +34 -0
  198. packages/aria_core/architecture.py +192 -0
  199. packages/aria_core/export.py +124 -0
  200. packages/aria_core/manifest.py +65 -0
  201. packages/aria_infra/__init__.py +15 -0
  202. packages/aria_infra/arthera.py +52 -0
  203. packages/aria_infra/doctor.py +246 -0
  204. packages/aria_infra/product.py +37 -0
  205. packages/aria_mcp/__init__.py +25 -0
  206. packages/aria_mcp/bridge.py +38 -0
  207. packages/aria_mcp/config.py +97 -0
  208. packages/aria_mcp/tools.py +61 -0
  209. packages/aria_sdk/__init__.py +19 -0
  210. packages/aria_sdk/client.py +396 -0
  211. packages/aria_sdk/providers.py +70 -0
  212. packages/aria_sdk/streaming.py +73 -0
  213. packages/aria_sdk/types.py +86 -0
  214. packages/aria_services/__init__.py +55 -0
  215. packages/aria_services/context.py +258 -0
  216. packages/aria_services/data.py +11 -0
  217. packages/aria_services/provider_health.py +189 -0
  218. packages/aria_services/registry.py +213 -0
  219. packages/aria_services/usage.py +138 -0
  220. packages/aria_skills/__init__.py +5 -0
  221. packages/aria_skills/registry.py +59 -0
  222. packages/aria_tools/__init__.py +5 -0
  223. packages/aria_tools/registry.py +128 -0
  224. packages/quant_engine/__init__.py +6 -0
  225. packages/quant_engine/sports/__init__.py +72 -0
  226. packages/quant_engine/sports/calibrator.py +353 -0
  227. packages/quant_engine/sports/dixon_coles.py +234 -0
  228. packages/quant_engine/sports/elo.py +299 -0
  229. packages/quant_engine/sports/form.py +188 -0
  230. packages/quant_engine/sports/h2h.py +195 -0
  231. packages/quant_engine/sports/ml_model.py +354 -0
  232. packages/quant_engine/sports/predictor.py +311 -0
  233. packages/quant_engine/sports/tracker.py +664 -0
  234. packages/quant_engine/stochastic/__init__.py +27 -0
  235. packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  236. packages/quant_engine/stochastic/ito_calculus.py +477 -0
  237. packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  238. packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  239. packages/quant_engine/stochastic/options_pricing.py +573 -0
  240. packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  241. plan_utils.py +194 -0
  242. plugin_loader.py +328 -0
  243. portfolio_ledger.py +262 -0
  244. privacy/__init__.py +5 -0
  245. privacy/feedback.py +123 -0
  246. project_tools.py +525 -0
  247. providers/__init__.py +30 -0
  248. providers/llm/__init__.py +19 -0
  249. providers/llm/anthropic.py +184 -0
  250. providers/llm/base.py +139 -0
  251. providers/llm/ollama.py +128 -0
  252. providers/llm/openai_compat.py +282 -0
  253. providers/llm/registry.py +358 -0
  254. realty_data_tools.py +659 -0
  255. report_generator.py +1314 -0
  256. runtime/__init__.py +103 -0
  257. runtime/agent_loop.py +1183 -0
  258. runtime/approval.py +51 -0
  259. runtime/events.py +102 -0
  260. runtime/gateway.py +128 -0
  261. runtime/lsp.py +346 -0
  262. runtime/subagent.py +258 -0
  263. runtime/tool_executor.py +104 -0
  264. runtime/tool_policy.py +106 -0
  265. safety/__init__.py +21 -0
  266. safety/permissions.py +275 -0
  267. setup_wizard.py +653 -0
  268. strategy_vault.py +420 -0
  269. ui/__init__.py +100 -0
  270. ui/banner.py +310 -0
  271. ui/completer.py +391 -0
  272. ui/console.py +271 -0
  273. ui/image_render.py +243 -0
  274. ui/input_box.py +376 -0
  275. ui/picker.py +195 -0
  276. ui/render/__init__.py +11 -0
  277. ui/render/finance.py +1480 -0
  278. ui/render/market.py +225 -0
  279. ui/render/output.py +681 -0
  280. ui/render/team.py +346 -0
  281. ui/robot.py +235 -0
  282. workspace/__init__.py +6 -0
  283. workspace/files.py +170 -0
  284. workspace/verify.py +113 -0
@@ -0,0 +1,405 @@
1
+ """DataCommandsMixin — data, alert, correlation, and comparison commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class DataCommandsMixin:
7
+ """Mixin: data analysis and comparison commands."""
8
+
9
+ async def cmd_data(self, args: str):
10
+ """
11
+ /data sql "SELECT ..." — DuckDB SQL 查询
12
+ /data export [filename] — 导出上次结果到 Excel
13
+ /data load <csv_path> — 加载 CSV 到 DuckDB
14
+ /data tables — 列出已加载的表
15
+ """
16
+ import asyncio as _asyncio
17
+ loop = _asyncio.get_event_loop()
18
+ parts = args.strip().split(None, 1) if args.strip() else []
19
+ sub = parts[0].lower() if parts else "help"
20
+ rest = parts[1] if len(parts) > 1 else ""
21
+
22
+ try:
23
+ from data_analysis_tools import (sql_query, sql_list_tables,
24
+ export_to_excel, load_csv_data)
25
+ except ImportError as e:
26
+ if HAS_RICH:
27
+ console.print(f"[red]data_analysis_tools 未加载: {e}[/red]")
28
+ return
29
+
30
+ if sub == "sql":
31
+ query = rest.strip().strip('"').strip("'")
32
+ if not query:
33
+ if HAS_RICH:
34
+ console.print("[dim]用法: /data sql \"SELECT ...\"|/dim]")
35
+ return
36
+ if HAS_RICH:
37
+ with console.status("[dim]执行 SQL...[/dim]", spinner="dots"):
38
+ r = await loop.run_in_executor(None, sql_query, {"query": query})
39
+ else:
40
+ r = sql_query({"query": query})
41
+ _render_sql_result(r)
42
+
43
+ elif sub == "export":
44
+ fname = rest.strip() or None
45
+ watchlist = self.terminal.config.get("watchlist", ["AAPL", "MSFT", "SPY"])
46
+ try:
47
+ import yfinance as _yf
48
+ raw = _yf.download(watchlist[:5], period="1mo", progress=False, auto_adjust=True)
49
+ closes = raw["Close"] if hasattr(raw.columns, "levels") else raw
50
+ export_data = {"价格历史": closes.reset_index().to_dict("records")}
51
+ except Exception:
52
+ export_data = {"示例数据": [{"symbol": s, "note": "需 yfinance"} for s in watchlist]}
53
+ p = {"data": export_data, "filename": fname}
54
+ if HAS_RICH:
55
+ with console.status("[dim]生成 Excel...[/dim]", spinner="dots"):
56
+ r = await loop.run_in_executor(None, export_to_excel, p)
57
+ else:
58
+ r = export_to_excel(p)
59
+ if r.get("success"):
60
+ msg = f"✓ 已导出: {r['path']} ({r['total_rows']} 行)"
61
+ if HAS_RICH:
62
+ console.print(f"[green]{msg}[/green]")
63
+ else:
64
+ print(msg)
65
+ else:
66
+ if HAS_RICH:
67
+ console.print(f"[red]{r.get('error')}[/red]")
68
+
69
+ elif sub == "load":
70
+ csv_path = rest.strip()
71
+ if not csv_path:
72
+ if HAS_RICH:
73
+ console.print("[dim]用法: /data load <csv文件路径>[/dim]")
74
+ return
75
+ if HAS_RICH:
76
+ with console.status("[dim]加载 CSV...[/dim]", spinner="dots"):
77
+ r = await loop.run_in_executor(None, load_csv_data, {"path": csv_path})
78
+ else:
79
+ r = load_csv_data({"path": csv_path})
80
+ if r.get("success"):
81
+ if HAS_RICH:
82
+ console.print(f"[green]✓ 已加载 {r['rows']} 行 → 表 {r['table_name']}[/green]")
83
+ console.print(f"[dim]列: {', '.join(r['columns'][:10])}[/dim]")
84
+ console.print(f"[dim]现在可以: /data sql \"SELECT * FROM {r['table_name']} LIMIT 10\"[/dim]")
85
+ else:
86
+ if HAS_RICH:
87
+ console.print(f"[red]{r.get('error')}[/red]")
88
+
89
+ elif sub == "tables":
90
+ r = sql_list_tables()
91
+ if r.get("success"):
92
+ tables = r.get("tables", [])
93
+ if HAS_RICH:
94
+ if tables:
95
+ console.print(f"[bold]已加载表:[/bold] {', '.join(tables)}")
96
+ else:
97
+ console.print("[dim]暂无已加载的表。使用 /data load <csv> 加载数据[/dim]")
98
+
99
+ else:
100
+ if HAS_RICH:
101
+ console.print("[dim]用法: /data [sql|export|load|tables][/dim]")
102
+ console.print("[dim] /data sql \"SELECT * FROM my_table LIMIT 10\"[/dim]")
103
+ console.print("[dim] /data load ~/Desktop/data.csv[/dim]")
104
+ console.print("[dim] /data export my_report.xlsx[/dim]")
105
+ console.print("[dim] /data tables[/dim]")
106
+
107
+ async def cmd_alert(self, args: str):
108
+ """
109
+ /alert add AAPL gt 200 — 设置预警(gt/lt/cross_up/cross_down)
110
+ /alert list — 列出所有预警
111
+ /alert delete <id> — 删除预警
112
+ /alert check — 检查所有预警状态
113
+ """
114
+ import asyncio as _asyncio
115
+ loop = _asyncio.get_event_loop()
116
+ parts = args.strip().split() if args.strip() else []
117
+ sub = parts[0].lower() if parts else "list"
118
+
119
+ try:
120
+ from data_analysis_tools import (add_price_alert, list_price_alerts,
121
+ delete_price_alert, check_alerts)
122
+ except ImportError as e:
123
+ if HAS_RICH:
124
+ console.print(f"[red]data_analysis_tools 未加载: {e}[/red]")
125
+ return
126
+
127
+ if sub == "add":
128
+ if len(parts) < 4:
129
+ if HAS_RICH:
130
+ console.print("[dim]用法: /alert add <symbol> <gt|lt|cross_up|cross_down> <price> [备注][/dim]")
131
+ return
132
+ sym = parts[1].upper()
133
+ cond = parts[2].lower()
134
+ try:
135
+ price = float(parts[3])
136
+ except ValueError:
137
+ if HAS_RICH:
138
+ console.print("[red]价格必须是数字[/red]")
139
+ return
140
+ note = " ".join(parts[4:]) if len(parts) > 4 else ""
141
+ r = add_price_alert({"symbol": sym, "condition": cond, "price": price, "note": note})
142
+ if r.get("success"):
143
+ msg = r.get("message", "预警已设置")
144
+ if HAS_RICH:
145
+ console.print(f"[green]✓ {msg}[/green]")
146
+ else:
147
+ print(f"✓ {msg}")
148
+ else:
149
+ if HAS_RICH:
150
+ console.print(f"[red]{r.get('error')}[/red]")
151
+
152
+ elif sub == "list":
153
+ r = list_price_alerts()
154
+ _render_alerts(r)
155
+
156
+ elif sub in ("delete", "del", "remove"):
157
+ alert_id = parts[1] if len(parts) > 1 else ""
158
+ if not alert_id:
159
+ if HAS_RICH:
160
+ console.print("[dim]用法: /alert delete <预警ID>[/dim]")
161
+ return
162
+ r = delete_price_alert({"alert_id": alert_id})
163
+ if r.get("success"):
164
+ if HAS_RICH:
165
+ console.print(f"[green]✓ 已删除预警 {r['deleted_id']}[/green]")
166
+ else:
167
+ if HAS_RICH:
168
+ console.print(f"[red]{r.get('error')}[/red]")
169
+
170
+ elif sub == "check":
171
+ if HAS_RICH:
172
+ with console.status("[dim]检查价格预警...[/dim]", spinner="dots"):
173
+ r = await loop.run_in_executor(None, check_alerts)
174
+ else:
175
+ r = check_alerts()
176
+ triggered = r.get("triggered", [])
177
+ if triggered:
178
+ if HAS_RICH:
179
+ console.print(f"[bold yellow]🔔 {len(triggered)} 个预警已触发![/bold yellow]")
180
+ for a in triggered:
181
+ console.print(f" [yellow]{a['symbol']}[/yellow] {a.get('condition','')} "
182
+ f"{a['price']} → 当前 [bold]{a.get('triggered_price','')}[/bold]")
183
+ else:
184
+ msg = r.get("message", "暂无触发的预警")
185
+ if HAS_RICH:
186
+ console.print(f"[dim]{msg}[/dim]")
187
+
188
+ else:
189
+ if HAS_RICH:
190
+ console.print("[dim]用法: /alert [add|list|delete|check][/dim]")
191
+
192
+ async def cmd_corr(self, args: str):
193
+ """/corr AAPL MSFT TSLA SPY [1y|2y|6mo] — 计算相关性矩阵"""
194
+ import asyncio as _asyncio
195
+ loop = _asyncio.get_event_loop()
196
+ parts = args.strip().upper().split() if args.strip() else []
197
+
198
+ period = "1y"
199
+ if parts and parts[-1].lower() in ("1y", "2y", "3y", "6mo", "ytd", "5y"):
200
+ period = parts[-1].lower()
201
+ parts = parts[:-1]
202
+
203
+ symbols = parts if parts else ["AAPL", "MSFT", "TSLA", "SPY", "QQQ"]
204
+
205
+ try:
206
+ from data_analysis_tools import calc_correlation_matrix
207
+ except ImportError as e:
208
+ if HAS_RICH:
209
+ console.print(f"[red]data_analysis_tools 未加载: {e}[/red]")
210
+ return
211
+
212
+ if HAS_RICH:
213
+ with console.status(f"[dim]计算 {', '.join(symbols)} 相关性矩阵...[/dim]", spinner="dots"):
214
+ r = await loop.run_in_executor(None, calc_correlation_matrix,
215
+ {"symbols": symbols, "period": period})
216
+ else:
217
+ r = calc_correlation_matrix({"symbols": symbols, "period": period})
218
+ _render_corr_matrix(r)
219
+
220
+ async def cmd_portfolio_bt(self, args: str):
221
+ """/ptbt AAPL MSFT GOOG [0.4 0.3 0.3] [2y] [monthly] — 多资产组合回测"""
222
+ import asyncio as _asyncio
223
+ loop = _asyncio.get_event_loop()
224
+ parts = args.strip().split() if args.strip() else []
225
+
226
+ try:
227
+ from data_analysis_tools import portfolio_backtest
228
+ except ImportError as e:
229
+ if HAS_RICH:
230
+ console.print(f"[red]data_analysis_tools 未加载: {e}[/red]")
231
+ return
232
+
233
+ symbols, weights, period, rebalance = [], [], "2y", "monthly"
234
+ _PERIODS = {"1y", "2y", "3y", "5y", "6mo", "ytd", "max"}
235
+ _REBALANCE = {"monthly", "quarterly", "none"}
236
+ for p in parts:
237
+ pl = p.lower()
238
+ if pl in _PERIODS:
239
+ period = pl
240
+ continue
241
+ if pl in _REBALANCE:
242
+ rebalance = pl
243
+ continue
244
+ try:
245
+ f = float(p)
246
+ if f < 2:
247
+ weights.append(f)
248
+ else:
249
+ symbols.append(p.upper())
250
+ except ValueError:
251
+ symbols.append(p.upper())
252
+
253
+ if not symbols:
254
+ symbols = ["AAPL", "MSFT", "GOOGL", "SPY"]
255
+ if HAS_RICH:
256
+ console.print(f"[dim]未指定标的,使用默认: {symbols}[/dim]")
257
+
258
+ p_params = {"symbols": symbols, "period": period, "rebalance": rebalance}
259
+ if weights:
260
+ p_params["weights"] = weights
261
+
262
+ if HAS_RICH:
263
+ with console.status(f"[dim]回测 {', '.join(symbols)} ({period})...[/dim]", spinner="dots"):
264
+ r = await loop.run_in_executor(None, portfolio_backtest, p_params)
265
+ else:
266
+ r = portfolio_backtest(p_params)
267
+ _render_portfolio_bt(r)
268
+
269
+ async def cmd_peer(self, args: str):
270
+ """/peer <symbol> [peer1 peer2 ...] — 同行估值对比"""
271
+ parts = args.strip().upper().split() if args.strip() else []
272
+ symbol = parts[0] if parts else "AAPL"
273
+ peers = parts[1:] if len(parts) > 1 else []
274
+
275
+ if not _HAS_LOCAL_FINANCE:
276
+ if HAS_RICH:
277
+ console.print("[red]local_finance_tools 未加载[/red]")
278
+ return
279
+
280
+ import asyncio as _asyncio
281
+ loop = _asyncio.get_event_loop()
282
+ if HAS_RICH:
283
+ with console.status(f"[dim]获取 {symbol} 同行数据...[/dim]", spinner="dots"):
284
+ from local_finance_tools import _peer_comparison
285
+ r = await loop.run_in_executor(None, _peer_comparison,
286
+ {"symbol": symbol, "peers": peers})
287
+ else:
288
+ from local_finance_tools import _peer_comparison
289
+ r = _peer_comparison({"symbol": symbol, "peers": peers})
290
+
291
+ _render_peer_comparison(r)
292
+
293
+ async def cmd_compare(self, args: str):
294
+ """多策略横向对比 → /api/v1/backtest/compare-strategies"""
295
+ parts = args.split() if args else ["SPY"]
296
+ symbol = parts[0].upper() if parts else "SPY"
297
+ start = parts[1] if len(parts) > 1 else "2020-01-01"
298
+ end = parts[2] if len(parts) > 2 else __import__("datetime").date.today().isoformat()
299
+ api_url = self.terminal.config.get("api_url", "http://localhost:8000")
300
+ import aiohttp
301
+
302
+ _STRATS = ["momentum", "mean_reversion", "breakout", "turtle", "ma_crossover"]
303
+
304
+ async def _do():
305
+ payload = {"symbol": symbol, "strategies": _STRATS,
306
+ "start_date": start, "end_date": end, "initial_capital": 100000, "commission_rate": 0.0003}
307
+ async with aiohttp.ClientSession() as sess:
308
+ async with sess.post(f"{api_url}/api/v1/backtest/compare-strategies", json=payload, timeout=aiohttp.ClientTimeout(total=90)) as resp:
309
+ if resp.status != 200:
310
+ raise RuntimeError(f"HTTP {resp.status}")
311
+ body = await resp.json()
312
+ return body.get("data", body)
313
+
314
+ def _do_local():
315
+ """Fallback: run each strategy via the local backtest engine and
316
+ assemble the same shape the backend endpoint returns. Used when the
317
+ backend is down or lacks the compare-strategies endpoint."""
318
+ import asyncio as _aio
319
+ rows = []
320
+ bench_pct = 0.0
321
+ for strat in _STRATS:
322
+ try:
323
+ res = LOCAL_TOOLS["backtest_strategy"][0](
324
+ {"symbol": symbol, "strategy": strat})
325
+ except Exception:
326
+ continue
327
+ if not res.get("success"):
328
+ continue
329
+ d = res.get("data", res)
330
+ ann = float(d.get("annual_return", 0) or 0)
331
+ mdd = float(d.get("max_drawdown", 0) or 0)
332
+ calmar = (ann / abs(mdd)) if mdd else 0.0
333
+ rows.append({
334
+ "name": strat,
335
+ "annualized_return_pct": ann * 100,
336
+ "sharpe_ratio": float(d.get("sharpe_ratio", 0) or 0),
337
+ "max_drawdown_pct": mdd * 100,
338
+ "calmar_ratio": calmar,
339
+ "sortino_ratio": float(d.get("sortino_ratio", 0) or 0),
340
+ "win_rate_pct": float(d.get("win_rate", 0) or 0) * 100,
341
+ "n_trades": int(d.get("total_trades", 0) or 0),
342
+ })
343
+ br = d.get("benchmark_return")
344
+ if br is not None and br == br: # not NaN
345
+ bench_pct = float(br) * 100
346
+ rows.sort(key=lambda r: r["sharpe_ratio"], reverse=True)
347
+ for i, r in enumerate(rows, 1):
348
+ r["rank_by_sharpe"] = i
349
+ return {"strategies": rows, "benchmark": {"annualized_return_pct": bench_pct},
350
+ "provider": "local"}
351
+
352
+ async def _do_with_fallback():
353
+ try:
354
+ return await _do()
355
+ except Exception:
356
+ # Backend unavailable / missing endpoint → local engine
357
+ if HAS_RICH:
358
+ console.print(" [dim]后端不可用,使用本地回测引擎对比…[/dim]")
359
+ return _do_local()
360
+
361
+ if HAS_RICH:
362
+ with console.status(f"[dim]Comparing strategies on {symbol}...[/dim]", spinner="dots"):
363
+ try:
364
+ data = await _do_with_fallback()
365
+ except Exception as e:
366
+ _print_error(str(e), "tool")
367
+ return
368
+ else:
369
+ print(f"Comparing strategies on {symbol}...")
370
+ try:
371
+ data = await _do_with_fallback()
372
+ except Exception as e:
373
+ _print_error(str(e), "tool")
374
+ return
375
+ if not data.get("strategies"):
376
+ _print_error("策略对比无结果", "本地回测引擎未返回数据,检查标的代码是否正确")
377
+ return
378
+
379
+ strategies = data.get("strategies", [])
380
+ bh = data.get("benchmark", {})
381
+ if HAS_RICH:
382
+ from rich.table import Table
383
+ tbl = Table(title=f"[bold]{symbol} Strategy Comparison[/bold] {start} → {end}", show_header=True, header_style="bold")
384
+ for col in ["Rank", "Strategy", "Ann.Ret%", "Sharpe", "MaxDD%", "Calmar", "Sortino", "Win%", "Trades"]:
385
+ tbl.add_column(col, justify="right")
386
+ for s in strategies:
387
+ tbl.add_row(
388
+ str(s.get("rank_by_sharpe", "")),
389
+ s["name"],
390
+ f"{s.get('annualized_return_pct',0):+.1f}%",
391
+ f"{s.get('sharpe_ratio',0):.3f}",
392
+ f"{s.get('max_drawdown_pct',0):.1f}%",
393
+ f"{s.get('calmar_ratio',0):.2f}",
394
+ f"{s.get('sortino_ratio',0):.2f}",
395
+ f"{s.get('win_rate_pct',0):.0f}%",
396
+ str(s.get("n_trades",0)),
397
+ )
398
+ tbl.add_row("—", "[dim]Buy & Hold[/dim]",
399
+ f"{bh.get('annualized_return_pct',0):+.1f}%",
400
+ f"{bh.get('sharpe_ratio',0):.3f}",
401
+ f"{bh.get('max_drawdown_pct',0):.1f}%", "—", "—", "—", "2")
402
+ console.print(tbl)
403
+ else:
404
+ for s in strategies:
405
+ print(f"{s['name']}: Ann={s.get('annualized_return_pct',0):+.1f}% Sharpe={s.get('sharpe_ratio',0):.2f} DD={s.get('max_drawdown_pct',0):.1f}%")
@@ -0,0 +1,179 @@
1
+ """DiagnosticCommandsMixin — runtime/status/trace/health commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+
8
+
9
+ class DiagnosticCommandsMixin:
10
+ """Mixin providing runtime diagnostics commands."""
11
+
12
+ async def cmd_status(self, args: str):
13
+ """Runtime status panel: engine · tools · model · context · risk"""
14
+ t = self.terminal
15
+ cfg = t.config
16
+ model_id = cfg.get("model", "qwen2.5:7b")
17
+ tool_count = len(ARIA_TOOLS) + len(LOCAL_TOOLS)
18
+ skill_count = len(SKILLS)
19
+
20
+ _lp = t._last_provider or ""
21
+ _badge = next((v.get("badge", "") for v in MODELS.values() if v["id"] == model_id), "")
22
+ if _lp == "ollama":
23
+ runtime = "local (Ollama)"
24
+ elif _lp in ("deepseek", "openai", "anthropic", "groq", "dashscope", "together"):
25
+ runtime = f"cloud ({_lp})"
26
+ elif _badge == "Cloud" or "cloud" in model_id.lower():
27
+ runtime = "cloud"
28
+ else:
29
+ runtime = "local" if getattr(t, "_ollama_alive", False) else "unknown"
30
+
31
+ conv = t.conversation
32
+ est_tok = sum(len(m.get("content", "")) for m in conv) // 3
33
+ max_ctx = get_model_cfg(model_id).get("num_ctx", 16384)
34
+ ctx_pct = min(100, int(est_tok / max_ctx * 100))
35
+ auto_compact = bool(cfg.get("auto_compact_context", True))
36
+ try:
37
+ auto_compact_threshold = float(cfg.get("auto_compact_threshold", 0.78))
38
+ except Exception:
39
+ auto_compact_threshold = 0.78
40
+ auto_compact_runs = int(getattr(t, "_auto_compact_count", 0) or 0)
41
+ provider_summary = None
42
+ try:
43
+ from packages.aria_services.provider_health import GLOBAL_PROVIDER_HEALTH
44
+ provider_summary = GLOBAL_PROVIDER_HEALTH.summary()
45
+ except Exception:
46
+ provider_summary = None
47
+
48
+ mk = next((k for k, v in MODELS.items() if v["id"] == model_id), None)
49
+ model_display = MODELS[mk]["name"] if mk else model_id
50
+
51
+ if HAS_RICH:
52
+ console.print()
53
+ console.print("[bold]Runtime Status[/bold]")
54
+ console.print()
55
+ rows = [
56
+ ("runtime", runtime),
57
+ ("model", model_display),
58
+ ("engine", "quant engine v3.0"),
59
+ ("tools", f"{tool_count} available · {skill_count} skills"),
60
+ ("risk", "enabled"),
61
+ ("context", f"{est_tok:,} / {max_ctx:,} tokens ({ctx_pct}%)"),
62
+ ("compact", f"{'on' if auto_compact else 'off'} · {int(auto_compact_threshold * 100)}% · {auto_compact_runs} runs"),
63
+ ]
64
+ if provider_summary is not None:
65
+ rows.append(("providers", f"{provider_summary.status} · {provider_summary.detail}"))
66
+ if getattr(t, "_project_session", None):
67
+ rows.append(("project", f"{t._project_session.name} ({t._project_session.stats.get('total_files',0)} files)"))
68
+ if getattr(t, "_file_session", None) and t._file_session.get_active():
69
+ fc = t._file_session.get_active()
70
+ rows.append(("file", f"{fc.filename} ({fc.size_kb:.0f} KB)"))
71
+ rows.append(("banner", cfg.get("banner", "full")))
72
+ rows.append(("workspace", os.getcwd().replace(os.path.expanduser("~"), "~")))
73
+ for k, v in rows:
74
+ console.print(f" [dim]{k:<12}[/dim][cyan]{v}[/cyan]")
75
+ console.print()
76
+ else:
77
+ print("\nRuntime Status")
78
+ print(f" runtime {runtime}")
79
+ print(f" model {model_display}")
80
+ print(f" tools {tool_count}")
81
+ print(f" context {est_tok}/{max_ctx}")
82
+ print(f" compact {'on' if auto_compact else 'off'} threshold={int(auto_compact_threshold * 100)}% runs={auto_compact_runs}")
83
+ if provider_summary is not None:
84
+ print(f" providers {provider_summary.status} {provider_summary.detail}")
85
+ print()
86
+
87
+ def cmd_trace(self, args: str):
88
+ """Show runtime trace for recent tool calls."""
89
+ trace = getattr(self.terminal, "runtime_trace", None)
90
+ if trace is None:
91
+ msg = "Runtime trace is unavailable."
92
+ console.print(f"[dim]{msg}[/dim]" if HAS_RICH else msg)
93
+ return
94
+ if "--json" in args.split():
95
+ payload = json.dumps(trace.to_dict(), ensure_ascii=False, indent=2)
96
+ if HAS_RICH:
97
+ console.print(Syntax(payload, "json", theme=_SYNTAX_THEME))
98
+ else:
99
+ print(payload)
100
+ return
101
+ turns = trace.turn_results[-5:]
102
+ calls = trace.tool_calls[-20:]
103
+ if not calls and not turns:
104
+ msg = "No tool calls recorded yet."
105
+ console.print(f"[dim]{msg}[/dim]" if HAS_RICH else msg)
106
+ return
107
+ if HAS_RICH:
108
+ console.print()
109
+ console.print("[bold]Runtime Trace[/bold]")
110
+ console.print()
111
+ if turns:
112
+ console.print(" [dim]Recent turns[/dim]")
113
+ for turn in turns:
114
+ ok = bool(turn.success)
115
+ style = "green" if ok else "red"
116
+ status = turn.status or ("ok" if ok else "err")
117
+ summary = turn.summary or turn.final_text[:120]
118
+ if len(summary) > 120:
119
+ summary = summary[:117] + "..."
120
+ console.print(
121
+ f" [{style}]{status:<8}[/{style}] "
122
+ f"[bold]{turn.provider or '?'}[/bold] "
123
+ f"[dim]{summary}[/dim]"
124
+ )
125
+ console.print()
126
+ for call in calls:
127
+ ok = bool(call.result.get("success"))
128
+ style = "green" if ok else "red"
129
+ console.print(
130
+ f" [{style}]{'ok' if ok else 'err':<3}[/{style}] "
131
+ f"[bold]{call.tool}[/bold] "
132
+ f"[dim]{call.elapsed_ms:.0f} ms[/dim]"
133
+ )
134
+ if not ok and call.result.get("error"):
135
+ console.print(f" [red]{str(call.result.get('error'))[:180]}[/red]")
136
+ console.print()
137
+ else:
138
+ print("\nRuntime Trace")
139
+ for turn in turns:
140
+ ok = "ok" if turn.success else "err"
141
+ summary = turn.summary or turn.final_text[:120]
142
+ if len(summary) > 120:
143
+ summary = summary[:117] + "..."
144
+ print(f" {ok:<3} {turn.provider or '?'} {summary}")
145
+ for call in calls:
146
+ ok = "ok" if call.result.get("success") else "err"
147
+ print(f" {ok:<3} {call.tool} {call.elapsed_ms:.0f} ms")
148
+ print()
149
+
150
+ async def cmd_health(self, args: str):
151
+ import aiohttp
152
+ if HAS_RICH:
153
+ console.print()
154
+ urls = [
155
+ ("AWS Backend", self.terminal.api_url, "/health"),
156
+ ("Local Server", self.terminal.config.get("local_url", "http://localhost:8001"), "/health"),
157
+ ("Ollama", self.terminal.config.get("ollama_url", "http://localhost:11434"), "/api/tags"),
158
+ ]
159
+ for label, url, path in urls:
160
+ try:
161
+ async with aiohttp.ClientSession() as session:
162
+ async with session.get(f"{url}{path}", timeout=aiohttp.ClientTimeout(total=5)) as resp:
163
+ data = await resp.json()
164
+ if label == "Ollama":
165
+ models = [m.get("name", "?") for m in data.get("models", [])[:3]]
166
+ detail = ", ".join(models)
167
+ else:
168
+ detail = f"v{data.get('version', '?')}"
169
+ if HAS_RICH:
170
+ console.print(f" [green]●[/green] [dim]{label}[/dim] {detail}")
171
+ else:
172
+ print(f" + {label} {detail}")
173
+ except Exception:
174
+ if HAS_RICH:
175
+ console.print(f" [red]●[/red] [dim]{label}[/dim] offline")
176
+ else:
177
+ print(f" - {label} offline")
178
+ if HAS_RICH:
179
+ console.print()