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,99 @@
1
+ """
2
+ agents/financial/macro.py — 宏观环境 Agent
3
+ ===========================================
4
+ 分析:利率环境、汇率、行业周期、政策面、大盘趋势。
5
+ """
6
+ from __future__ import annotations
7
+ from typing import Any, Dict, List
8
+ from ..base import BaseAgent, AgentResult
9
+
10
+
11
+ class MacroAgent(BaseAgent):
12
+ name = "macro"
13
+ description = "宏观分析:利率/汇率/政策/大盘趋势"
14
+
15
+ _SYSTEM = (
16
+ "You are a macro strategist covering China and global markets. "
17
+ "Focus on: interest rate environment, USD/CNY trend, sector cycle, "
18
+ "regulatory policy, and market sentiment. Be concise and data-driven. "
19
+ "End with: TAILWIND / NEUTRAL / HEADWIND for the given stock."
20
+ )
21
+
22
+ async def fetch_data(self, symbol: str) -> Dict[str, Any]:
23
+ data = await super().fetch_data(symbol)
24
+ # 尝试拉取指数数据作为大盘参考
25
+ if self.data:
26
+ for idx in ["000001", "399006"]: # 上证 + 创业板
27
+ try:
28
+ q = self.data.quote(idx)
29
+ if q:
30
+ data.setdefault("indices", {})[idx] = q.to_dict()
31
+ except Exception:
32
+ pass
33
+ return data
34
+
35
+ async def analyze(self, symbol: str, data: Dict[str, Any]) -> AgentResult:
36
+ quote = data.get("quote", {})
37
+ indices = data.get("indices", {})
38
+
39
+ idx_summary = ""
40
+ for code, q in indices.items():
41
+ name = {"000001": "上证", "399006": "创业板"}.get(code, code)
42
+ idx_summary += f" {name}: {q.get('price',0):.2f} {q.get('change_pct',0):+.2f}%\n"
43
+
44
+ prompt = (
45
+ f"Symbol: {symbol}\n"
46
+ f"Market Indices:\n{idx_summary or ' (unavailable)'}\n\n"
47
+ "Analyze the macroeconomic backdrop for this stock:\n"
48
+ "1. Current rate/liquidity environment (PBOC stance)\n"
49
+ "2. Sector regulatory environment (any recent policies)\n"
50
+ "3. Market sentiment and positioning\n"
51
+ "4. Key macro risks to watch\n"
52
+ "Conclusion: TAILWIND / NEUTRAL / HEADWIND"
53
+ )
54
+
55
+ analysis = await self._call_llm(self._SYSTEM, prompt, max_tokens=500, quote=quote)
56
+ if not analysis:
57
+ analysis = _template_macro(symbol, indices)
58
+
59
+ signal = _extract_signal(analysis)
60
+ key_points = _extract_key_points(analysis)
61
+
62
+ return AgentResult(
63
+ agent=self.name, symbol=symbol,
64
+ analysis=analysis, confidence=0.65,
65
+ signal=signal, key_points=key_points,
66
+ data_used={"indices": indices},
67
+ )
68
+
69
+
70
+ def _extract_signal(text: str) -> str:
71
+ t = text.upper()
72
+ if "TAILWIND" in t or "看多" in t: return "BUY"
73
+ if "HEADWIND" in t or "看空" in t: return "SELL"
74
+ return "HOLD"
75
+
76
+
77
+ def _extract_key_points(text: str) -> List[str]:
78
+ points = []
79
+ for line in text.split("\n"):
80
+ line = line.strip()
81
+ if line.startswith(("1.", "2.", "3.", "4.", "•", "-", "·")) and len(line) > 5:
82
+ points.append(line.lstrip("1234567890.-•· "))
83
+ return points[:4]
84
+
85
+
86
+ _IDX_NAMES = {"000001": "上证", "399006": "创业板"}
87
+
88
+ def _template_macro(symbol: str, indices: Dict) -> str:
89
+ idx_str = "、".join(
90
+ f"{_IDX_NAMES.get(k, k)} {v.get('change_pct', 0):+.2f}%"
91
+ for k, v in indices.items()
92
+ ) if indices else "指数数据不可用"
93
+ return (
94
+ f"{symbol} 宏观分析(模板):\n"
95
+ f"• 市场指数: {idx_str}\n"
96
+ "• 当前货币政策偏宽松,流动性充裕\n"
97
+ "• 建议关注政策面变化和外资动向\n"
98
+ "• 结论: NEUTRAL"
99
+ )
@@ -0,0 +1,207 @@
1
+ """
2
+ agents/financial/news.py — 新闻与舆情分析 Agent
3
+ ================================================
4
+ 分析近期新闻标题、公告和媒体情绪,识别关键事件类型。
5
+ 数据源:yfinance.news(美股/港股)、akshare公告(A股,可选)
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import re
12
+ from datetime import datetime, timezone
13
+ from typing import Any, Dict, List
14
+
15
+ from ..base import BaseAgent, AgentResult
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ _EVENT_KEYWORDS = {
20
+ "earnings": ["earnings", "revenue", "profit", "EPS", "业绩", "净利润", "营收"],
21
+ "upgrade": ["upgrade", "outperform", "buy", "overweight", "上调", "买入"],
22
+ "downgrade": ["downgrade", "underperform", "sell", "underweight", "下调", "卖出"],
23
+ "insider": ["insider", "director", "CEO", "增持", "减持", "大股东"],
24
+ "regulatory": ["SEC", "CSRC", "证监会", "regulatory", "investigation", "调查"],
25
+ "dividend": ["dividend", "分红", "派息", "股息"],
26
+ "merger": ["merger", "acquisition", "takeover", "合并", "收购"],
27
+ }
28
+
29
+
30
+ class NewsAgent(BaseAgent):
31
+
32
+ name = "news"
33
+ description = "新闻与舆情分析 — 近期重大事件、公告、媒体情绪"
34
+
35
+ _SYSTEM = (
36
+ "You are a financial news analyst. Analyze the provided news headlines "
37
+ "and summaries for a stock. Identify the most impactful recent events, "
38
+ "classify sentiment (POSITIVE / NEUTRAL / NEGATIVE), and assess likely "
39
+ "price impact. Focus on material events: earnings, analyst changes, "
40
+ "regulatory actions, M&A, and insider activity. "
41
+ "Conclude with: POSITIVE / NEUTRAL / NEGATIVE"
42
+ )
43
+
44
+ async def fetch_data(self, symbol: str) -> Dict[str, Any]:
45
+ data = await super().fetch_data(symbol)
46
+ news_items: List[Dict] = []
47
+
48
+ # 1. yfinance news (works for US / HK stocks)
49
+ try:
50
+ import yfinance as yf
51
+ ticker = yf.Ticker(symbol)
52
+ raw = ticker.news or []
53
+ now = datetime.now(timezone.utc).timestamp()
54
+ for item in raw[:12]:
55
+ pub = item.get("providerPublishTime", 0)
56
+ age_days = (now - pub) / 86400 if pub else 999
57
+ news_items.append({
58
+ "title": item.get("title", ""),
59
+ "summary": item.get("summary", "") or item.get("description", ""),
60
+ "source": item.get("publisher", ""),
61
+ "age_days": round(age_days, 1),
62
+ })
63
+ except Exception as e:
64
+ logger.debug("[news] yfinance fetch failed for %s: %s", symbol, e)
65
+
66
+ # 2. akshare A-share announcements (optional)
67
+ if not news_items and re.match(r"^[036]\d{5}$", symbol):
68
+ try:
69
+ import akshare as ak
70
+ df = ak.stock_news_em(symbol=symbol)
71
+ if df is not None and not df.empty:
72
+ for _, row in df.head(10).iterrows():
73
+ news_items.append({
74
+ "title": str(row.get("新闻标题", "")),
75
+ "summary": str(row.get("新闻内容", ""))[:200],
76
+ "source": str(row.get("新闻来源", "")),
77
+ "age_days": 0,
78
+ })
79
+ except Exception as e:
80
+ logger.debug("[news] akshare fetch failed for %s: %s", symbol, e)
81
+
82
+ data["news"] = news_items
83
+ return data
84
+
85
+ async def analyze(self, symbol: str, data: Dict[str, Any]) -> AgentResult:
86
+ news = data.get("news", [])
87
+ quote = data.get("quote", {})
88
+ price = quote.get("price", 0)
89
+
90
+ if not news:
91
+ return AgentResult(
92
+ agent=self.name, symbol=symbol,
93
+ analysis=f"{symbol}: 未获取到近期新闻数据。",
94
+ confidence=0.3, signal="HOLD",
95
+ key_points=["无近期新闻数据"],
96
+ )
97
+
98
+ events = _classify_events(news)
99
+ news_block = _format_news(news[:8])
100
+
101
+ prompt = (
102
+ f"Stock: {symbol} Price: {price}\n\n"
103
+ f"Recent News ({len(news)} items):\n{news_block}\n\n"
104
+ "Tasks:\n"
105
+ "1. Identify the 2-3 most impactful recent events\n"
106
+ "2. Classify overall media sentiment\n"
107
+ "3. Assess potential price catalyst (short-term, 1-5 days)\n"
108
+ "4. Conclude with: POSITIVE / NEUTRAL / NEGATIVE"
109
+ )
110
+
111
+ analysis = await self._call_llm(self._SYSTEM, prompt, max_tokens=500)
112
+ if not analysis:
113
+ analysis = _template_analysis(symbol, news, events)
114
+
115
+ signal = _extract_signal(analysis, events)
116
+ confidence = _estimate_confidence(news, events)
117
+ key_points = _build_key_points(news, events)
118
+
119
+ return AgentResult(
120
+ agent=self.name, symbol=symbol,
121
+ analysis=analysis,
122
+ confidence=confidence,
123
+ signal=signal,
124
+ key_points=key_points,
125
+ data_used={"news_count": len(news), "events": list(events.keys())},
126
+ )
127
+
128
+
129
+ # ── Helpers ───────────────────────────────────────────────────────────────────
130
+
131
+ def _classify_events(news: List[Dict]) -> Dict[str, int]:
132
+ counts: Dict[str, int] = {}
133
+ for item in news:
134
+ text = (item.get("title", "") + " " + item.get("summary", "")).lower()
135
+ for event_type, keywords in _EVENT_KEYWORDS.items():
136
+ if any(kw.lower() in text for kw in keywords):
137
+ counts[event_type] = counts.get(event_type, 0) + 1
138
+ return counts
139
+
140
+
141
+ def _format_news(news: List[Dict]) -> str:
142
+ lines = []
143
+ for i, item in enumerate(news, 1):
144
+ age = f"{item['age_days']:.0f}d ago" if item.get("age_days", 999) < 30 else ""
145
+ src = item.get("source", "")
146
+ title = item.get("title", "").strip()
147
+ lines.append(f"{i}. [{src}] {title} {age}".strip())
148
+ return "\n".join(lines)
149
+
150
+
151
+ def _extract_signal(analysis: str, events: Dict[str, int]) -> str:
152
+ text = analysis.upper()
153
+ if "POSITIVE" in text:
154
+ if events.get("upgrade", 0) > 0 or "STRONG" in text:
155
+ return "BUY"
156
+ return "BUY"
157
+ if "NEGATIVE" in text:
158
+ if events.get("downgrade", 0) > 0 or events.get("regulatory", 0) > 0:
159
+ return "SELL"
160
+ return "SELL"
161
+ return "HOLD"
162
+
163
+
164
+ def _estimate_confidence(news: List[Dict], events: Dict[str, int]) -> float:
165
+ base = 0.45
166
+ recent = sum(1 for n in news if n.get("age_days", 999) <= 3)
167
+ base += min(recent * 0.05, 0.15)
168
+ if events.get("earnings") or events.get("upgrade") or events.get("downgrade"):
169
+ base += 0.1
170
+ return min(round(base, 2), 0.75)
171
+
172
+
173
+ def _build_key_points(news: List[Dict], events: Dict[str, int]) -> List[str]:
174
+ points = []
175
+ recent = [n for n in news if n.get("age_days", 999) <= 3]
176
+ if recent:
177
+ points.append(f"近3日 {len(recent)} 条新鲜新闻")
178
+ for etype, count in events.items():
179
+ label = {
180
+ "earnings": "业绩/财报相关",
181
+ "upgrade": "分析师升级评级",
182
+ "downgrade": "分析师降级评级",
183
+ "insider": "内部人士交易",
184
+ "regulatory": "监管/调查事件",
185
+ "dividend": "股息/分红公告",
186
+ "merger": "并购/重组消息",
187
+ }.get(etype, etype)
188
+ points.append(f"{label} × {count}")
189
+ return points[:5]
190
+
191
+
192
+ def _template_analysis(symbol: str, news: List[Dict], events: Dict[str, int]) -> str:
193
+ recent = [n for n in news if n.get("age_days", 999) <= 7]
194
+ sentiment = "中性"
195
+ if events.get("upgrade", 0) > events.get("downgrade", 0):
196
+ sentiment = "偏正面"
197
+ elif events.get("downgrade", 0) > 0 or events.get("regulatory", 0) > 0:
198
+ sentiment = "偏负面"
199
+
200
+ titles = "\n".join(f" • {n['title'][:60]}" for n in recent[:3])
201
+ return (
202
+ f"{symbol} 近期新闻情绪:{sentiment}\n"
203
+ f"近7日 {len(recent)} 条相关新闻\n"
204
+ f"主要标题:\n{titles or ' (无最新标题)'}\n"
205
+ f"事件分类:{', '.join(events.keys()) or '无特定事件'}\n"
206
+ f"结论:{'POSITIVE' if sentiment == '偏正面' else ('NEGATIVE' if sentiment == '偏负面' else 'NEUTRAL')}"
207
+ )
@@ -0,0 +1,132 @@
1
+ """
2
+ agents/financial/risk.py — 风险评估 Agent
3
+ ==========================================
4
+ 分析:波动率、最大回撤、与大盘相关性、仓位建议。
5
+ """
6
+ from __future__ import annotations
7
+ from typing import Any, Dict, List
8
+ from ..base import BaseAgent, AgentResult
9
+
10
+ try:
11
+ import numpy as np
12
+ _HAS_NP = True
13
+ except ImportError:
14
+ _HAS_NP = False
15
+ np = None # type: ignore[assignment]
16
+
17
+
18
+ class RiskAgent(BaseAgent):
19
+ name = "risk"
20
+ description = "风险评估:波动率/回撤/相关性/仓位建议"
21
+
22
+ _SYSTEM = (
23
+ "You are a risk manager. Assess: historical volatility, max drawdown, "
24
+ "liquidity risk, correlation to market, and position sizing recommendation. "
25
+ "Output: risk score 1-10 (1=very low, 10=very high) and "
26
+ "POSITION: <percentage> (e.g. POSITION: 10%)."
27
+ )
28
+
29
+ async def fetch_data(self, symbol: str) -> Dict[str, Any]:
30
+ data = await super().fetch_data(symbol)
31
+ if self.data:
32
+ h = self.data.history(symbol, days=252) # 1年
33
+ if h and h.data is not None:
34
+ data["risk_metrics"] = _compute_risk(h.data)
35
+ return data
36
+
37
+ async def analyze(self, symbol: str, data: Dict[str, Any]) -> AgentResult:
38
+ quote = data.get("quote", {})
39
+ metrics = data.get("risk_metrics", {})
40
+ price = quote.get("price", 0)
41
+
42
+ risk_str = (
43
+ f" Ann. Volatility: {metrics.get('ann_vol',0):.1f}%\n"
44
+ f" Max Drawdown: {metrics.get('max_dd',0):.1f}%\n"
45
+ f" Sharpe (1Y): {metrics.get('sharpe',0):.2f}\n"
46
+ f" Beta est.: {metrics.get('beta',1.0):.2f}\n"
47
+ f" Avg Daily Vol: {metrics.get('avg_vol_usd',0):,.0f}"
48
+ ) if metrics else " (risk metrics unavailable)"
49
+
50
+ prompt = (
51
+ f"Stock: {symbol} Price: {price}\n"
52
+ f"Risk Metrics:\n{risk_str}\n\n"
53
+ "Assess:\n"
54
+ "1. Volatility profile (high/medium/low)\n"
55
+ "2. Drawdown history and recovery\n"
56
+ "3. Liquidity adequacy\n"
57
+ "4. Tail risk and black swan exposure\n"
58
+ "5. Recommended position size for a diversified portfolio\n"
59
+ "Output format: Risk Score: X/10\nPOSITION: Y%"
60
+ )
61
+
62
+ analysis = await self._call_llm(self._SYSTEM, prompt, max_tokens=400, quote=quote)
63
+ if not analysis:
64
+ analysis = _template_risk(symbol, metrics)
65
+
66
+ signal = _risk_to_signal(metrics)
67
+ confidence = 0.70
68
+ key_points = [
69
+ f"年化波动率 {metrics.get('ann_vol',0):.1f}%",
70
+ f"最大回撤 {metrics.get('max_dd',0):.1f}%",
71
+ f"夏普比率 {metrics.get('sharpe',0):.2f}",
72
+ ] if metrics else ["风险数据不可用"]
73
+
74
+ return AgentResult(
75
+ agent=self.name, symbol=symbol,
76
+ analysis=analysis, confidence=confidence,
77
+ signal=signal, key_points=key_points,
78
+ data_used=metrics,
79
+ )
80
+
81
+
82
+ def _compute_risk(df) -> Dict:
83
+ if not _HAS_NP:
84
+ return {}
85
+ try:
86
+ close = df["close"].values if "close" in df.columns else df.iloc[:, -1].values
87
+ vol = df["volume"].values if "volume" in df.columns else np.ones(len(close))
88
+ rets = np.diff(close) / close[:-1]
89
+
90
+ ann_vol = float(np.std(rets) * np.sqrt(252) * 100)
91
+ ann_ret = float(np.mean(rets) * 252 * 100)
92
+ sharpe = ann_ret / max(ann_vol, 0.01)
93
+ cum = np.cumprod(1 + rets)
94
+ peaks = np.maximum.accumulate(cum)
95
+ drawdowns = (cum - peaks) / peaks
96
+ max_dd = float(drawdowns.min() * 100)
97
+ avg_vol = float(np.mean(vol[-20:]) * close[-1]) if len(close) > 20 else 0
98
+
99
+ return {
100
+ "ann_vol": round(ann_vol, 1),
101
+ "ann_ret": round(ann_ret, 1),
102
+ "sharpe": round(sharpe, 2),
103
+ "max_dd": round(max_dd, 1),
104
+ "beta": 1.0, # 需要指数数据对比
105
+ "avg_vol_usd": round(avg_vol, 0),
106
+ }
107
+ except Exception:
108
+ return {}
109
+
110
+
111
+ def _risk_to_signal(metrics: Dict) -> str:
112
+ vol = metrics.get("ann_vol", 30)
113
+ dd = metrics.get("max_dd", -20)
114
+ if vol < 20 and dd > -15:
115
+ return "BUY"
116
+ if vol > 50 or dd < -40:
117
+ return "REDUCE"
118
+ return "HOLD"
119
+
120
+
121
+ def _template_risk(symbol: str, m: Dict) -> str:
122
+ vol = m.get("ann_vol", 0)
123
+ dd = m.get("max_dd", 0)
124
+ risk_level = "高" if vol > 40 else ("中" if vol > 20 else "低")
125
+ return (
126
+ f"{symbol} 风险分析(模板):\n"
127
+ f"• 年化波动率: {vol:.1f}%(风险水平: {risk_level})\n"
128
+ f"• 最大回撤: {dd:.1f}%\n"
129
+ f"• 夏普比率: {m.get('sharpe',0):.2f}\n"
130
+ f"• 建议仓位: {'5-10%' if vol>40 else '10-15%' if vol>20 else '15-20%'}\n"
131
+ f"• Risk Score: {min(10, int(vol/5))}/10"
132
+ )