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.
- agents/__init__.py +32 -0
- agents/base.py +190 -0
- agents/deep/__init__.py +37 -0
- agents/deep/calibration_loop.py +144 -0
- agents/deep/critic.py +125 -0
- agents/deep/deepen.py +193 -0
- agents/deep/models.py +149 -0
- agents/deep/pipeline.py +164 -0
- agents/deep/quant_fusion.py +192 -0
- agents/deep/themes.py +95 -0
- agents/deep/tiers.py +106 -0
- agents/financial/__init__.py +10 -0
- agents/financial/catalyst.py +279 -0
- agents/financial/debate.py +145 -0
- agents/financial/earnings.py +303 -0
- agents/financial/fundamental.py +159 -0
- agents/financial/macro.py +99 -0
- agents/financial/news.py +207 -0
- agents/financial/risk.py +132 -0
- agents/financial/sector.py +279 -0
- agents/financial/synthesis.py +274 -0
- agents/financial/technical.py +258 -0
- agents/portfolio_agent.py +333 -0
- agents/realty/__init__.py +62 -0
- agents/realty/asset_diagnosis.py +150 -0
- agents/realty/business_match.py +165 -0
- agents/realty/cashflow_verify.py +208 -0
- agents/realty/contract_rules.py +209 -0
- agents/realty/energy_anomaly.py +188 -0
- agents/realty/exit_settlement.py +207 -0
- agents/realty/fulfillment_risk.py +205 -0
- agents/realty/ops_optimize.py +159 -0
- agents/realty/revenue_share.py +214 -0
- agents/registry.py +144 -0
- agents/sports/__init__.py +0 -0
- agents/sports/football_agent.py +169 -0
- agents/team.py +289 -0
- aliyun_data_client.py +660 -0
- apps/README.md +12 -0
- apps/__init__.py +2 -0
- apps/channels/README.md +15 -0
- apps/cli/README.md +13 -0
- apps/cli/__init__.py +2 -0
- apps/cli/bootstrap.py +99 -0
- apps/cli/codegen_paths.py +29 -0
- apps/cli/commands/__init__.py +16 -0
- apps/cli/commands/analysis_cmds.py +288 -0
- apps/cli/commands/backtest_cmds.py +1887 -0
- apps/cli/commands/broker_cmds.py +1154 -0
- apps/cli/commands/business_workflow_cmds.py +289 -0
- apps/cli/commands/catalog.py +84 -0
- apps/cli/commands/data_cmds.py +405 -0
- apps/cli/commands/diagnostic_cmds.py +179 -0
- apps/cli/commands/diagnostic_ops_cmds.py +696 -0
- apps/cli/commands/finance_render.py +12 -0
- apps/cli/commands/market.py +399 -0
- apps/cli/commands/market_cmds.py +1276 -0
- apps/cli/commands/market_context.py +425 -0
- apps/cli/commands/market_render.py +7 -0
- apps/cli/commands/model_cmds.py +1579 -0
- apps/cli/commands/ops_cmds.py +668 -0
- apps/cli/commands/portfolio_cmds.py +962 -0
- apps/cli/commands/report.py +377 -0
- apps/cli/commands/scaffold_templates.py +617 -0
- apps/cli/commands/session_cmds.py +179 -0
- apps/cli/commands/session_ux_cmds.py +280 -0
- apps/cli/commands/team.py +588 -0
- apps/cli/commands/team_render.py +8 -0
- apps/cli/commands/ui_cmds.py +358 -0
- apps/cli/commands/workflow_cmds.py +279 -0
- apps/cli/commands/workspace_cmds.py +1414 -0
- apps/cli/config_paths.py +70 -0
- apps/cli/config_store.py +61 -0
- apps/cli/deterministic.py +122 -0
- apps/cli/direct.py +48 -0
- apps/cli/github_app_auth.py +135 -0
- apps/cli/handlers/__init__.py +11 -0
- apps/cli/handlers/broker_handlers.py +122 -0
- apps/cli/handlers/chart_handlers.py +1309 -0
- apps/cli/handlers/market_handlers.py +2509 -0
- apps/cli/handlers/realty_handlers.py +114 -0
- apps/cli/handlers/strategy_advice.py +82 -0
- apps/cli/hooks.py +180 -0
- apps/cli/i18n.py +284 -0
- apps/cli/intent.py +136 -0
- apps/cli/intent_router.py +217 -0
- apps/cli/lifecycle_hooks.py +48 -0
- apps/cli/main.py +29 -0
- apps/cli/market_metadata.py +135 -0
- apps/cli/market_universe.py +265 -0
- apps/cli/message_processing.py +257 -0
- apps/cli/plan_mode.py +139 -0
- apps/cli/plotly_html.py +15 -0
- apps/cli/prediction_feedback.py +202 -0
- apps/cli/preflight.py +497 -0
- apps/cli/project_aria.py +60 -0
- apps/cli/prompts/__init__.py +0 -0
- apps/cli/prompts/coding.py +658 -0
- apps/cli/prompts/system_prompts.py +531 -0
- apps/cli/prompts/ui.py +434 -0
- apps/cli/providers/__init__.py +1 -0
- apps/cli/providers/base.py +271 -0
- apps/cli/providers/chat_routing.py +80 -0
- apps/cli/providers/llm/__init__.py +1 -0
- apps/cli/providers/llm/ollama_stream.py +1170 -0
- apps/cli/providers/llm/sse_stream.py +216 -0
- apps/cli/providers/runtime_bridge.py +185 -0
- apps/cli/runtime_consumer.py +489 -0
- apps/cli/session_export.py +87 -0
- apps/cli/session_jsonl.py +207 -0
- apps/cli/session_store.py +112 -0
- apps/cli/todo_tracker.py +190 -0
- apps/cli/tools/__init__.py +40 -0
- apps/cli/tools/context.py +46 -0
- apps/cli/tools/file_tools.py +112 -0
- apps/cli/tools/market_tools.py +549 -0
- apps/cli/tools/notebook_tools.py +111 -0
- apps/cli/tools/system_tools.py +669 -0
- apps/cli/tools/write_tools.py +715 -0
- apps/cli/tradingview_bridge.py +434 -0
- apps/cli/update_check.py +152 -0
- apps/cli/utils/__init__.py +0 -0
- apps/cli/utils/market_detect.py +1578 -0
- apps/daemon/README.md +14 -0
- apps/vscode/README.md +115 -0
- apps/vscode/package.json +70 -0
- aria_cli.py +11636 -0
- aria_code-4.1.3.dist-info/METADATA +952 -0
- aria_code-4.1.3.dist-info/RECORD +284 -0
- aria_code-4.1.3.dist-info/WHEEL +5 -0
- aria_code-4.1.3.dist-info/entry_points.txt +2 -0
- aria_code-4.1.3.dist-info/licenses/LICENSE +121 -0
- aria_code-4.1.3.dist-info/top_level.txt +50 -0
- aria_daemon.py +1295 -0
- aria_feishu_bot.py +1359 -0
- aria_relay_client.py +182 -0
- aria_relay_server.py +405 -0
- aria_telegram_bot.py +202 -0
- ariarc.py +328 -0
- artifacts.py +491 -0
- backtest_report.py +472 -0
- brokers/__init__.py +72 -0
- brokers/base.py +207 -0
- brokers/capabilities.py +264 -0
- brokers/cn/__init__.py +10 -0
- brokers/cn/easytrader_broker.py +193 -0
- brokers/cn/futu_broker.py +194 -0
- brokers/cn/longbridge_broker.py +190 -0
- brokers/cn/tiger_broker.py +196 -0
- brokers/cn/xtquant_broker.py +175 -0
- brokers/config.py +364 -0
- brokers/intl/__init__.py +5 -0
- brokers/intl/alpaca_broker.py +183 -0
- brokers/intl/ibkr_broker.py +215 -0
- brokers/intl/webull_broker.py +156 -0
- brokers/paper_broker.py +259 -0
- brokers/planning.py +296 -0
- brokers/registry.py +181 -0
- brokers/trading.py +237 -0
- change_store.py +127 -0
- command_safety.py +19 -0
- computer_use_tools.py +504 -0
- dashboard_generator.py +578 -0
- data_analysis_tools.py +808 -0
- data_cleaner.py +483 -0
- data_service.py +481 -0
- datasources/__init__.py +23 -0
- datasources/base.py +166 -0
- datasources/router.py +221 -0
- datasources/sources/__init__.py +15 -0
- datasources/sources/akshare_source.py +269 -0
- datasources/sources/alpha_vantage_source.py +202 -0
- datasources/sources/edgar_source.py +218 -0
- datasources/sources/finnhub_source.py +197 -0
- datasources/sources/fred_source.py +219 -0
- datasources/sources/tushare_source.py +141 -0
- datasources/sources/web_scraper_source.py +278 -0
- datasources/sources/world_bank_source.py +205 -0
- datasources/sources/yfinance_source.py +152 -0
- demo_player.py +204 -0
- doctor.py +508 -0
- file_analysis_tools.py +734 -0
- finance_formulas.py +389 -0
- football_data_client.py +1670 -0
- intent_classifier.py +358 -0
- local_finance_tools.py +3221 -0
- local_llm_provider.py +552 -0
- macro_tools.py +368 -0
- market_data_client.py +1899 -0
- mcp_client.py +506 -0
- memory_manager.py +245 -0
- model_capability.py +416 -0
- notification_tools.py +248 -0
- packages/__init__.py +23 -0
- packages/aria_agents/__init__.py +5 -0
- packages/aria_agents/manifest.py +69 -0
- packages/aria_core/__init__.py +34 -0
- packages/aria_core/architecture.py +192 -0
- packages/aria_core/export.py +124 -0
- packages/aria_core/manifest.py +65 -0
- packages/aria_infra/__init__.py +15 -0
- packages/aria_infra/arthera.py +52 -0
- packages/aria_infra/doctor.py +246 -0
- packages/aria_infra/product.py +37 -0
- packages/aria_mcp/__init__.py +25 -0
- packages/aria_mcp/bridge.py +38 -0
- packages/aria_mcp/config.py +97 -0
- packages/aria_mcp/tools.py +61 -0
- packages/aria_sdk/__init__.py +19 -0
- packages/aria_sdk/client.py +396 -0
- packages/aria_sdk/providers.py +70 -0
- packages/aria_sdk/streaming.py +73 -0
- packages/aria_sdk/types.py +86 -0
- packages/aria_services/__init__.py +55 -0
- packages/aria_services/context.py +258 -0
- packages/aria_services/data.py +11 -0
- packages/aria_services/provider_health.py +189 -0
- packages/aria_services/registry.py +213 -0
- packages/aria_services/usage.py +138 -0
- packages/aria_skills/__init__.py +5 -0
- packages/aria_skills/registry.py +59 -0
- packages/aria_tools/__init__.py +5 -0
- packages/aria_tools/registry.py +128 -0
- packages/quant_engine/__init__.py +6 -0
- packages/quant_engine/sports/__init__.py +72 -0
- packages/quant_engine/sports/calibrator.py +353 -0
- packages/quant_engine/sports/dixon_coles.py +234 -0
- packages/quant_engine/sports/elo.py +299 -0
- packages/quant_engine/sports/form.py +188 -0
- packages/quant_engine/sports/h2h.py +195 -0
- packages/quant_engine/sports/ml_model.py +354 -0
- packages/quant_engine/sports/predictor.py +311 -0
- packages/quant_engine/sports/tracker.py +664 -0
- packages/quant_engine/stochastic/__init__.py +27 -0
- packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
- packages/quant_engine/stochastic/ito_calculus.py +477 -0
- packages/quant_engine/stochastic/kelly_criterion.py +181 -0
- packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
- packages/quant_engine/stochastic/options_pricing.py +573 -0
- packages/quant_engine/stochastic/stochastic_processes.py +90 -0
- plan_utils.py +194 -0
- plugin_loader.py +328 -0
- portfolio_ledger.py +262 -0
- privacy/__init__.py +5 -0
- privacy/feedback.py +123 -0
- project_tools.py +525 -0
- providers/__init__.py +30 -0
- providers/llm/__init__.py +19 -0
- providers/llm/anthropic.py +184 -0
- providers/llm/base.py +139 -0
- providers/llm/ollama.py +128 -0
- providers/llm/openai_compat.py +282 -0
- providers/llm/registry.py +358 -0
- realty_data_tools.py +659 -0
- report_generator.py +1314 -0
- runtime/__init__.py +103 -0
- runtime/agent_loop.py +1183 -0
- runtime/approval.py +51 -0
- runtime/events.py +102 -0
- runtime/gateway.py +128 -0
- runtime/lsp.py +346 -0
- runtime/subagent.py +258 -0
- runtime/tool_executor.py +104 -0
- runtime/tool_policy.py +106 -0
- safety/__init__.py +21 -0
- safety/permissions.py +275 -0
- setup_wizard.py +653 -0
- strategy_vault.py +420 -0
- ui/__init__.py +100 -0
- ui/banner.py +310 -0
- ui/completer.py +391 -0
- ui/console.py +271 -0
- ui/image_render.py +243 -0
- ui/input_box.py +376 -0
- ui/picker.py +195 -0
- ui/render/__init__.py +11 -0
- ui/render/finance.py +1480 -0
- ui/render/market.py +225 -0
- ui/render/output.py +681 -0
- ui/render/team.py +346 -0
- ui/robot.py +235 -0
- workspace/__init__.py +6 -0
- workspace/files.py +170 -0
- workspace/verify.py +113 -0
agents/__init__.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
agents/ — Aria Code 可组合多智能体系统
|
|
3
|
+
=======================================
|
|
4
|
+
用户可在项目根放置 aria_agents/ 目录,其中的 Agent 类自动被发现。
|
|
5
|
+
|
|
6
|
+
内置 Agent:
|
|
7
|
+
macro → 宏观环境、利率、行业周期
|
|
8
|
+
fundamental → 财务指标、估值、竞争壁垒
|
|
9
|
+
technical → 图形形态、动量、关键价位
|
|
10
|
+
risk → 风险评分、仓位建议
|
|
11
|
+
synthesis → 汇总以上,输出可操作建议
|
|
12
|
+
|
|
13
|
+
自定义 Agent 示例 (aria_agents/northbound_agent.py):
|
|
14
|
+
from agents.base import BaseAgent, AgentResult
|
|
15
|
+
|
|
16
|
+
class NorthboundAgent(BaseAgent):
|
|
17
|
+
name = "northbound"
|
|
18
|
+
description = "北向资金分析专家"
|
|
19
|
+
|
|
20
|
+
async def analyze(self, symbol: str, data: dict) -> AgentResult:
|
|
21
|
+
...
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from .base import BaseAgent, AgentResult
|
|
25
|
+
from .registry import AgentRegistry, get_registry
|
|
26
|
+
from .team import AgentTeam, run_team
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"BaseAgent", "AgentResult",
|
|
30
|
+
"AgentRegistry", "get_registry",
|
|
31
|
+
"AgentTeam", "run_team",
|
|
32
|
+
]
|
agents/base.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""
|
|
2
|
+
agents/base.py — Agent 统一抽象基类
|
|
3
|
+
=====================================
|
|
4
|
+
所有 agent 继承 BaseAgent,实现 analyze() 方法。
|
|
5
|
+
LLM provider 和数据源从外部注入,agent 本身不关心底层实现。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class AgentResult:
|
|
20
|
+
"""Agent 分析结果,统一输出格式"""
|
|
21
|
+
agent: str # agent 名称
|
|
22
|
+
symbol: str # 分析标的
|
|
23
|
+
analysis: str # 核心分析文本
|
|
24
|
+
confidence: float # 置信度 0.0-1.0
|
|
25
|
+
signal: str = "HOLD" # BUY / HOLD / SELL / STRONG_BUY / STRONG_SELL
|
|
26
|
+
key_points: List[str] = field(default_factory=list) # 关键结论(用于 synthesis)
|
|
27
|
+
data_used: Dict[str, Any] = field(default_factory=dict) # 使用的原始数据
|
|
28
|
+
error: Optional[str] = None # 失败时的错误信息
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def success(self) -> bool:
|
|
32
|
+
return self.error is None
|
|
33
|
+
|
|
34
|
+
def to_dict(self) -> Dict:
|
|
35
|
+
return {
|
|
36
|
+
"agent": self.agent,
|
|
37
|
+
"symbol": self.symbol,
|
|
38
|
+
"analysis": self.analysis,
|
|
39
|
+
"confidence": self.confidence,
|
|
40
|
+
"signal": self.signal,
|
|
41
|
+
"key_points": self.key_points,
|
|
42
|
+
"error": self.error,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class BaseAgent(ABC):
|
|
47
|
+
"""
|
|
48
|
+
所有 Agent 的抽象基类。
|
|
49
|
+
|
|
50
|
+
子类必须声明:
|
|
51
|
+
name: str — 唯一标识(用于 /team --agents macro,fundamental)
|
|
52
|
+
description: str — 简短描述(显示在 /help 中)
|
|
53
|
+
|
|
54
|
+
子类必须实现:
|
|
55
|
+
analyze(symbol, data) → AgentResult
|
|
56
|
+
|
|
57
|
+
可选覆盖:
|
|
58
|
+
fetch_data(symbol) → dict — 自定义数据获取逻辑
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
name: str = "base"
|
|
62
|
+
description: str = "基础 Agent"
|
|
63
|
+
|
|
64
|
+
# Post-training IPO facts injected into every agent's system prompt
|
|
65
|
+
_POST_TRAINING_FACTS = (
|
|
66
|
+
"\n\n## ⚡ Critical post-training facts (override training memory)\n"
|
|
67
|
+
"- **SPCX** = SpaceX (Space Exploration Technologies Corp.) — IPO'd June 12, 2026 "
|
|
68
|
+
"on NASDAQ. It is NOT an ETF. ⛔ Never say 'SPCX is a China ETF' or 'SPCX is private'.\n"
|
|
69
|
+
"- **2026 FIFA World Cup** opened June 11, 2026 in USA/Canada/Mexico.\n"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Language rule injected per-call based on detected user language
|
|
73
|
+
_LANG_RULES = {
|
|
74
|
+
"zh": "\n\n## Language rule\nRespond in Chinese (中文). Technical terms (RSI, MACD, P/E, EPS) may stay in English.\n",
|
|
75
|
+
"en": "\n\n## Language rule\nRespond in English.\n",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
llm_provider=None, # BaseLLMProvider 实例(可选,None 则用模板生成)
|
|
81
|
+
data_router=None, # DataRouter 实例(可选)
|
|
82
|
+
on_token: Optional[Callable[[str], None]] = None, # 流式 token 回调
|
|
83
|
+
config: Optional[Dict] = None,
|
|
84
|
+
lang: str = "zh", # user language: "zh" | "en"
|
|
85
|
+
):
|
|
86
|
+
self.llm = llm_provider
|
|
87
|
+
self.data = data_router
|
|
88
|
+
self.on_token = on_token
|
|
89
|
+
self.config = config or {}
|
|
90
|
+
self.lang = lang
|
|
91
|
+
|
|
92
|
+
async def fetch_data(self, symbol: str) -> Dict[str, Any]:
|
|
93
|
+
"""
|
|
94
|
+
从数据路由器获取分析所需数据。
|
|
95
|
+
子类可覆盖此方法以自定义数据获取逻辑。
|
|
96
|
+
"""
|
|
97
|
+
if not self.data:
|
|
98
|
+
return {}
|
|
99
|
+
result = {}
|
|
100
|
+
try:
|
|
101
|
+
q = self.data.quote(symbol)
|
|
102
|
+
if q:
|
|
103
|
+
result["quote"] = q.to_dict()
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.debug(f"[{self.name}] fetch quote {symbol}: {e}")
|
|
106
|
+
return result
|
|
107
|
+
|
|
108
|
+
def _data_guard(self, quote: Dict[str, Any]) -> str:
|
|
109
|
+
"""Return a warning string if real data is unavailable; empty string if data is present."""
|
|
110
|
+
price = quote.get("price") if quote else None
|
|
111
|
+
if not price or float(price) == 0:
|
|
112
|
+
return (
|
|
113
|
+
"\n\n## ⛔ DATA UNAVAILABLE — STRICT RULES\n"
|
|
114
|
+
"Real market data could not be fetched (price=0 or missing).\n"
|
|
115
|
+
"You MUST:\n"
|
|
116
|
+
"1. State clearly that no real data is available.\n"
|
|
117
|
+
"2. NEVER invent specific prices, P/E ratios, EPS, revenue, RSI, MACD, or any numbers.\n"
|
|
118
|
+
"3. NEVER give specific price targets, stop-loss levels, or entry prices.\n"
|
|
119
|
+
"4. Give only qualitative analysis based on publicly known company characteristics.\n"
|
|
120
|
+
"5. End with the signal word (BUY/HOLD/SELL) but with low confidence (≤40%).\n"
|
|
121
|
+
)
|
|
122
|
+
return ""
|
|
123
|
+
|
|
124
|
+
async def _call_llm(
|
|
125
|
+
self,
|
|
126
|
+
system: str,
|
|
127
|
+
user: str,
|
|
128
|
+
max_tokens: int = 800,
|
|
129
|
+
quote: Optional[Dict[str, Any]] = None,
|
|
130
|
+
) -> str:
|
|
131
|
+
"""调用 LLM 生成分析文本(无 LLM 时返回空字符串)"""
|
|
132
|
+
if not self.llm:
|
|
133
|
+
return ""
|
|
134
|
+
# Inject language rule + post-training facts + data guard into system prompt
|
|
135
|
+
_lang_rule = self._LANG_RULES.get(self.lang, self._LANG_RULES["zh"])
|
|
136
|
+
_data_warn = self._data_guard(quote or {})
|
|
137
|
+
system = system + self._POST_TRAINING_FACTS + _lang_rule + _data_warn
|
|
138
|
+
from providers.llm.base import Message
|
|
139
|
+
messages = [
|
|
140
|
+
Message(role="system", content=system),
|
|
141
|
+
Message(role="user", content=user),
|
|
142
|
+
]
|
|
143
|
+
full_text = ""
|
|
144
|
+
try:
|
|
145
|
+
async for event in self.llm.stream(
|
|
146
|
+
messages, max_tokens=max_tokens
|
|
147
|
+
):
|
|
148
|
+
t = event.get("type")
|
|
149
|
+
if t == "token":
|
|
150
|
+
tok = event.get("text", "")
|
|
151
|
+
full_text += tok
|
|
152
|
+
if self.on_token:
|
|
153
|
+
self.on_token(tok)
|
|
154
|
+
elif t == "error":
|
|
155
|
+
logger.warning(f"[{self.name}] LLM 错误: {event.get('message')}")
|
|
156
|
+
break
|
|
157
|
+
except Exception as e:
|
|
158
|
+
logger.warning(f"[{self.name}] LLM 调用失败: {e}")
|
|
159
|
+
return full_text.strip()
|
|
160
|
+
|
|
161
|
+
@abstractmethod
|
|
162
|
+
async def analyze(self, symbol: str, data: Dict[str, Any]) -> AgentResult:
|
|
163
|
+
"""
|
|
164
|
+
核心分析方法。
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
symbol: 股票/资产代码
|
|
168
|
+
data: 由 fetch_data() 预取的数据字典
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
AgentResult
|
|
172
|
+
"""
|
|
173
|
+
...
|
|
174
|
+
|
|
175
|
+
async def run(self, symbol: str) -> AgentResult:
|
|
176
|
+
"""完整执行:fetch_data → analyze,异常自动捕获。"""
|
|
177
|
+
try:
|
|
178
|
+
data = await self.fetch_data(symbol)
|
|
179
|
+
result = await self.analyze(symbol, data)
|
|
180
|
+
return result
|
|
181
|
+
except Exception as e:
|
|
182
|
+
logger.error(f"[{self.name}] run({symbol}) 失败: {e}", exc_info=True)
|
|
183
|
+
return AgentResult(
|
|
184
|
+
agent=self.name, symbol=symbol,
|
|
185
|
+
analysis="", confidence=0.0,
|
|
186
|
+
error=str(e),
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def __repr__(self) -> str:
|
|
190
|
+
return f"{self.__class__.__name__}(name={self.name!r})"
|
agents/deep/__init__.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Deep analysis pipeline — Claude-Code-style layered research on top of AgentTeam.
|
|
2
|
+
|
|
3
|
+
Adds the four "deep" layers the flat team pipeline was missing:
|
|
4
|
+
|
|
5
|
+
P0 deepen — tool-augmented evidence gathering for material/uncertain findings
|
|
6
|
+
P1 hierarchical + critic — theme-grouped sub-synthesis, then a self-check pass
|
|
7
|
+
P2 quant fusion — fuse ML/backtest/risk signals as ground truth + calibrate confidence
|
|
8
|
+
P3 tiered output + provenance — brief / standard / deep reports with data lineage
|
|
9
|
+
|
|
10
|
+
Everything degrades gracefully: any layer whose dependency (LLM, quant tools, data
|
|
11
|
+
cleaner) is unavailable is skipped, never raised. The deterministic parts (theme
|
|
12
|
+
grouping, calibration math, critic rules, tier rendering) run with no LLM/network,
|
|
13
|
+
which keeps the pipeline testable.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from .models import (
|
|
19
|
+
Critique,
|
|
20
|
+
CritiqueIssue,
|
|
21
|
+
DeepAnalysisResult,
|
|
22
|
+
Provenance,
|
|
23
|
+
QuantEvidence,
|
|
24
|
+
ThemeGroup,
|
|
25
|
+
)
|
|
26
|
+
from .pipeline import DeepAnalysisPipeline, run_deep_analysis
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"Critique",
|
|
30
|
+
"CritiqueIssue",
|
|
31
|
+
"DeepAnalysisResult",
|
|
32
|
+
"Provenance",
|
|
33
|
+
"QuantEvidence",
|
|
34
|
+
"ThemeGroup",
|
|
35
|
+
"DeepAnalysisPipeline",
|
|
36
|
+
"run_deep_analysis",
|
|
37
|
+
]
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""P2 closed loop — log predictions, later score them against realised price.
|
|
2
|
+
|
|
3
|
+
Calibration only improves if confidence is checked against what actually happened.
|
|
4
|
+
This module:
|
|
5
|
+
|
|
6
|
+
1. logs every deep verdict (symbol, signal, confidence, reference price, time),
|
|
7
|
+
2. once a prediction's horizon has elapsed, fetches the realised return and
|
|
8
|
+
marks it correct/incorrect,
|
|
9
|
+
3. feeds the outcome into CalibrationStore so the reliability factor — and thus
|
|
10
|
+
future calibrated confidence — drifts toward the true hit-rate.
|
|
11
|
+
|
|
12
|
+
Everything is injectable (price function, clock) so it tests without network.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import time
|
|
20
|
+
import uuid
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Callable, Dict, List, Optional
|
|
23
|
+
|
|
24
|
+
from .quant_fusion import CalibrationStore
|
|
25
|
+
|
|
26
|
+
_BULL = ("STRONG_BUY", "BUY")
|
|
27
|
+
_BEAR = ("STRONG_SELL", "SELL")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def correctness(signal: str, ret: float, threshold: float = 0.02) -> bool:
|
|
31
|
+
"""Was the call right given the realised return?"""
|
|
32
|
+
if signal in _BULL:
|
|
33
|
+
return ret > threshold
|
|
34
|
+
if signal in _BEAR:
|
|
35
|
+
return ret < -threshold
|
|
36
|
+
return abs(ret) <= threshold # HOLD: right when it stayed flat
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class PredictionLog:
|
|
40
|
+
"""Append-only log of deep verdicts, JSON-list backed (low volume)."""
|
|
41
|
+
|
|
42
|
+
def __init__(self, path: Optional[Path] = None):
|
|
43
|
+
self.path = Path(path or os.path.expanduser("~/.arthera/deep_predictions.json"))
|
|
44
|
+
self._items: List[Dict] = []
|
|
45
|
+
try:
|
|
46
|
+
if self.path.exists():
|
|
47
|
+
self._items = json.loads(self.path.read_text())
|
|
48
|
+
except Exception:
|
|
49
|
+
self._items = []
|
|
50
|
+
|
|
51
|
+
def _save(self) -> None:
|
|
52
|
+
try:
|
|
53
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
self.path.write_text(json.dumps(self._items, ensure_ascii=False, indent=2))
|
|
55
|
+
except Exception:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
def log(self, symbol: str, signal: str, confidence: float,
|
|
59
|
+
ref_price: float, ts: Optional[float] = None) -> str:
|
|
60
|
+
pid = uuid.uuid4().hex[:12]
|
|
61
|
+
self._items.append({
|
|
62
|
+
"id": pid, "symbol": symbol, "signal": signal,
|
|
63
|
+
"confidence": float(confidence), "ref_price": float(ref_price),
|
|
64
|
+
"ts": ts if ts is not None else time.time(), "evaluated": False,
|
|
65
|
+
})
|
|
66
|
+
self._save()
|
|
67
|
+
return pid
|
|
68
|
+
|
|
69
|
+
def pending(self, horizon_days: float, now: Optional[float] = None) -> List[Dict]:
|
|
70
|
+
now = now if now is not None else time.time()
|
|
71
|
+
cutoff = now - horizon_days * 86400
|
|
72
|
+
return [p for p in self._items if not p.get("evaluated") and p["ts"] <= cutoff]
|
|
73
|
+
|
|
74
|
+
def mark_evaluated(self, pid: str, ret: float, correct: bool,
|
|
75
|
+
source: str = "price") -> None:
|
|
76
|
+
for p in self._items:
|
|
77
|
+
if p["id"] == pid:
|
|
78
|
+
p["evaluated"] = True
|
|
79
|
+
p["realised_return"] = ret
|
|
80
|
+
p["correct"] = correct
|
|
81
|
+
p["source"] = source
|
|
82
|
+
break
|
|
83
|
+
self._save()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def evaluate_due(
|
|
87
|
+
store: CalibrationStore,
|
|
88
|
+
log: PredictionLog,
|
|
89
|
+
price_fn: Callable[[str], Optional[float]],
|
|
90
|
+
horizon_days: float = 5.0,
|
|
91
|
+
threshold: float = 0.02,
|
|
92
|
+
now: Optional[float] = None,
|
|
93
|
+
) -> Dict[str, int]:
|
|
94
|
+
"""Score every prediction past its horizon and update calibration. Returns counts."""
|
|
95
|
+
evaluated = hits = 0
|
|
96
|
+
for p in log.pending(horizon_days, now=now):
|
|
97
|
+
try:
|
|
98
|
+
cur = price_fn(p["symbol"])
|
|
99
|
+
except Exception:
|
|
100
|
+
cur = None
|
|
101
|
+
if not cur or not p.get("ref_price"):
|
|
102
|
+
continue
|
|
103
|
+
ret = (cur - p["ref_price"]) / p["ref_price"]
|
|
104
|
+
ok = correctness(p["signal"], ret, threshold)
|
|
105
|
+
store.record_outcome(p["signal"], p["confidence"], ok)
|
|
106
|
+
log.mark_evaluated(p["id"], round(ret, 5), ok)
|
|
107
|
+
evaluated += 1
|
|
108
|
+
hits += 1 if ok else 0
|
|
109
|
+
return {"evaluated": evaluated, "hits": hits,
|
|
110
|
+
"hit_rate": round(hits / evaluated, 3) if evaluated else 0.0}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def evaluate_from_ledger(
|
|
114
|
+
store: CalibrationStore,
|
|
115
|
+
log: PredictionLog,
|
|
116
|
+
ledger,
|
|
117
|
+
) -> Dict[str, int]:
|
|
118
|
+
"""Score predictions against the portfolio ledger's REALISED P&L (actual closed
|
|
119
|
+
trades) — a stronger ground truth than market price for symbols you traded.
|
|
120
|
+
|
|
121
|
+
BUY is right when the symbol's realised P&L is positive, SELL when negative.
|
|
122
|
+
HOLD is skipped (no clean threshold without a cost basis). ``ledger`` is any
|
|
123
|
+
object exposing ``get_realized_pnl() -> [{symbol, realized_pnl, ...}]``.
|
|
124
|
+
"""
|
|
125
|
+
try:
|
|
126
|
+
realized = ledger.get_realized_pnl()
|
|
127
|
+
except Exception:
|
|
128
|
+
return {"evaluated": 0, "hits": 0, "hit_rate": 0.0}
|
|
129
|
+
pnl_map = {str(r.get("symbol", "")).upper(): r.get("realized_pnl", 0.0)
|
|
130
|
+
for r in (realized or []) if r.get("realized_pnl")}
|
|
131
|
+
|
|
132
|
+
evaluated = hits = 0
|
|
133
|
+
for p in log.pending(0.0): # any un-evaluated prediction
|
|
134
|
+
sym = str(p["symbol"]).upper()
|
|
135
|
+
if sym not in pnl_map or p["signal"] not in (_BULL + _BEAR):
|
|
136
|
+
continue
|
|
137
|
+
pnl = pnl_map[sym]
|
|
138
|
+
ok = (pnl > 0) if p["signal"] in _BULL else (pnl < 0)
|
|
139
|
+
store.record_outcome(p["signal"], p["confidence"], ok)
|
|
140
|
+
log.mark_evaluated(p["id"], round(pnl, 2), ok, source="ledger")
|
|
141
|
+
evaluated += 1
|
|
142
|
+
hits += 1 if ok else 0
|
|
143
|
+
return {"evaluated": evaluated, "hits": hits,
|
|
144
|
+
"hit_rate": round(hits / evaluated, 3) if evaluated else 0.0}
|
agents/deep/critic.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""P1b — a self-check pass over the assembled analysis.
|
|
2
|
+
|
|
3
|
+
Claude Code verifies its own work; the flat pipeline never did. The critic applies
|
|
4
|
+
deterministic rules (no LLM, fully testable) to flag the failure modes that quietly
|
|
5
|
+
ruin a research note: thin agent coverage, no risk angle, a strong call on weak
|
|
6
|
+
confidence, or the quant signal contradicting the qualitative verdict. An optional
|
|
7
|
+
LLM pass can add free-text findings on top.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import List, Optional
|
|
13
|
+
|
|
14
|
+
from ..base import AgentResult
|
|
15
|
+
from .models import Critique, CritiqueIssue, Provenance, QuantEvidence
|
|
16
|
+
from .themes import theme_of
|
|
17
|
+
|
|
18
|
+
_STRONG = ("STRONG_BUY", "STRONG_SELL")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def critique(
|
|
22
|
+
agent_results: List[AgentResult],
|
|
23
|
+
final_signal: str,
|
|
24
|
+
calibrated_confidence: float,
|
|
25
|
+
quant: Optional[QuantEvidence] = None,
|
|
26
|
+
agreement: str = "neutral",
|
|
27
|
+
provenance: Optional[List[Provenance]] = None,
|
|
28
|
+
key_point_count: int = 0,
|
|
29
|
+
) -> Critique:
|
|
30
|
+
"""Deterministic self-check. Returns a Critique; ``passed`` is False on any
|
|
31
|
+
high-severity issue so the caller can soften an over-confident conclusion."""
|
|
32
|
+
issues: List[CritiqueIssue] = []
|
|
33
|
+
|
|
34
|
+
total = len(agent_results)
|
|
35
|
+
ok = sum(1 for r in agent_results if r.success)
|
|
36
|
+
|
|
37
|
+
# 1) coverage
|
|
38
|
+
if ok < 2:
|
|
39
|
+
issues.append(CritiqueIssue("high", "thin_coverage",
|
|
40
|
+
f"只有 {ok} 个 agent 成功,结论证据不足,不应作为决策依据。"))
|
|
41
|
+
elif total and ok / total < 0.5:
|
|
42
|
+
issues.append(CritiqueIssue("medium", "thin_coverage",
|
|
43
|
+
f"{total - ok}/{total} 个 agent 失败,覆盖偏薄。"))
|
|
44
|
+
|
|
45
|
+
# 2) risk angle present?
|
|
46
|
+
has_risk = any(r.success and theme_of(r.agent) == "risk" for r in agent_results)
|
|
47
|
+
if not has_risk:
|
|
48
|
+
issues.append(CritiqueIssue("medium", "missing_risk",
|
|
49
|
+
"缺少有效的风险维度分析,下行风险可能被低估。"))
|
|
50
|
+
|
|
51
|
+
# 3) quant contradicts qualitative verdict
|
|
52
|
+
if quant and quant.available and agreement == "disagree":
|
|
53
|
+
issues.append(CritiqueIssue("high", "conflict",
|
|
54
|
+
f"量化信号({quant.verdict()})与定性结论({final_signal})相反,置信度已下调;建议人工复核。"))
|
|
55
|
+
|
|
56
|
+
# 4) strong call on weak confidence
|
|
57
|
+
if final_signal in _STRONG and calibrated_confidence < 0.5:
|
|
58
|
+
issues.append(CritiqueIssue("medium", "unsupported",
|
|
59
|
+
f"给出强信号 {final_signal} 但校准后置信度仅 {calibrated_confidence:.0%},"
|
|
60
|
+
"强度与把握不匹配。"))
|
|
61
|
+
|
|
62
|
+
# 5) strong call with little supporting evidence
|
|
63
|
+
if final_signal in _STRONG and key_point_count < 3:
|
|
64
|
+
issues.append(CritiqueIssue("medium", "unsupported",
|
|
65
|
+
"强信号但关键论据少于 3 条,论证偏薄。"))
|
|
66
|
+
|
|
67
|
+
# 6) stale data
|
|
68
|
+
for p in (provenance or []):
|
|
69
|
+
if p.freshness.endswith("d old"):
|
|
70
|
+
issues.append(CritiqueIssue("low", "stale_data",
|
|
71
|
+
f"{p.field} 数据为 {p.freshness}(来源 {p.source}),注意时效。"))
|
|
72
|
+
|
|
73
|
+
passed = not any(i.severity == "high" for i in issues)
|
|
74
|
+
return Critique(issues=issues, passed=passed)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def soften_signal(final_signal: str) -> str:
|
|
78
|
+
"""Step a verdict one notch toward HOLD (used when the critic fails)."""
|
|
79
|
+
ladder = {"STRONG_BUY": "BUY", "BUY": "HOLD", "HOLD": "HOLD",
|
|
80
|
+
"SELL": "HOLD", "STRONG_SELL": "SELL"}
|
|
81
|
+
return ladder.get(final_signal, "HOLD")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
_SEV = {"高": "high", "中": "medium", "低": "low",
|
|
85
|
+
"high": "high", "medium": "medium", "low": "low"}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def parse_llm_issues(text: str, max_issues: int = 3) -> List[CritiqueIssue]:
|
|
89
|
+
"""Parse the LLM reviewer's reply (``高|问题`` per line) into issues. Pure/testable."""
|
|
90
|
+
import re
|
|
91
|
+
issues: List[CritiqueIssue] = []
|
|
92
|
+
for line in (text or "").splitlines():
|
|
93
|
+
line = line.strip().lstrip("-•*0123456789. ").strip()
|
|
94
|
+
if not line or line.upper().startswith("OK") or line in ("无", "无问题"):
|
|
95
|
+
continue
|
|
96
|
+
parts = re.split(r"[||::]", line, maxsplit=1)
|
|
97
|
+
if len(parts) == 2 and _SEV.get(parts[0].strip().lower()):
|
|
98
|
+
sev, msg = _SEV[parts[0].strip().lower()], parts[1].strip()
|
|
99
|
+
else:
|
|
100
|
+
sev, msg = "medium", line
|
|
101
|
+
if msg:
|
|
102
|
+
issues.append(CritiqueIssue(sev, "llm_review", msg))
|
|
103
|
+
if len(issues) >= max_issues:
|
|
104
|
+
break
|
|
105
|
+
return issues
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
async def llm_critique(
|
|
109
|
+
symbol: str,
|
|
110
|
+
synthesis: str,
|
|
111
|
+
theme_summaries: str,
|
|
112
|
+
llm,
|
|
113
|
+
max_issues: int = 3,
|
|
114
|
+
) -> List[CritiqueIssue]:
|
|
115
|
+
"""Optional LLM reviewer — flags unsupported claims / missing risk / contradiction
|
|
116
|
+
/ overconfidence in the synthesis. Returns extra issues (empty if no LLM)."""
|
|
117
|
+
if llm is None or not (synthesis or "").strip():
|
|
118
|
+
return []
|
|
119
|
+
from .deepen import _collect_llm
|
|
120
|
+
system = ("你是严格的研究审稿人。只挑【确凿的】问题:无数据支撑的论断、漏掉的下行风险、"
|
|
121
|
+
"自相矛盾、过度自信。每行一个,格式 `高|问题` / `中|问题` / `低|问题`,"
|
|
122
|
+
f"最多 {max_issues} 条。没有问题就只输出 OK。不要多余的话。")
|
|
123
|
+
user = f"标的: {symbol}\n各维度小结: {theme_summaries}\n\n综合结论:\n{synthesis}\n\n审查:"
|
|
124
|
+
resp = await _collect_llm(llm, system, user, max_tokens=300)
|
|
125
|
+
return parse_llm_issues(resp, max_issues)
|