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.
Files changed (93) hide show
  1. package/.claude/agents/equity-agent.md +26 -0
  2. package/.claude/agents/macro-agent.md +25 -0
  3. package/.claude/commands/analyze.md +40 -0
  4. package/.claude/commands/macro.md +29 -0
  5. package/.claude/settings.json +12 -0
  6. package/CODEX_GOAL.md +46 -0
  7. package/README.md +206 -0
  8. package/bin/cli.js +67 -0
  9. package/bin/erlangshen +2 -0
  10. package/bin/xiaoergod +2 -0
  11. package/frontend/index.html +700 -0
  12. package/knowledge/crypto_guide.md +147 -0
  13. package/knowledge/economic_indicators.md +125 -0
  14. package/knowledge/financial_glossary.md +148 -0
  15. package/knowledge/first_principles.md +50 -0
  16. package/knowledge/first_principles_deep.md +115 -0
  17. package/knowledge/global_markets.md +173 -0
  18. package/knowledge/insights.md +141 -0
  19. package/knowledge/market_basics.md +116 -0
  20. package/knowledge/memos/session_20260513_003616.json +6 -0
  21. package/knowledge/memos/session_20260513_003822.json +6 -0
  22. package/knowledge/risk_management.md +151 -0
  23. package/knowledge/team_context.md +42 -0
  24. package/knowledge/trading_strategies.md +114 -0
  25. package/package.json +42 -0
  26. package/requirements.txt +14 -0
  27. package/scripts/postinstall.js +188 -0
  28. package/scripts/preuninstall.js +22 -0
  29. package/src/__init__.py +4 -0
  30. package/src/__pycache__/__init__.cpython-313.pyc +0 -0
  31. package/src/agents/__init__.py +3 -0
  32. package/src/agents/base.py +103 -0
  33. package/src/agents/base_agent.py +86 -0
  34. package/src/agents/equity.py +136 -0
  35. package/src/agents/equity_agent.py +91 -0
  36. package/src/agents/erlang.py +165 -0
  37. package/src/agents/macro.py +137 -0
  38. package/src/agents/macro_agent.py +81 -0
  39. package/src/agents/multi_asset.py +147 -0
  40. package/src/agents/multi_asset_agent.py +87 -0
  41. package/src/api/__init__.py +1 -0
  42. package/src/api/__pycache__/__init__.cpython-313.pyc +0 -0
  43. package/src/api/__pycache__/server.cpython-313.pyc +0 -0
  44. package/src/api/cli.py +435 -0
  45. package/src/api/cli_enhanced.py +537 -0
  46. package/src/api/server.py +266 -0
  47. package/src/brain.py +200 -0
  48. package/src/cli.py +153 -0
  49. package/src/commands/__init__.py +3 -0
  50. package/src/commands/analyze.py +131 -0
  51. package/src/commands/macro.py +100 -0
  52. package/src/commands/memo.py +216 -0
  53. package/src/commands/portfolio.py +154 -0
  54. package/src/commands/report.py +228 -0
  55. package/src/commands/risk.py +183 -0
  56. package/src/commands/search.py +183 -0
  57. package/src/commands/stock.py +124 -0
  58. package/src/config.py +327 -0
  59. package/src/core/__init__.py +1 -0
  60. package/src/core/brain.py +645 -0
  61. package/src/core/cerebellum.py +175 -0
  62. package/src/core/investment_universe.py +423 -0
  63. package/src/core/knowledge.py +207 -0
  64. package/src/core/memory.py +115 -0
  65. package/src/hooks/__init__.py +3 -0
  66. package/src/hooks/session_end.py +57 -0
  67. package/src/hooks/session_start.py +75 -0
  68. package/src/knowledge/__init__.py +1 -0
  69. package/src/mcp/__init__.py +3 -0
  70. package/src/mcp/feishu.py +331 -0
  71. package/src/mcp/fund_tools.py +323 -0
  72. package/src/mcp/macro.py +452 -0
  73. package/src/mcp/market.py +331 -0
  74. package/src/mcp/registry.py +168 -0
  75. package/src/network/__init__.py +15 -0
  76. package/src/network/detector.py +125 -0
  77. package/src/network/proxy.py +199 -0
  78. package/src/network/router.py +103 -0
  79. package/src/prompts/__init__.py +1 -0
  80. package/src/prompts/analysis_framework.md +164 -0
  81. package/src/prompts/persona.md +65 -0
  82. package/src/prompts/report_template.md +144 -0
  83. package/src/skills/__init__.py +3 -0
  84. package/src/skills/framework.py +105 -0
  85. package/src/skills/templates.py +342 -0
  86. package/src/tools/__init__.py +1 -0
  87. package/src/tools/file_tools.py +209 -0
  88. package/src/tools/macro_tools.py +152 -0
  89. package/src/tools/market_tools.py +1172 -0
  90. package/src/tools/registry.py +398 -0
  91. package/src/tools/search_tools.py +777 -0
  92. package/tests/__init__.py +1 -0
  93. 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