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 +170 -0
- package/bin/index.js +187 -0
- package/lib/api.js +162 -0
- package/lib/config.js +64 -0
- package/lib/output.js +279 -0
- package/package.json +28 -0
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
|
+
}
|