daxiapi-cli 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.
package/README.md ADDED
@@ -0,0 +1,170 @@
1
+ # daxiapi-cli
2
+
3
+ 大虾皮(daxiapi.com)金融数据API命令行工具。需要注册 daxiapi.com 网站,并且获取API Token之后才能使用。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ # 全局安装
9
+ npm install -g daxiapi-cli
10
+
11
+ # 或使用 pnpm
12
+ pnpm add -g daxiapi-cli
13
+ ```
14
+
15
+ ## 配置
16
+
17
+ 首次使用需要配置 API Token(在 daxiapi.com 用户中心申请):
18
+
19
+ ```bash
20
+ # 方式一:配置文件
21
+ daxiapi config set token YOUR_API_TOKEN
22
+
23
+ # 方式二:环境变量
24
+ export DAXIAPI_TOKEN=YOUR_API_TOKEN
25
+ ```
26
+
27
+ ## 使用
28
+
29
+ ### 配置管理
30
+
31
+ ```bash
32
+ # 设置Token
33
+ daxiapi config set token YOUR_API_TOKEN
34
+
35
+ # 设置API地址(可选)
36
+ daxiapi config set baseUrl https://daxiapi.com
37
+
38
+ # 查看所有配置
39
+ daxiapi config get
40
+
41
+ # 删除配置
42
+ daxiapi config delete token
43
+ ```
44
+
45
+ ### 市场数据
46
+
47
+ ```bash
48
+ # 市场概览(默认)
49
+ daxiapi market
50
+
51
+ # 市场温度
52
+ daxiapi market -d
53
+
54
+ # 大小盘风格
55
+ daxiapi market -s
56
+
57
+ # 指数估值
58
+ daxiapi market -v
59
+
60
+ # 收盘新闻
61
+ daxiapi market -n
62
+
63
+ # 涨跌停股票池
64
+ daxiapi market -z
65
+ ```
66
+
67
+ ### 指数K线
68
+
69
+ ```bash
70
+ # 获取上证指数K线
71
+ daxiapi index
72
+ ```
73
+
74
+ ### 板块数据
75
+
76
+ ```bash
77
+ # 板块热力图
78
+ daxiapi sector
79
+
80
+ # 指定排序字段和天数
81
+ daxiapi sector -o zdf -l 10
82
+
83
+ # 获取板块内个股排名
84
+ daxiapi sector -c BK0477 -o cs
85
+
86
+ # 获取概念内个股
87
+ daxiapi sector -g GN1234
88
+ ```
89
+
90
+ ### 个股查询
91
+
92
+ ```bash
93
+ # 查询单个股票
94
+ daxiapi stock 000001
95
+
96
+ # 查询多个股票
97
+ daxiapi stock 000001 600031 300750
98
+
99
+ # JSON格式输出
100
+ daxiapi stock 000001 --json
101
+ ```
102
+
103
+ ### 搜索
104
+
105
+ ```bash
106
+ # 搜索股票
107
+ daxiapi search 三一重工
108
+
109
+ # 搜索行业
110
+ daxiapi search 机械 -t hy
111
+ ```
112
+
113
+ > 💡 提示:`daxiapi` 命令也可以使用简写 `dxp`
114
+
115
+ ## 输出示例
116
+
117
+ ### 市场概览
118
+
119
+ ```
120
+ 📈 市场概览
121
+
122
+ 主要指数:
123
+ ┌─────────┬────────┬───────────┬───────┬─────────┬─────────┐
124
+ │ (index) │ 名称 │ 涨跌幅 │ CS │ 5日 │ 20日 │
125
+ ├─────────┼────────┼───────────┼───────┼─────────┼─────────┤
126
+ │ 0 │ '上证' │ '0.50%' │ 5.23 │ '2.10%' │ '3.50%' │
127
+ │ 1 │ '深证' │ '0.80%' │ 6.45 │ '2.50%' │ '4.20%' │
128
+ │ 2 │ '创业' │ '1.20%' │ 8.12 │ '3.00%' │ '5.10%' │
129
+ └─────────┴────────┴───────────┴───────┴─────────┴─────────┘
130
+ ```
131
+
132
+ ### 个股详情
133
+
134
+ ```
135
+ 📊 个股详情
136
+
137
+ 三一重工 (600031)
138
+ ────────────────────────────────────────
139
+ 价格: 18.50 涨跌幅: 2.50%
140
+
141
+ 动量指标:
142
+ CS(短期): 8.25 SM(中期): 5.30 ML(长期): 3.20
143
+ RPS: 85 SCTR: 72
144
+
145
+ 形态标签: Good, LPS
146
+ 技术形态: VCP, SOS
147
+ ```
148
+
149
+ ## 核心指标说明
150
+
151
+ | 指标 | 说明 |
152
+ |------|------|
153
+ | CS | 短期动量,股价与20日均线乖离率。>0在均线上方,>30可能反转 |
154
+ | SM | 中期动量,短期与中期均线乖离率。>0多头排列 |
155
+ | ML | 长期动量,中期与长期均线乖离率 |
156
+ | RPS | 欧奈尔相对强度,>80为强势股 |
157
+ | SCTR | 技术排名百分比,如60代表强于市场60%股票 |
158
+ | VCP | 马克米勒维尼波动收缩模式 |
159
+ | SOS | Wyckoff强势走势行为 |
160
+ | LPS | Wyckoff最后支撑点 |
161
+
162
+ ## 限流规则
163
+
164
+ - 每日上限:1000次
165
+ - 每分钟上限:10次
166
+ - 超限返回 429 错误
167
+
168
+ ## License
169
+
170
+ MIT
package/bin/index.js ADDED
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { program } = require('commander');
4
+ const chalk = require('chalk');
5
+ const config = require('../lib/config');
6
+ const api = require('../lib/api');
7
+ const output = require('../lib/output');
8
+
9
+ // 显示欢迎信息
10
+ function showWelcome() {
11
+ console.log(chalk.cyan.bold('\n📊 大虾皮 CLI - A股量化数据工具\n'));
12
+ }
13
+
14
+ // 检查Token配置
15
+ function checkToken() {
16
+ const token = config.getToken();
17
+ if (!token) {
18
+ console.log(chalk.yellow('⚠️ 未配置 API Token'));
19
+ console.log(chalk.gray(' 请先运行: daxiapi config set token YOUR_TOKEN'));
20
+ console.log(chalk.gray(' 或设置环境变量: DAXIAPI_TOKEN=YOUR_TOKEN\n'));
21
+ process.exit(1);
22
+ }
23
+ return token;
24
+ }
25
+
26
+ program
27
+ .name('daxiapi')
28
+ .version('1.0.0')
29
+ .description('大虾皮(daxiapi.com)金融数据API命令行工具');
30
+
31
+ // ==================== 配置命令 ====================
32
+ program
33
+ .command('config')
34
+ .description('配置管理')
35
+ .argument('<action>', 'set/get/delete')
36
+ .argument('[key]', '配置项名称')
37
+ .argument('[value]', '配置项值')
38
+ .action((action, key, value) => {
39
+ switch (action) {
40
+ case 'set':
41
+ if (!key || !value) {
42
+ console.log(chalk.red('用法: daxiapi config set <key> <value>'));
43
+ console.log(chalk.gray('示例: daxiapi config set token abc123...'));
44
+ return;
45
+ }
46
+ config.set(key, value);
47
+ console.log(chalk.green(`✓ 已设置 ${key}`));
48
+ break;
49
+ case 'get':
50
+ if (!key) {
51
+ const all = config.getAll();
52
+ console.log(JSON.stringify(all, null, 2));
53
+ } else {
54
+ console.log(config.get(key) || '');
55
+ }
56
+ break;
57
+ case 'delete':
58
+ config.delete(key);
59
+ console.log(chalk.green(`✓ 已删除 ${key}`));
60
+ break;
61
+ default:
62
+ console.log(chalk.red('未知操作:', action));
63
+ }
64
+ });
65
+
66
+ // ==================== 市场数据命令 ====================
67
+ program
68
+ .command('market')
69
+ .description('市场数据')
70
+ .option('-d, --degree', '获取市场温度')
71
+ .option('-s, --style', '获取大小盘风格')
72
+ .option('-v, --value', '获取指数估值')
73
+ .option('-n, --news', '获取收盘新闻')
74
+ .option('-z, --zdt', '获取涨跌停股票池')
75
+ .action(async (options) => {
76
+ showWelcome();
77
+ const token = checkToken();
78
+
79
+ try {
80
+ if (options.degree) {
81
+ const data = await api.getMarketDegree(token);
82
+ output.text(data);
83
+ } else if (options.style) {
84
+ const data = await api.getMarketStyle(token);
85
+ output.table(data);
86
+ } else if (options.value) {
87
+ const data = await api.getMarketValueData(token);
88
+ output.valueTable(data);
89
+ } else if (options.news) {
90
+ const data = await api.getMarketNews(token);
91
+ output.newsList(data);
92
+ } else if (options.zdt) {
93
+ const data = await api.getZdtPool(token);
94
+ output.text(data);
95
+ } else {
96
+ // 默认获取市场概览
97
+ const data = await api.getMarketData(token);
98
+ output.marketOverview(data);
99
+ }
100
+ } catch (err) {
101
+ output.error(err);
102
+ }
103
+ });
104
+
105
+ // ==================== 指数K线命令 ====================
106
+ program
107
+ .command('index')
108
+ .description('获取上证指数K线数据')
109
+ .action(async () => {
110
+ showWelcome();
111
+ const token = checkToken();
112
+
113
+ try {
114
+ const data = await api.getIndexK(token);
115
+ output.klines(data);
116
+ } catch (err) {
117
+ output.error(err);
118
+ }
119
+ });
120
+
121
+ // ==================== 板块数据命令 ====================
122
+ program
123
+ .command('sector')
124
+ .description('板块数据')
125
+ .option('-c, --code <code>', '板块代码,获取板块内个股')
126
+ .option('-o, --order <field>', '排序字段', 'cs')
127
+ .option('-l, --limit <days>', '天数', '5')
128
+ .option('-g, --gn <gnId>', '概念代码,获取概念内个股')
129
+ .action(async (options) => {
130
+ showWelcome();
131
+ const token = checkToken();
132
+
133
+ try {
134
+ if (options.code) {
135
+ const data = await api.getSectorRankStock(token, options.code, options.order);
136
+ output.stockList(data);
137
+ } else if (options.gn) {
138
+ const data = await api.getGainianStock(token, options.gn);
139
+ output.stockList(data);
140
+ } else {
141
+ const data = await api.getSectorData(token, options.order, parseInt(options.limit));
142
+ output.sectorHeatmap(data);
143
+ }
144
+ } catch (err) {
145
+ output.error(err);
146
+ }
147
+ });
148
+
149
+ // ==================== 个股查询命令 ====================
150
+ program
151
+ .command('stock <codes...>')
152
+ .description('获取个股数据,支持多个股票代码')
153
+ .option('-j, --json', '输出JSON格式')
154
+ .action(async (codes, options) => {
155
+ showWelcome();
156
+ const token = checkToken();
157
+
158
+ try {
159
+ const data = await api.getStockData(token, codes.join(','));
160
+ if (options.json) {
161
+ console.log(JSON.stringify(data, null, 2));
162
+ } else {
163
+ output.stockDetail(data);
164
+ }
165
+ } catch (err) {
166
+ output.error(err);
167
+ }
168
+ });
169
+
170
+ // ==================== 搜索命令 ====================
171
+ program
172
+ .command('search <keyword>')
173
+ .description('搜索股票或行业')
174
+ .option('-t, --type <type>', '类型: stock/hy', 'stock')
175
+ .action(async (keyword, options) => {
176
+ showWelcome();
177
+ const token = checkToken();
178
+
179
+ try {
180
+ const data = await api.queryStock(token, keyword, options.type);
181
+ output.searchResult(data);
182
+ } catch (err) {
183
+ output.error(err);
184
+ }
185
+ });
186
+
187
+ program.parse();
package/lib/api.js ADDED
@@ -0,0 +1,162 @@
1
+ const axios = require('axios');
2
+ const config = require('./config');
3
+
4
+ const BASE_URL = config.get('baseUrl') || 'https://daxiapi.com';
5
+
6
+ /**
7
+ * 创建请求实例
8
+ */
9
+ function createClient(token) {
10
+ return axios.create({
11
+ baseURL: `${BASE_URL}/coze`,
12
+ headers: {
13
+ 'X-API-Token': token,
14
+ 'Content-Type': 'application/json'
15
+ },
16
+ timeout: 30000
17
+ });
18
+ }
19
+
20
+ /**
21
+ * GET 请求
22
+ */
23
+ async function get(client, path) {
24
+ const { data } = await client.get(path);
25
+ if (data.errCode !== 0) {
26
+ throw new Error(data.errMsg || `API Error: ${data.errCode}`);
27
+ }
28
+ return data.data;
29
+ }
30
+
31
+ /**
32
+ * POST 请求
33
+ */
34
+ async function post(client, path, body = {}) {
35
+ const { data } = await client.post(path, body);
36
+ if (data.errCode !== 0) {
37
+ throw new Error(data.errMsg || `API Error: ${data.errCode}`);
38
+ }
39
+ return data.data;
40
+ }
41
+
42
+ // ==================== API 方法 ====================
43
+
44
+ /**
45
+ * 获取指数K线
46
+ */
47
+ async function getIndexK(token) {
48
+ const client = createClient(token);
49
+ return get(client, '/get_index_k');
50
+ }
51
+
52
+ /**
53
+ * 获取市场数据
54
+ */
55
+ async function getMarketData(token) {
56
+ const client = createClient(token);
57
+ return get(client, '/get_market_data');
58
+ }
59
+
60
+ /**
61
+ * 获取市场温度
62
+ */
63
+ async function getMarketDegree(token) {
64
+ const client = createClient(token);
65
+ return get(client, '/get_market_degree');
66
+ }
67
+
68
+ /**
69
+ * 获取市场风格
70
+ */
71
+ async function getMarketStyle(token) {
72
+ const client = createClient(token);
73
+ return get(client, '/get_market_style');
74
+ }
75
+
76
+ /**
77
+ * 获取指数估值
78
+ */
79
+ async function getMarketValueData(token) {
80
+ const client = createClient(token);
81
+ return get(client, '/get_market_value_data');
82
+ }
83
+
84
+ /**
85
+ * 获取收盘新闻
86
+ */
87
+ async function getMarketNews(token) {
88
+ const client = createClient(token);
89
+ return get(client, '/get_market_end_news');
90
+ }
91
+
92
+ /**
93
+ * 获取涨跌停股票池
94
+ */
95
+ async function getZdtPool(token) {
96
+ const client = createClient(token);
97
+ return post(client, '/get_zdt_pool');
98
+ }
99
+
100
+ /**
101
+ * 获取板块热力图
102
+ */
103
+ async function getSectorData(token, orderBy = 'cs', lmt = 5) {
104
+ const client = createClient(token);
105
+ return post(client, '/get_sector_data', { orderBy, lmt });
106
+ }
107
+
108
+ /**
109
+ * 获取板块内个股排名
110
+ */
111
+ async function getSectorRankStock(token, sectorCode, orderBy = 'cs') {
112
+ const client = createClient(token);
113
+ return post(client, '/get_sector_rank_stock', { sectorCode, orderBy });
114
+ }
115
+
116
+ /**
117
+ * 获取概念内个股
118
+ */
119
+ async function getGainianStock(token, gnId) {
120
+ const client = createClient(token);
121
+ return post(client, '/get_gainian_stock', { gnId });
122
+ }
123
+
124
+ /**
125
+ * 获取个股数据
126
+ */
127
+ async function getStockData(token, codes) {
128
+ const client = createClient(token);
129
+ return post(client, '/get_stock_data', { code: codes });
130
+ }
131
+
132
+ /**
133
+ * 搜索股票/行业
134
+ */
135
+ async function queryStock(token, keyword, type = 'stock') {
136
+ const client = createClient(token);
137
+ return post(client, '/query_stock_data', { q: keyword, type });
138
+ }
139
+
140
+ /**
141
+ * 获取概念板块数据
142
+ */
143
+ async function getGnTable(token) {
144
+ const client = createClient(token);
145
+ return post(client, '/get_gn_table');
146
+ }
147
+
148
+ module.exports = {
149
+ getIndexK,
150
+ getMarketData,
151
+ getMarketDegree,
152
+ getMarketStyle,
153
+ getMarketValueData,
154
+ getMarketNews,
155
+ getZdtPool,
156
+ getSectorData,
157
+ getSectorRankStock,
158
+ getGainianStock,
159
+ getStockData,
160
+ queryStock,
161
+ getGnTable
162
+ };
package/lib/config.js ADDED
@@ -0,0 +1,64 @@
1
+ const Conf = require('conf');
2
+
3
+ const schema = {
4
+ token: {
5
+ type: 'string',
6
+ description: 'API Token'
7
+ },
8
+ baseUrl: {
9
+ type: 'string',
10
+ default: 'https://daxiapi.com',
11
+ description: 'API基础URL'
12
+ }
13
+ };
14
+
15
+ const config = new Conf({
16
+ projectName: 'daxiapi-cli',
17
+ schema
18
+ });
19
+
20
+ /**
21
+ * 获取Token(优先环境变量)
22
+ */
23
+ function getToken() {
24
+ return process.env.DAXIAPI_TOKEN || config.get('token');
25
+ }
26
+
27
+ /**
28
+ * 获取配置项
29
+ */
30
+ function get(key) {
31
+ return config.get(key);
32
+ }
33
+
34
+ /**
35
+ * 设置配置项
36
+ */
37
+ function set(key, value) {
38
+ config.set(key, value);
39
+ }
40
+
41
+ /**
42
+ * 获取所有配置
43
+ */
44
+ function getAll() {
45
+ return {
46
+ token: getToken() ? '******' + getToken().slice(-4) : undefined,
47
+ baseUrl: config.get('baseUrl')
48
+ };
49
+ }
50
+
51
+ /**
52
+ * 删除配置项
53
+ */
54
+ function del(key) {
55
+ config.delete(key);
56
+ }
57
+
58
+ module.exports = {
59
+ getToken,
60
+ get,
61
+ set,
62
+ getAll,
63
+ delete: del
64
+ };
package/lib/output.js ADDED
@@ -0,0 +1,279 @@
1
+ const chalk = require('chalk');
2
+ const consoleTable = require('console.table');
3
+
4
+ /**
5
+ * 错误输出
6
+ */
7
+ function error(err) {
8
+ console.log(chalk.red.bold('\n❌ 错误:'), err.message);
9
+ if (err.response?.status === 401) {
10
+ console.log(chalk.yellow(' Token无效或已过期,请重新配置'));
11
+ } else if (err.response?.status === 429) {
12
+ console.log(chalk.yellow(' 请求频率超限,请稍后重试'));
13
+ }
14
+ }
15
+
16
+ /**
17
+ * 纯文本输出
18
+ */
19
+ function text(data) {
20
+ if (typeof data === 'string') {
21
+ console.log(data);
22
+ } else {
23
+ console.log(JSON.stringify(data, null, 2));
24
+ }
25
+ }
26
+
27
+ /**
28
+ * 表格输出
29
+ */
30
+ function table(data) {
31
+ if (typeof data === 'string') {
32
+ // Markdown表格直接输出
33
+ console.log(data);
34
+ } else if (Array.isArray(data)) {
35
+ console.table(data);
36
+ } else {
37
+ console.log(JSON.stringify(data, null, 2));
38
+ }
39
+ }
40
+
41
+ /**
42
+ * 市场概览
43
+ */
44
+ function marketOverview(data) {
45
+ console.log(chalk.bold.cyan('📈 市场概览\n'));
46
+
47
+ if (data.index && data.index.length > 0) {
48
+ console.log(chalk.bold('主要指数:'));
49
+ const indexData = data.index.map(item => ({
50
+ '名称': item.name,
51
+ '涨跌幅': formatPercent(item.zdf),
52
+ 'CS': item.cs?.toFixed(2) || '-',
53
+ '5日': formatPercent(item.zdf5),
54
+ '20日': formatPercent(item.zdf20)
55
+ }));
56
+ console.table(indexData);
57
+ }
58
+
59
+ if (data.above_ma200_ratio !== undefined) {
60
+ console.log(chalk.bold('\n市场宽度:'));
61
+ console.log(` 200日均线上方: ${chalk.cyan((data.above_ma200_ratio * 100).toFixed(1) + '%')}`);
62
+ }
63
+
64
+ if (data.zdfRange) {
65
+ console.log(chalk.bold('\n涨跌分布:'));
66
+ const range = data.zdfRange;
67
+ console.log(` 涨停(>10%): ${chalk.red(range.zdf_10 || 0)}`);
68
+ console.log(` 大涨(7-10%): ${chalk.red(range.zdf_10z7 || 0)}`);
69
+ console.log(` 上涨(5-7%): ${chalk.red(range.zdf_7z5 || 0)}`);
70
+ console.log(` 微涨(2-5%): ${chalk.red(range.zdf_5z2 || 0)}`);
71
+ console.log(` 平盘: ${chalk.gray(range.zdf_0 || 0)}`);
72
+ console.log(` 微跌(0-2%): ${chalk.green(range.zdf_2z0 || 0)}`);
73
+ console.log(` 下跌(2-5%): ${chalk.green(range.zdf_0z_2 || 0)}`);
74
+ console.log(` 大跌(5-7%): ${chalk.green(range.zdf_7z_5 || 0)}`);
75
+ console.log(` 跌停(<-10%): ${chalk.green(range.zdf__10 || 0)}`);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * 指数估值表格
81
+ */
82
+ function valueTable(data) {
83
+ console.log(chalk.bold.cyan('💰 指数估值\n'));
84
+
85
+ if (data.items && data.items.length > 0) {
86
+ const tableData = data.items.map(item => ({
87
+ 名称: item.name,
88
+ PE: item.PE?.toFixed(2) || '-',
89
+ PE分位: (item.PEPercentile * 100).toFixed(1) + '%',
90
+ PB: item.PB?.toFixed(2) || '-',
91
+ PB分位: (item.PBPercentile * 100).toFixed(1) + '%',
92
+ 温度: formatTemp(item.wendu)
93
+ }));
94
+ console.table(tableData);
95
+ console.log(chalk.gray(`\n数据日期: ${data.fullDate}`));
96
+ }
97
+ }
98
+
99
+ /**
100
+ * 新闻列表
101
+ */
102
+ function newsList(data) {
103
+ console.log(chalk.bold.cyan('📰 收盘新闻\n'));
104
+
105
+ if (data.title) {
106
+ console.log(chalk.bold(data.title));
107
+ if (data.summary) {
108
+ console.log(chalk.gray(data.summary));
109
+ }
110
+ if (data.showTime) {
111
+ console.log(chalk.gray(`时间: ${data.showTime}`));
112
+ }
113
+ if (data.uniqueUrl) {
114
+ console.log(chalk.blue(`链接: ${data.uniqueUrl}`));
115
+ }
116
+ }
117
+ }
118
+
119
+ /**
120
+ * K线数据
121
+ */
122
+ function klines(data) {
123
+ console.log(chalk.bold.cyan(`📊 ${data.name} K线数据\n`));
124
+
125
+ if (data.klines && data.klines.length > 0) {
126
+ const tableData = data.klines.slice(-10).map(item => ({
127
+ 日期: item.date,
128
+ 开盘: item.open?.toFixed(2),
129
+ 收盘: item.close?.toFixed(2),
130
+ 最高: item.high?.toFixed(2),
131
+ 最低: item.low?.toFixed(2),
132
+ 成交量: formatVolume(item.vol)
133
+ }));
134
+ console.table(tableData);
135
+ }
136
+ }
137
+
138
+ /**
139
+ * 板块热力图
140
+ */
141
+ function sectorHeatmap(data) {
142
+ console.log(chalk.bold.cyan('🔥 板块热力图\n'));
143
+
144
+ if (data.csHeatmap) {
145
+ console.log(chalk.bold('CS动量热力图:'));
146
+ console.log(data.csHeatmap);
147
+ }
148
+
149
+ if (data.crossover) {
150
+ console.log(chalk.bold('\n突破箱体板块:'));
151
+ console.log(data.crossover);
152
+ }
153
+ }
154
+
155
+ /**
156
+ * 股票列表
157
+ */
158
+ function stockList(data) {
159
+ console.log(chalk.bold.cyan('📋 股票列表\n'));
160
+
161
+ if (Array.isArray(data) && data.length > 0) {
162
+ const tableData = data.slice(0, 20).map(item => ({
163
+ 代码: item.code || item.stockId,
164
+ 名称: item.name,
165
+ 收盘价: item.close?.toFixed(2) || '-',
166
+ 涨跌幅: formatPercent(item.zdf),
167
+ CS: item.cs?.toFixed(2) || '-',
168
+ RPS: item.rps_score?.toFixed(0) || '-',
169
+ 标签: (item.tags || []).join(',')
170
+ }));
171
+ console.table(tableData);
172
+ }
173
+ }
174
+
175
+ /**
176
+ * 股票详情
177
+ */
178
+ function stockDetail(data) {
179
+ console.log(chalk.bold.cyan('📊 个股详情\n'));
180
+
181
+ if (Array.isArray(data)) {
182
+ data.forEach(stock => {
183
+ console.log(chalk.bold.blue(`${stock.name} (${stock.code})`));
184
+ console.log(chalk.gray('─'.repeat(40)));
185
+
186
+ console.log(`价格: ${stock.close?.toFixed(2)} 涨跌幅: ${formatPercent(stock.zdf)}`);
187
+ console.log(`今开: ${stock.open?.toFixed(2)} 最高: ${stock.high?.toFixed(2)} 最低: ${stock.low?.toFixed(2)}`);
188
+ console.log('');
189
+
190
+ console.log(chalk.bold('动量指标:'));
191
+ console.log(` CS(短期): ${stock.cs?.toFixed(2)} SM(中期): ${stock.sm?.toFixed(2)} ML(长期): ${stock.ml?.toFixed(2)}`);
192
+ console.log(` RPS: ${stock.rps_score?.toFixed(0)} SCTR: ${stock.sctr?.toFixed(0)}`);
193
+ console.log('');
194
+
195
+ console.log(chalk.bold('均线系统:'));
196
+ console.log(` MA20: ${stock.ma20?.toFixed(2)} MA50: ${stock.ma50?.toFixed(2)}`);
197
+ console.log(` MA150: ${stock.ma150?.toFixed(2)} MA200: ${stock.ma200?.toFixed(2)}`);
198
+ console.log('');
199
+
200
+ if (stock.tags && stock.tags.length > 0) {
201
+ console.log(chalk.bold('形态标签:'), stock.tags.join(', '));
202
+ }
203
+
204
+ const signals = [];
205
+ if (stock.isVCP) signals.push('VCP');
206
+ if (stock.isSOS) signals.push('SOS');
207
+ if (stock.isLPS) signals.push('LPS');
208
+ if (stock.isSpring) signals.push('Spring');
209
+ if (stock.isCrossoverBox) signals.push('突破箱体');
210
+ if (signals.length > 0) {
211
+ console.log(chalk.bold('技术形态:'), chalk.green(signals.join(', ')));
212
+ }
213
+
214
+ console.log('');
215
+ });
216
+ }
217
+ }
218
+
219
+ /**
220
+ * 搜索结果
221
+ */
222
+ function searchResult(data) {
223
+ console.log(chalk.bold.cyan('🔍 搜索结果\n'));
224
+
225
+ if (Array.isArray(data) && data.length > 0) {
226
+ const tableData = data.map(item => ({
227
+ 代码: item.code,
228
+ 名称: item.name,
229
+ 拼音: item.pinyin,
230
+ 类型: item.type === 'hy' ? '行业' : '个股'
231
+ }));
232
+ console.table(tableData);
233
+ } else {
234
+ console.log(chalk.yellow('未找到匹配结果'));
235
+ }
236
+ }
237
+
238
+ // ==================== 辅助函数 ====================
239
+
240
+ function formatPercent(value) {
241
+ if (value === null || value === undefined) return '-';
242
+ const num = typeof value === 'string' ? parseFloat(value) : value;
243
+ if (isNaN(num)) return '-';
244
+ const str = num.toFixed(2) + '%';
245
+ return num >= 0 ? chalk.red(str) : chalk.green(str);
246
+ }
247
+
248
+ function formatTemp(value) {
249
+ if (value === null || value === undefined) return '-';
250
+ const num = typeof value === 'string' ? parseFloat(value) : value;
251
+ if (isNaN(num)) return '-';
252
+
253
+ if (num < 20) return chalk.blue(num.toFixed(1) + '°C');
254
+ if (num < 40) return chalk.cyan(num.toFixed(1) + '°C');
255
+ if (num < 60) return chalk.yellow(num.toFixed(1) + '°C');
256
+ if (num < 80) return chalk.rgb(255, 165, 0)(num.toFixed(1) + '°C');
257
+ return chalk.red(num.toFixed(1) + '°C');
258
+ }
259
+
260
+ function formatVolume(vol) {
261
+ if (!vol) return '-';
262
+ if (vol >= 100000000) return (vol / 100000000).toFixed(2) + '亿';
263
+ if (vol >= 10000) return (vol / 10000).toFixed(2) + '万';
264
+ return vol.toString();
265
+ }
266
+
267
+ module.exports = {
268
+ error,
269
+ text,
270
+ table,
271
+ marketOverview,
272
+ valueTable,
273
+ newsList,
274
+ klines,
275
+ sectorHeatmap,
276
+ stockList,
277
+ stockDetail,
278
+ searchResult
279
+ };
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "daxiapi-cli",
3
+ "version": "1.0.0",
4
+ "description": "大虾皮(daxiapi.com)金融数据API命令行工具",
5
+ "bin": {
6
+ "daxiapi": "./bin/index.js",
7
+ "dxp": "./bin/index.js"
8
+ },
9
+ "scripts": {
10
+ "test": "node bin/index.js"
11
+ },
12
+ "keywords": [
13
+ "stock",
14
+ "api",
15
+ "daxiapi",
16
+ "cli",
17
+ "finance"
18
+ ],
19
+ "author": "daxiapi.com",
20
+ "license": "MIT",
21
+ "dependencies": {
22
+ "axios": "^1.6.0",
23
+ "chalk": "^4.1.2",
24
+ "commander": "^11.1.0",
25
+ "conf": "^10.2.0",
26
+ "console.table": "^0.10.0"
27
+ }
28
+ }