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,228 @@
1
+ """
2
+ /report 命令 - 报告生成
3
+ """
4
+
5
+ from typing import Optional
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ from src.brain import Brain
9
+ from src.mcp.registry import MCPRegistry
10
+
11
+
12
+ class ReportCommand:
13
+ """
14
+ 报告生成命令处理器
15
+
16
+ 用法:
17
+ /report 月度总结
18
+ /report 季度策略
19
+ /report 个股研究 贵州茅台
20
+ """
21
+
22
+ def __init__(self, brain: Brain, mcp: MCPRegistry):
23
+ self.brain = brain
24
+ self.mcp = mcp
25
+ self.knowledge_dir = Path("~/.openclaw-agent-06/workspace/erlangshen/knowledge/reports").expanduser()
26
+ self.knowledge_dir.mkdir(parents=True, exist_ok=True)
27
+
28
+ async def execute(self, args: str) -> str:
29
+ """
30
+ 生成报告
31
+
32
+ Args:
33
+ args: 报告类型和主题
34
+
35
+ Returns:
36
+ 报告内容
37
+ """
38
+ if not args:
39
+ return self._help()
40
+
41
+ # 解析报告类型
42
+ report_type = self._parse_report_type(args)
43
+
44
+ # 生成报告
45
+ report_content = await self._generate_report(args, report_type)
46
+
47
+ # 保存报告
48
+ report_path = await self._save_report(args, report_content)
49
+
50
+ return f"{report_content}\n\n📁 报告已保存至: {report_path}"
51
+
52
+ def _parse_report_type(self, args: str) -> str:
53
+ """解析报告类型"""
54
+ args_lower = args.lower()
55
+
56
+ if "月度" in args or "月报" in args:
57
+ return "月度报告"
58
+ elif "季度" in args or "季报" in args:
59
+ return "季度报告"
60
+ elif "年度" in args or "年报" in args:
61
+ return "年度报告"
62
+ elif "周" in args or "周报" in args:
63
+ return "周报"
64
+ elif "个股" in args or "研究" in args:
65
+ return "个股研究报告"
66
+ elif "策略" in args:
67
+ return "策略报告"
68
+ elif "宏观" in args:
69
+ return "宏观报告"
70
+ else:
71
+ return "专题报告"
72
+
73
+ async def _generate_report(self, args: str, report_type: str) -> str:
74
+ """生成报告内容"""
75
+ templates = {
76
+ "月度报告": """# {title}
77
+
78
+ **生成时间**: {date}
79
+
80
+ ## 本月回顾
81
+
82
+ ### 宏观经济
83
+ (本月宏观经济运行情况)
84
+
85
+ ### 市场表现
86
+ (本月市场走势回顾)
87
+
88
+ ### 重要事件
89
+ (本月重要事件梳理)
90
+
91
+ ## 下月展望
92
+
93
+ ### 宏观预判
94
+ (对下月宏观经济的判断)
95
+
96
+ ### 市场观点
97
+ (对下月市场的展望)
98
+
99
+ ### 投资建议
100
+ (具体投资建议)
101
+ """,
102
+ "季度报告": """# {title}
103
+
104
+ **生成时间**: {date}
105
+
106
+ ## 本季度概述
107
+
108
+ ### 宏观经济
109
+ (本季度宏观经济运行情况)
110
+
111
+ ### 市场表现
112
+ (本季度市场走势回顾)
113
+
114
+ ### 业绩归因
115
+ (组合业绩归因分析)
116
+
117
+ ## 下季度展望
118
+
119
+ ### 宏观预判
120
+ (对下季度宏观经济的判断)
121
+
122
+ ### 市场观点
123
+ (对下季度市场的展望)
124
+
125
+ ### 配置建议
126
+ (大类资产配置建议)
127
+ """,
128
+ "个股研究报告": """# {title}
129
+
130
+ **生成时间**: {date}
131
+
132
+ ## 投资摘要
133
+
134
+ ### 核心观点
135
+ (1-2句话概括投资逻辑)
136
+
137
+ ### 关键数据
138
+ | 指标 | 数值 |
139
+ |------|------|
140
+ | 当前价格 | - |
141
+ | 目标价格 | - |
142
+ | 评级 | - |
143
+
144
+ ## 公司概况
145
+
146
+ ### 主营业务
147
+ (公司主要业务介绍)
148
+
149
+ ### 竞争优势
150
+ (公司的核心竞争优势)
151
+
152
+ ## 财务分析
153
+
154
+ ### 盈利能力
155
+ (营收、利润分析)
156
+
157
+ ### 成长性
158
+ (同比、环比分析)
159
+
160
+ ### 估值水平
161
+ (PE、PB分析)
162
+
163
+ ## 风险提示
164
+
165
+ ## 投资建议
166
+ """,
167
+ }
168
+
169
+ template = templates.get(report_type, templates["月度报告"])
170
+
171
+ # 填充模板
172
+ title = args.replace("月度", "").replace("季度", "").replace("报告", "").strip()
173
+ if not title:
174
+ title = report_type
175
+
176
+ content = template.format(
177
+ title=title,
178
+ date=datetime.now().strftime("%Y年%m月%d日")
179
+ )
180
+
181
+ # 使用LLM增强报告内容
182
+ enhanced = await self.brain.think(
183
+ prompt=f"""请基于以下主题生成{report_type}内容:
184
+
185
+ 主题:{args}
186
+
187
+ 请生成完整、专业的报告内容,替换下方模板中的占位内容:
188
+
189
+ {content}
190
+ """,
191
+ system="你是一位专业的投资研究报告撰写人,擅长生成结构清晰、数据翔实的投资报告。"
192
+ )
193
+
194
+ return enhanced if enhanced else content
195
+
196
+ async def _save_report(self, args: str, content: str) -> Path:
197
+ """保存报告"""
198
+ # 生成文件名
199
+ date_str = datetime.now().strftime("%Y%m%d_%H%M%S")
200
+ safe_title = "".join(c if c.isalnum() else "_" for c in args[:20])
201
+ filename = f"{date_str}_{safe_title}.md"
202
+
203
+ filepath = self.knowledge_dir / filename
204
+
205
+ with open(filepath, "w", encoding="utf-8") as f:
206
+ f.write(content)
207
+
208
+ return filepath
209
+
210
+ def _help(self) -> str:
211
+ """帮助信息"""
212
+ return """
213
+ /report - 报告生成命令
214
+
215
+ 用法:
216
+ /report <报告类型> [主题]
217
+
218
+ 示例:
219
+ /report 月度总结
220
+ /report 季度策略
221
+ /report 周报
222
+ /report 个股研究 贵州茅台
223
+ /report 宏观月报
224
+
225
+ 支持的报告类型:
226
+ 月度报告, 季度报告, 年度报告, 周报,
227
+ 个股研究报告, 策略报告, 宏观报告
228
+ """
@@ -0,0 +1,183 @@
1
+ """
2
+ /risk 命令 - 风险分析
3
+ """
4
+
5
+ from typing import Optional, Dict
6
+ from datetime import datetime
7
+ from src.brain import Brain
8
+ from src.mcp.registry import MCPRegistry
9
+
10
+
11
+ class RiskCommand:
12
+ """
13
+ 风险分析命令处理器
14
+
15
+ 用法:
16
+ /risk 市场风险
17
+ /risk 持仓风险
18
+ /risk 压力测试
19
+ """
20
+
21
+ def __init__(self, brain: Brain, mcp: MCPRegistry):
22
+ self.brain = brain
23
+ self.mcp = mcp
24
+
25
+ async def execute(self, args: str) -> str:
26
+ """
27
+ 执行风险分析
28
+
29
+ Args:
30
+ args: 分析类型
31
+
32
+ Returns:
33
+ 分析结果
34
+ """
35
+ if not args:
36
+ return self._help()
37
+
38
+ args_lower = args.lower()
39
+
40
+ if "市场" in args or "系统性" in args:
41
+ return await self._market_risk()
42
+ elif "持仓" in args or "个券" in args or "信用" in args:
43
+ return await self._position_risk()
44
+ elif "压力" in args or "测试" in args:
45
+ return await self._stress_test()
46
+ elif "流动性" in args:
47
+ return await self._liquidity_risk()
48
+ elif "VaR" in args or "风险价值" in args:
49
+ return await self._var_analysis()
50
+ else:
51
+ return await self._general_risk_analysis(args)
52
+
53
+ async def _market_risk(self) -> str:
54
+ """市场风险分析"""
55
+ prompt = """请分析当前市场风险状况:
56
+
57
+ 分析维度:
58
+ 1. 整体市场估值 (PE, PB分位)
59
+ 2. 情绪指标 (换手率、融资融券)
60
+ 3. 资金流向
61
+ 4. 外围市场联动
62
+ 5. 政策环境
63
+
64
+ 请给出:
65
+ - 当前市场风险等级 (低/中/高)
66
+ - 主要风险点
67
+ - 风险缓释建议
68
+ """
69
+ return await self.brain.analyze(prompt, framework="市场风险框架")
70
+
71
+ async def _position_risk(self) -> str:
72
+ """持仓风险分析"""
73
+ prompt = """请分析持仓风险:
74
+
75
+ 持仓情况示例:
76
+ - 单一股票最大持仓: 20%
77
+ - 行业集中度: 信息技术 35%
78
+ - 信用评级分布: AAA 50%, AA 30%, A 20%
79
+
80
+ 分析维度:
81
+ 1. 集中度风险 (个股、行业)
82
+ 2. 流动性风险
83
+ 3. 信用风险
84
+ 4. 杠杆风险
85
+
86
+ 请给出:
87
+ - 风险评估
88
+ - 风险分散建议
89
+ """
90
+ return await self.brain.analyze(prompt, framework="持仓风险框架")
91
+
92
+ async def _stress_test(self) -> str:
93
+ """压力测试"""
94
+ prompt = """请进行压力测试分析:
95
+
96
+ 测试情景:
97
+ 1. 情景A: 市场下跌20%
98
+ 2. 情景B: 利率上行100bp
99
+ 3. 情景C: 人民币贬值10%
100
+ 4. 情景D: 组合情景 (同时发生)
101
+
102
+ 请分析:
103
+ - 各情景下组合预期损失
104
+ - 最坏情况下的损失
105
+ - 风险缓释措施
106
+ """
107
+ return await self.brain.analyze(prompt, framework="压力测试框架")
108
+
109
+ async def _liquidity_risk(self) -> str:
110
+ """流动性风险分析"""
111
+ prompt = """请分析流动性风险:
112
+
113
+ 分析维度:
114
+ 1. 组合整体流动性
115
+ 2. 低流动性资产比例
116
+ 3. 持仓变现能力
117
+ 4. 申赎压力
118
+
119
+ 请给出:
120
+ - 流动性风险评估
121
+ - 流动性预警指标
122
+ - 流动性管理建议
123
+ """
124
+ return await self.brain.analyze(prompt, framework="流动性风险框架")
125
+
126
+ async def _var_analysis(self) -> str:
127
+ """VaR分析"""
128
+ prompt = """请进行VaR (Value at Risk) 分析:
129
+
130
+ 组合配置:
131
+ - 沪深300: 30%
132
+ - 中证500: 15%
133
+ - 恒生科技: 10%
134
+ - 纳斯达克: 10%
135
+ - 黄金: 10%
136
+ - 债券: 15%
137
+ - 现金: 10%
138
+
139
+ 请计算/估算:
140
+ 1. 1-day VaR (95%置信度)
141
+ 2. 10-day VaR
142
+ 3. Expected Shortfall (CVaR)
143
+ 4. 最大回撤估计
144
+
145
+ 说明分析方法假设。
146
+ """
147
+ return await self.brain.analyze(prompt, framework="VaR分析框架")
148
+
149
+ async def _general_risk_analysis(self, args: str) -> str:
150
+ """通用风险分析"""
151
+ prompt = f"""请分析以下风险相关问题:
152
+
153
+ 问题:{args}
154
+
155
+ 请提供专业的风险分析,包括:
156
+ 1. 风险识别
157
+ 2. 风险评估
158
+ 3. 风险缓释建议
159
+ """
160
+ return await self.brain.analyze(prompt, framework="风险管理框架")
161
+
162
+ def _help(self) -> str:
163
+ """帮助信息"""
164
+ return """
165
+ /risk - 风险分析命令
166
+
167
+ 用法:
168
+ /risk <风险类型>
169
+
170
+ 子命令:
171
+ /risk 市场风险 - 分析整体市场风险
172
+ /risk 持仓风险 - 分析持仓集中度风险
173
+ /risk 压力测试 - 情景压力测试
174
+ /risk 流动性风险 - 流动性风险分析
175
+ /risk VaR - 风险价值分析
176
+
177
+ 示例:
178
+ /risk 市场风险
179
+ /risk 持仓风险
180
+ /risk 压力测试
181
+ /risk 流动性风险
182
+ /risk VaR
183
+ """
@@ -0,0 +1,183 @@
1
+ """
2
+ /search 命令 - 搜索
3
+ """
4
+
5
+ from typing import Optional, List, Dict, Any
6
+ from pathlib import Path
7
+ from src.brain import Brain
8
+ from src.mcp.registry import MCPRegistry
9
+
10
+
11
+ class SearchCommand:
12
+ """
13
+ 搜索命令处理器
14
+
15
+ 用法:
16
+ /search 茅台
17
+ /search 宏观研报
18
+ /search 量化策略
19
+ """
20
+
21
+ def __init__(self, brain: Brain, mcp: MCPRegistry):
22
+ self.brain = brain
23
+ self.mcp = mcp
24
+ self.knowledge_dir = None
25
+
26
+ async def execute(self, args: str) -> str:
27
+ """
28
+ 执行搜索
29
+
30
+ Args:
31
+ args: 搜索关键词
32
+
33
+ Returns:
34
+ 搜索结果
35
+ """
36
+ if not args:
37
+ return self._help()
38
+
39
+ # 优先搜索本地知识库
40
+ local_results = await self._search_local(args)
41
+
42
+ # 如果本地没有,生成综合回答
43
+ if not local_results:
44
+ result = await self._generate_answer(args)
45
+ return result
46
+
47
+ return self._format_results(local_results)
48
+
49
+ async def _search_local(self, query: str) -> List[Dict[str, Any]]:
50
+ """搜索本地知识库"""
51
+ knowledge_base = Path("~/.openclaw-agent-06/workspace/erlangshen/knowledge").expanduser()
52
+
53
+ if not knowledge_base.exists():
54
+ return []
55
+
56
+ results = []
57
+
58
+ # 搜索纪要
59
+ memos_dir = knowledge_base / "memos"
60
+ if memos_dir.exists():
61
+ for file in memos_dir.glob("*.md"):
62
+ if self._matches_query(file, query):
63
+ results.append({
64
+ "type": "memo",
65
+ "title": file.stem,
66
+ "path": str(file),
67
+ "content": self._get_preview(file, query)
68
+ })
69
+
70
+ # 搜索报告
71
+ reports_dir = knowledge_base / "reports"
72
+ if reports_dir.exists():
73
+ for file in reports_dir.glob("*.md"):
74
+ if self._matches_query(file, query):
75
+ results.append({
76
+ "type": "report",
77
+ "title": file.stem,
78
+ "path": str(file),
79
+ "content": self._get_preview(file, query)
80
+ })
81
+
82
+ # 搜索洞察
83
+ insights_dir = knowledge_base / "insights"
84
+ if insights_dir.exists():
85
+ for file in insights_dir.glob("*.md"):
86
+ if self._matches_query(file, query):
87
+ results.append({
88
+ "type": "insight",
89
+ "title": file.stem,
90
+ "path": str(file),
91
+ "content": self._get_preview(file, query)
92
+ })
93
+
94
+ return results
95
+
96
+ def _matches_query(self, file: Path, query: str) -> bool:
97
+ """检查文件是否匹配查询"""
98
+ try:
99
+ content = file.read_text(encoding="utf-8").lower()
100
+ query_lower = query.lower()
101
+
102
+ # 简单的关键词匹配
103
+ keywords = query_lower.split()
104
+ for kw in keywords:
105
+ if kw in content:
106
+ return True
107
+
108
+ return False
109
+ except Exception:
110
+ return False
111
+
112
+ def _get_preview(self, file: Path, query: str, max_lines: int = 5) -> str:
113
+ """获取匹配内容预览"""
114
+ try:
115
+ content = file.read_text(encoding="utf-8")
116
+ lines = content.split("\n")
117
+
118
+ preview = []
119
+ query_lower = query.lower()
120
+
121
+ for line in lines:
122
+ if query_lower in line.lower():
123
+ preview.append(line.strip())
124
+
125
+ if len(preview) >= max_lines:
126
+ break
127
+
128
+ return "\n".join(preview) if preview else content[:200]
129
+ except Exception:
130
+ return ""
131
+
132
+ async def _generate_answer(self, query: str) -> str:
133
+ """生成综合回答"""
134
+ prompt = f"""请回答以下问题:
135
+
136
+ 问题:{query}
137
+
138
+ 请提供:
139
+ 1. 简洁直接的回答
140
+ 2. 相关背景信息
141
+ 3. 可能的行动建议
142
+
143
+ 如果你不知道,请明确说明。
144
+ """
145
+
146
+ return await self.brain.think(
147
+ prompt,
148
+ system="你是一个知识渊博的投资顾问。请简洁、专业地回答问题。"
149
+ )
150
+
151
+ def _format_results(self, results: List[Dict[str, Any]]) -> str:
152
+ """格式化搜索结果"""
153
+ output = f"找到 {len(results)} 条相关结果:\n\n"
154
+
155
+ for i, result in enumerate(results, 1):
156
+ output += f"{i}. 【{result['type']}】{result['title']}\n"
157
+ output += f" {result['content'][:100]}...\n"
158
+ output += f" 📁 {result['path']}\n\n"
159
+
160
+ return output
161
+
162
+ def _help(self) -> str:
163
+ """帮助信息"""
164
+ return """
165
+ /search - 搜索命令
166
+
167
+ 用法:
168
+ /search <关键词>
169
+
170
+ 示例:
171
+ /search 茅台
172
+ /search 宏观
173
+ /search 量化策略
174
+ /search 美联储
175
+
176
+ 搜索范围:
177
+ - 本地知识库 (纪要、报告、洞察)
178
+ - 实时生成回答
179
+
180
+ 说明:
181
+ 搜索会优先查找本地已保存的纪要和报告,
182
+ 如果没有找到相关记录,会基于LLM生成回答。
183
+ """
@@ -0,0 +1,124 @@
1
+ """
2
+ /stock 命令 - 股票分析
3
+ """
4
+
5
+ from typing import Optional, List
6
+ import re
7
+ from src.agents.equity import EquityAgent
8
+ from src.brain import Brain
9
+ from src.mcp.registry import MCPRegistry
10
+
11
+
12
+ class StockCommand:
13
+ """
14
+ 股票分析命令处理器
15
+
16
+ 用法:
17
+ /stock 贵州茅台
18
+ /stock 000001
19
+ /stock 腾讯 阿里巴巴
20
+ """
21
+
22
+ def __init__(self, brain: Brain, mcp: MCPRegistry):
23
+ self.brain = brain
24
+ self.mcp = mcp
25
+ self.agent = EquityAgent(brain, mcp)
26
+
27
+ async def execute(self, args: str) -> str:
28
+ """
29
+ 执行股票分析
30
+
31
+ Args:
32
+ args: 分析查询
33
+
34
+ Returns:
35
+ 分析结果
36
+ """
37
+ if not args:
38
+ return self._help()
39
+
40
+ # 提取股票代码或名称
41
+ symbols = self._extract_symbols(args)
42
+
43
+ if len(symbols) == 1:
44
+ return await self.agent.analyze_stock(symbols[0])
45
+ elif len(symbols) > 1:
46
+ return await self.agent.compare_stocks(symbols)
47
+ else:
48
+ # 无法识别股票,分析查询意图
49
+ return await self.agent.analyze_sector(args)
50
+
51
+ def _extract_symbols(self, query: str) -> List[str]:
52
+ """从查询中提取股票代码"""
53
+ # A股代码模式 (6位数字)
54
+ a_stock_pattern = r'\b(\d{6})\b'
55
+ a_stocks = re.findall(a_stock_pattern, query)
56
+
57
+ # 常见股票名称映射
58
+ name_to_symbol = {
59
+ "茅台": "600519.SH",
60
+ "贵州茅台": "600519.SH",
61
+ "平安": "601318.SH",
62
+ "中国平安": "601318.SH",
63
+ "招商": "600036.SH",
64
+ "招商银行": "600036.SH",
65
+ "平安银行": "000001.SZ",
66
+ "万科": "000002.SZ",
67
+ "平安好医生": "1833.HK",
68
+ "腾讯": "0700.HK",
69
+ "阿里巴巴": "9988.HK",
70
+ "美团": "3690.HK",
71
+ "比亚迪": "002594.SZ",
72
+ "美的": "000333.SZ",
73
+ "美的集团": "000333.SZ",
74
+ "恒瑞": "600276.SH",
75
+ "恒瑞医药": "600276.SH",
76
+ "中信": "600030.SH",
77
+ "中芯": "688981.SH",
78
+ "中芯国际": "688981.SH",
79
+ "宁德": "300750.SZ",
80
+ "宁德时代": "300750.SZ",
81
+ "隆基": "601012.SH",
82
+ "隆基绿能": "601012.SH",
83
+ }
84
+
85
+ symbols = []
86
+
87
+ # 检查名称
88
+ for name, symbol in name_to_symbol.items():
89
+ if name in query:
90
+ symbols.append(symbol)
91
+
92
+ # 添加数字代码
93
+ for code in a_stocks:
94
+ # 判断交易所
95
+ if code.startswith(('0', '3')):
96
+ symbol = f"{code}.SZ"
97
+ else:
98
+ symbol = f"{code}.SH"
99
+
100
+ if symbol not in symbols:
101
+ symbols.append(symbol)
102
+
103
+ return symbols
104
+
105
+ def _help(self) -> str:
106
+ """帮助信息"""
107
+ return """
108
+ /stock - 股票分析命令
109
+
110
+ 用法:
111
+ /stock <股票名称或代码>
112
+ /stock <股票1> <股票2> ... (比较)
113
+
114
+ 示例:
115
+ /stock 贵州茅台
116
+ /stock 600519
117
+ /stock 腾讯 阿里巴巴 美团
118
+ /stock 比亚迪
119
+
120
+ 支持:
121
+ - A股代码 (自动识别沪深)
122
+ - 港股代码
123
+ - 股票名称 (部分支持)
124
+ """