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/registry.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
agents/registry.py — Agent 注册与自动发现
|
|
3
|
+
==========================================
|
|
4
|
+
1. 内置 agent 预注册(technical / fundamental / macro / risk)
|
|
5
|
+
2. 扫描 ./aria_agents/ → 自动注册用户自定义 agent
|
|
6
|
+
3. 提供 get() / list() / register() 接口
|
|
7
|
+
|
|
8
|
+
自定义 Agent 放置规则:
|
|
9
|
+
项目根/aria_agents/my_agent.py
|
|
10
|
+
类需继承 BaseAgent,且声明 name 属性
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import importlib.util
|
|
16
|
+
import inspect
|
|
17
|
+
import logging
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Dict, List, Optional, Type
|
|
21
|
+
|
|
22
|
+
from .base import BaseAgent
|
|
23
|
+
from .financial.technical import TechnicalAgent
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
# ── 内置 agent 预注册 ─────────────────────────────────────────────────────────
|
|
28
|
+
_BUILTIN: Dict[str, Type[BaseAgent]] = {
|
|
29
|
+
"technical": TechnicalAgent,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# 尝试加载其他内置 agent(文件存在则注册)
|
|
33
|
+
def _try_import_builtin(module_path: str, name: str) -> None:
|
|
34
|
+
try:
|
|
35
|
+
parts = module_path.split(".")
|
|
36
|
+
mod = importlib.import_module(module_path)
|
|
37
|
+
for _, cls in inspect.getmembers(mod, inspect.isclass):
|
|
38
|
+
if issubclass(cls, BaseAgent) and cls is not BaseAgent:
|
|
39
|
+
_BUILTIN[cls.name] = cls
|
|
40
|
+
except ImportError:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
_try_import_builtin("agents.financial.macro", "macro")
|
|
45
|
+
_try_import_builtin("agents.financial.fundamental", "fundamental")
|
|
46
|
+
_try_import_builtin("agents.financial.risk", "risk")
|
|
47
|
+
_try_import_builtin("agents.financial.synthesis", "synthesis")
|
|
48
|
+
_try_import_builtin("agents.financial.news", "news")
|
|
49
|
+
_try_import_builtin("agents.financial.catalyst", "catalyst")
|
|
50
|
+
_try_import_builtin("agents.financial.debate", "debate")
|
|
51
|
+
_try_import_builtin("agents.financial.earnings", "earnings")
|
|
52
|
+
_try_import_builtin("agents.financial.sector", "sector")
|
|
53
|
+
_try_import_builtin("agents.portfolio_agent", "portfolio")
|
|
54
|
+
|
|
55
|
+
# ── 经营权共创平台 Agent 包 ────────────────────────────────────────────────────
|
|
56
|
+
_try_import_builtin("agents.realty.asset_diagnosis", "asset_diagnosis")
|
|
57
|
+
_try_import_builtin("agents.realty.business_match", "business_match")
|
|
58
|
+
_try_import_builtin("agents.realty.contract_rules", "contract_rules")
|
|
59
|
+
_try_import_builtin("agents.realty.revenue_share", "revenue_share")
|
|
60
|
+
_try_import_builtin("agents.realty.cashflow_verify", "cashflow_verify")
|
|
61
|
+
_try_import_builtin("agents.realty.energy_anomaly", "energy_anomaly")
|
|
62
|
+
_try_import_builtin("agents.realty.fulfillment_risk", "fulfillment_risk")
|
|
63
|
+
_try_import_builtin("agents.realty.ops_optimize", "ops_optimize")
|
|
64
|
+
_try_import_builtin("agents.realty.exit_settlement", "exit_settlement")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class AgentRegistry:
|
|
68
|
+
"""
|
|
69
|
+
Agent 注册中心(单例)。
|
|
70
|
+
|
|
71
|
+
用法:
|
|
72
|
+
registry = get_registry()
|
|
73
|
+
registry.register(MyAgent)
|
|
74
|
+
agent_cls = registry.get("my_agent")
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(self):
|
|
78
|
+
self._agents: Dict[str, Type[BaseAgent]] = dict(_BUILTIN)
|
|
79
|
+
self._scanned = False
|
|
80
|
+
|
|
81
|
+
def register(self, cls: Type[BaseAgent]) -> None:
|
|
82
|
+
"""手动注册一个 Agent 类"""
|
|
83
|
+
if not (isinstance(cls, type) and issubclass(cls, BaseAgent)):
|
|
84
|
+
raise TypeError(f"{cls} 不是 BaseAgent 子类")
|
|
85
|
+
self._agents[cls.name] = cls
|
|
86
|
+
logger.info(f"✓ 注册 Agent: {cls.name}")
|
|
87
|
+
|
|
88
|
+
def get(self, name: str) -> Optional[Type[BaseAgent]]:
|
|
89
|
+
"""按名称获取 Agent 类"""
|
|
90
|
+
self._ensure_scanned()
|
|
91
|
+
return self._agents.get(name.lower())
|
|
92
|
+
|
|
93
|
+
def list(self) -> List[Dict]:
|
|
94
|
+
"""列出所有已注册 Agent"""
|
|
95
|
+
self._ensure_scanned()
|
|
96
|
+
return [
|
|
97
|
+
{"name": name, "description": cls.description, "builtin": name in _BUILTIN}
|
|
98
|
+
for name, cls in self._agents.items()
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
def _ensure_scanned(self) -> None:
|
|
102
|
+
if not self._scanned:
|
|
103
|
+
self._scan_project()
|
|
104
|
+
self._scanned = True
|
|
105
|
+
|
|
106
|
+
def _scan_project(self) -> None:
|
|
107
|
+
"""扫描当前工作目录及父级的 aria_agents/ 目录"""
|
|
108
|
+
search_paths = [Path.cwd(), Path.cwd().parent]
|
|
109
|
+
for base in search_paths:
|
|
110
|
+
agents_dir = base / "aria_agents"
|
|
111
|
+
if not agents_dir.is_dir():
|
|
112
|
+
continue
|
|
113
|
+
for py_file in sorted(agents_dir.glob("*.py")):
|
|
114
|
+
if py_file.name.startswith("_"):
|
|
115
|
+
continue
|
|
116
|
+
self._load_file(py_file)
|
|
117
|
+
break # 找到第一个 aria_agents/ 即停止向上搜索
|
|
118
|
+
|
|
119
|
+
def _load_file(self, path: Path) -> None:
|
|
120
|
+
"""加载一个 .py 文件,自动注册其中的 BaseAgent 子类"""
|
|
121
|
+
try:
|
|
122
|
+
spec = importlib.util.spec_from_file_location(path.stem, path)
|
|
123
|
+
module = importlib.util.module_from_spec(spec)
|
|
124
|
+
spec.loader.exec_module(module)
|
|
125
|
+
for _, cls in inspect.getmembers(module, inspect.isclass):
|
|
126
|
+
if (cls.__module__ == module.__name__
|
|
127
|
+
and issubclass(cls, BaseAgent)
|
|
128
|
+
and cls is not BaseAgent
|
|
129
|
+
and hasattr(cls, "name")
|
|
130
|
+
and cls.name != "base"):
|
|
131
|
+
self._agents[cls.name] = cls
|
|
132
|
+
logger.info(f"✓ 自动发现 Agent: {cls.name} ({path.name})")
|
|
133
|
+
except Exception as e:
|
|
134
|
+
logger.warning(f"加载 agent 文件 {path} 失败: {e}")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# ── 单例 ──────────────────────────────────────────────────────────────────────
|
|
138
|
+
_registry: Optional[AgentRegistry] = None
|
|
139
|
+
|
|
140
|
+
def get_registry() -> AgentRegistry:
|
|
141
|
+
global _registry
|
|
142
|
+
if _registry is None:
|
|
143
|
+
_registry = AgentRegistry()
|
|
144
|
+
return _registry
|
|
File without changes
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""
|
|
2
|
+
agents/sports/football_agent.py — Football Analysis Agent
|
|
3
|
+
===========================================================
|
|
4
|
+
LLM-powered football match analysis using Poisson prediction + form data.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Any, Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class MatchPrediction:
|
|
18
|
+
home_team: str
|
|
19
|
+
away_team: str
|
|
20
|
+
league: str
|
|
21
|
+
home_win: float
|
|
22
|
+
draw: float
|
|
23
|
+
away_win: float
|
|
24
|
+
btts: float
|
|
25
|
+
lambda_home: float
|
|
26
|
+
lambda_away: float
|
|
27
|
+
most_likely: str
|
|
28
|
+
top_scores: List[Dict]
|
|
29
|
+
implied_odds: Dict[str, float]
|
|
30
|
+
analysis: str = ""
|
|
31
|
+
key_factors: List[str] = field(default_factory=list)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def verdict(self) -> str:
|
|
35
|
+
probs = {"home": self.home_win, "draw": self.draw, "away": self.away_win}
|
|
36
|
+
winner = max(probs, key=probs.get)
|
|
37
|
+
dominant = probs[winner] > 0.50
|
|
38
|
+
if winner == "home":
|
|
39
|
+
return f"主队{'强势' if dominant else '略有'}优势 ({self.home_win:.0%})"
|
|
40
|
+
elif winner == "away":
|
|
41
|
+
return f"客队{'强势' if dominant else '略有'}优势 ({self.away_win:.0%})"
|
|
42
|
+
else:
|
|
43
|
+
return f"势均力敌,平局概率较高 ({self.draw:.0%})"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class FootballAgent:
|
|
47
|
+
"""
|
|
48
|
+
Football analysis combining Poisson prediction + LLM interpretation.
|
|
49
|
+
Can run standalone (no LLM) or enhanced with LLM narrative.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
name = "football"
|
|
53
|
+
description = "足球赛事分析与预测 — 泊松模型 + 近期战绩 + xG数据"
|
|
54
|
+
|
|
55
|
+
def __init__(self, llm_call=None):
|
|
56
|
+
self._llm = llm_call # Optional: async fn(prompt) -> str
|
|
57
|
+
|
|
58
|
+
async def predict(
|
|
59
|
+
self,
|
|
60
|
+
home_team: str,
|
|
61
|
+
away_team: str,
|
|
62
|
+
league: str = "pl",
|
|
63
|
+
with_llm: bool = True,
|
|
64
|
+
) -> MatchPrediction:
|
|
65
|
+
from football_data_client import predict_match, get_team_stats
|
|
66
|
+
|
|
67
|
+
raw = predict_match(home_team, away_team, league)
|
|
68
|
+
h_stats = get_team_stats(league, home_team)
|
|
69
|
+
a_stats = get_team_stats(league, away_team)
|
|
70
|
+
|
|
71
|
+
key_factors = []
|
|
72
|
+
if h_stats:
|
|
73
|
+
key_factors.append(f"{home_team} 近5场: {h_stats['form']} (场均进球 {h_stats['avg_gf']})")
|
|
74
|
+
if a_stats:
|
|
75
|
+
key_factors.append(f"{away_team} 近5场: {a_stats['form']} (场均进球 {a_stats['avg_gf']})")
|
|
76
|
+
key_factors.append(f"主场优势系数: ×1.25 (泊松模型)")
|
|
77
|
+
|
|
78
|
+
analysis = ""
|
|
79
|
+
if with_llm and self._llm:
|
|
80
|
+
prompt = _build_analysis_prompt(raw, h_stats, a_stats)
|
|
81
|
+
try:
|
|
82
|
+
analysis = await self._llm(prompt)
|
|
83
|
+
except Exception as exc:
|
|
84
|
+
logger.warning("LLM analysis failed: %s", exc)
|
|
85
|
+
analysis = _fallback_analysis(raw)
|
|
86
|
+
else:
|
|
87
|
+
analysis = _fallback_analysis(raw)
|
|
88
|
+
|
|
89
|
+
return MatchPrediction(
|
|
90
|
+
home_team=home_team,
|
|
91
|
+
away_team=away_team,
|
|
92
|
+
league=league,
|
|
93
|
+
home_win=raw["home_win"],
|
|
94
|
+
draw=raw["draw"],
|
|
95
|
+
away_win=raw["away_win"],
|
|
96
|
+
btts=raw["btts"],
|
|
97
|
+
lambda_home=raw["lambda_home"],
|
|
98
|
+
lambda_away=raw["lambda_away"],
|
|
99
|
+
most_likely=raw["most_likely_score"],
|
|
100
|
+
top_scores=raw["top_scorelines"],
|
|
101
|
+
implied_odds=raw["implied_odds"],
|
|
102
|
+
analysis=analysis,
|
|
103
|
+
key_factors=key_factors,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _fallback_analysis(raw: Dict) -> str:
|
|
108
|
+
hw, d, aw = raw["home_win"], raw["draw"], raw["away_win"]
|
|
109
|
+
lh, la = raw["lambda_home"], raw["lambda_away"]
|
|
110
|
+
ht, at = raw["home_team"], raw["away_team"]
|
|
111
|
+
|
|
112
|
+
lines = []
|
|
113
|
+
if hw > aw + 0.15:
|
|
114
|
+
lines.append(f"泊松模型显示 **{ht}** 主场优势明显(胜率 {hw:.0%})")
|
|
115
|
+
elif aw > hw + 0.15:
|
|
116
|
+
lines.append(f"泊松模型显示 **{at}** 客场表现更强(胜率 {aw:.0%})")
|
|
117
|
+
else:
|
|
118
|
+
lines.append(f"双方实力相当,平局可能性较大({d:.0%})")
|
|
119
|
+
|
|
120
|
+
lines.append(f"预期进球:{ht} {lh:.1f} / {at} {la:.1f}")
|
|
121
|
+
top = raw["top_scorelines"][:3]
|
|
122
|
+
score_str = " | ".join(f"{s['score']}({s['prob']}%)" for s in top)
|
|
123
|
+
lines.append(f"高概率比分:{score_str}")
|
|
124
|
+
btts = raw["btts"]
|
|
125
|
+
lines.append(f"双方均进球概率:{btts:.0%}")
|
|
126
|
+
|
|
127
|
+
return "\n".join(lines)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _build_analysis_prompt(raw: Dict, h_stats: Optional[Dict], a_stats: Optional[Dict]) -> str:
|
|
131
|
+
top_scores = "、".join(
|
|
132
|
+
f"{s['score']}({s['prob']}%)"
|
|
133
|
+
for s in raw.get("top_scorelines", [])[:5]
|
|
134
|
+
)
|
|
135
|
+
return f"""你是一位专业足球分析师。根据以下泊松预测模型数据,用中文分析这场比赛:
|
|
136
|
+
|
|
137
|
+
【比赛】{raw['home_team']} vs {raw['away_team']}
|
|
138
|
+
|
|
139
|
+
【预测概率】
|
|
140
|
+
主队胜: {raw['home_win']:.1%} 平局: {raw['draw']:.1%} 客队胜: {raw['away_win']:.1%}
|
|
141
|
+
预期进球: 主队 {raw['lambda_home']:.2f} / 客队 {raw['lambda_away']:.2f}
|
|
142
|
+
最可能比分: {raw['most_likely_score']}
|
|
143
|
+
候选比分(按模型概率降序): {top_scores}
|
|
144
|
+
双方均进球: {raw['btts']:.1%}
|
|
145
|
+
|
|
146
|
+
【近期战绩】
|
|
147
|
+
{_format_stats(raw['home_team'], h_stats)}
|
|
148
|
+
{_format_stats(raw['away_team'], a_stats)}
|
|
149
|
+
|
|
150
|
+
请提供:
|
|
151
|
+
1. 比赛走势分析(3-4句)
|
|
152
|
+
2. 关键影响因素(2-3条)
|
|
153
|
+
3. 预测建议(1句话结论)
|
|
154
|
+
|
|
155
|
+
规则:
|
|
156
|
+
- 必须按“候选比分”概率顺序讨论比分,不要把低概率比分排到第一。
|
|
157
|
+
- 不要编造射正率、历史交锋、最近5场客场等输入数据之外的具体数字。
|
|
158
|
+
- 如果给出多个准确比分,直接引用候选比分及概率。
|
|
159
|
+
|
|
160
|
+
简洁专业,不超过200字。"""
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _format_stats(team: str, stats: Optional[Dict]) -> str:
|
|
164
|
+
if not stats:
|
|
165
|
+
return f"{team}: 数据不足"
|
|
166
|
+
return (
|
|
167
|
+
f"{team}: 近{stats['last_n']}场 {stats['w']}胜{stats['d']}平{stats['l']}负, "
|
|
168
|
+
f"进{stats['gf']}球失{stats['ga']}球, 近5场战绩: {stats['form']}"
|
|
169
|
+
)
|
agents/team.py
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"""
|
|
2
|
+
agents/team.py — 多 Agent 并行执行与结果汇总
|
|
3
|
+
=============================================
|
|
4
|
+
/team AAPL → 运行默认4个内置 agent
|
|
5
|
+
/team AAPL --agents macro,technical → 只运行指定 agent
|
|
6
|
+
/team AAPL --agents macro,my_agent → 内置 + 自定义混合
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import logging
|
|
13
|
+
import time
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
from .base import BaseAgent, AgentResult
|
|
18
|
+
from .registry import get_registry
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
# 默认 team 构成
|
|
23
|
+
DEFAULT_TEAM = ["macro", "fundamental", "technical", "risk"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class TeamResult:
|
|
28
|
+
symbol: str
|
|
29
|
+
agents_run: List[str]
|
|
30
|
+
results: List[AgentResult]
|
|
31
|
+
synthesis: str = "" # 综合结论(synthesis agent 输出)
|
|
32
|
+
final_signal: str = "HOLD" # 多数表决
|
|
33
|
+
confidence: float = 0.0
|
|
34
|
+
elapsed_sec: float = 0.0
|
|
35
|
+
error: Optional[str] = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AgentTeam:
|
|
39
|
+
"""
|
|
40
|
+
并行运行多个 Agent,汇总结果。
|
|
41
|
+
|
|
42
|
+
用法:
|
|
43
|
+
team = AgentTeam(llm_provider=provider, data_router=router)
|
|
44
|
+
result = await team.run("NVDA", agents=["macro","technical","risk"])
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
llm_provider=None,
|
|
50
|
+
data_router=None,
|
|
51
|
+
on_token: Optional[Callable[[str], None]] = None,
|
|
52
|
+
on_agent_done: Optional[Callable[[str, AgentResult], None]] = None,
|
|
53
|
+
on_synthesis_start: Optional[Callable[[List["AgentResult"]], None]] = None,
|
|
54
|
+
timeout_per_agent: float = 60.0,
|
|
55
|
+
lang: str = "zh",
|
|
56
|
+
):
|
|
57
|
+
self.llm = llm_provider
|
|
58
|
+
self.data = data_router
|
|
59
|
+
self.on_token = on_token
|
|
60
|
+
self.on_agent_done = on_agent_done
|
|
61
|
+
self.on_synthesis_start = on_synthesis_start
|
|
62
|
+
self.timeout = timeout_per_agent
|
|
63
|
+
self.lang = lang
|
|
64
|
+
|
|
65
|
+
def _build_agent(self, name: str) -> Optional[BaseAgent]:
|
|
66
|
+
registry = get_registry()
|
|
67
|
+
cls = registry.get(name)
|
|
68
|
+
if not cls:
|
|
69
|
+
logger.warning(f"未知 Agent: {name},跳过")
|
|
70
|
+
return None
|
|
71
|
+
return cls(
|
|
72
|
+
llm_provider=self.llm,
|
|
73
|
+
data_router=self.data,
|
|
74
|
+
on_token=self.on_token,
|
|
75
|
+
lang=self.lang,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
async def _run_one(self, agent: BaseAgent, symbol: str) -> AgentResult:
|
|
79
|
+
try:
|
|
80
|
+
result = await asyncio.wait_for(
|
|
81
|
+
agent.run(symbol), timeout=self.timeout
|
|
82
|
+
)
|
|
83
|
+
if self.on_agent_done:
|
|
84
|
+
self.on_agent_done(agent.name, result)
|
|
85
|
+
return result
|
|
86
|
+
except asyncio.TimeoutError:
|
|
87
|
+
logger.warning(f"[{agent.name}] 超时 ({self.timeout}s)")
|
|
88
|
+
_timeout_result = AgentResult(
|
|
89
|
+
agent=agent.name, symbol=symbol,
|
|
90
|
+
analysis="", confidence=0.0, error="timeout",
|
|
91
|
+
)
|
|
92
|
+
# Still emit the leaf so the streaming tree shows ⎿ ⏺ <agent> 超时
|
|
93
|
+
if self.on_agent_done:
|
|
94
|
+
try:
|
|
95
|
+
self.on_agent_done(agent.name, _timeout_result)
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|
|
98
|
+
return _timeout_result
|
|
99
|
+
|
|
100
|
+
async def run(
|
|
101
|
+
self,
|
|
102
|
+
symbol: str,
|
|
103
|
+
agents: Optional[List[str]] = None,
|
|
104
|
+
market_context: Optional[Dict[str, Any]] = None,
|
|
105
|
+
) -> TeamResult:
|
|
106
|
+
"""并行运行所有 agent,等待全部完成后汇总。"""
|
|
107
|
+
names_to_run = agents or DEFAULT_TEAM
|
|
108
|
+
t0 = time.time()
|
|
109
|
+
|
|
110
|
+
# 过滤掉 synthesis 和 debate(各自在并行批次后单独运行)
|
|
111
|
+
regular = [n for n in names_to_run if n not in ("synthesis", "debate")]
|
|
112
|
+
agent_objects = [a for n in regular if (a := self._build_agent(n))]
|
|
113
|
+
|
|
114
|
+
if not agent_objects:
|
|
115
|
+
return TeamResult(
|
|
116
|
+
symbol=symbol, agents_run=[], results=[],
|
|
117
|
+
error="no_agents_available"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# 并行执行 — return_exceptions=True 确保单个 agent 异常不取消其余 agent
|
|
121
|
+
tasks = [self._run_one(a, symbol) for a in agent_objects]
|
|
122
|
+
_raw = await asyncio.gather(*tasks, return_exceptions=True)
|
|
123
|
+
results: List[AgentResult] = []
|
|
124
|
+
for _item, _agent in zip(_raw, agent_objects):
|
|
125
|
+
if isinstance(_item, BaseException):
|
|
126
|
+
logger.warning("[%s] 意外异常: %s", _agent.name, _item)
|
|
127
|
+
results.append(AgentResult(
|
|
128
|
+
agent=_agent.name, symbol=symbol,
|
|
129
|
+
analysis="", confidence=0.0,
|
|
130
|
+
error=f"exception: {type(_item).__name__}: {_item}",
|
|
131
|
+
))
|
|
132
|
+
else:
|
|
133
|
+
results.append(_item)
|
|
134
|
+
|
|
135
|
+
# DebateAgent — 显式请求 OR 信号冲突时自动触发
|
|
136
|
+
explicit_debate = "debate" in names_to_run
|
|
137
|
+
if explicit_debate or _needs_debate(results):
|
|
138
|
+
debate_agent = self._build_agent("debate")
|
|
139
|
+
if debate_agent:
|
|
140
|
+
debate_data = {"conflicting": [r.to_dict() for r in results if r.success]}
|
|
141
|
+
try:
|
|
142
|
+
debate_result = await asyncio.wait_for(
|
|
143
|
+
debate_agent.analyze(symbol, debate_data),
|
|
144
|
+
timeout=self.timeout,
|
|
145
|
+
)
|
|
146
|
+
results.append(debate_result)
|
|
147
|
+
logger.info("[debate] %s 信号冲突已调解", symbol)
|
|
148
|
+
except Exception as e:
|
|
149
|
+
logger.warning("[debate] 调解失败: %s", e)
|
|
150
|
+
|
|
151
|
+
# Fire on_synthesis_start callback so callers can print the agent table
|
|
152
|
+
# before synthesis begins streaming tokens.
|
|
153
|
+
if self.on_synthesis_start:
|
|
154
|
+
try:
|
|
155
|
+
self.on_synthesis_start(list(results))
|
|
156
|
+
except Exception:
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
final_signal, confidence = _vote_signal(results)
|
|
160
|
+
|
|
161
|
+
# synthesis — 把 agent 结果打包进 data,直接调 analyze() 而非 run()
|
|
162
|
+
synthesis_text = ""
|
|
163
|
+
if "synthesis" in names_to_run or len(agent_objects) >= 2:
|
|
164
|
+
synth_cls = get_registry().get("synthesis")
|
|
165
|
+
if synth_cls:
|
|
166
|
+
synth_agent = synth_cls(
|
|
167
|
+
llm_provider=self.llm,
|
|
168
|
+
data_router=self.data,
|
|
169
|
+
on_token=self.on_token,
|
|
170
|
+
)
|
|
171
|
+
synth_data = {
|
|
172
|
+
"agent_results": [r.to_dict() for r in results],
|
|
173
|
+
"consensus_signal": final_signal,
|
|
174
|
+
"consensus_confidence": confidence,
|
|
175
|
+
}
|
|
176
|
+
if market_context:
|
|
177
|
+
synth_data.update(market_context)
|
|
178
|
+
try:
|
|
179
|
+
synth_result = await asyncio.wait_for(
|
|
180
|
+
synth_agent.analyze(symbol, synth_data),
|
|
181
|
+
timeout=self.timeout,
|
|
182
|
+
)
|
|
183
|
+
synthesis_text = synth_result.analysis
|
|
184
|
+
except Exception as e:
|
|
185
|
+
logger.warning(f"[synthesis] 失败: {e}")
|
|
186
|
+
synthesis_text = _template_synthesis(results)
|
|
187
|
+
else:
|
|
188
|
+
synthesis_text = _template_synthesis(results)
|
|
189
|
+
else:
|
|
190
|
+
synthesis_text = _template_synthesis(results)
|
|
191
|
+
|
|
192
|
+
return TeamResult(
|
|
193
|
+
symbol = symbol,
|
|
194
|
+
agents_run = [a.name for a in agent_objects],
|
|
195
|
+
results = list(results),
|
|
196
|
+
synthesis = synthesis_text,
|
|
197
|
+
final_signal = final_signal,
|
|
198
|
+
confidence = confidence,
|
|
199
|
+
elapsed_sec = round(time.time() - t0, 1),
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# ── 独立函数(兼容旧 financial_agents.py 调用方式)──────────────────────────
|
|
204
|
+
|
|
205
|
+
async def run_team(
|
|
206
|
+
symbol: str,
|
|
207
|
+
agents: Optional[List[str]] = None,
|
|
208
|
+
llm_provider=None,
|
|
209
|
+
data_router=None,
|
|
210
|
+
on_token: Optional[Callable] = None,
|
|
211
|
+
on_agent_done: Optional[Callable] = None,
|
|
212
|
+
on_synthesis_start: Optional[Callable] = None,
|
|
213
|
+
lang: str = "zh",
|
|
214
|
+
market_context: Optional[Dict[str, Any]] = None,
|
|
215
|
+
) -> TeamResult:
|
|
216
|
+
"""
|
|
217
|
+
便捷函数,替代原 financial_agents.run_team_analysis()。
|
|
218
|
+
|
|
219
|
+
旧签名兼容:
|
|
220
|
+
result = await run_team_analysis("NVDA", ollama_url, model, on_token)
|
|
221
|
+
新签名:
|
|
222
|
+
result = await run_team("NVDA", llm_provider=provider, on_token=cb)
|
|
223
|
+
"""
|
|
224
|
+
team = AgentTeam(
|
|
225
|
+
llm_provider=llm_provider,
|
|
226
|
+
data_router=data_router,
|
|
227
|
+
on_token=on_token,
|
|
228
|
+
on_agent_done=on_agent_done,
|
|
229
|
+
on_synthesis_start=on_synthesis_start,
|
|
230
|
+
lang=lang,
|
|
231
|
+
)
|
|
232
|
+
return await team.run(symbol, agents=agents, market_context=market_context)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# ── 内部工具 ──────────────────────────────────────────────────────────────────
|
|
236
|
+
|
|
237
|
+
def _needs_debate(results: List[AgentResult]) -> bool:
|
|
238
|
+
"""当出现真实多空分歧(至少1个BUY + 1个SELL)时返回 True。"""
|
|
239
|
+
signals = [r.signal for r in results if r.success and r.signal]
|
|
240
|
+
bullish = sum(1 for s in signals if s in ("BUY", "STRONG_BUY"))
|
|
241
|
+
bearish = sum(1 for s in signals if s in ("SELL", "STRONG_SELL"))
|
|
242
|
+
return bullish >= 1 and bearish >= 1
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _vote_signal(results: List[AgentResult]) -> tuple:
|
|
246
|
+
"""多数表决最终信号"""
|
|
247
|
+
_SCORE = {
|
|
248
|
+
"STRONG_BUY": 2, "BUY": 1, "HOLD": 0, "SELL": -1, "STRONG_SELL": -2
|
|
249
|
+
}
|
|
250
|
+
valid = [r for r in results if r.success and r.signal in _SCORE]
|
|
251
|
+
if not valid:
|
|
252
|
+
return "HOLD", 0.0
|
|
253
|
+
|
|
254
|
+
avg_score = sum(_SCORE[r.signal] * r.confidence for r in valid) / len(valid)
|
|
255
|
+
avg_conf = sum(r.confidence for r in valid) / len(valid)
|
|
256
|
+
|
|
257
|
+
if avg_score >= 1.5:
|
|
258
|
+
return "STRONG_BUY", avg_conf
|
|
259
|
+
if avg_score >= 0.5:
|
|
260
|
+
return "BUY", avg_conf
|
|
261
|
+
if avg_score <= -1.5:
|
|
262
|
+
return "STRONG_SELL", avg_conf
|
|
263
|
+
if avg_score <= -0.5:
|
|
264
|
+
return "SELL", avg_conf
|
|
265
|
+
return "HOLD", avg_conf
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _template_synthesis(results: List[AgentResult]) -> str:
|
|
269
|
+
"""无 synthesis agent 时的模板汇总"""
|
|
270
|
+
if not results:
|
|
271
|
+
return "分析完成,无结果。"
|
|
272
|
+
lines = ["## 团队分析汇总\n"]
|
|
273
|
+
failed_count = sum(1 for r in results if not r.success)
|
|
274
|
+
if failed_count:
|
|
275
|
+
lines.append(f"> ⚠️ {failed_count}/{len(results)} 个 agent 未能完成分析"
|
|
276
|
+
f"(超时或 LLM 不可用),以下结论仅基于成功的 agent。\n")
|
|
277
|
+
for r in results:
|
|
278
|
+
if r.success:
|
|
279
|
+
lines.append(f"**{r.agent.upper()}** ({r.signal}, 置信度 {r.confidence:.0%})")
|
|
280
|
+
for pt in (r.key_points or [])[:3]:
|
|
281
|
+
lines.append(f" • {pt}")
|
|
282
|
+
else:
|
|
283
|
+
err_label = "超时" if r.error == "timeout" else (r.error or "分析失败")
|
|
284
|
+
lines.append(f"**{r.agent.upper()}** ⚠️ {err_label}")
|
|
285
|
+
signal, conf = _vote_signal(results)
|
|
286
|
+
lines.append(f"\n**综合结论**: {signal}(置信度 {conf:.0%})")
|
|
287
|
+
if failed_count == len(results):
|
|
288
|
+
lines.append("\n> ⚠️ 所有 agent 均未成功,此结论仅为默认值,不具参考意义。请确认 LLM 服务正常后重试。")
|
|
289
|
+
return "\n".join(lines)
|