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,331 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP 工具 - 飞书文档
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict, Any, Optional, List
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FeishuMCP:
|
|
12
|
+
"""
|
|
13
|
+
飞书 MCP
|
|
14
|
+
|
|
15
|
+
提供飞书文档、消息等操作接口
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.name = "feishu"
|
|
20
|
+
self._client = None
|
|
21
|
+
self._access_token = None
|
|
22
|
+
|
|
23
|
+
def _get_client(self):
|
|
24
|
+
"""获取飞书客户端"""
|
|
25
|
+
if self._client is None:
|
|
26
|
+
try:
|
|
27
|
+
from src.config import get_config
|
|
28
|
+
config = get_config()
|
|
29
|
+
|
|
30
|
+
if not config.feishu_app_id or not config.feishu_app_secret:
|
|
31
|
+
logger.warning("飞书配置不完整,请设置 feishu_app_id 和 feishu_app_secret")
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
# 飞书 API 客户端初始化
|
|
35
|
+
# 注意:需要安装 feishu SDK: pip install lark-oapi
|
|
36
|
+
try:
|
|
37
|
+
import lark
|
|
38
|
+
|
|
39
|
+
self._client = lark.Client(
|
|
40
|
+
app_id=config.feishu_app_id,
|
|
41
|
+
app_secret=config.feishu_app_secret,
|
|
42
|
+
)
|
|
43
|
+
except ImportError:
|
|
44
|
+
logger.warning("请安装飞书 SDK: pip install lark-oapi")
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
except Exception as e:
|
|
48
|
+
logger.warning(f"飞书客户端初始化失败: {e}")
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
return self._client
|
|
52
|
+
|
|
53
|
+
async def create_doc(
|
|
54
|
+
self,
|
|
55
|
+
title: str,
|
|
56
|
+
folder_token: Optional[str] = None
|
|
57
|
+
) -> Dict[str, Any]:
|
|
58
|
+
"""
|
|
59
|
+
创建飞书文档
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
title: 文档标题
|
|
63
|
+
folder_token: 文件夹token (可选)
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
文档信息
|
|
67
|
+
"""
|
|
68
|
+
client = self._get_client()
|
|
69
|
+
|
|
70
|
+
if not client:
|
|
71
|
+
return {
|
|
72
|
+
"success": False,
|
|
73
|
+
"error": "飞书客户端未初始化,请检查配置",
|
|
74
|
+
"source": "feishu_mcp"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
response = client.docx.document.create(
|
|
79
|
+
title=title,
|
|
80
|
+
folder_token=folder_token
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
"success": True,
|
|
85
|
+
"document_id": response.data.document.document_id,
|
|
86
|
+
"title": title,
|
|
87
|
+
"url": f"https://.feishu.cn/document/{response.data.document.document_id}",
|
|
88
|
+
"source": "feishu_mcp"
|
|
89
|
+
}
|
|
90
|
+
except Exception as e:
|
|
91
|
+
return {
|
|
92
|
+
"success": False,
|
|
93
|
+
"error": str(e),
|
|
94
|
+
"source": "feishu_mcp"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async def append_doc(
|
|
98
|
+
self,
|
|
99
|
+
document_id: str,
|
|
100
|
+
content: str
|
|
101
|
+
) -> Dict[str, Any]:
|
|
102
|
+
"""
|
|
103
|
+
向飞书文档追加内容
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
document_id: 文档ID
|
|
107
|
+
content: 追加的内容 (Markdown格式)
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
操作结果
|
|
111
|
+
"""
|
|
112
|
+
client = self._get_client()
|
|
113
|
+
|
|
114
|
+
if not client:
|
|
115
|
+
return {
|
|
116
|
+
"success": False,
|
|
117
|
+
"error": "飞书客户端未初始化",
|
|
118
|
+
"source": "feishu_mcp"
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
# 转换 Markdown 为飞书块
|
|
123
|
+
blocks = self._markdown_to_feishu_blocks(content)
|
|
124
|
+
|
|
125
|
+
# 插入文档
|
|
126
|
+
response = client.docx.document.blocks.append(
|
|
127
|
+
document_id=document_id,
|
|
128
|
+
children=blocks
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
"success": True,
|
|
133
|
+
"blocks_added": len(blocks),
|
|
134
|
+
"source": "feishu_mcp"
|
|
135
|
+
}
|
|
136
|
+
except Exception as e:
|
|
137
|
+
return {
|
|
138
|
+
"success": False,
|
|
139
|
+
"error": str(e),
|
|
140
|
+
"source": "feishu_mcp"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async def search_docs(
|
|
144
|
+
self,
|
|
145
|
+
query: str,
|
|
146
|
+
count: int = 10
|
|
147
|
+
) -> List[Dict[str, Any]]:
|
|
148
|
+
"""
|
|
149
|
+
搜索飞书文档
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
query: 搜索关键词
|
|
153
|
+
count: 返回数量
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
文档列表
|
|
157
|
+
"""
|
|
158
|
+
client = self._get_client()
|
|
159
|
+
|
|
160
|
+
if not client:
|
|
161
|
+
return []
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
response = client.docx.document.search(
|
|
165
|
+
query=query,
|
|
166
|
+
count=count
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
docs = []
|
|
170
|
+
for doc in response.data.documents:
|
|
171
|
+
docs.append({
|
|
172
|
+
"document_id": doc.document_id,
|
|
173
|
+
"title": doc.title,
|
|
174
|
+
"url": f"https://feishu.cn/document/{doc.document_id}"
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
return docs
|
|
178
|
+
except Exception as e:
|
|
179
|
+
logger.error(f"搜索文档失败: {e}")
|
|
180
|
+
return []
|
|
181
|
+
|
|
182
|
+
async def send_message(
|
|
183
|
+
self,
|
|
184
|
+
chat_id: str,
|
|
185
|
+
content: str,
|
|
186
|
+
msg_type: str = "text"
|
|
187
|
+
) -> Dict[str, Any]:
|
|
188
|
+
"""
|
|
189
|
+
发送飞书消息
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
chat_id: 群ID
|
|
193
|
+
content: 消息内容
|
|
194
|
+
msg_type: 消息类型 (text, post, interactive)
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
发送结果
|
|
198
|
+
"""
|
|
199
|
+
client = self._get_client()
|
|
200
|
+
|
|
201
|
+
if not client:
|
|
202
|
+
return {
|
|
203
|
+
"success": False,
|
|
204
|
+
"error": "飞书客户端未初始化",
|
|
205
|
+
"source": "feishu_mcp"
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
if msg_type == "text":
|
|
210
|
+
message_content = {
|
|
211
|
+
"text": content
|
|
212
|
+
}
|
|
213
|
+
else:
|
|
214
|
+
message_content = {
|
|
215
|
+
"text": content
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
response = client.im.message.create(
|
|
219
|
+
receive_id_type="chat_id",
|
|
220
|
+
receive_id=chat_id,
|
|
221
|
+
msg_type=msg_type,
|
|
222
|
+
content=message_content
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
"success": True,
|
|
227
|
+
"message_id": response.data.message_id,
|
|
228
|
+
"source": "feishu_mcp"
|
|
229
|
+
}
|
|
230
|
+
except Exception as e:
|
|
231
|
+
return {
|
|
232
|
+
"success": False,
|
|
233
|
+
"error": str(e),
|
|
234
|
+
"source": "feishu_mcp"
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
def _markdown_to_feishu_blocks(self, markdown: str) -> List[Dict]:
|
|
238
|
+
"""
|
|
239
|
+
将 Markdown 转换为飞书块
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
markdown: Markdown 文本
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
飞书块列表
|
|
246
|
+
"""
|
|
247
|
+
# 简化实现,实际应使用更复杂的解析
|
|
248
|
+
blocks = []
|
|
249
|
+
|
|
250
|
+
lines = markdown.split("\n")
|
|
251
|
+
for line in lines:
|
|
252
|
+
if line.startswith("# "):
|
|
253
|
+
blocks.append({
|
|
254
|
+
"block_type": 2, # heading1
|
|
255
|
+
"heading1": {
|
|
256
|
+
"elements": [{"text_run": {"content": line[2:]}}],
|
|
257
|
+
"style": {}
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
elif line.startswith("## "):
|
|
261
|
+
blocks.append({
|
|
262
|
+
"block_type": 3, # heading2
|
|
263
|
+
"heading2": {
|
|
264
|
+
"elements": [{"text_run": {"content": line[3:]}}],
|
|
265
|
+
"style": {}
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
elif line.startswith("### "):
|
|
269
|
+
blocks.append({
|
|
270
|
+
"block_type": 4, # heading3
|
|
271
|
+
"heading3": {
|
|
272
|
+
"elements": [{"text_run": {"content": line[4:]}}],
|
|
273
|
+
"style": {}
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
elif line.startswith("-"):
|
|
277
|
+
blocks.append({
|
|
278
|
+
"block_type": 12, # bullet
|
|
279
|
+
"bullet": {
|
|
280
|
+
"elements": [{"text_run": {"content": line[1:].strip()}}],
|
|
281
|
+
"style": {}
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
elif line.strip():
|
|
285
|
+
blocks.append({
|
|
286
|
+
"block_type": 2, # paragraph
|
|
287
|
+
"paragraph": {
|
|
288
|
+
"elements": [{"text_run": {"content": line}}],
|
|
289
|
+
"style": {}
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
return blocks
|
|
294
|
+
|
|
295
|
+
def list_tools(self) -> List[Dict[str, Any]]:
|
|
296
|
+
"""列出所有可用工具"""
|
|
297
|
+
return [
|
|
298
|
+
{
|
|
299
|
+
"name": "create_doc",
|
|
300
|
+
"description": "创建飞书文档",
|
|
301
|
+
"parameters": {
|
|
302
|
+
"title": "文档标题",
|
|
303
|
+
"folder_token": "文件夹token (可选)"
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
"name": "append_doc",
|
|
308
|
+
"description": "向文档追加内容",
|
|
309
|
+
"parameters": {
|
|
310
|
+
"document_id": "文档ID",
|
|
311
|
+
"content": "内容 (Markdown格式)"
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
"name": "search_docs",
|
|
316
|
+
"description": "搜索飞书文档",
|
|
317
|
+
"parameters": {
|
|
318
|
+
"query": "搜索关键词",
|
|
319
|
+
"count": "返回数量"
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"name": "send_message",
|
|
324
|
+
"description": "发送飞书消息",
|
|
325
|
+
"parameters": {
|
|
326
|
+
"chat_id": "群ID",
|
|
327
|
+
"content": "消息内容",
|
|
328
|
+
"msg_type": "消息类型"
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
]
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP 工具 - 基金数据
|
|
3
|
+
私募/公募基金数据接口
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
from typing import List, Dict, Any, Optional
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
# 导入投资系统数据库连接
|
|
13
|
+
sys.path.insert(0, '/Users/wanghui/.openclaw-agent-06/workspace/investment-strategy')
|
|
14
|
+
from backend.core.database import execute_remote_query
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FundMCP:
|
|
18
|
+
"""
|
|
19
|
+
基金数据 MCP
|
|
20
|
+
|
|
21
|
+
提供私募基金、公募基金数据接口
|
|
22
|
+
数据来源:远程数据库 (193.112.183.130:3306)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self.name = "fund"
|
|
27
|
+
|
|
28
|
+
# ==================== 私募基金接口 ====================
|
|
29
|
+
|
|
30
|
+
async def get_private_fund_managers(self, limit: int = 50) -> List[Dict[str, Any]]:
|
|
31
|
+
"""
|
|
32
|
+
获取私募基金管理人列表
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
limit: 返回数量上限,默认50
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
管理人信息列表
|
|
39
|
+
"""
|
|
40
|
+
try:
|
|
41
|
+
sql = """
|
|
42
|
+
SELECT * FROM 私募基金管理人信息表
|
|
43
|
+
LIMIT %s
|
|
44
|
+
"""
|
|
45
|
+
rows = execute_remote_query('product', sql, (limit,))
|
|
46
|
+
return [dict(r) for r in rows] if rows else []
|
|
47
|
+
except Exception as e:
|
|
48
|
+
logger.error(f"获取私募基金管理人失败: {e}")
|
|
49
|
+
return []
|
|
50
|
+
|
|
51
|
+
async def get_private_fund_nav(
|
|
52
|
+
self, fund_name: Optional[str] = None, limit: int = 100
|
|
53
|
+
) -> List[Dict[str, Any]]:
|
|
54
|
+
"""
|
|
55
|
+
获取私募产品净值
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
fund_name: 产品名称(模糊匹配),可选
|
|
59
|
+
limit: 返回数量上限,默认100
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
净值数据列表
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
if fund_name:
|
|
66
|
+
sql = """
|
|
67
|
+
SELECT * FROM 私募产品净值表
|
|
68
|
+
WHERE 产品名称 LIKE %s
|
|
69
|
+
ORDER BY 净值日期 DESC
|
|
70
|
+
LIMIT %s
|
|
71
|
+
"""
|
|
72
|
+
rows = execute_remote_query('product', sql, (f'%{fund_name}%', limit))
|
|
73
|
+
else:
|
|
74
|
+
sql = """
|
|
75
|
+
SELECT * FROM 私募产品净值表
|
|
76
|
+
ORDER BY 净值日期 DESC
|
|
77
|
+
LIMIT %s
|
|
78
|
+
"""
|
|
79
|
+
rows = execute_remote_query('product', sql, (limit,))
|
|
80
|
+
return [dict(r) for r in rows] if rows else []
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.error(f"获取私募产品净值失败: {e}")
|
|
83
|
+
return []
|
|
84
|
+
|
|
85
|
+
async def get_private_fund_info(self, fund_name: Optional[str] = None, limit: int = 50) -> List[Dict[str, Any]]:
|
|
86
|
+
"""
|
|
87
|
+
获取私募基金备案信息
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
fund_name: 产品名称(模糊匹配),可选
|
|
91
|
+
limit: 返回数量上限,默认50
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
备案信息列表
|
|
95
|
+
"""
|
|
96
|
+
try:
|
|
97
|
+
if fund_name:
|
|
98
|
+
sql = """
|
|
99
|
+
SELECT * FROM 所有私募基金备案信息表
|
|
100
|
+
WHERE 产品名称 LIKE %s
|
|
101
|
+
LIMIT %s
|
|
102
|
+
"""
|
|
103
|
+
rows = execute_remote_query('product', sql, (f'%{fund_name}%', limit))
|
|
104
|
+
else:
|
|
105
|
+
sql = """
|
|
106
|
+
SELECT * FROM 所有私募基金备案信息表
|
|
107
|
+
LIMIT %s
|
|
108
|
+
"""
|
|
109
|
+
rows = execute_remote_query('product', sql, (limit,))
|
|
110
|
+
return [dict(r) for r in rows] if rows else []
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.error(f"获取私募基金备案信息失败: {e}")
|
|
113
|
+
return []
|
|
114
|
+
|
|
115
|
+
# ==================== 公募基金接口 ====================
|
|
116
|
+
|
|
117
|
+
async def get_public_fund_info(
|
|
118
|
+
self, fund_code: Optional[str] = None, limit: int = 50
|
|
119
|
+
) -> List[Dict[str, Any]]:
|
|
120
|
+
"""
|
|
121
|
+
获取公募基金基本信息
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
fund_code: 基金代码,精确匹配;为空时返回列表
|
|
125
|
+
limit: 返回数量上限,默认50
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
基金基本信息列表
|
|
129
|
+
"""
|
|
130
|
+
try:
|
|
131
|
+
if fund_code:
|
|
132
|
+
sql = """
|
|
133
|
+
SELECT * FROM 公募基金基本信息表
|
|
134
|
+
WHERE 基金代码 = %s
|
|
135
|
+
"""
|
|
136
|
+
rows = execute_remote_query('product', sql, (fund_code,))
|
|
137
|
+
else:
|
|
138
|
+
sql = """
|
|
139
|
+
SELECT * FROM 公募基金基本信息表
|
|
140
|
+
LIMIT %s
|
|
141
|
+
"""
|
|
142
|
+
rows = execute_remote_query('product', sql, (limit,))
|
|
143
|
+
return [dict(r) for r in rows] if rows else []
|
|
144
|
+
except Exception as e:
|
|
145
|
+
logger.error(f"获取公募基金信息失败: {e}")
|
|
146
|
+
return []
|
|
147
|
+
|
|
148
|
+
async def get_public_fund_nav(
|
|
149
|
+
self, fund_code: str, limit: int = 100
|
|
150
|
+
) -> List[Dict[str, Any]]:
|
|
151
|
+
"""
|
|
152
|
+
获取公募基金净值
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
fund_code: 基金代码(必填)
|
|
156
|
+
limit: 返回数量上限,默认100
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
净值数据列表
|
|
160
|
+
"""
|
|
161
|
+
try:
|
|
162
|
+
if not fund_code:
|
|
163
|
+
return []
|
|
164
|
+
sql = """
|
|
165
|
+
SELECT * FROM 公募基金净值表
|
|
166
|
+
WHERE 基金代码 = %s
|
|
167
|
+
ORDER BY 净值日期 DESC
|
|
168
|
+
LIMIT %s
|
|
169
|
+
"""
|
|
170
|
+
rows = execute_remote_query('product', sql, (fund_code, limit))
|
|
171
|
+
return [dict(r) for r in rows] if rows else []
|
|
172
|
+
except Exception as e:
|
|
173
|
+
logger.error(f"获取公募基金净值失败: {e}")
|
|
174
|
+
return []
|
|
175
|
+
|
|
176
|
+
async def get_public_fund_holdings(
|
|
177
|
+
self, fund_code: str, limit: int = 50
|
|
178
|
+
) -> List[Dict[str, Any]]:
|
|
179
|
+
"""
|
|
180
|
+
获取公募基金个股持仓汇总
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
fund_code: 基金代码
|
|
184
|
+
limit: 返回数量上限,默认50
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
持仓汇总列表
|
|
188
|
+
"""
|
|
189
|
+
try:
|
|
190
|
+
if not fund_code:
|
|
191
|
+
return []
|
|
192
|
+
sql = """
|
|
193
|
+
SELECT * FROM 公募基金个股持仓汇总表
|
|
194
|
+
WHERE 基金代码 = %s
|
|
195
|
+
LIMIT %s
|
|
196
|
+
"""
|
|
197
|
+
rows = execute_remote_query('product', sql, (fund_code, limit))
|
|
198
|
+
return [dict(r) for r in rows] if rows else []
|
|
199
|
+
except Exception as e:
|
|
200
|
+
logger.error(f"获取公募基金持仓失败: {e}")
|
|
201
|
+
return []
|
|
202
|
+
|
|
203
|
+
async def get_etf_list(self, limit: int = 100) -> List[Dict[str, Any]]:
|
|
204
|
+
"""
|
|
205
|
+
获取ETF列表
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
limit: 返回数量上限,默认100
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
ETF基金列表
|
|
212
|
+
"""
|
|
213
|
+
try:
|
|
214
|
+
sql = """
|
|
215
|
+
SELECT * FROM ETF基金净值表
|
|
216
|
+
LIMIT %s
|
|
217
|
+
"""
|
|
218
|
+
rows = execute_remote_query('product', sql, (limit,))
|
|
219
|
+
return [dict(r) for r in rows] if rows else []
|
|
220
|
+
except Exception as e:
|
|
221
|
+
logger.error(f"获取ETF列表失败: {e}")
|
|
222
|
+
return []
|
|
223
|
+
|
|
224
|
+
async def get_fund_manager_info(
|
|
225
|
+
self, manager_name: Optional[str] = None, limit: int = 50
|
|
226
|
+
) -> List[Dict[str, Any]]:
|
|
227
|
+
"""
|
|
228
|
+
获取基金经理信息
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
manager_name: 基金经理姓名(模糊匹配),可选
|
|
232
|
+
limit: 返回数量上限,默认50
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
基金经理信息列表
|
|
236
|
+
"""
|
|
237
|
+
try:
|
|
238
|
+
if manager_name:
|
|
239
|
+
sql = """
|
|
240
|
+
SELECT * FROM 基金经理信息对照表
|
|
241
|
+
WHERE 基金经理 LIKE %s
|
|
242
|
+
LIMIT %s
|
|
243
|
+
"""
|
|
244
|
+
rows = execute_remote_query('product', sql, (f'%{manager_name}%', limit))
|
|
245
|
+
else:
|
|
246
|
+
sql = """
|
|
247
|
+
SELECT * FROM 基金经理信息对照表
|
|
248
|
+
LIMIT %s
|
|
249
|
+
"""
|
|
250
|
+
rows = execute_remote_query('product', sql, (limit,))
|
|
251
|
+
return [dict(r) for r in rows] if rows else []
|
|
252
|
+
except Exception as e:
|
|
253
|
+
logger.error(f"获取基金经理信息失败: {e}")
|
|
254
|
+
return []
|
|
255
|
+
|
|
256
|
+
# ==================== 工具列表 ====================
|
|
257
|
+
|
|
258
|
+
def list_tools(self) -> List[Dict[str, Any]]:
|
|
259
|
+
"""列出所有可用工具"""
|
|
260
|
+
return [
|
|
261
|
+
{
|
|
262
|
+
"name": "get_private_fund_managers",
|
|
263
|
+
"description": "获取私募基金管理人列表",
|
|
264
|
+
"parameters": {
|
|
265
|
+
"limit": "返回数量上限,默认50"
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
"name": "get_private_fund_nav",
|
|
270
|
+
"description": "获取私募产品净值",
|
|
271
|
+
"parameters": {
|
|
272
|
+
"fund_name": "产品名称(模糊匹配),可选",
|
|
273
|
+
"limit": "返回数量上限,默认100"
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
"name": "get_private_fund_info",
|
|
278
|
+
"description": "获取私募基金备案信息",
|
|
279
|
+
"parameters": {
|
|
280
|
+
"fund_name": "产品名称(模糊匹配),可选",
|
|
281
|
+
"limit": "返回数量上限,默认50"
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"name": "get_public_fund_info",
|
|
286
|
+
"description": "获取公募基金基本信息",
|
|
287
|
+
"parameters": {
|
|
288
|
+
"fund_code": "基金代码,精确匹配;为空时返回列表",
|
|
289
|
+
"limit": "返回数量上限,默认50"
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"name": "get_public_fund_nav",
|
|
294
|
+
"description": "获取公募基金净值",
|
|
295
|
+
"parameters": {
|
|
296
|
+
"fund_code": "基金代码(必填)",
|
|
297
|
+
"limit": "返回数量上限,默认100"
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
"name": "get_public_fund_holdings",
|
|
302
|
+
"description": "获取公募基金个股持仓汇总",
|
|
303
|
+
"parameters": {
|
|
304
|
+
"fund_code": "基金代码",
|
|
305
|
+
"limit": "返回数量上限,默认50"
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
"name": "get_etf_list",
|
|
310
|
+
"description": "获取ETF列表",
|
|
311
|
+
"parameters": {
|
|
312
|
+
"limit": "返回数量上限,默认100"
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
"name": "get_fund_manager_info",
|
|
317
|
+
"description": "获取基金经理信息",
|
|
318
|
+
"parameters": {
|
|
319
|
+
"manager_name": "基金经理姓名(模糊匹配),可选",
|
|
320
|
+
"limit": "返回数量上限,默认50"
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
]
|