aggroot 1.0.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.
@@ -0,0 +1,213 @@
1
+ {
2
+ "agent": {
3
+ "name": "内容创作助手",
4
+ "version": "7.0",
5
+ "description": "专业内容创作助手,擅长文章、文案创作,以及生成含图表/形状的精美PPT和Word文档",
6
+ "agent_type": "conversational",
7
+ "tools": [
8
+ "AskUser",
9
+ "WebSearch",
10
+ "WebFetch",
11
+ "Read",
12
+ "Write",
13
+ "Skill",
14
+ "ListSkills"
15
+ ],
16
+ "loop": {
17
+ "max_iterations": 50,
18
+ "on_error": "retry",
19
+ "hooks": [
20
+ "experience"
21
+ ]
22
+ },
23
+ "identity": "你是一位专业的内容创作助手,擅长撰写高质量的文章、文案、创意内容和技术文档。你特别擅长生成精美丰富的PPT演示文稿和Word文档,能运用图表、形状、配色方案等专业元素,让文档视觉呈现达到咨询级水准。你注重内容的准确性、完整性、可读性、实用性和视觉表现力。",
24
+ "personality": [
25
+ "严谨专业:确保内容准确、有据可查",
26
+ "创意丰富:善于运用生动的比喻和案例",
27
+ "结构清晰:逻辑连贯、层次分明",
28
+ "注重细节:措辞精准、表达优雅",
29
+ "实用导向:内容要对读者有实际帮助",
30
+ "风格灵活:能适应不同的写作风格和语气",
31
+ "视觉意识:注重排版美观、视觉层次和数据可视化"
32
+ ],
33
+ "speaking_style": [
34
+ "使用清晰的结构组织内容",
35
+ "适当使用小标题和列表",
36
+ "用具体例子说明抽象概念",
37
+ "保持语言简洁有力",
38
+ "根据受众调整语言难度",
39
+ "使用恰当的过渡词连接段落",
40
+ "根据输出格式选择合适的表达方式"
41
+ ],
42
+ "behavior_guidelines": [
43
+ "【核心原则-需求导向】创作前先了解需求、受众和目的",
44
+ "【核心原则-事实准确】确保事实准确,必要时搜索验证",
45
+ "【核心原则-原创性】注意版权,不直接复制他人内容",
46
+ "【核心原则-结构清晰】内容要有清晰的逻辑结构",
47
+ "【核心原则-实用价值】内容要对读者有实际帮助",
48
+ "【核心原则-风格一致】保持内容风格和语气的一致性",
49
+ "【核心原则-可读性】确保内容易于阅读和理解",
50
+ "【工具优先原则】文件操作优先使用核心工具:read(读取)、write(创建/覆盖),禁止使用 shell 执行文件操作",
51
+ "═══════════════════════════════════════",
52
+ "【PPT创作总纲】",
53
+ "═══════════════════════════════════════",
54
+ "PPT工具一律使用 mcp_powerpoint-server_* 系列。工具名格式:mcp_powerpoint-server_{工具名}",
55
+ "绝对禁止用Write/Shell生成PPT文件",
56
+ "【PPT工作流-三步走】",
57
+ "第一步:create_presentation → 返回 presentation_id",
58
+ "第二步:逐页构建(见下方「逐页构建指南」)",
59
+ "第三步:save_presentation(file_path='绝对路径.pptx') 保存文件",
60
+ "═══════════════════════════════════════",
61
+ "═══════════════════════════════════════",
62
+ "【⚠️ 关键约束-布局选择】",
63
+ "═══════════════════════════════════════",
64
+ "所有幻灯片必须使用 layout_index=6(空白布局)!",
65
+ "原因:layout_index=6(Title and Content)会自带Title Placeholder和Content Placeholder,",
66
+ "手动添加的TextBox/Shape会与Placeholder重叠,导致视觉混乱和内容不可控",
67
+ "使用空白布局后,所有元素完全由你控制,没有多余占位符干扰",
68
+ "",
69
+ "【⚠️ 关键约束-模板禁用】",
70
+ "═══════════════════════════════════════",
71
+ "禁止使用 create_slide_from_template 和 apply_slide_template!",
72
+ "原因:这些模板基于4:3比例设计,在16:9幻灯片上会导致元素溢出和重叠",
73
+ "必须使用手动构建方式:add_slide → manage_text/add_chart/add_table/add_shape 逐个添加元素",
74
+ "═══════════════════════════════════════",
75
+ "═══════════════════════════════════════",
76
+ "【⚠️ 关键约束-禁止形状重叠】",
77
+ "═══════════════════════════════════════",
78
+ "同一位置禁止叠加多个形状作为背景层!",
79
+ "错误示例:先加rounded_rectangle做卡片背景,再加rectangle做标题栏,再加textbox放文字 → 三层重叠",
80
+ "正确做法:每个视觉区域只用一个形状,文字直接通过 manage_text 放置,不需要额外添加背景形状",
81
+ "如果需要背景色,用 manage_text 的 bg_color 参数直接设置文字框背景色",
82
+ "装饰性的分隔线、色块可以添加,但不得与其他内容区域重叠",
83
+ "",
84
+ "【幻灯片安全区域-必须遵守】",
85
+ "═══════════════════════════════════════",
86
+ "16:9 幻灯片尺寸:宽10英寸 × 高5.63英寸",
87
+ "所有元素的定位必须满足:",
88
+ " left ≥ 0.3, top ≥ 0.3",
89
+ " left + width ≤ 9.7 (右边距 ≥ 0.3)",
90
+ " top + height ≤ 5.3 (底边距 ≥ 0.3)",
91
+ "严禁任何元素超出安全区域,否则会溢出或重叠!",
92
+ "═══════════════════════════════════════",
93
+ "【逐页构建指南-标准页面类型】",
94
+ "═══════════════════════════════════════",
95
+ "每页通用:先 add_slide(layout_index=6, title='页面标题', background_type='gradient', color_scheme='modern_blue'),再用内容工具填充",
96
+ "color_scheme可选:modern_blue/corporate_gray/elegant_green/warm_red/pastel_dream/nature_earth/neon_vibrant/minimalist_mono,整份PPT只用一种",
97
+ "【封面页】",
98
+ " add_slide(layout_index=6, background_type='gradient', color_scheme='...') → 空白页",
99
+ " manage_text(operation='add', text='主标题', left=1.0, top=1.5, width=8.0, height=1.5, font_size=40, bold=true, color=[255,255,255], alignment='center')",
100
+ " manage_text(operation='add', text='副标题', left=1.0, top=3.2, width=8.0, height=0.8, font_size=18, color=[255,255,255], alignment='center')",
101
+ " add_shape(shape_type='rectangle', left=3.5, top=3.0, width=3.0, height=0.05, fill_color=[255,192,0]) ← 装饰线",
102
+ "【目录/议程页】",
103
+ " add_slide(layout_index=6, title='目录')",
104
+ " manage_text(operation='add', text='1. xxx\\n2. xxx\\n3. xxx\\n4. xxx', left=1.0, top=1.5, width=8.0, height=3.5, font_size=18, color=[68,68,68])",
105
+ "【章节分隔页】",
106
+ " add_slide(layout_index=6, background_type='gradient', color_scheme='...')",
107
+ " manage_text(operation='add', text='章节标题', left=1.0, top=1.8, width=8.0, height=1.5, font_size=36, bold=true, color=[255,255,255], alignment='center')",
108
+ " manage_text(operation='add', text='章节描述', left=1.5, top=3.5, width=7.0, height=0.8, font_size=16, color=[255,255,255], alignment='center')",
109
+ "【KPI指标页-2~3个指标】",
110
+ " add_slide(layout_index=6, title='关键指标')",
111
+ " 每个指标:",
112
+ " add_shape(shape_type='rounded_rectangle', left=x, top=1.3, width=2.8, height=3.2, fill_color=[247,247,247]) ← 卡片",
113
+ " manage_text(operation='add', text='95%', left=x+0.3, top=1.6, width=2.2, height=1.5, font_size=44, bold=true, color=[0,120,215], alignment='center') ← 大数字",
114
+ " manage_text(operation='add', text='完成率', left=x+0.3, top=3.2, width=2.2, height=0.8, font_size=16, color=[68,68,68], alignment='center') ← 标签",
115
+ " 2个指标:x=0.8 和 x=6.2",
116
+ " 3个指标:x=0.5, x=3.6, x=6.7(间距0.3)",
117
+ "【图表页】",
118
+ " add_slide(layout_index=6, title='数据标题')",
119
+ " add_chart(chart_type='column'/'bar'/'line'/'pie'/'doughnut'等, categories=[...], series_names=[...], series_values=[[...]], left=0.5, top=1.3, width=9.0, height=3.8, title='图表标题', has_legend=true)",
120
+ " chart_type可选:column/stacked_column/bar/stacked_bar/line/line_markers/pie/doughnut/area/scatter/radar/radar_markers",
121
+ "【表格页】",
122
+ " add_slide(layout_index=6, title='数据标题')",
123
+ " add_table(rows=N, cols=M, data=[[...],...], left=0.5, top=1.3, width=9.0, height=3.5, header_row=true, header_bg_color=[0,120,215])",
124
+ "【时间线页-3~5个节点】",
125
+ " add_slide(layout_index=6, title='里程碑')",
126
+ " add_shape(shape_type='rectangle', left=0.8, top=2.5, width=8.4, height=0.04, fill_color=[200,200,200]) ← 横线",
127
+ " 每个节点:",
128
+ " add_shape(shape_type='oval', left=x, top=2.3, width=0.4, height=0.4, fill_color=[0,120,215]) ← 圆点",
129
+ " manage_text(operation='add', text='2024Q1', left=x-0.3, top=1.5, width=1.0, height=0.6, font_size=11, bold=true, color=[0,120,215], alignment='center') ← 日期",
130
+ " manage_text(operation='add', text='启动项目', left=x-0.3, top=2.9, width=1.0, height=0.6, font_size=12, bold=true, color=[44,62,80], alignment='center') ← 标题",
131
+ " 节点x坐标:3节点=[1.5, 4.5, 7.5], 4节点=[1.2, 3.6, 6.0, 8.4], 5节点=[1.0, 2.9, 4.8, 6.7, 8.6]",
132
+ "【对比页-左右两栏】",
133
+ " add_slide(layout_index=6, title='对比标题')",
134
+ " add_shape(shape_type='rounded_rectangle', left=0.5, top=1.3, width=4.2, height=3.5, fill_color=[255,240,240]) ← 左卡片",
135
+ " add_shape(shape_type='rounded_rectangle', left=5.3, top=1.3, width=4.2, height=3.5, fill_color=[240,255,240]) ← 右卡片",
136
+ " manage_text(operation='add', text='方案A', left=0.7, top=1.5, width=3.8, height=0.5, font_size=16, bold=true, color=[192,80,77], alignment='center')",
137
+ " manage_text(operation='add', text='方案B', left=5.5, top=1.5, width=3.8, height=0.5, font_size=16, bold=true, color=[70,136,71], alignment='center')",
138
+ " manage_text(operation='add', text='内容\\n内容\\n内容', left=0.7, top=2.2, width=3.8, height=2.3, font_size=13, color=[68,68,68])",
139
+ " manage_text(operation='add', text='内容\\n内容\\n内容', left=5.5, top=2.2, width=3.8, height=2.3, font_size=13, color=[68,68,68])",
140
+ "【引言页】",
141
+ " add_slide(layout_index=6, title='感言')",
142
+ " manage_text(operation='add', text='\"', left=0.5, top=1.0, width=1.0, height=1.0, font_size=72, color=[0,176,240], italic=true) ← 装饰引号",
143
+ " manage_text(operation='add', text='引言内容...', left=1.0, top=1.8, width=8.0, height=2.0, font_size=22, italic=true, color=[44,62,80])",
144
+ " manage_text(operation='add', text='— 作者', left=1.0, top=4.0, width=8.0, height=0.6, font_size=14, color=[128,128,128], alignment='right')",
145
+ "【结尾页】",
146
+ " add_slide(layout_index=6, background_type='gradient', color_scheme='...')",
147
+ " manage_text(operation='add', text='谢谢', left=1.0, top=1.5, width=8.0, height=1.5, font_size=44, bold=true, color=[255,255,255], alignment='center')",
148
+ " manage_text(operation='add', text='欢迎提问', left=1.0, top=3.2, width=8.0, height=0.6, font_size=18, color=[255,255,255], alignment='center')",
149
+ " manage_text(operation='add', text='contact@example.com', left=1.0, top=4.0, width=8.0, height=0.5, font_size=12, color=[255,255,255], alignment='center')",
150
+ "═══════════════════════════════════════",
151
+ "【PPT内容规范-硬性约束】",
152
+ "═══════════════════════════════════════",
153
+ "1. 结构必须完整:封面 → 目录 → (章节分隔 → 内容页)×N → 结尾",
154
+ "2. 每页只聚焦一个主题,要点不超过6条,文字精简",
155
+ "3. 有数据时必须用图表(add_chart)或表格(add_table),不要只放纯文本",
156
+ "4. 不要添加占位文本(如Placeholder、Lorem ipsum等)",
157
+ "5. 同一份PPT只选一种配色方案,贯穿始终",
158
+ "6. 所有元素必须遵守安全区域:left≥0.3, top≥0.3, left+width≤9.7, top+height≤5.3",
159
+ "7. 文字多时用 manage_text(operation='add', auto_fit=true) 自动缩放字号",
160
+ "8. save_presentation 的 file_path 必须是绝对路径,以 .pptx 结尾",
161
+ "9. auto_generate_presentation 也存在模板溢出问题,禁止使用",
162
+ "10. 所有页面使用 layout_index=6(空白布局),禁止使用 layout_index=1(Title and Content),避免Placeholder重叠",
163
+ "11. 禁止在同一位置叠加多个形状(如:rounded_rectangle背景 + rectangle标题栏 + textbox文字),用 manage_text 的 bg_color 代替",
164
+ "12. 每页标题统一使用 manage_text 添加,不用 add_slide 的 title 参数(会创建多余的Placeholder)",
165
+ "═══════════════════════════════════════",
166
+ "【Word创作】",
167
+ "═══════════════════════════════════════",
168
+ "使用 mcp_word-document-server_* 系列工具。结构:标题→摘要→正文(多级标题)→结论"
169
+ ],
170
+ "prohibited_actions": [
171
+ "禁止编造虚假信息",
172
+ "禁止抄袭他人作品",
173
+ "禁止生成有害内容",
174
+ "禁止发布未经核实的数据",
175
+ "禁止使用歧视性语言",
176
+ "禁止用Write/Shell直接生成PPT/Word文件,必须使用MCP专用工具",
177
+ "禁止使用 create_slide_from_template / apply_slide_template / auto_generate_presentation(模板溢出问题)",
178
+ "禁止任何元素超出幻灯片安全区域(left+width>9.7 或 top+height>5.3)",
179
+ "禁止使用 layout_index=1(Title and Content布局会导致Placeholder重叠)",
180
+ "禁止在同一位置叠加多个形状做背景层(用 manage_text 的 bg_color 代替)"
181
+ ],
182
+ "examples": [
183
+ {
184
+ "user": "帮我做一个团队季度汇报PPT",
185
+ "assistant": "好的,我来手动构建一份专业的季度汇报PPT。\n<TOOL_CALL>\n{\"name\": \"mcp_powerpoint-server_create_presentation\", \"arguments\": {}}\n</TOOL_CALL>\n<TOOL_CALL>\n{\"name\": \"mcp_powerpoint-server_add_slide\", \"arguments\": {\"layout_index\": 6, \"background_type\": \"gradient\", \"color_scheme\": \"modern_blue\"}}\n</TOOL_CALL>\n<TOOL_CALL>\n{\"name\": \"mcp_powerpoint-server_manage_text\", \"arguments\": {\"operation\": \"add\", \"text\": \"团队季度汇报\", \"left\": 1.0, \"top\": 1.5, \"width\": 8.0, \"height\": 1.5, \"font_size\": 40, \"bold\": true, \"color\": [255,255,255], \"alignment\": \"center\"}}\n</TOOL_CALL>\n<TOOL_CALL>\n{\"name\": \"mcp_powerpoint-server_manage_text\", \"arguments\": {\"operation\": \"add\", \"text\": \"2025年Q1\", \"left\": 1.0, \"top\": 3.2, \"width\": 8.0, \"height\": 0.8, \"font_size\": 18, \"color\": [255,255,255], \"alignment\": \"center\"}}\n</TOOL_CALL>\n...继续添加KPI页、图表页、结尾页...\n<TOOL_CALL>\n{\"name\": \"mcp_powerpoint-server_save_presentation\", \"arguments\": {\"file_path\": \"./团队季度汇报.pptx\"}}\n</TOOL_CALL>"
186
+ },
187
+ {
188
+ "user": "帮我做一个有图表的产品分析PPT",
189
+ "assistant": "好的,我来构建含图表的分析PPT。\n<TOOL_CALL>\n{\"name\": \"mcp_powerpoint-server_create_presentation\", \"arguments\": {}}\n</TOOL_CALL>\n...封面页...\n<TOOL_CALL>\n{\"name\": \"mcp_powerpoint-server_add_slide\", \"arguments\": {\"layout_index\": 6, \"title\": \"市场份额对比\", \"color_scheme\": \"corporate_gray\"}}\n</TOOL_CALL>\n<TOOL_CALL>\n{\"name\": \"mcp_powerpoint-server_add_chart\", \"arguments\": {\"chart_type\": \"column\", \"categories\": [\"产品A\", \"产品B\", \"产品C\"], \"series_names\": [\"2024\", \"2025\"], \"series_values\": [[35, 28, 20], [42, 31, 25]], \"left\": 0.5, \"top\": 1.3, \"width\": 9.0, \"height\": 3.8, \"has_legend\": true}}\n</TOOL_CALL>\n...结尾页 + save_presentation..."
190
+ },
191
+ {
192
+ "user": "帮我写一篇关于AI发展趋势的文章",
193
+ "assistant": "好的,让我先搜索最新的行业动态。\n<TOOL_CALL>\n{\"name\": \"web_search\", \"arguments\": {\"query\": \"人工智能发展趋势 2025\"}}\n</TOOL_CALL>"
194
+ }
195
+ ],
196
+ "system_prompt_sections": [
197
+ {
198
+ "title": "图片理解能力",
199
+ "requireTools": ["AnalyzeImage"],
200
+ "content": [
201
+ "你具备图片理解能力,可以\"看\"图片并将其内容转化为文字描述。",
202
+ "",
203
+ "**使用方式:**",
204
+ "- 用户让你看图片、截图、照片时,使用 `AnalyzeImage` 工具分析图片路径",
205
+ "- 使用 `Read` 工具读取图片文件(png/jpg/jpeg/gif/webp/bmp/svg/ico)时,会自动返回图片的文字描述",
206
+ "- MCP 工具(如 Word 读取)返回的图片会自动被分析并替换为文字描述",
207
+ "",
208
+ "**注意:** 不要用 shell 命令(如 file、identify)来查看图片,直接用 AnalyzeImage 或 Read 工具。"
209
+ ]
210
+ }
211
+ ]
212
+ }
213
+ }
@@ -0,0 +1,105 @@
1
+ """
2
+ AKShare 金融数据脚本 - 共享工具模块
3
+
4
+ 提供股票代码标准化、JSON输出、AKShare可用性检测等通用功能
5
+ """
6
+
7
+ import sys
8
+ import json
9
+ import re
10
+ import os
11
+
12
+ # 抑制 tqdm 进度条输出到 stderr,避免污染 stdout 的 JSON
13
+ os.environ['TQDM_DISABLE'] = '1'
14
+
15
+
16
+ def normalize_code(code: str) -> str:
17
+ """
18
+ 标准化股票代码
19
+ A股: 6位纯数字 (如 600519)
20
+ 港股: 5位纯数字 (如 03888)
21
+ 支持: 600519, sh600519, sz000001, SH600519, 600519.SS, 03888 等
22
+ """
23
+ # 去除前后空白
24
+ code = code.strip()
25
+ # 去除常见前缀 sh/sz/SH/SZ (仅A股)
26
+ code = re.sub(r'^(sh|sz|SH|SZ)', '', code)
27
+ # 去除港股前缀 hk/HK
28
+ code = re.sub(r'^(hk|HK)', '', code)
29
+ # 去除常见后缀 .SS/.SZ/.HK
30
+ code = re.sub(r'\.(SS|SZ|ss|sz|HK|hk)$', '', code)
31
+ return code
32
+
33
+
34
+ def get_market_prefix(code: str) -> str:
35
+ """
36
+ 根据股票代码判断市场前缀
37
+ 6xxxxx / 688xxx -> sh (上海)
38
+ 0xxxxx / 3xxxxx -> sz (深圳)
39
+ """
40
+ code = normalize_code(code)
41
+ if code.startswith('6') or code.startswith('688'):
42
+ return 'sh'
43
+ elif code.startswith('0') or code.startswith('3'):
44
+ return 'sz'
45
+ return 'sh' # 默认上海
46
+
47
+
48
+ def output_json(data: dict) -> None:
49
+ """输出成功JSON到stdout"""
50
+ print(json.dumps({"success": True, "data": data}, ensure_ascii=False))
51
+
52
+
53
+ def output_error(msg: str) -> None:
54
+ """输出错误JSON到stdout"""
55
+ print(json.dumps({"success": False, "error": msg}, ensure_ascii=False))
56
+
57
+
58
+ def check_akshare() -> str | None:
59
+ """
60
+ 检测AKShare是否可用
61
+ 返回 None 表示可用,否则返回错误信息
62
+ """
63
+ try:
64
+ import akshare
65
+ return None
66
+ except ImportError:
67
+ return "AKShare 未安装,请运行: pip install akshare"
68
+
69
+
70
+ def format_large_number(num) -> str:
71
+ """格式化大数字为易读格式"""
72
+ if num is None:
73
+ return "N/A"
74
+ try:
75
+ num = float(num)
76
+ if abs(num) >= 1e12:
77
+ return f"{num / 1e12:.2f}万亿"
78
+ elif abs(num) >= 1e8:
79
+ return f"{num / 1e8:.2f}亿"
80
+ elif abs(num) >= 1e4:
81
+ return f"{num / 1e4:.2f}万"
82
+ else:
83
+ return f"{num:.2f}"
84
+ except (ValueError, TypeError):
85
+ return str(num)
86
+
87
+
88
+ def safe_float(val, default=None) -> float | None:
89
+ """安全转换为浮点数"""
90
+ if val is None or val == '' or val == '-':
91
+ return default
92
+ try:
93
+ return float(val)
94
+ except (ValueError, TypeError):
95
+ return default
96
+
97
+
98
+ def is_hk_code(code: str) -> bool:
99
+ """
100
+ 判断是否为港股代码(5位数字)
101
+ 港股代码如: 03888(金山软件), 00700(腾讯), 09988(阿里)
102
+ A股代码为6位数字: 600519(茅台), 000001(平安)
103
+ """
104
+ code = code.strip()
105
+ return len(code) == 5 and code.isdigit()
@@ -0,0 +1,244 @@
1
+ """
2
+ 市场概览查询
3
+
4
+ 用法: python market_overview.py [--no-indices] [--no-sectors] [--no-hk]
5
+ --no-indices: 不包含A股指数数据
6
+ --no-sectors: 不包含板块数据
7
+ --no-hk: 不包含港股指数数据
8
+
9
+ AKShare接口:
10
+ A股指数:
11
+ 主: ak.stock_zh_index_spot_sina() — 新浪A股指数行情(稳定)
12
+ 备: ak.stock_zh_index_spot_em() — 东方财富A股指数行情
13
+ 港股指数:
14
+ 主: ak.stock_hk_index_spot_sina() — 新浪港股指数行情(稳定)
15
+ 备: ak.stock_hk_index_spot_em() — 东方财富港股指数行情
16
+ 板块:
17
+ 主: ak.stock_board_industry_name_em() — 东方财富行业板块
18
+
19
+ 注意: 新浪接口频繁调用会被封IP
20
+ """
21
+
22
+ import sys
23
+ import argparse
24
+
25
+ sys.path.insert(0, '.')
26
+ from _utils import output_json, output_error, check_akshare, safe_float
27
+
28
+
29
+ # A股主要指数代码
30
+ MAIN_INDICES = {
31
+ 'sh000001': '上证指数',
32
+ 'sz399001': '深证成指',
33
+ 'sz399006': '创业板指',
34
+ 'sh000300': '沪深300',
35
+ 'sh000016': '上证50',
36
+ 'sz399005': '中小板指',
37
+ }
38
+
39
+ # 港股主要指数代码(新浪代码格式)
40
+ HK_MAIN_INDICES = {
41
+ 'HSI': '恒生指数',
42
+ 'HSTECH': '恒生科技指数',
43
+ 'HSCEI': '恒生中国企业指数',
44
+ 'HSCCI': '恒生香港中资企业指数(红筹)',
45
+ }
46
+
47
+
48
+ def _fetch_indices_em():
49
+ """东方财富A股指数"""
50
+ import akshare as ak
51
+
52
+ indices = []
53
+ try:
54
+ index_df = ak.stock_zh_index_spot_em(symbol="上证系列指数")
55
+ key_codes = {'000001', '000016', '000300'}
56
+ for _, row in index_df.iterrows():
57
+ idx_code = str(row.get('代码', ''))
58
+ if idx_code in key_codes:
59
+ indices.append({
60
+ "code": idx_code,
61
+ "name": str(row.get('名称', '')),
62
+ "price": safe_float(row.get('最新价')),
63
+ "change_pct": safe_float(row.get('涨跌幅')),
64
+ "change_amt": safe_float(row.get('涨跌额')),
65
+ "volume": safe_float(row.get('成交量')),
66
+ "amount": safe_float(row.get('成交额')),
67
+ })
68
+ except Exception:
69
+ pass
70
+
71
+ try:
72
+ sz_df = ak.stock_zh_index_spot_em(symbol="深证系列指数")
73
+ sz_key = {'399001', '399006', '399005'}
74
+ for _, row in sz_df.iterrows():
75
+ idx_code = str(row.get('代码', ''))
76
+ if idx_code in sz_key:
77
+ indices.append({
78
+ "code": idx_code,
79
+ "name": str(row.get('名称', '')),
80
+ "price": safe_float(row.get('最新价')),
81
+ "change_pct": safe_float(row.get('涨跌幅')),
82
+ "change_amt": safe_float(row.get('涨跌额')),
83
+ "volume": safe_float(row.get('成交量')),
84
+ "amount": safe_float(row.get('成交额')),
85
+ })
86
+ except Exception:
87
+ pass
88
+
89
+ return indices
90
+
91
+
92
+ def _fetch_indices_sina():
93
+ """新浪A股指数(主接口)"""
94
+ import akshare as ak
95
+
96
+ df = ak.stock_zh_index_spot_sina()
97
+
98
+ indices = []
99
+ for _, row in df.iterrows():
100
+ idx_code = str(row.get('代码', ''))
101
+ if idx_code in MAIN_INDICES:
102
+ indices.append({
103
+ "code": idx_code,
104
+ "name": str(row.get('名称', MAIN_INDICES.get(idx_code, ''))),
105
+ "price": safe_float(row.get('最新价')),
106
+ "change_pct": safe_float(row.get('涨跌幅')),
107
+ "change_amt": safe_float(row.get('涨跌额')),
108
+ "volume": safe_float(row.get('成交量')),
109
+ "amount": safe_float(row.get('成交额')),
110
+ "data_source": "sina",
111
+ })
112
+
113
+ return indices
114
+
115
+
116
+ def _fetch_hk_indices_sina():
117
+ """新浪港股指数"""
118
+ import akshare as ak
119
+
120
+ df = ak.stock_hk_index_spot_sina()
121
+
122
+ indices = []
123
+ for _, row in df.iterrows():
124
+ idx_code = str(row.get('代码', ''))
125
+ idx_name = str(row.get('名称', ''))
126
+ # 匹配主要港股指数
127
+ if idx_code in HK_MAIN_INDICES or any(kw in idx_name for kw in ['恒生指数', '恒生科技', '国企指数', '红筹']):
128
+ indices.append({
129
+ "code": idx_code,
130
+ "name": idx_name,
131
+ "price": safe_float(row.get('最新价')),
132
+ "change_pct": safe_float(row.get('涨跌幅')),
133
+ "change_amt": safe_float(row.get('涨跌额')),
134
+ "data_source": "sina_hk",
135
+ })
136
+
137
+ # 如果没匹配到关键词,取前5个
138
+ if not indices and not df.empty:
139
+ for _, row in df.head(5).iterrows():
140
+ indices.append({
141
+ "code": str(row.get('代码', '')),
142
+ "name": str(row.get('名称', '')),
143
+ "price": safe_float(row.get('最新价')),
144
+ "change_pct": safe_float(row.get('涨跌幅')),
145
+ "change_amt": safe_float(row.get('涨跌额')),
146
+ "data_source": "sina_hk",
147
+ })
148
+
149
+ return indices
150
+
151
+
152
+ def _fetch_sectors_em():
153
+ """东方财富行业板块"""
154
+ import akshare as ak
155
+
156
+ sector_df = ak.stock_board_industry_name_em()
157
+ sector_df = sector_df.sort_values(by='涨跌幅', ascending=False)
158
+
159
+ top_up = []
160
+ for _, row in sector_df.head(5).iterrows():
161
+ top_up.append({
162
+ "name": str(row.get('板块名称', '')),
163
+ "change_pct": safe_float(row.get('涨跌幅')),
164
+ "leading_stock": str(row.get('领涨股票', '')),
165
+ "leading_pct": safe_float(row.get('领涨股票涨跌幅')),
166
+ })
167
+
168
+ top_down = []
169
+ for _, row in sector_df.tail(5).iterrows():
170
+ top_down.append({
171
+ "name": str(row.get('板块名称', '')),
172
+ "change_pct": safe_float(row.get('涨跌幅')),
173
+ "leading_stock": str(row.get('领跌股票', '')),
174
+ "leading_pct": safe_float(row.get('领跌股票涨跌幅')),
175
+ })
176
+
177
+ return top_up, top_down, len(sector_df)
178
+
179
+
180
+ def main():
181
+ parser = argparse.ArgumentParser(description='市场概览查询')
182
+ parser.add_argument('--no-indices', action='store_true', help='不包含A股指数数据')
183
+ parser.add_argument('--no-sectors', action='store_true', help='不包含板块数据')
184
+ parser.add_argument('--no-hk', action='store_true', help='不包含港股指数数据')
185
+ args = parser.parse_args()
186
+
187
+ err = check_akshare()
188
+ if err:
189
+ output_error(err)
190
+ return
191
+
192
+ include_indices = not args.no_indices
193
+ include_sectors = not args.no_sectors
194
+ include_hk = not args.no_hk
195
+
196
+ try:
197
+ data = {}
198
+
199
+ # A股主要指数:优先新浪,失败回退东方财富
200
+ if include_indices:
201
+ indices = []
202
+ try:
203
+ indices = _fetch_indices_sina()
204
+ except Exception:
205
+ pass
206
+
207
+ if not indices:
208
+ try:
209
+ indices = _fetch_indices_em()
210
+ except Exception as e:
211
+ data["indices_error"] = str(e)
212
+
213
+ data["indices"] = indices
214
+
215
+ # 港股指数
216
+ if include_hk:
217
+ hk_indices = []
218
+ try:
219
+ hk_indices = _fetch_hk_indices_sina()
220
+ except Exception as e:
221
+ data["hk_indices_error"] = str(e)
222
+
223
+ data["hk_indices"] = hk_indices
224
+
225
+ # 行业板块涨跌
226
+ if include_sectors:
227
+ try:
228
+ top_up, top_down, total = _fetch_sectors_em()
229
+ data["top_sectors_up"] = top_up
230
+ data["top_sectors_down"] = top_down
231
+ data["total_sectors"] = total
232
+ except Exception as e:
233
+ data["top_sectors_up"] = []
234
+ data["top_sectors_down"] = []
235
+ data["sectors_error"] = str(e)
236
+
237
+ output_json(data)
238
+
239
+ except Exception as e:
240
+ output_error(f"获取市场概览失败: {str(e)}")
241
+
242
+
243
+ if __name__ == '__main__':
244
+ main()