erlangshen 0.1.0
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.
- package/.claude/agents/equity-agent.md +26 -0
- package/.claude/agents/macro-agent.md +25 -0
- package/.claude/commands/analyze.md +40 -0
- package/.claude/commands/macro.md +29 -0
- package/.claude/settings.json +12 -0
- package/CODEX_GOAL.md +46 -0
- package/README.md +206 -0
- package/bin/cli.js +67 -0
- package/bin/erlangshen +2 -0
- package/bin/xiaoergod +2 -0
- package/frontend/index.html +700 -0
- package/knowledge/crypto_guide.md +147 -0
- package/knowledge/economic_indicators.md +125 -0
- package/knowledge/financial_glossary.md +148 -0
- package/knowledge/first_principles.md +50 -0
- package/knowledge/first_principles_deep.md +115 -0
- package/knowledge/global_markets.md +173 -0
- package/knowledge/insights.md +141 -0
- package/knowledge/market_basics.md +116 -0
- package/knowledge/memos/session_20260513_003616.json +6 -0
- package/knowledge/memos/session_20260513_003822.json +6 -0
- package/knowledge/risk_management.md +151 -0
- package/knowledge/team_context.md +42 -0
- package/knowledge/trading_strategies.md +114 -0
- package/package.json +42 -0
- package/requirements.txt +14 -0
- package/scripts/postinstall.js +188 -0
- package/scripts/preuninstall.js +22 -0
- package/src/__init__.py +4 -0
- package/src/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/agents/__init__.py +3 -0
- package/src/agents/base.py +103 -0
- package/src/agents/base_agent.py +86 -0
- package/src/agents/equity.py +136 -0
- package/src/agents/equity_agent.py +91 -0
- package/src/agents/erlang.py +165 -0
- package/src/agents/macro.py +137 -0
- package/src/agents/macro_agent.py +81 -0
- package/src/agents/multi_asset.py +147 -0
- package/src/agents/multi_asset_agent.py +87 -0
- package/src/api/__init__.py +1 -0
- package/src/api/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/api/__pycache__/server.cpython-313.pyc +0 -0
- package/src/api/cli.py +435 -0
- package/src/api/cli_enhanced.py +537 -0
- package/src/api/server.py +266 -0
- package/src/brain.py +200 -0
- package/src/cli.py +153 -0
- package/src/commands/__init__.py +3 -0
- package/src/commands/analyze.py +131 -0
- package/src/commands/macro.py +100 -0
- package/src/commands/memo.py +216 -0
- package/src/commands/portfolio.py +154 -0
- package/src/commands/report.py +228 -0
- package/src/commands/risk.py +183 -0
- package/src/commands/search.py +183 -0
- package/src/commands/stock.py +124 -0
- package/src/config.py +327 -0
- package/src/core/__init__.py +1 -0
- package/src/core/brain.py +645 -0
- package/src/core/cerebellum.py +175 -0
- package/src/core/investment_universe.py +423 -0
- package/src/core/knowledge.py +207 -0
- package/src/core/memory.py +115 -0
- package/src/hooks/__init__.py +3 -0
- package/src/hooks/session_end.py +57 -0
- package/src/hooks/session_start.py +75 -0
- package/src/knowledge/__init__.py +1 -0
- package/src/mcp/__init__.py +3 -0
- package/src/mcp/feishu.py +331 -0
- package/src/mcp/fund_tools.py +323 -0
- package/src/mcp/macro.py +452 -0
- package/src/mcp/market.py +331 -0
- package/src/mcp/registry.py +168 -0
- package/src/network/__init__.py +15 -0
- package/src/network/detector.py +125 -0
- package/src/network/proxy.py +199 -0
- package/src/network/router.py +103 -0
- package/src/prompts/__init__.py +1 -0
- package/src/prompts/analysis_framework.md +164 -0
- package/src/prompts/persona.md +65 -0
- package/src/prompts/report_template.md +144 -0
- package/src/skills/__init__.py +3 -0
- package/src/skills/framework.py +105 -0
- package/src/skills/templates.py +342 -0
- package/src/tools/__init__.py +1 -0
- package/src/tools/file_tools.py +209 -0
- package/src/tools/macro_tools.py +152 -0
- package/src/tools/market_tools.py +1172 -0
- package/src/tools/registry.py +398 -0
- package/src/tools/search_tools.py +777 -0
- package/tests/__init__.py +1 -0
- package/tests/test_erlangshen.py +140 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool Registry - 工具注册表
|
|
3
|
+
管理所有可用工具的注册和调用
|
|
4
|
+
|
|
5
|
+
工具分类:
|
|
6
|
+
- market: 行情数据 (股票、期货、ETF、加密货币、宏观指标)
|
|
7
|
+
- search: 搜索工具 (网络搜索、新闻、学术搜索、公司信息)
|
|
8
|
+
- macro: 宏观数据 (FRED指标、中国宏观、央行数据)
|
|
9
|
+
- file: 文件工具 (读取、写入、搜索)
|
|
10
|
+
"""
|
|
11
|
+
from typing import Any, Optional, Dict, List
|
|
12
|
+
from loguru import logger
|
|
13
|
+
import aiohttp
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ToolRegistry:
|
|
17
|
+
"""
|
|
18
|
+
工具注册表
|
|
19
|
+
|
|
20
|
+
提供工具的注册、发现和调用
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, config: Optional[Dict] = None):
|
|
24
|
+
self._tools: Dict[str, Any] = {}
|
|
25
|
+
self._categories: Dict[str, List[str]] = {}
|
|
26
|
+
self._instances: Dict[str, Any] = {}
|
|
27
|
+
self._config = config or {}
|
|
28
|
+
self._http_session: Optional[aiohttp.ClientSession] = None
|
|
29
|
+
logger.info("ToolRegistry initialized")
|
|
30
|
+
|
|
31
|
+
def register(
|
|
32
|
+
self,
|
|
33
|
+
name: str,
|
|
34
|
+
tool_class: type,
|
|
35
|
+
category: str = "general",
|
|
36
|
+
description: str = "",
|
|
37
|
+
**init_kwargs,
|
|
38
|
+
) -> None:
|
|
39
|
+
"""
|
|
40
|
+
注册工具
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
name: 工具名称
|
|
44
|
+
tool_class: 工具类
|
|
45
|
+
category: 分类
|
|
46
|
+
description: 描述
|
|
47
|
+
**init_kwargs: 工具类初始化参数
|
|
48
|
+
"""
|
|
49
|
+
# 实例化工具
|
|
50
|
+
try:
|
|
51
|
+
instance = tool_class(**init_kwargs)
|
|
52
|
+
except TypeError:
|
|
53
|
+
# 如果不需要参数
|
|
54
|
+
instance = tool_class()
|
|
55
|
+
|
|
56
|
+
self._tools[name] = {
|
|
57
|
+
"instance": instance,
|
|
58
|
+
"class": tool_class,
|
|
59
|
+
"category": category,
|
|
60
|
+
"description": description,
|
|
61
|
+
"init_kwargs": init_kwargs,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if category not in self._categories:
|
|
65
|
+
self._categories[category] = []
|
|
66
|
+
if name not in self._categories[category]:
|
|
67
|
+
self._categories[category].append(name)
|
|
68
|
+
|
|
69
|
+
self._instances[name] = instance
|
|
70
|
+
logger.info(f"Registered tool: {name} [{category}]")
|
|
71
|
+
|
|
72
|
+
def register_instance(
|
|
73
|
+
self,
|
|
74
|
+
name: str,
|
|
75
|
+
instance: Any,
|
|
76
|
+
category: str = "general",
|
|
77
|
+
description: str = "",
|
|
78
|
+
) -> None:
|
|
79
|
+
"""
|
|
80
|
+
注册已有工具实例
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
name: 工具名称
|
|
84
|
+
instance: 工具实例
|
|
85
|
+
category: 分类
|
|
86
|
+
description: 描述
|
|
87
|
+
"""
|
|
88
|
+
self._tools[name] = {
|
|
89
|
+
"instance": instance,
|
|
90
|
+
"category": category,
|
|
91
|
+
"description": description,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if category not in self._categories:
|
|
95
|
+
self._categories[category] = []
|
|
96
|
+
if name not in self._categories[category]:
|
|
97
|
+
self._categories[category].append(name)
|
|
98
|
+
|
|
99
|
+
self._instances[name] = instance
|
|
100
|
+
logger.info(f"Registered instance: {name} [{category}]")
|
|
101
|
+
|
|
102
|
+
def get(self, name: str) -> Optional[Any]:
|
|
103
|
+
"""获取工具实例"""
|
|
104
|
+
tool_info = self._tools.get(name)
|
|
105
|
+
if tool_info:
|
|
106
|
+
return tool_info["instance"]
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
def list_tools(self, category: Optional[str] = None) -> List[str]:
|
|
110
|
+
"""列出工具"""
|
|
111
|
+
if category:
|
|
112
|
+
return self._categories.get(category, [])
|
|
113
|
+
return list(self._tools.keys())
|
|
114
|
+
|
|
115
|
+
def list_categories(self) -> List[str]:
|
|
116
|
+
"""列出分类"""
|
|
117
|
+
return list(self._categories.keys())
|
|
118
|
+
|
|
119
|
+
def get_info(self, name: str) -> Optional[dict]:
|
|
120
|
+
"""获取工具信息"""
|
|
121
|
+
info = self._tools.get(name)
|
|
122
|
+
if info:
|
|
123
|
+
return {
|
|
124
|
+
"name": name,
|
|
125
|
+
"category": info["category"],
|
|
126
|
+
"description": info.get("description", ""),
|
|
127
|
+
}
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
def get_category_tools(self, category: str) -> Dict[str, dict]:
|
|
131
|
+
"""获取某个分类的所有工具信息"""
|
|
132
|
+
tools = {}
|
|
133
|
+
for name in self._categories.get(category, []):
|
|
134
|
+
info = self.get_info(name)
|
|
135
|
+
if info:
|
|
136
|
+
tools[name] = info
|
|
137
|
+
return tools
|
|
138
|
+
|
|
139
|
+
async def execute(self, name: str, **kwargs) -> Any:
|
|
140
|
+
"""执行工具"""
|
|
141
|
+
tool_info = self._tools.get(name)
|
|
142
|
+
if not tool_info:
|
|
143
|
+
return {"error": f"Tool not found: {name}"}
|
|
144
|
+
|
|
145
|
+
tool = tool_info["instance"]
|
|
146
|
+
if hasattr(tool, "execute"):
|
|
147
|
+
return await tool.execute(**kwargs)
|
|
148
|
+
elif callable(tool):
|
|
149
|
+
return await tool(**kwargs)
|
|
150
|
+
return {"error": f"Tool {name} is not callable"}
|
|
151
|
+
|
|
152
|
+
async def get_http_session(self) -> aiohttp.ClientSession:
|
|
153
|
+
"""获取HTTP Session"""
|
|
154
|
+
if self._http_session is None or self._http_session.closed:
|
|
155
|
+
self._http_session = aiohttp.ClientSession()
|
|
156
|
+
return self._http_session
|
|
157
|
+
|
|
158
|
+
def close(self) -> None:
|
|
159
|
+
"""关闭资源"""
|
|
160
|
+
if self._http_session and not self._http_session.closed:
|
|
161
|
+
# 异步关闭需要用 asyncio
|
|
162
|
+
pass
|
|
163
|
+
logger.info("ToolRegistry closed")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# ==================== 工具初始化函数 ====================
|
|
167
|
+
|
|
168
|
+
def create_registry(config: Optional[Dict] = None) -> ToolRegistry:
|
|
169
|
+
"""
|
|
170
|
+
创建并初始化工具注册表
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
config: 配置字典
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
ToolRegistry: 初始化后的工具注册表
|
|
177
|
+
"""
|
|
178
|
+
from .market_tools import MarketTools
|
|
179
|
+
from .search_tools import SearchTools
|
|
180
|
+
from .file_tools import FileTools
|
|
181
|
+
|
|
182
|
+
registry = ToolRegistry(config)
|
|
183
|
+
|
|
184
|
+
# 注册市场工具
|
|
185
|
+
market_config = config or {}
|
|
186
|
+
registry.register(
|
|
187
|
+
"market",
|
|
188
|
+
MarketTools,
|
|
189
|
+
category="market",
|
|
190
|
+
description="行情数据工具: 股票、期货、ETF、加密货币、宏观指标",
|
|
191
|
+
db_connection=None,
|
|
192
|
+
config=market_config,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# 注册搜索工具
|
|
196
|
+
search_config = {
|
|
197
|
+
"serpapi_key": config.get("serpapi_key") if config else None,
|
|
198
|
+
"cache_ttl": config.get("cache_ttl", 300) if config else 300,
|
|
199
|
+
}
|
|
200
|
+
registry.register(
|
|
201
|
+
"search",
|
|
202
|
+
SearchTools,
|
|
203
|
+
category="search",
|
|
204
|
+
description="搜索工具: 网络搜索、新闻搜索、学术搜索、公司信息",
|
|
205
|
+
config=search_config,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# 注册文件工具
|
|
209
|
+
registry.register(
|
|
210
|
+
"file",
|
|
211
|
+
FileTools,
|
|
212
|
+
category="file",
|
|
213
|
+
description="文件工具: 读取、写入、搜索知识库",
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# 注册快捷方法
|
|
217
|
+
_register_shortcut_methods(registry)
|
|
218
|
+
|
|
219
|
+
return registry
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _register_shortcut_methods(registry: ToolRegistry) -> None:
|
|
223
|
+
"""注册快捷调用方法"""
|
|
224
|
+
market = registry.get("market")
|
|
225
|
+
search = registry.get("search")
|
|
226
|
+
|
|
227
|
+
if market:
|
|
228
|
+
# 股票工具
|
|
229
|
+
registry.register_instance(
|
|
230
|
+
"get_stock_price",
|
|
231
|
+
_create_shortcut(market, "get_stock_price"),
|
|
232
|
+
category="market",
|
|
233
|
+
description="获取股票价格",
|
|
234
|
+
)
|
|
235
|
+
registry.register_instance(
|
|
236
|
+
"get_stock_history",
|
|
237
|
+
_create_shortcut(market, "get_stock_history"),
|
|
238
|
+
category="market",
|
|
239
|
+
description="获取股票历史",
|
|
240
|
+
)
|
|
241
|
+
registry.register_instance(
|
|
242
|
+
"get_index_quote",
|
|
243
|
+
_create_shortcut(market, "get_index_quote"),
|
|
244
|
+
category="market",
|
|
245
|
+
description="获取指数行情",
|
|
246
|
+
)
|
|
247
|
+
registry.register_instance(
|
|
248
|
+
"get_etf_info",
|
|
249
|
+
_create_shortcut(market, "get_etf_info"),
|
|
250
|
+
category="market",
|
|
251
|
+
description="获取ETF信息",
|
|
252
|
+
)
|
|
253
|
+
registry.register_instance(
|
|
254
|
+
"get_futures_price",
|
|
255
|
+
_create_shortcut(market, "get_futures_price"),
|
|
256
|
+
category="market",
|
|
257
|
+
description="获取期货价格",
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# 加密货币工具
|
|
261
|
+
registry.register_instance(
|
|
262
|
+
"get_crypto_price",
|
|
263
|
+
_create_shortcut(market, "get_crypto_price"),
|
|
264
|
+
category="market",
|
|
265
|
+
description="获取加密货币价格",
|
|
266
|
+
)
|
|
267
|
+
registry.register_instance(
|
|
268
|
+
"get_crypto_prices",
|
|
269
|
+
_create_shortcut(market, "get_crypto_prices"),
|
|
270
|
+
category="market",
|
|
271
|
+
description="批量获取加密货币价格",
|
|
272
|
+
)
|
|
273
|
+
registry.register_instance(
|
|
274
|
+
"get_crypto_history",
|
|
275
|
+
_create_shortcut(market, "get_crypto_history"),
|
|
276
|
+
category="market",
|
|
277
|
+
description="获取加密货币历史",
|
|
278
|
+
)
|
|
279
|
+
registry.register_instance(
|
|
280
|
+
"get_binance_klines",
|
|
281
|
+
_create_shortcut(market, "get_binance_klines"),
|
|
282
|
+
category="market",
|
|
283
|
+
description="获取Binance K线",
|
|
284
|
+
)
|
|
285
|
+
registry.register_instance(
|
|
286
|
+
"get_binance_ticker",
|
|
287
|
+
_create_shortcut(market, "get_binance_ticker"),
|
|
288
|
+
category="market",
|
|
289
|
+
description="获取Binance实时行情",
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# 宏观工具
|
|
293
|
+
registry.register_instance(
|
|
294
|
+
"get_fred_indicator",
|
|
295
|
+
_create_shortcut(market, "get_fred_indicator"),
|
|
296
|
+
category="market",
|
|
297
|
+
description="获取FRED宏观指标",
|
|
298
|
+
)
|
|
299
|
+
registry.register_instance(
|
|
300
|
+
"get_commodity_price",
|
|
301
|
+
_create_shortcut(market, "get_commodity_price"),
|
|
302
|
+
category="market",
|
|
303
|
+
description="获取大宗商品价格",
|
|
304
|
+
)
|
|
305
|
+
registry.register_instance(
|
|
306
|
+
"get_gold_price",
|
|
307
|
+
_create_shortcut(market, "get_gold_price"),
|
|
308
|
+
category="market",
|
|
309
|
+
description="获取黄金价格",
|
|
310
|
+
)
|
|
311
|
+
registry.register_instance(
|
|
312
|
+
"get_oil_price",
|
|
313
|
+
_create_shortcut(market, "get_oil_price"),
|
|
314
|
+
category="market",
|
|
315
|
+
description="获取原油价格",
|
|
316
|
+
)
|
|
317
|
+
registry.register_instance(
|
|
318
|
+
"get_forex_rate",
|
|
319
|
+
_create_shortcut(market, "get_forex_rate"),
|
|
320
|
+
category="market",
|
|
321
|
+
description="获取外汇汇率",
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
if search:
|
|
325
|
+
# 搜索工具
|
|
326
|
+
registry.register_instance(
|
|
327
|
+
"web_search",
|
|
328
|
+
_create_shortcut(search, "web_search"),
|
|
329
|
+
category="search",
|
|
330
|
+
description="网络搜索",
|
|
331
|
+
)
|
|
332
|
+
registry.register_instance(
|
|
333
|
+
"news_search",
|
|
334
|
+
_create_shortcut(search, "news_search"),
|
|
335
|
+
category="search",
|
|
336
|
+
description="新闻搜索",
|
|
337
|
+
)
|
|
338
|
+
registry.register_instance(
|
|
339
|
+
"academic_search",
|
|
340
|
+
_create_shortcut(search, "academic_search"),
|
|
341
|
+
category="search",
|
|
342
|
+
description="学术搜索",
|
|
343
|
+
)
|
|
344
|
+
registry.register_instance(
|
|
345
|
+
"company_search",
|
|
346
|
+
_create_shortcut(search, "company_search"),
|
|
347
|
+
category="search",
|
|
348
|
+
description="公司信息搜索",
|
|
349
|
+
)
|
|
350
|
+
registry.register_instance(
|
|
351
|
+
"get_financial_news",
|
|
352
|
+
_create_shortcut(search, "get_financial_news"),
|
|
353
|
+
category="search",
|
|
354
|
+
description="财经新闻",
|
|
355
|
+
)
|
|
356
|
+
registry.register_instance(
|
|
357
|
+
"get_macro_news",
|
|
358
|
+
_create_shortcut(search, "get_macro_news"),
|
|
359
|
+
category="search",
|
|
360
|
+
description="宏观新闻",
|
|
361
|
+
)
|
|
362
|
+
registry.register_instance(
|
|
363
|
+
"get_industry_news",
|
|
364
|
+
_create_shortcut(search, "get_industry_news"),
|
|
365
|
+
category="search",
|
|
366
|
+
description="行业新闻",
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _create_shortcut(instance: Any, method_name: str):
|
|
371
|
+
"""创建快捷调用函数"""
|
|
372
|
+
async def shortcut(**kwargs):
|
|
373
|
+
method = getattr(instance, method_name)
|
|
374
|
+
if callable(method):
|
|
375
|
+
return await method(**kwargs)
|
|
376
|
+
return {"error": f"Method {method_name} not callable"}
|
|
377
|
+
return shortcut
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
# ==================== 全局注册表 ====================
|
|
381
|
+
|
|
382
|
+
_global_registry: Optional[ToolRegistry] = None
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def get_registry(config: Optional[Dict] = None) -> ToolRegistry:
|
|
386
|
+
"""获取全局工具注册表"""
|
|
387
|
+
global _global_registry
|
|
388
|
+
if _global_registry is None:
|
|
389
|
+
_global_registry = create_registry(config)
|
|
390
|
+
return _global_registry
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def reset_registry() -> None:
|
|
394
|
+
"""重置全局注册表"""
|
|
395
|
+
global _global_registry
|
|
396
|
+
if _global_registry:
|
|
397
|
+
_global_registry.close()
|
|
398
|
+
_global_registry = None
|