akshare-cli 0.2.0__py3-none-any.whl
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.
- akshare_cli/__init__.py +3 -0
- akshare_cli/cli.py +1285 -0
- akshare_cli/core/__init__.py +1 -0
- akshare_cli/core/cache.py +127 -0
- akshare_cli/core/export.py +115 -0
- akshare_cli/core/registry.py +236 -0
- akshare_cli/core/session.py +115 -0
- akshare_cli/tests/__init__.py +1 -0
- akshare_cli/tests/conftest.py +37 -0
- akshare_cli/tests/test_cache.py +111 -0
- akshare_cli/tests/test_core.py +439 -0
- akshare_cli/tests/test_doc_examples.py +636 -0
- akshare_cli/tests/test_full_e2e.py +262 -0
- akshare_cli/utils/__init__.py +1 -0
- akshare_cli/utils/formatting.py +62 -0
- akshare_cli-0.2.0.dist-info/METADATA +1212 -0
- akshare_cli-0.2.0.dist-info/RECORD +20 -0
- akshare_cli-0.2.0.dist-info/WHEEL +5 -0
- akshare_cli-0.2.0.dist-info/entry_points.txt +2 -0
- akshare_cli-0.2.0.dist-info/top_level.txt +1 -0
akshare_cli/cli.py
ADDED
|
@@ -0,0 +1,1285 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
AKShare CLI Harness - Main CLI Entry Point
|
|
5
|
+
|
|
6
|
+
A complete CLI for the akshare financial data library.
|
|
7
|
+
Supports one-shot commands, REPL mode, and JSON output.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
akshare-cli call stock_zh_a_hist --symbol 000001
|
|
11
|
+
akshare-cli stock hist 000001 --json
|
|
12
|
+
akshare-cli search fund --json
|
|
13
|
+
akshare-cli repl
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import functools
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import shlex
|
|
20
|
+
import sys
|
|
21
|
+
import traceback
|
|
22
|
+
from typing import Optional
|
|
23
|
+
|
|
24
|
+
import click
|
|
25
|
+
import pandas as pd
|
|
26
|
+
|
|
27
|
+
from akshare_cli.core.cache import _result_cache
|
|
28
|
+
from akshare_cli.core.registry import (
|
|
29
|
+
call_function,
|
|
30
|
+
get_all_functions,
|
|
31
|
+
get_domains,
|
|
32
|
+
get_function,
|
|
33
|
+
get_function_info,
|
|
34
|
+
list_functions_by_domain,
|
|
35
|
+
search_functions,
|
|
36
|
+
)
|
|
37
|
+
from akshare_cli.core.session import Session
|
|
38
|
+
from akshare_cli.core.export import auto_export
|
|
39
|
+
from akshare_cli.utils.formatting import (
|
|
40
|
+
display_result,
|
|
41
|
+
print_error,
|
|
42
|
+
print_info,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Global session
|
|
46
|
+
_session = Session()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class OutputConfig:
|
|
50
|
+
"""Holds output configuration for the current invocation."""
|
|
51
|
+
|
|
52
|
+
def __init__(self):
|
|
53
|
+
self.format = "table"
|
|
54
|
+
self.output_file = None
|
|
55
|
+
self.max_rows = None
|
|
56
|
+
self.show_index = False
|
|
57
|
+
self.no_header = False
|
|
58
|
+
|
|
59
|
+
def apply(self, use_json=False, use_csv=False, output_file=None, limit=None, no_header=False):
|
|
60
|
+
"""Apply output options, only overriding if explicitly set."""
|
|
61
|
+
if use_json:
|
|
62
|
+
self.format = "json"
|
|
63
|
+
if use_csv:
|
|
64
|
+
self.format = "csv"
|
|
65
|
+
if output_file is not None:
|
|
66
|
+
self.output_file = output_file
|
|
67
|
+
if limit is not None:
|
|
68
|
+
self.max_rows = limit
|
|
69
|
+
if no_header:
|
|
70
|
+
self.no_header = no_header
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
_output_config = OutputConfig()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def output_options(func):
|
|
77
|
+
"""Decorator that adds --json/--csv/--output/--limit/--no-header to any command."""
|
|
78
|
+
@click.option("--json", "use_json", is_flag=True, help="Output as JSON.")
|
|
79
|
+
@click.option("--csv", "use_csv", is_flag=True, help="Output as CSV.")
|
|
80
|
+
@click.option("--output", "-o", "output_file", default=None, help="Save output to file.")
|
|
81
|
+
@click.option("--limit", "-n", "limit", type=int, default=None, help="Limit number of rows.")
|
|
82
|
+
@click.option("--no-header", "no_header", is_flag=True, help="Suppress table headers.")
|
|
83
|
+
@functools.wraps(func)
|
|
84
|
+
def wrapper(*args, use_json=False, use_csv=False, output_file=None, limit=None, no_header=False, **kwargs):
|
|
85
|
+
_output_config.apply(use_json, use_csv, output_file, limit, no_header)
|
|
86
|
+
return func(*args, **kwargs)
|
|
87
|
+
return wrapper
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _handle_result(result, func_name: str = "", kwargs: dict = None):
|
|
91
|
+
"""Process and display a function result."""
|
|
92
|
+
kwargs = kwargs or {}
|
|
93
|
+
|
|
94
|
+
if isinstance(result, pd.DataFrame):
|
|
95
|
+
_session.record_call(func_name, kwargs, result)
|
|
96
|
+
|
|
97
|
+
if result.empty:
|
|
98
|
+
if _output_config.format == "json":
|
|
99
|
+
click.echo(json.dumps({"total_rows": 0, "columns": [], "data": []}, indent=2))
|
|
100
|
+
else:
|
|
101
|
+
click.echo("No data returned (empty DataFrame).")
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
output = display_result(
|
|
105
|
+
result,
|
|
106
|
+
output_format=_output_config.format,
|
|
107
|
+
max_rows=_output_config.max_rows,
|
|
108
|
+
show_index=_output_config.show_index,
|
|
109
|
+
output_file=_output_config.output_file,
|
|
110
|
+
)
|
|
111
|
+
click.echo(output)
|
|
112
|
+
|
|
113
|
+
if _output_config.format == "table" and _output_config.output_file is None:
|
|
114
|
+
click.echo(
|
|
115
|
+
f"\n[{result.shape[0]} rows x {result.shape[1]} columns]",
|
|
116
|
+
err=True,
|
|
117
|
+
)
|
|
118
|
+
else:
|
|
119
|
+
if _output_config.format == "json":
|
|
120
|
+
click.echo(json.dumps({"result": str(result)}, indent=2))
|
|
121
|
+
else:
|
|
122
|
+
click.echo(str(result))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@click.group(invoke_without_command=True)
|
|
126
|
+
@click.option("--json", "use_json", is_flag=True, help="Output as JSON.")
|
|
127
|
+
@click.option("--csv", "use_csv", is_flag=True, help="Output as CSV.")
|
|
128
|
+
@click.option("--output", "-o", "output_file", help="Save output to file.")
|
|
129
|
+
@click.option("--limit", "-n", type=int, help="Limit number of rows.")
|
|
130
|
+
@click.option("--no-header", is_flag=True, help="Suppress table headers.")
|
|
131
|
+
@click.option("--no-cache", is_flag=True, help="禁用结果缓存,强制从 API 获取最新数据。")
|
|
132
|
+
@click.version_option(version="0.1.0", prog_name="akshare-cli")
|
|
133
|
+
@click.pass_context
|
|
134
|
+
def cli(ctx, use_json, use_csv, output_file, limit, no_header, no_cache):
|
|
135
|
+
"""AKShare CLI - 命令行金融数据工具
|
|
136
|
+
|
|
137
|
+
支持调用 akshare 库的 1090+ 个函数,获取股票、基金、期货、债券、外汇、宏观等金融数据。
|
|
138
|
+
输出选项 (--json, --csv, --output, --limit) 可以放在子命令的前面或后面。
|
|
139
|
+
|
|
140
|
+
\b
|
|
141
|
+
基本用法:
|
|
142
|
+
akshare-cli <命令> [参数] 一次性查询
|
|
143
|
+
akshare-cli repl 进入交互模式
|
|
144
|
+
|
|
145
|
+
示例:
|
|
146
|
+
akshare-cli call stock_zh_a_hist --symbol 000001 --json
|
|
147
|
+
akshare-cli stock hist 000001 --json
|
|
148
|
+
akshare-cli --json search fund_etf
|
|
149
|
+
akshare-cli macro gdp --csv --output gdp.csv
|
|
150
|
+
akshare-cli repl
|
|
151
|
+
|
|
152
|
+
更多信息:
|
|
153
|
+
每个命令都支持 --help 查看详细帮助
|
|
154
|
+
日期参数未指定时会自动填充为当天日期
|
|
155
|
+
"""
|
|
156
|
+
ctx.ensure_object(dict)
|
|
157
|
+
|
|
158
|
+
_output_config.apply(use_json, use_csv, output_file, limit, no_header)
|
|
159
|
+
|
|
160
|
+
if no_cache:
|
|
161
|
+
_result_cache.enabled = False
|
|
162
|
+
|
|
163
|
+
if ctx.invoked_subcommand is None:
|
|
164
|
+
click.echo(ctx.get_help())
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# ─── CALL: Dynamic function dispatch ─────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@cli.command("call", context_settings=dict(
|
|
171
|
+
ignore_unknown_options=True,
|
|
172
|
+
allow_extra_args=True,
|
|
173
|
+
))
|
|
174
|
+
@click.argument("params", nargs=-1, type=click.UNPROCESSED)
|
|
175
|
+
@click.pass_context
|
|
176
|
+
def cmd_call(ctx, params):
|
|
177
|
+
"""调用任意 akshare 函数
|
|
178
|
+
|
|
179
|
+
通过函数名直接调用 akshare 库的任意函数。参数以 --key value 形式传入。
|
|
180
|
+
输出选项 (--json/--csv/--output) 可以放在任意位置。
|
|
181
|
+
日期参数未指定时会自动填充为当天日期。
|
|
182
|
+
|
|
183
|
+
\b
|
|
184
|
+
用法:
|
|
185
|
+
call <函数名> [--参数 值 ...]
|
|
186
|
+
|
|
187
|
+
示例:
|
|
188
|
+
# 获取股票历史数据 (JSON 输出)
|
|
189
|
+
akshare-cli call stock_zh_a_hist --symbol 000001 --json
|
|
190
|
+
|
|
191
|
+
# --json 放在前面也可以
|
|
192
|
+
akshare-cli call --json stock_zh_a_spot_em
|
|
193
|
+
|
|
194
|
+
# 导出到文件
|
|
195
|
+
akshare-cli call futures_hist_em --symbol 螺纹主连 --output data.csv
|
|
196
|
+
|
|
197
|
+
# 获取经济新闻 (自动使用今天日期)
|
|
198
|
+
akshare-cli call news_economic_baidu --json
|
|
199
|
+
|
|
200
|
+
# 获取可转债数据,限制 10 行
|
|
201
|
+
akshare-cli call bond_zh_cov --json --limit 10
|
|
202
|
+
|
|
203
|
+
查看函数用法:
|
|
204
|
+
akshare-cli call --full-help <函数名> 查看指定函数的完整用法
|
|
205
|
+
akshare-cli search <关键词> 搜索函数
|
|
206
|
+
akshare-cli info <函数名> 查看参数详情
|
|
207
|
+
"""
|
|
208
|
+
# First pass: strip output flags from the token list
|
|
209
|
+
remaining = list(params)
|
|
210
|
+
call_output_json = False
|
|
211
|
+
call_output_csv = False
|
|
212
|
+
call_output_file = None
|
|
213
|
+
call_limit = None
|
|
214
|
+
full_help = False
|
|
215
|
+
|
|
216
|
+
filtered = []
|
|
217
|
+
i = 0
|
|
218
|
+
while i < len(remaining):
|
|
219
|
+
p = remaining[i]
|
|
220
|
+
if p == "--json":
|
|
221
|
+
call_output_json = True
|
|
222
|
+
i += 1
|
|
223
|
+
elif p == "--csv":
|
|
224
|
+
call_output_csv = True
|
|
225
|
+
i += 1
|
|
226
|
+
elif p == "--full-help":
|
|
227
|
+
full_help = True
|
|
228
|
+
i += 1
|
|
229
|
+
elif p in ("--output", "-o") and i + 1 < len(remaining):
|
|
230
|
+
call_output_file = remaining[i + 1]
|
|
231
|
+
i += 2
|
|
232
|
+
elif p in ("--limit", "-n") and i + 1 < len(remaining):
|
|
233
|
+
try:
|
|
234
|
+
call_limit = int(remaining[i + 1])
|
|
235
|
+
except ValueError:
|
|
236
|
+
pass
|
|
237
|
+
i += 2
|
|
238
|
+
elif p == "--no-header":
|
|
239
|
+
_output_config.no_header = True
|
|
240
|
+
i += 1
|
|
241
|
+
else:
|
|
242
|
+
filtered.append(p)
|
|
243
|
+
i += 1
|
|
244
|
+
|
|
245
|
+
_output_config.apply(call_output_json, call_output_csv, call_output_file, call_limit)
|
|
246
|
+
|
|
247
|
+
# Second pass: the first non-flag token is the function name
|
|
248
|
+
func_name = None
|
|
249
|
+
func_params = []
|
|
250
|
+
for j, tok in enumerate(filtered):
|
|
251
|
+
if not tok.startswith("--"):
|
|
252
|
+
func_name = tok
|
|
253
|
+
func_params = filtered[:j] + filtered[j + 1:]
|
|
254
|
+
break
|
|
255
|
+
|
|
256
|
+
# Handle --full-help
|
|
257
|
+
if full_help:
|
|
258
|
+
_print_full_help(func_name)
|
|
259
|
+
return
|
|
260
|
+
|
|
261
|
+
if not func_name:
|
|
262
|
+
print_error("Missing function name. Usage: call <func_name> [--param value ...]")
|
|
263
|
+
sys.exit(1)
|
|
264
|
+
|
|
265
|
+
kwargs = _parse_extra_params(func_params)
|
|
266
|
+
try:
|
|
267
|
+
result = call_function(func_name, kwargs)
|
|
268
|
+
_handle_result(result, func_name, kwargs)
|
|
269
|
+
except ValueError as e:
|
|
270
|
+
err_msg = str(e)
|
|
271
|
+
if "not found" in err_msg:
|
|
272
|
+
print_error(err_msg)
|
|
273
|
+
suggestions = search_functions(func_name)
|
|
274
|
+
if suggestions:
|
|
275
|
+
click.echo("\n你是不是想找:")
|
|
276
|
+
for s in suggestions[:5]:
|
|
277
|
+
click.echo(f" {s}")
|
|
278
|
+
else:
|
|
279
|
+
print_error(f"调用 {func_name} 失败: {e}")
|
|
280
|
+
if os.environ.get("AKSHARE_CLI_DEBUG"):
|
|
281
|
+
traceback.print_exc()
|
|
282
|
+
sys.exit(1)
|
|
283
|
+
except Exception as e:
|
|
284
|
+
print_error(f"调用 {func_name} 失败: {e}")
|
|
285
|
+
if os.environ.get("AKSHARE_CLI_DEBUG"):
|
|
286
|
+
traceback.print_exc()
|
|
287
|
+
sys.exit(1)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _print_full_help(func_name: Optional[str] = None) -> None:
|
|
291
|
+
"""Print detailed usage help for one function or list popular examples."""
|
|
292
|
+
if func_name:
|
|
293
|
+
info = get_function_info(func_name)
|
|
294
|
+
if info is None:
|
|
295
|
+
print_error(f"函数 '{func_name}' 未找到。")
|
|
296
|
+
suggestions = search_functions(func_name)
|
|
297
|
+
if suggestions:
|
|
298
|
+
click.echo("\n你是不是想找:")
|
|
299
|
+
for s in suggestions[:5]:
|
|
300
|
+
click.echo(f" {s}")
|
|
301
|
+
sys.exit(1)
|
|
302
|
+
|
|
303
|
+
if _output_config.format == "json":
|
|
304
|
+
# Build structured help object
|
|
305
|
+
help_obj = {
|
|
306
|
+
"function": info["name"],
|
|
307
|
+
"module": info["module"],
|
|
308
|
+
"description": info["docstring"] or "",
|
|
309
|
+
"parameters": [],
|
|
310
|
+
"usage": f"akshare-cli call {info['name']}",
|
|
311
|
+
}
|
|
312
|
+
parts = [f"akshare-cli call {info['name']}"]
|
|
313
|
+
for p in info["params"]:
|
|
314
|
+
pdata = {"name": p["name"]}
|
|
315
|
+
if p.get("type"):
|
|
316
|
+
pdata["type"] = p["type"]
|
|
317
|
+
if p.get("has_default"):
|
|
318
|
+
pdata["default"] = p.get("default")
|
|
319
|
+
pdata["required"] = False
|
|
320
|
+
else:
|
|
321
|
+
pdata["required"] = True
|
|
322
|
+
help_obj["parameters"].append(pdata)
|
|
323
|
+
if pdata.get("required"):
|
|
324
|
+
parts.append(f"--{p['name']} <值>")
|
|
325
|
+
else:
|
|
326
|
+
parts.append(f"[--{p['name']} {p.get('default', '')}]")
|
|
327
|
+
help_obj["usage"] = " ".join(parts)
|
|
328
|
+
click.echo(json.dumps(help_obj, indent=2, ensure_ascii=False, default=str))
|
|
329
|
+
else:
|
|
330
|
+
click.echo(f"函数: {info['name']}")
|
|
331
|
+
click.echo(f"模块: {info['module']}")
|
|
332
|
+
if info["docstring"]:
|
|
333
|
+
click.echo(f"说明: {info['docstring']}")
|
|
334
|
+
click.echo()
|
|
335
|
+
|
|
336
|
+
# Parameter table
|
|
337
|
+
if info["params"]:
|
|
338
|
+
click.echo("参数:")
|
|
339
|
+
for p in info["params"]:
|
|
340
|
+
name = p["name"]
|
|
341
|
+
type_str = p.get("type", "Any")
|
|
342
|
+
if p.get("has_default"):
|
|
343
|
+
default = p.get("default")
|
|
344
|
+
click.echo(f" --{name:<20s} 类型: {type_str:<8s} 默认值: {default!r}")
|
|
345
|
+
else:
|
|
346
|
+
click.echo(f" --{name:<20s} 类型: {type_str:<8s} (必填)")
|
|
347
|
+
else:
|
|
348
|
+
click.echo("参数: 无")
|
|
349
|
+
click.echo()
|
|
350
|
+
|
|
351
|
+
# Usage example
|
|
352
|
+
parts = [f"akshare-cli call {info['name']}"]
|
|
353
|
+
for p in info["params"]:
|
|
354
|
+
if not p.get("has_default"):
|
|
355
|
+
parts.append(f"--{p['name']} <值>")
|
|
356
|
+
parts.append("[--json]")
|
|
357
|
+
click.echo("用法:")
|
|
358
|
+
click.echo(f" {' '.join(parts)}")
|
|
359
|
+
else:
|
|
360
|
+
# No function name: show general usage
|
|
361
|
+
click.echo("用法: akshare-cli call --full-help <函数名>")
|
|
362
|
+
click.echo()
|
|
363
|
+
click.echo("查看指定函数的完整用法说明,包括参数、类型、默认值。")
|
|
364
|
+
click.echo()
|
|
365
|
+
click.echo("示例:")
|
|
366
|
+
click.echo(" akshare-cli call --full-help stock_zh_a_hist")
|
|
367
|
+
click.echo(" akshare-cli call --full-help news_economic_baidu")
|
|
368
|
+
click.echo(" akshare-cli call --full-help futures_hist_em")
|
|
369
|
+
click.echo(" akshare-cli call --full-help bond_zh_cov --json")
|
|
370
|
+
click.echo()
|
|
371
|
+
click.echo("搜索函数:")
|
|
372
|
+
click.echo(" akshare-cli search <关键词>")
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _parse_extra_params(params):
|
|
376
|
+
"""Parse CLI params like --symbol 000001 --period daily into a dict."""
|
|
377
|
+
kwargs = {}
|
|
378
|
+
i = 0
|
|
379
|
+
while i < len(params):
|
|
380
|
+
p = params[i]
|
|
381
|
+
if p.startswith("--"):
|
|
382
|
+
key = p[2:]
|
|
383
|
+
if i + 1 < len(params) and not params[i + 1].startswith("--"):
|
|
384
|
+
kwargs[key] = params[i + 1]
|
|
385
|
+
i += 2
|
|
386
|
+
else:
|
|
387
|
+
kwargs[key] = "true"
|
|
388
|
+
i += 1
|
|
389
|
+
else:
|
|
390
|
+
i += 1
|
|
391
|
+
return kwargs
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
# ─── SEARCH: Find functions ──────────────────────────────────────────────────
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
@cli.command("search")
|
|
398
|
+
@click.argument("keyword")
|
|
399
|
+
@output_options
|
|
400
|
+
def cmd_search(keyword):
|
|
401
|
+
"""按关键词搜索 akshare 函数
|
|
402
|
+
|
|
403
|
+
在 1090+ 个函数中模糊搜索,返回包含关键词的所有函数名及简介。
|
|
404
|
+
|
|
405
|
+
\b
|
|
406
|
+
示例:
|
|
407
|
+
# 搜索股票相关函数
|
|
408
|
+
akshare-cli search stock_zh
|
|
409
|
+
|
|
410
|
+
# 搜索 ETF 基金函数 (JSON 输出)
|
|
411
|
+
akshare-cli search fund_etf --json
|
|
412
|
+
|
|
413
|
+
# 搜索新闻接口
|
|
414
|
+
akshare-cli search news
|
|
415
|
+
"""
|
|
416
|
+
results = search_functions(keyword)
|
|
417
|
+
if not results:
|
|
418
|
+
if _output_config.format == "json":
|
|
419
|
+
click.echo(json.dumps({"keyword": keyword, "count": 0, "functions": []}, indent=2))
|
|
420
|
+
else:
|
|
421
|
+
click.echo(f"No functions found matching '{keyword}'.")
|
|
422
|
+
return
|
|
423
|
+
|
|
424
|
+
if _output_config.format == "json":
|
|
425
|
+
click.echo(json.dumps({"keyword": keyword, "count": len(results), "functions": results}, indent=2))
|
|
426
|
+
else:
|
|
427
|
+
click.echo(f"Found {len(results)} functions matching '{keyword}':\n")
|
|
428
|
+
for name in results:
|
|
429
|
+
info = get_function_info(name)
|
|
430
|
+
doc_line = ""
|
|
431
|
+
if info and info["docstring"]:
|
|
432
|
+
doc_line = info["docstring"].split("\n")[0][:60]
|
|
433
|
+
click.echo(f" {name:<50s} {doc_line}")
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
# ─── LIST: List functions ────────────────────────────────────────────────────
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
@cli.command("list")
|
|
440
|
+
@click.argument("domain", required=False)
|
|
441
|
+
@output_options
|
|
442
|
+
def cmd_list(domain):
|
|
443
|
+
"""列出可用的 akshare 函数
|
|
444
|
+
|
|
445
|
+
不指定域时显示所有域的函数数量概览;指定域时列出该域下所有函数。
|
|
446
|
+
|
|
447
|
+
\b
|
|
448
|
+
常见域:
|
|
449
|
+
stock 股票 fund 基金
|
|
450
|
+
futures 期货 bond 债券
|
|
451
|
+
forex 外汇 macro 宏观经济
|
|
452
|
+
index 指数 option 期权
|
|
453
|
+
news 新闻
|
|
454
|
+
|
|
455
|
+
示例:
|
|
456
|
+
# 查看所有域的概览
|
|
457
|
+
akshare-cli list
|
|
458
|
+
|
|
459
|
+
# 列出股票域的所有函数
|
|
460
|
+
akshare-cli list stock
|
|
461
|
+
|
|
462
|
+
# JSON 格式输出
|
|
463
|
+
akshare-cli list fund --json
|
|
464
|
+
"""
|
|
465
|
+
if domain:
|
|
466
|
+
funcs = list_functions_by_domain(domain)
|
|
467
|
+
if not funcs:
|
|
468
|
+
if _output_config.format == "json":
|
|
469
|
+
click.echo(json.dumps({"domain": domain, "count": 0, "functions": [], "available_domains": get_domains()}, indent=2))
|
|
470
|
+
else:
|
|
471
|
+
click.echo(f"No functions found for domain '{domain}'.")
|
|
472
|
+
click.echo(f"\nAvailable domains: {', '.join(get_domains())}")
|
|
473
|
+
return
|
|
474
|
+
|
|
475
|
+
if _output_config.format == "json":
|
|
476
|
+
click.echo(json.dumps({"domain": domain, "count": len(funcs), "functions": funcs}, indent=2))
|
|
477
|
+
else:
|
|
478
|
+
click.echo(f"{domain} ({len(funcs)} functions):\n")
|
|
479
|
+
for name in funcs:
|
|
480
|
+
click.echo(f" {name}")
|
|
481
|
+
else:
|
|
482
|
+
domains = get_domains()
|
|
483
|
+
all_funcs = get_all_functions()
|
|
484
|
+
if _output_config.format == "json":
|
|
485
|
+
domain_counts = {}
|
|
486
|
+
for d in domains:
|
|
487
|
+
domain_counts[d] = len(list_functions_by_domain(d))
|
|
488
|
+
click.echo(json.dumps({
|
|
489
|
+
"total_functions": len(all_funcs),
|
|
490
|
+
"domains": domain_counts,
|
|
491
|
+
}, indent=2))
|
|
492
|
+
else:
|
|
493
|
+
click.echo(f"AKShare has {len(all_funcs)} functions across {len(domains)} domains:\n")
|
|
494
|
+
for d in domains:
|
|
495
|
+
count = len(list_functions_by_domain(d))
|
|
496
|
+
click.echo(f" {d:<25s} {count:>4d} functions")
|
|
497
|
+
click.echo(f"\nUse 'list <domain>' to see functions in a domain.")
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
# ─── INFO: Function details ──────────────────────────────────────────────────
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
@cli.command("info")
|
|
504
|
+
@click.argument("func_name")
|
|
505
|
+
@output_options
|
|
506
|
+
def cmd_info(func_name):
|
|
507
|
+
"""查看函数的详细信息
|
|
508
|
+
|
|
509
|
+
显示函数的参数列表、类型、默认值和文档说明。
|
|
510
|
+
在调用陌生函数前先用 info 查看参数要求。
|
|
511
|
+
|
|
512
|
+
\b
|
|
513
|
+
示例:
|
|
514
|
+
# 查看股票历史数据函数的参数
|
|
515
|
+
akshare-cli info stock_zh_a_hist
|
|
516
|
+
|
|
517
|
+
# JSON 格式输出
|
|
518
|
+
akshare-cli info news_economic_baidu --json
|
|
519
|
+
|
|
520
|
+
输出内容:
|
|
521
|
+
- 函数名和模块
|
|
522
|
+
- 参数列表(名称、类型、默认值、是否必填)
|
|
523
|
+
- 函数文档说明
|
|
524
|
+
"""
|
|
525
|
+
info = get_function_info(func_name)
|
|
526
|
+
if info is None:
|
|
527
|
+
if _output_config.format == "json":
|
|
528
|
+
click.echo(json.dumps({"error": f"Function '{func_name}' not found"}, indent=2))
|
|
529
|
+
else:
|
|
530
|
+
print_error(f"Function '{func_name}' not found.")
|
|
531
|
+
suggestions = search_functions(func_name)
|
|
532
|
+
if suggestions:
|
|
533
|
+
click.echo("\nDid you mean one of these?")
|
|
534
|
+
for s in suggestions[:5]:
|
|
535
|
+
click.echo(f" {s}")
|
|
536
|
+
sys.exit(1)
|
|
537
|
+
|
|
538
|
+
if _output_config.format == "json":
|
|
539
|
+
click.echo(json.dumps(info, indent=2, ensure_ascii=False, default=str))
|
|
540
|
+
else:
|
|
541
|
+
click.echo(f"Function: {info['name']}")
|
|
542
|
+
click.echo(f"Module: {info['module']}")
|
|
543
|
+
click.echo()
|
|
544
|
+
if info["params"]:
|
|
545
|
+
click.echo("Parameters:")
|
|
546
|
+
for p in info["params"]:
|
|
547
|
+
default_str = f" = {p['default']!r}" if p.get("has_default") else " (required)"
|
|
548
|
+
type_str = f" : {p.get('type', 'Any')}" if p.get("type") else ""
|
|
549
|
+
click.echo(f" --{p['name']}{type_str}{default_str}")
|
|
550
|
+
else:
|
|
551
|
+
click.echo("Parameters: none")
|
|
552
|
+
click.echo()
|
|
553
|
+
if info["docstring"]:
|
|
554
|
+
click.echo("Description:")
|
|
555
|
+
click.echo(f" {info['docstring']}")
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
# ─── DOMAIN SHORTCUTS ────────────────────────────────────────────────────────
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
@cli.group("stock")
|
|
562
|
+
def cmd_stock():
|
|
563
|
+
"""股票数据快捷命令
|
|
564
|
+
|
|
565
|
+
\b
|
|
566
|
+
子命令:
|
|
567
|
+
hist 获取股票历史 K 线数据 (OHLCV)
|
|
568
|
+
spot 获取股票实时行情
|
|
569
|
+
|
|
570
|
+
示例:
|
|
571
|
+
akshare-cli stock hist 000001 --json
|
|
572
|
+
akshare-cli stock spot --market sh --csv
|
|
573
|
+
"""
|
|
574
|
+
pass
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
@cmd_stock.command("hist")
|
|
578
|
+
@click.argument("symbol")
|
|
579
|
+
@click.option("--period", default="daily", help="daily/weekly/monthly")
|
|
580
|
+
@click.option("--start", "start_date", default="19700101", help="Start date YYYYMMDD")
|
|
581
|
+
@click.option("--end", "end_date", default="20500101", help="End date YYYYMMDD")
|
|
582
|
+
@click.option("--adjust", default="", help="qfq/hfq/'' (forward/backward/none)")
|
|
583
|
+
@output_options
|
|
584
|
+
def cmd_stock_hist(symbol, period, start_date, end_date, adjust):
|
|
585
|
+
"""获取股票历史 K 线数据
|
|
586
|
+
|
|
587
|
+
获取 A 股历史 OHLCV 数据(开盘价、最高价、最低价、收盘价、成交量)。
|
|
588
|
+
数据来源:东方财富 (stock_zh_a_hist)
|
|
589
|
+
|
|
590
|
+
\b
|
|
591
|
+
参数:
|
|
592
|
+
SYMBOL 股票代码 (如 000001, 600519)
|
|
593
|
+
--period 周期: daily/weekly/monthly (默认 daily)
|
|
594
|
+
--start 开始日期 YYYYMMDD (默认 19700101)
|
|
595
|
+
--end 结束日期 YYYYMMDD (默认 20500101)
|
|
596
|
+
--adjust 复权: qfq(前复权)/hfq(后复权)/空串(不复权, 默认)
|
|
597
|
+
|
|
598
|
+
示例:
|
|
599
|
+
# 平安银行日线数据
|
|
600
|
+
akshare-cli stock hist 000001 --json
|
|
601
|
+
|
|
602
|
+
# 贵州茅台周线 + 前复权
|
|
603
|
+
akshare-cli stock hist 600519 --period weekly --adjust qfq
|
|
604
|
+
|
|
605
|
+
# 指定日期范围
|
|
606
|
+
akshare-cli stock hist 000001 --start 20260101 --end 20260311 --csv
|
|
607
|
+
"""
|
|
608
|
+
kwargs = {
|
|
609
|
+
"symbol": symbol,
|
|
610
|
+
"period": period,
|
|
611
|
+
"start_date": start_date,
|
|
612
|
+
"end_date": end_date,
|
|
613
|
+
"adjust": adjust,
|
|
614
|
+
}
|
|
615
|
+
try:
|
|
616
|
+
result = call_function("stock_zh_a_hist", kwargs)
|
|
617
|
+
_handle_result(result, "stock_zh_a_hist", kwargs)
|
|
618
|
+
except Exception as e:
|
|
619
|
+
print_error(f"Failed: {e}")
|
|
620
|
+
sys.exit(1)
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
@cmd_stock.command("spot")
|
|
624
|
+
@click.option("--market", default="all", help="all/sh/sz/bj/hk/us")
|
|
625
|
+
@output_options
|
|
626
|
+
def cmd_stock_spot(market):
|
|
627
|
+
"""获取股票实时行情
|
|
628
|
+
|
|
629
|
+
获取各市场股票实时报价数据。数据来源:东方财富。
|
|
630
|
+
|
|
631
|
+
\b
|
|
632
|
+
参数:
|
|
633
|
+
--market 市场选择 (默认 all)
|
|
634
|
+
all 全部 A 股
|
|
635
|
+
sh 上海 A 股
|
|
636
|
+
sz 深圳 A 股
|
|
637
|
+
bj 北交所
|
|
638
|
+
hk 港股
|
|
639
|
+
us 美股
|
|
640
|
+
|
|
641
|
+
示例:
|
|
642
|
+
akshare-cli stock spot --json
|
|
643
|
+
akshare-cli stock spot --market sh --csv
|
|
644
|
+
akshare-cli stock spot --market hk --json --limit 20
|
|
645
|
+
|
|
646
|
+
注意: 全市场数据量较大 (5000+ 行),建议配合 --limit 使用
|
|
647
|
+
"""
|
|
648
|
+
func_map = {
|
|
649
|
+
"all": "stock_zh_a_spot_em",
|
|
650
|
+
"sh": "stock_sh_a_spot_em",
|
|
651
|
+
"sz": "stock_sz_a_spot_em",
|
|
652
|
+
"bj": "stock_bj_a_spot_em",
|
|
653
|
+
"hk": "stock_hk_spot_em",
|
|
654
|
+
"us": "stock_us_spot_em",
|
|
655
|
+
}
|
|
656
|
+
func_name = func_map.get(market, "stock_zh_a_spot_em")
|
|
657
|
+
try:
|
|
658
|
+
result = call_function(func_name, {})
|
|
659
|
+
_handle_result(result, func_name, {})
|
|
660
|
+
except Exception as e:
|
|
661
|
+
print_error(f"Failed: {e}")
|
|
662
|
+
sys.exit(1)
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
@cli.group("fund")
|
|
666
|
+
def cmd_fund():
|
|
667
|
+
"""基金数据快捷命令
|
|
668
|
+
|
|
669
|
+
\b
|
|
670
|
+
子命令:
|
|
671
|
+
etf 获取 ETF 基金数据
|
|
672
|
+
|
|
673
|
+
示例:
|
|
674
|
+
akshare-cli fund etf --json 列出所有 ETF
|
|
675
|
+
akshare-cli fund etf 159707 --json 获取单只 ETF 历史数据
|
|
676
|
+
"""
|
|
677
|
+
pass
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
@cmd_fund.command("etf")
|
|
681
|
+
@click.argument("symbol", required=False)
|
|
682
|
+
@click.option("--period", default="daily", help="daily/weekly/monthly")
|
|
683
|
+
@click.option("--start", "start_date", default="19700101", help="Start date")
|
|
684
|
+
@click.option("--end", "end_date", default="20500101", help="End date")
|
|
685
|
+
@click.option("--adjust", default="", help="qfq/hfq/''")
|
|
686
|
+
@output_options
|
|
687
|
+
def cmd_fund_etf(symbol, period, start_date, end_date, adjust):
|
|
688
|
+
"""获取 ETF 基金数据
|
|
689
|
+
|
|
690
|
+
不传代码时列出所有 ETF 实时行情;传代码时获取单只 ETF 历史数据。
|
|
691
|
+
数据来源:东方财富
|
|
692
|
+
|
|
693
|
+
\b
|
|
694
|
+
参数:
|
|
695
|
+
[SYMBOL] ETF 代码 (可选,如 159707, 510300)
|
|
696
|
+
--period 周期: daily/weekly/monthly (默认 daily)
|
|
697
|
+
--start 开始日期 YYYYMMDD
|
|
698
|
+
--end 结束日期 YYYYMMDD
|
|
699
|
+
--adjust 复权: qfq/hfq/空串
|
|
700
|
+
|
|
701
|
+
示例:
|
|
702
|
+
# 列出所有 ETF 实时行情
|
|
703
|
+
akshare-cli fund etf --json
|
|
704
|
+
|
|
705
|
+
# 获取单只 ETF 历史数据
|
|
706
|
+
akshare-cli fund etf 159707 --json
|
|
707
|
+
|
|
708
|
+
# 前复权周线数据
|
|
709
|
+
akshare-cli fund etf 510300 --period weekly --adjust qfq --csv
|
|
710
|
+
"""
|
|
711
|
+
if symbol:
|
|
712
|
+
kwargs = {
|
|
713
|
+
"symbol": symbol,
|
|
714
|
+
"period": period,
|
|
715
|
+
"start_date": start_date,
|
|
716
|
+
"end_date": end_date,
|
|
717
|
+
"adjust": adjust,
|
|
718
|
+
}
|
|
719
|
+
try:
|
|
720
|
+
result = call_function("fund_etf_hist_em", kwargs)
|
|
721
|
+
_handle_result(result, "fund_etf_hist_em", kwargs)
|
|
722
|
+
except Exception as e:
|
|
723
|
+
print_error(f"Failed: {e}")
|
|
724
|
+
sys.exit(1)
|
|
725
|
+
else:
|
|
726
|
+
try:
|
|
727
|
+
result = call_function("fund_etf_spot_em", {})
|
|
728
|
+
_handle_result(result, "fund_etf_spot_em", {})
|
|
729
|
+
except Exception as e:
|
|
730
|
+
print_error(f"Failed: {e}")
|
|
731
|
+
sys.exit(1)
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
@cli.group("futures")
|
|
735
|
+
def cmd_futures():
|
|
736
|
+
"""期货数据快捷命令
|
|
737
|
+
|
|
738
|
+
\b
|
|
739
|
+
子命令:
|
|
740
|
+
hist 获取期货历史 K 线数据
|
|
741
|
+
list 列出可用期货合约
|
|
742
|
+
|
|
743
|
+
示例:
|
|
744
|
+
akshare-cli futures hist 螺纹主连 --json
|
|
745
|
+
akshare-cli futures list --json
|
|
746
|
+
"""
|
|
747
|
+
pass
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
@cmd_futures.command("hist")
|
|
751
|
+
@click.argument("symbol")
|
|
752
|
+
@click.option("--period", default="daily", help="daily/weekly/monthly")
|
|
753
|
+
@click.option("--start", "start_date", default="19900101", help="Start date")
|
|
754
|
+
@click.option("--end", "end_date", default="20500101", help="End date")
|
|
755
|
+
@output_options
|
|
756
|
+
def cmd_futures_hist(symbol, period, start_date, end_date):
|
|
757
|
+
"""获取期货历史 K 线数据
|
|
758
|
+
|
|
759
|
+
数据来源:东方财富 (futures_hist_em)
|
|
760
|
+
|
|
761
|
+
\b
|
|
762
|
+
参数:
|
|
763
|
+
SYMBOL 期货合约名称 (如 螺纹主连, 热卷主连)
|
|
764
|
+
--period 周期: daily/weekly/monthly (默认 daily)
|
|
765
|
+
--start 开始日期 YYYYMMDD
|
|
766
|
+
--end 结束日期 YYYYMMDD
|
|
767
|
+
|
|
768
|
+
示例:
|
|
769
|
+
akshare-cli futures hist 螺纹主连 --json
|
|
770
|
+
akshare-cli futures hist 热卷主连 --period weekly --csv
|
|
771
|
+
akshare-cli futures hist 沥青主连 --start 20260101 --json
|
|
772
|
+
|
|
773
|
+
提示: 先用 'futures list' 查看可用合约名称
|
|
774
|
+
"""
|
|
775
|
+
kwargs = {
|
|
776
|
+
"symbol": symbol,
|
|
777
|
+
"period": period,
|
|
778
|
+
"start_date": start_date,
|
|
779
|
+
"end_date": end_date,
|
|
780
|
+
}
|
|
781
|
+
try:
|
|
782
|
+
result = call_function("futures_hist_em", kwargs)
|
|
783
|
+
_handle_result(result, "futures_hist_em", kwargs)
|
|
784
|
+
except Exception as e:
|
|
785
|
+
print_error(f"Failed: {e}")
|
|
786
|
+
sys.exit(1)
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
@cmd_futures.command("list")
|
|
790
|
+
@output_options
|
|
791
|
+
def cmd_futures_list():
|
|
792
|
+
"""列出可用期货合约
|
|
793
|
+
|
|
794
|
+
显示所有可查询的期货合约名称。数据来源:东方财富 (futures_hist_table_em)
|
|
795
|
+
|
|
796
|
+
\b
|
|
797
|
+
示例:
|
|
798
|
+
akshare-cli futures list --json
|
|
799
|
+
akshare-cli futures list --csv --output contracts.csv
|
|
800
|
+
"""
|
|
801
|
+
try:
|
|
802
|
+
result = call_function("futures_hist_table_em", {})
|
|
803
|
+
_handle_result(result, "futures_hist_table_em", {})
|
|
804
|
+
except Exception as e:
|
|
805
|
+
print_error(f"Failed: {e}")
|
|
806
|
+
sys.exit(1)
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
@cli.group("bond")
|
|
810
|
+
def cmd_bond():
|
|
811
|
+
"""债券数据快捷命令
|
|
812
|
+
|
|
813
|
+
\b
|
|
814
|
+
子命令:
|
|
815
|
+
convertible 获取可转债数据
|
|
816
|
+
|
|
817
|
+
示例:
|
|
818
|
+
akshare-cli bond convertible --json
|
|
819
|
+
|
|
820
|
+
更多债券函数可通过 call 命令调用:
|
|
821
|
+
akshare-cli call bond_china_yield --json
|
|
822
|
+
akshare-cli call bond_zh_us_rate --json
|
|
823
|
+
"""
|
|
824
|
+
pass
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
@cmd_bond.command("convertible")
|
|
828
|
+
@output_options
|
|
829
|
+
def cmd_bond_convertible():
|
|
830
|
+
"""获取可转债数据
|
|
831
|
+
|
|
832
|
+
获取全市场可转债实时数据。数据来源:集思录 (bond_cb_jsl)
|
|
833
|
+
|
|
834
|
+
\b
|
|
835
|
+
示例:
|
|
836
|
+
akshare-cli bond convertible --json
|
|
837
|
+
akshare-cli bond convertible --csv --output bonds.csv
|
|
838
|
+
akshare-cli bond convertible --json --limit 20
|
|
839
|
+
"""
|
|
840
|
+
try:
|
|
841
|
+
result = call_function("bond_cb_jsl", {})
|
|
842
|
+
_handle_result(result, "bond_cb_jsl", {})
|
|
843
|
+
except Exception as e:
|
|
844
|
+
print_error(f"Failed: {e}")
|
|
845
|
+
sys.exit(1)
|
|
846
|
+
|
|
847
|
+
|
|
848
|
+
@cli.group("index")
|
|
849
|
+
def cmd_index():
|
|
850
|
+
"""指数数据快捷命令
|
|
851
|
+
|
|
852
|
+
\b
|
|
853
|
+
子命令:
|
|
854
|
+
spot 获取指数实时行情
|
|
855
|
+
|
|
856
|
+
示例:
|
|
857
|
+
akshare-cli index spot --json 全球指数
|
|
858
|
+
akshare-cli index spot --market cn --json 中国指数
|
|
859
|
+
"""
|
|
860
|
+
pass
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
@cmd_index.command("spot")
|
|
864
|
+
@click.option("--market", default="global", help="global/cn")
|
|
865
|
+
@output_options
|
|
866
|
+
def cmd_index_spot(market):
|
|
867
|
+
"""获取指数实时行情
|
|
868
|
+
|
|
869
|
+
\b
|
|
870
|
+
参数:
|
|
871
|
+
--market 市场选择 (默认 global)
|
|
872
|
+
global 全球主要指数 (index_global_spot_em)
|
|
873
|
+
cn 中国指数 (stock_zh_index_spot_em)
|
|
874
|
+
|
|
875
|
+
示例:
|
|
876
|
+
akshare-cli index spot --json
|
|
877
|
+
akshare-cli index spot --market cn --json --limit 20
|
|
878
|
+
"""
|
|
879
|
+
func_name = "index_global_spot_em" if market == "global" else "stock_zh_index_spot_em"
|
|
880
|
+
try:
|
|
881
|
+
result = call_function(func_name, {})
|
|
882
|
+
_handle_result(result, func_name, {})
|
|
883
|
+
except Exception as e:
|
|
884
|
+
print_error(f"Failed: {e}")
|
|
885
|
+
sys.exit(1)
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
@cli.group("macro")
|
|
889
|
+
def cmd_macro():
|
|
890
|
+
"""宏观经济数据快捷命令
|
|
891
|
+
|
|
892
|
+
\b
|
|
893
|
+
子命令:
|
|
894
|
+
gdp 中国 GDP 数据
|
|
895
|
+
cpi 中国 CPI 数据 (支持月度/年度)
|
|
896
|
+
|
|
897
|
+
示例:
|
|
898
|
+
akshare-cli macro gdp --json
|
|
899
|
+
akshare-cli macro cpi --freq yearly --csv
|
|
900
|
+
|
|
901
|
+
更多宏观数据可通过 call 命令调用:
|
|
902
|
+
akshare-cli call macro_china_ppi_yearly --json
|
|
903
|
+
akshare-cli call macro_china_lpr --json
|
|
904
|
+
akshare-cli call macro_china_m2_yearly --json
|
|
905
|
+
"""
|
|
906
|
+
pass
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
@cmd_macro.command("gdp")
|
|
910
|
+
@output_options
|
|
911
|
+
def cmd_macro_gdp():
|
|
912
|
+
"""获取中国 GDP 数据
|
|
913
|
+
|
|
914
|
+
获取中国年度 GDP 数据。数据来源:macro_china_gdp_yearly
|
|
915
|
+
|
|
916
|
+
\b
|
|
917
|
+
示例:
|
|
918
|
+
akshare-cli macro gdp --json
|
|
919
|
+
akshare-cli macro gdp --csv --output gdp.csv
|
|
920
|
+
"""
|
|
921
|
+
try:
|
|
922
|
+
result = call_function("macro_china_gdp_yearly", {})
|
|
923
|
+
_handle_result(result, "macro_china_gdp_yearly", {})
|
|
924
|
+
except Exception as e:
|
|
925
|
+
print_error(f"Failed: {e}")
|
|
926
|
+
sys.exit(1)
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
@cmd_macro.command("cpi")
|
|
930
|
+
@click.option("--freq", default="monthly", help="monthly/yearly")
|
|
931
|
+
@output_options
|
|
932
|
+
def cmd_macro_cpi(freq):
|
|
933
|
+
"""获取中国 CPI 数据
|
|
934
|
+
|
|
935
|
+
获取中国消费者物价指数 (CPI) 数据。
|
|
936
|
+
|
|
937
|
+
\b
|
|
938
|
+
参数:
|
|
939
|
+
--freq 频率: monthly(月度)/yearly(年度) (默认 monthly)
|
|
940
|
+
|
|
941
|
+
示例:
|
|
942
|
+
# 月度 CPI
|
|
943
|
+
akshare-cli macro cpi --json
|
|
944
|
+
|
|
945
|
+
# 年度 CPI
|
|
946
|
+
akshare-cli macro cpi --freq yearly --csv
|
|
947
|
+
"""
|
|
948
|
+
func_name = f"macro_china_cpi_{freq}"
|
|
949
|
+
try:
|
|
950
|
+
result = call_function(func_name, {})
|
|
951
|
+
_handle_result(result, func_name, {})
|
|
952
|
+
except Exception as e:
|
|
953
|
+
print_error(f"Failed: {e}")
|
|
954
|
+
sys.exit(1)
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
@cli.group("forex")
|
|
958
|
+
def cmd_forex():
|
|
959
|
+
"""外汇数据快捷命令
|
|
960
|
+
|
|
961
|
+
\b
|
|
962
|
+
子命令:
|
|
963
|
+
spot 获取外汇实时汇率
|
|
964
|
+
hist 获取外汇历史汇率
|
|
965
|
+
|
|
966
|
+
示例:
|
|
967
|
+
akshare-cli forex spot --json
|
|
968
|
+
akshare-cli forex hist USDCNH --json
|
|
969
|
+
"""
|
|
970
|
+
pass
|
|
971
|
+
|
|
972
|
+
|
|
973
|
+
@cmd_forex.command("spot")
|
|
974
|
+
@output_options
|
|
975
|
+
def cmd_forex_spot():
|
|
976
|
+
"""获取外汇实时汇率
|
|
977
|
+
|
|
978
|
+
获取主要货币对实时汇率。数据来源:东方财富 (forex_spot_em)
|
|
979
|
+
|
|
980
|
+
\b
|
|
981
|
+
示例:
|
|
982
|
+
akshare-cli forex spot --json
|
|
983
|
+
akshare-cli forex spot --csv --limit 10
|
|
984
|
+
"""
|
|
985
|
+
try:
|
|
986
|
+
result = call_function("forex_spot_em", {})
|
|
987
|
+
_handle_result(result, "forex_spot_em", {})
|
|
988
|
+
except Exception as e:
|
|
989
|
+
print_error(f"Failed: {e}")
|
|
990
|
+
sys.exit(1)
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
@cmd_forex.command("hist")
|
|
994
|
+
@click.argument("symbol", default="USDCNH")
|
|
995
|
+
@output_options
|
|
996
|
+
def cmd_forex_hist(symbol):
|
|
997
|
+
"""获取外汇历史汇率
|
|
998
|
+
|
|
999
|
+
数据来源:东方财富 (forex_hist_em)
|
|
1000
|
+
|
|
1001
|
+
\b
|
|
1002
|
+
参数:
|
|
1003
|
+
[SYMBOL] 货币对 (默认 USDCNH)
|
|
1004
|
+
常用: USDCNH, EURUSD, GBPUSD, USDJPY
|
|
1005
|
+
|
|
1006
|
+
示例:
|
|
1007
|
+
akshare-cli forex hist USDCNH --json
|
|
1008
|
+
akshare-cli forex hist EURUSD --csv
|
|
1009
|
+
"""
|
|
1010
|
+
kwargs = {"symbol": symbol}
|
|
1011
|
+
try:
|
|
1012
|
+
result = call_function("forex_hist_em", kwargs)
|
|
1013
|
+
_handle_result(result, "forex_hist_em", kwargs)
|
|
1014
|
+
except Exception as e:
|
|
1015
|
+
print_error(f"Failed: {e}")
|
|
1016
|
+
sys.exit(1)
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
@cli.group("option")
|
|
1020
|
+
def cmd_option():
|
|
1021
|
+
"""期权数据快捷命令
|
|
1022
|
+
|
|
1023
|
+
\b
|
|
1024
|
+
子命令:
|
|
1025
|
+
info 获取期权合约信息
|
|
1026
|
+
|
|
1027
|
+
示例:
|
|
1028
|
+
akshare-cli option info --json
|
|
1029
|
+
"""
|
|
1030
|
+
pass
|
|
1031
|
+
|
|
1032
|
+
|
|
1033
|
+
@cmd_option.command("info")
|
|
1034
|
+
@output_options
|
|
1035
|
+
def cmd_option_info():
|
|
1036
|
+
"""获取期权合约信息
|
|
1037
|
+
|
|
1038
|
+
获取期权合约基本信息。数据来源:option_contract_info_ctp
|
|
1039
|
+
|
|
1040
|
+
\b
|
|
1041
|
+
示例:
|
|
1042
|
+
akshare-cli option info --json
|
|
1043
|
+
akshare-cli option info --csv --output options.csv
|
|
1044
|
+
"""
|
|
1045
|
+
try:
|
|
1046
|
+
result = call_function("option_contract_info_ctp", {})
|
|
1047
|
+
_handle_result(result, "option_contract_info_ctp", {})
|
|
1048
|
+
except Exception as e:
|
|
1049
|
+
print_error(f"Failed: {e}")
|
|
1050
|
+
sys.exit(1)
|
|
1051
|
+
|
|
1052
|
+
|
|
1053
|
+
# ─── EXPORT ──────────────────────────────────────────────────────────────────
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
@cli.command("export")
|
|
1057
|
+
@click.argument("filepath")
|
|
1058
|
+
def cmd_export(filepath):
|
|
1059
|
+
"""导出上一次查询结果到文件
|
|
1060
|
+
|
|
1061
|
+
将最近一次查询返回的 DataFrame 导出到指定文件。
|
|
1062
|
+
|
|
1063
|
+
\b
|
|
1064
|
+
支持格式:
|
|
1065
|
+
.csv CSV 文件
|
|
1066
|
+
.json JSON 文件
|
|
1067
|
+
.xlsx Excel 文件
|
|
1068
|
+
.md Markdown 表格
|
|
1069
|
+
|
|
1070
|
+
示例:
|
|
1071
|
+
akshare-cli export result.csv
|
|
1072
|
+
akshare-cli export data.xlsx
|
|
1073
|
+
akshare-cli export report.md
|
|
1074
|
+
|
|
1075
|
+
注意: 需要先运行查询命令,否则无数据可导出
|
|
1076
|
+
"""
|
|
1077
|
+
if _session.last_result is None:
|
|
1078
|
+
print_error("No data to export. Run a query first.")
|
|
1079
|
+
sys.exit(1)
|
|
1080
|
+
|
|
1081
|
+
try:
|
|
1082
|
+
saved = auto_export(_session.last_result, filepath)
|
|
1083
|
+
click.echo(f"Exported to {saved}")
|
|
1084
|
+
except Exception as e:
|
|
1085
|
+
print_error(f"Export failed: {e}")
|
|
1086
|
+
sys.exit(1)
|
|
1087
|
+
|
|
1088
|
+
|
|
1089
|
+
# ─── REPL ────────────────────────────────────────────────────────────────────
|
|
1090
|
+
|
|
1091
|
+
|
|
1092
|
+
@cli.command("repl")
|
|
1093
|
+
def cmd_repl():
|
|
1094
|
+
"""启动交互式 REPL 会话
|
|
1095
|
+
|
|
1096
|
+
进入交互模式,可以连续执行多个查询,保留会话状态。
|
|
1097
|
+
|
|
1098
|
+
\b
|
|
1099
|
+
REPL 命令:
|
|
1100
|
+
call <函数> [--参数 值 ...] 调用函数
|
|
1101
|
+
search <关键词> 搜索函数
|
|
1102
|
+
list [域] 列出函数
|
|
1103
|
+
info <函数> 查看函数详情
|
|
1104
|
+
export <文件路径> 导出上次结果
|
|
1105
|
+
history 查看历史记录
|
|
1106
|
+
set <键> <值> 设置偏好
|
|
1107
|
+
json on|off 切换 JSON 输出
|
|
1108
|
+
help 显示帮助
|
|
1109
|
+
quit / exit / q 退出
|
|
1110
|
+
|
|
1111
|
+
所有命令都支持 --json, --csv, --output 输出选项。
|
|
1112
|
+
|
|
1113
|
+
示例:
|
|
1114
|
+
akshare-cli repl
|
|
1115
|
+
"""
|
|
1116
|
+
click.echo("AKShare CLI REPL - Type 'help' for commands, 'quit' to exit.")
|
|
1117
|
+
click.echo(f"Loaded {len(get_all_functions())} functions.\n")
|
|
1118
|
+
|
|
1119
|
+
while True:
|
|
1120
|
+
try:
|
|
1121
|
+
line = input("akshare> ").strip()
|
|
1122
|
+
except (EOFError, KeyboardInterrupt):
|
|
1123
|
+
click.echo("\nBye!")
|
|
1124
|
+
break
|
|
1125
|
+
|
|
1126
|
+
if not line:
|
|
1127
|
+
continue
|
|
1128
|
+
|
|
1129
|
+
if line in ("quit", "exit", "q"):
|
|
1130
|
+
click.echo("Bye!")
|
|
1131
|
+
break
|
|
1132
|
+
|
|
1133
|
+
if line == "help":
|
|
1134
|
+
click.echo(
|
|
1135
|
+
"Commands:\n"
|
|
1136
|
+
" call <func> [--param value ...] Call function\n"
|
|
1137
|
+
" search <keyword> Search functions\n"
|
|
1138
|
+
" list [domain] List functions\n"
|
|
1139
|
+
" info <func> Function details\n"
|
|
1140
|
+
" export <filepath> Export last result\n"
|
|
1141
|
+
" history Show history\n"
|
|
1142
|
+
" set <key> <value> Set preference\n"
|
|
1143
|
+
" json on|off Toggle JSON output\n"
|
|
1144
|
+
" quit Exit\n"
|
|
1145
|
+
"\nAll commands support --json, --csv, --output flags.\n"
|
|
1146
|
+
)
|
|
1147
|
+
continue
|
|
1148
|
+
|
|
1149
|
+
if line == "history":
|
|
1150
|
+
for i, entry in enumerate(_session.history):
|
|
1151
|
+
click.echo(
|
|
1152
|
+
f" {i+1}. [{entry['timestamp'][:19]}] {entry['function']} "
|
|
1153
|
+
f"-> {entry.get('rows', '?')} rows"
|
|
1154
|
+
)
|
|
1155
|
+
continue
|
|
1156
|
+
|
|
1157
|
+
if line.startswith("json "):
|
|
1158
|
+
mode = line.split()[1]
|
|
1159
|
+
if mode == "on":
|
|
1160
|
+
_output_config.format = "json"
|
|
1161
|
+
click.echo("JSON output enabled.")
|
|
1162
|
+
else:
|
|
1163
|
+
_output_config.format = "table"
|
|
1164
|
+
click.echo("Table output enabled.")
|
|
1165
|
+
continue
|
|
1166
|
+
|
|
1167
|
+
if line.startswith("set "):
|
|
1168
|
+
parts = line.split(maxsplit=2)
|
|
1169
|
+
if len(parts) == 3:
|
|
1170
|
+
_session.set_preference(parts[1], parts[2])
|
|
1171
|
+
click.echo(f"Set {parts[1]} = {parts[2]}")
|
|
1172
|
+
else:
|
|
1173
|
+
click.echo("Usage: set <key> <value>")
|
|
1174
|
+
continue
|
|
1175
|
+
|
|
1176
|
+
# Parse and dispatch REPL commands through Click
|
|
1177
|
+
try:
|
|
1178
|
+
args = shlex.split(line)
|
|
1179
|
+
except ValueError:
|
|
1180
|
+
args = line.split()
|
|
1181
|
+
|
|
1182
|
+
if not args:
|
|
1183
|
+
continue
|
|
1184
|
+
|
|
1185
|
+
try:
|
|
1186
|
+
cli.main(args=args, standalone_mode=False)
|
|
1187
|
+
except SystemExit:
|
|
1188
|
+
pass
|
|
1189
|
+
except Exception as e:
|
|
1190
|
+
print_error(str(e))
|
|
1191
|
+
if os.environ.get("AKSHARE_CLI_DEBUG"):
|
|
1192
|
+
traceback.print_exc()
|
|
1193
|
+
|
|
1194
|
+
|
|
1195
|
+
# ─── VERSION ─────────────────────────────────────────────────────────────────
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
@cli.command("version")
|
|
1199
|
+
@output_options
|
|
1200
|
+
def cmd_version():
|
|
1201
|
+
"""显示版本信息
|
|
1202
|
+
|
|
1203
|
+
显示 CLI 工具和 akshare 库的版本号。
|
|
1204
|
+
|
|
1205
|
+
\b
|
|
1206
|
+
示例:
|
|
1207
|
+
akshare-cli version
|
|
1208
|
+
akshare-cli version --json
|
|
1209
|
+
"""
|
|
1210
|
+
from akshare_cli import __version__ as harness_version
|
|
1211
|
+
|
|
1212
|
+
info = {"cli_harness_version": harness_version}
|
|
1213
|
+
try:
|
|
1214
|
+
ak_mod = __import__("akshare")
|
|
1215
|
+
info["akshare_version"] = ak_mod.__version__
|
|
1216
|
+
except ImportError:
|
|
1217
|
+
info["akshare_version"] = "not installed"
|
|
1218
|
+
|
|
1219
|
+
if _output_config.format == "json":
|
|
1220
|
+
click.echo(json.dumps(info, indent=2))
|
|
1221
|
+
else:
|
|
1222
|
+
for k, v in info.items():
|
|
1223
|
+
click.echo(f"{k}: {v}")
|
|
1224
|
+
|
|
1225
|
+
|
|
1226
|
+
# ─── CACHE MANAGEMENT ────────────────────────────────────────────────────────
|
|
1227
|
+
|
|
1228
|
+
|
|
1229
|
+
@cli.group("cache")
|
|
1230
|
+
def cmd_cache():
|
|
1231
|
+
"""管理结果缓存
|
|
1232
|
+
|
|
1233
|
+
查看缓存统计信息或清空缓存。API 调用结果会自动缓存以加速重复查询。
|
|
1234
|
+
实时行情类接口不会被缓存。
|
|
1235
|
+
|
|
1236
|
+
\b
|
|
1237
|
+
子命令:
|
|
1238
|
+
stats 显示缓存命中率等统计信息
|
|
1239
|
+
clear 清空所有缓存
|
|
1240
|
+
|
|
1241
|
+
示例:
|
|
1242
|
+
akshare-cli cache stats
|
|
1243
|
+
akshare-cli cache clear
|
|
1244
|
+
"""
|
|
1245
|
+
pass
|
|
1246
|
+
|
|
1247
|
+
|
|
1248
|
+
@cmd_cache.command("stats")
|
|
1249
|
+
def cache_stats():
|
|
1250
|
+
"""显示缓存统计信息
|
|
1251
|
+
|
|
1252
|
+
\b
|
|
1253
|
+
示例:
|
|
1254
|
+
akshare-cli cache stats
|
|
1255
|
+
"""
|
|
1256
|
+
stats = _result_cache.stats()
|
|
1257
|
+
if _output_config.format == "json":
|
|
1258
|
+
click.echo(json.dumps(stats, indent=2, ensure_ascii=False))
|
|
1259
|
+
else:
|
|
1260
|
+
click.echo(f"缓存状态: {'启用' if stats['enabled'] else '禁用'}")
|
|
1261
|
+
click.echo(f"缓存条目: {stats['entries']}")
|
|
1262
|
+
click.echo(f"命中次数: {stats['hits']}")
|
|
1263
|
+
click.echo(f"未命中: {stats['misses']}")
|
|
1264
|
+
click.echo(f"命中率: {stats['hit_rate']}")
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
@cmd_cache.command("clear")
|
|
1268
|
+
def cache_clear():
|
|
1269
|
+
"""清空所有缓存
|
|
1270
|
+
|
|
1271
|
+
\b
|
|
1272
|
+
示例:
|
|
1273
|
+
akshare-cli cache clear
|
|
1274
|
+
"""
|
|
1275
|
+
_result_cache.clear()
|
|
1276
|
+
click.echo("缓存已清空。")
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
def main():
|
|
1280
|
+
"""Entry point for console_scripts."""
|
|
1281
|
+
cli()
|
|
1282
|
+
|
|
1283
|
+
|
|
1284
|
+
if __name__ == "__main__":
|
|
1285
|
+
main()
|