daxiapi-cli 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,120 @@
1
+ const config = require('../lib/config');
2
+ const api = require('../lib/api');
3
+ const {handleError, createParameterError} = require('../lib/error');
4
+ const {output} = require('../lib/output');
5
+
6
+ module.exports = function (program) {
7
+ const sectorCmd = program.command('sector');
8
+
9
+ sectorCmd
10
+ .description('板块数据')
11
+ .option('--order <field>', '排序字段', 'cs')
12
+ .option('--limit <num>', '数量限制', 5)
13
+ .action(async options => {
14
+ try {
15
+ const token = config.getToken();
16
+ if (!token) {
17
+ const error = new Error('未配置 API Token');
18
+ error.response = {status: 401};
19
+ throw error;
20
+ }
21
+
22
+ const data = await api.getSectorData(token, options.order, options.limit);
23
+ output(data);
24
+ } catch (error) {
25
+ handleError(error);
26
+ process.exit(1);
27
+ }
28
+ });
29
+
30
+ sectorCmd
31
+ .command('bk')
32
+ .description('行业板块数据')
33
+ .action(async () => {
34
+ try {
35
+ const token = config.getToken();
36
+ if (!token) {
37
+ const error = new Error('未配置 API Token');
38
+ error.response = {status: 401};
39
+ throw error;
40
+ }
41
+
42
+ const data = await api.getBkData(token);
43
+ output(data);
44
+ } catch (error) {
45
+ handleError(error);
46
+ process.exit(1);
47
+ }
48
+ });
49
+
50
+ sectorCmd
51
+ .command('stocks')
52
+ .description('板块个股排名')
53
+ .requiredOption('--code <bkCode>', '板块代码')
54
+ .option('--order <field>', '排序字段', 'cs')
55
+ .action(async options => {
56
+ try {
57
+ const token = config.getToken();
58
+ if (!token) {
59
+ const error = new Error('未配置 API Token');
60
+ error.response = {status: 401};
61
+ throw error;
62
+ }
63
+
64
+ const data = await api.getSectorRankStock(token, options.code, options.order);
65
+ output(data);
66
+ } catch (error) {
67
+ handleError(error);
68
+ process.exit(1);
69
+ }
70
+ });
71
+
72
+ sectorCmd
73
+ .command('top')
74
+ .description('热门股票')
75
+ .action(async () => {
76
+ try {
77
+ const token = config.getToken();
78
+ if (!token) {
79
+ const error = new Error('未配置 API Token');
80
+ error.response = {status: 401};
81
+ throw error;
82
+ }
83
+
84
+ const data = await api.getTopStocks(token);
85
+ output(data);
86
+ } catch (error) {
87
+ handleError(error);
88
+ process.exit(1);
89
+ }
90
+ });
91
+
92
+ sectorCmd
93
+ .command('gn')
94
+ .description('热门概念')
95
+ .option('--type <type>', '数据源类型 (dfcf|ths)', 'ths')
96
+ .action(async options => {
97
+ try {
98
+ const token = config.getToken();
99
+ if (!token) {
100
+ const error = new Error('未配置 API Token');
101
+ error.response = {status: 401};
102
+ throw error;
103
+ }
104
+
105
+ if (!['dfcf', 'ths'].includes(options.type)) {
106
+ throw createParameterError(
107
+ '参数无效',
108
+ ["参数 'type' 必须是 dfcf 或 ths"],
109
+ ['daxiapi sector gn --type ths', 'daxiapi sector gn --type dfcf']
110
+ );
111
+ }
112
+
113
+ const data = await api.getGnHot(token, options.type);
114
+ output(data);
115
+ } catch (error) {
116
+ handleError(error);
117
+ process.exit(1);
118
+ }
119
+ });
120
+ };
@@ -0,0 +1,79 @@
1
+ const config = require('../lib/config');
2
+ const api = require('../lib/api');
3
+ const {handleError, createParameterError} = require('../lib/error');
4
+ const {output} = require('../lib/output');
5
+
6
+ module.exports = function(program) {
7
+ const stockCmd = program.command('stock');
8
+
9
+ stockCmd
10
+ .description('股票数据查询')
11
+ .argument('<codes...>', '股票代码(支持多个)')
12
+ .action(async (codes) => {
13
+ try {
14
+ const token = config.getToken();
15
+ if (!token) {
16
+ const error = new Error('未配置 API Token');
17
+ error.response = {status: 401};
18
+ throw error;
19
+ }
20
+
21
+ if (!codes || codes.length === 0) {
22
+ throw createParameterError(
23
+ '参数无效',
24
+ ["参数 'codes' 不能为空"],
25
+ [
26
+ 'daxiapi stock 000001',
27
+ 'daxiapi stock 000001 600031 300750'
28
+ ]
29
+ );
30
+ }
31
+
32
+ const data = await api.getStockData(token, codes);
33
+ output(data);
34
+ } catch (error) {
35
+ handleError(error);
36
+ process.exit(1);
37
+ }
38
+ });
39
+
40
+ stockCmd
41
+ .command('gn <gnId>')
42
+ .description('概念股查询')
43
+ .option('--type <type>', '数据源类型 (dfcf|ths)', 'ths')
44
+ .action(async (gnId, options) => {
45
+ try {
46
+ const token = config.getToken();
47
+ if (!token) {
48
+ const error = new Error('未配置 API Token');
49
+ error.response = {status: 401};
50
+ throw error;
51
+ }
52
+
53
+ if (!gnId) {
54
+ throw createParameterError(
55
+ '参数无效',
56
+ ["参数 'gnId' 不能为空"],
57
+ ['daxiapi stock gn GN1234']
58
+ );
59
+ }
60
+
61
+ if (!['dfcf', 'ths'].includes(options.type)) {
62
+ throw createParameterError(
63
+ '参数无效',
64
+ ["参数 'type' 必须是 dfcf 或 ths"],
65
+ [
66
+ 'daxiapi stock gn GN1234 --type ths',
67
+ 'daxiapi stock gn GN1234 --type dfcf'
68
+ ]
69
+ );
70
+ }
71
+
72
+ const data = await api.getGainianStock(token, gnId, options.type);
73
+ output(data);
74
+ } catch (error) {
75
+ handleError(error);
76
+ process.exit(1);
77
+ }
78
+ });
79
+ };
@@ -0,0 +1,38 @@
1
+ const config = require('../lib/config');
2
+ const api = require('../lib/api');
3
+ const {handleError, createParameterError} = require('../lib/error');
4
+ const {output} = require('../lib/output');
5
+
6
+ module.exports = function(program) {
7
+ program
8
+ .command('zdt')
9
+ .description('涨跌停池')
10
+ .option('--type <type>', '类型 (zt|dt)', 'zt')
11
+ .action(async (options) => {
12
+ try {
13
+ const token = config.getToken();
14
+ if (!token) {
15
+ const error = new Error('未配置 API Token');
16
+ error.response = {status: 401};
17
+ throw error;
18
+ }
19
+
20
+ if (!['zt', 'dt'].includes(options.type)) {
21
+ throw createParameterError(
22
+ '参数无效',
23
+ ["参数 'type' 必须是 zt 或 dt"],
24
+ [
25
+ 'daxiapi zdt --type zt',
26
+ 'daxiapi zdt --type dt'
27
+ ]
28
+ );
29
+ }
30
+
31
+ const data = await api.getZdtPool(token, options.type);
32
+ output(data);
33
+ } catch (error) {
34
+ handleError(error);
35
+ process.exit(1);
36
+ }
37
+ });
38
+ };
package/lib/api.js CHANGED
@@ -3,305 +3,126 @@ const config = require('./config');
3
3
 
4
4
  const BASE_URL = config.get('baseUrl') || 'https://daxiapi.com';
5
5
 
6
- // 市场类型映射
7
- const MARKET_MAP = {
8
- '6': 'sh', // 上海
9
- '0': 'sz', // 深圳
10
- '3': 'sz', // 创业板
11
- '9': 'bj',
12
- '688': 'sh', // 科创板
13
- '689': 'sh' // 科创板CDR
14
- };
15
-
16
- /**
17
- * 获取市场类型前缀
18
- */
19
- function getMarketPrefix(code) {
20
- if (code.startsWith('688') || code.startsWith('689')) return 'sh';
21
- if (code.startsWith('60')) return 'sh';
22
- return 'sz';
23
- }
24
-
25
- /**
26
- * 创建请求实例
27
- */
28
6
  function createClient(token) {
29
7
  return axios.create({
30
8
  baseURL: `${BASE_URL}/coze`,
31
9
  headers: {
32
- 'X-API-Token': token,
10
+ 'Authorization': `Bearer ${token}`,
33
11
  'Content-Type': 'application/json'
34
12
  },
35
13
  timeout: 30000
36
14
  });
37
15
  }
38
16
 
39
- /**
40
- * GET 请求
41
- */
42
17
  async function get(client, path) {
43
- const { data } = await client.get(path);
18
+ const {data} = await client.get(path);
44
19
  if (data.errCode !== 0) {
45
- throw new Error(data.errMsg || `API Error: ${data.errCode}`);
20
+ const error = new Error(data.errMsg || `API Error: ${data.errCode}`);
21
+ error.response = {status: data.errCode};
22
+ throw error;
46
23
  }
47
24
  return data.data;
48
25
  }
49
26
 
50
- /**
51
- * POST 请求
52
- */
53
27
  async function post(client, path, body = {}) {
54
- const { data } = await client.post(path, body);
28
+ const {data} = await client.post(path, body);
55
29
  if (data.errCode !== 0) {
56
- throw new Error(data.errMsg || `API Error: ${data.errCode}`);
30
+ const error = new Error(data.errMsg || `API Error: ${data.errCode}`);
31
+ error.response = {status: data.errCode};
32
+ throw error;
57
33
  }
58
34
  return data.data;
59
35
  }
60
36
 
61
- // ==================== API 方法 ====================
62
-
63
- /**
64
- * 获取指数K线
65
- */
66
- async function getIndexK(token) {
67
- const client = createClient(token);
68
- return get(client, '/get_index_k');
69
- }
70
-
71
- /**
72
- * 获取市场数据
73
- */
74
37
  async function getMarketData(token) {
75
38
  const client = createClient(token);
76
- return get(client, '/get_market_data');
39
+ return get(client, '/get_index_data');
77
40
  }
78
41
 
79
- /**
80
- * 获取市场温度
81
- */
82
- async function getMarketDegree(token) {
42
+ async function getMarketTemp(token) {
83
43
  const client = createClient(token);
84
- return get(client, '/get_market_degree');
44
+ return get(client, '/get_market_temp');
85
45
  }
86
46
 
87
- /**
88
- * 获取市场风格
89
- */
90
47
  async function getMarketStyle(token) {
91
48
  const client = createClient(token);
92
49
  return get(client, '/get_market_style');
93
50
  }
94
51
 
95
- /**
96
- * 获取指数估值
97
- */
98
52
  async function getMarketValueData(token) {
99
53
  const client = createClient(token);
100
54
  return get(client, '/get_market_value_data');
101
55
  }
102
56
 
103
- /**
104
- * 获取收盘新闻
105
- */
106
- async function getMarketNews(token) {
57
+ async function getBkData(token) {
107
58
  const client = createClient(token);
108
- return get(client, '/get_market_end_news');
59
+ return get(client, '/get_bk_data');
109
60
  }
110
61
 
111
- /**
112
- * 获取涨跌停股票池
113
- */
114
- async function getZdtPool(token) {
62
+ async function getSectorData(token, orderBy = 'cs', limit = 5) {
115
63
  const client = createClient(token);
116
- return post(client, '/get_zdt_pool');
64
+ return post(client, '/get_sector_data', {orderBy, lmt: limit});
117
65
  }
118
66
 
119
- /**
120
- * 获取板块热力图
121
- */
122
- async function getSectorData(token, orderBy = 'cs', lmt = 5) {
67
+ async function getSectorRankStock(token, sectorCode, orderBy = 'cs') {
123
68
  const client = createClient(token);
124
- return post(client, '/get_sector_data', { orderBy, lmt });
69
+ return post(client, '/get_sector_rank_stock', {sectorCode, orderBy});
125
70
  }
126
71
 
127
- /**
128
- * 获取板块内个股排名
129
- */
130
- async function getSectorRankStock(token, sectorCode, orderBy = 'cs') {
72
+ async function getTopStocks(token) {
131
73
  const client = createClient(token);
132
- return post(client, '/get_sector_rank_stock', { sectorCode, orderBy });
74
+ return post(client, '/get_top_stocks', {});
133
75
  }
134
76
 
135
- /**
136
- * 获取概念内个股
137
- */
138
- async function getGainianStock(token, gnId) {
77
+ async function getGnHot(token, type = 'ths') {
139
78
  const client = createClient(token);
140
- return post(client, '/get_gainian_stock', { gnId });
79
+ return post(client, '/get_gn_hot', {type});
141
80
  }
142
81
 
143
- /**
144
- * 获取个股数据
145
- */
146
82
  async function getStockData(token, codes) {
147
83
  const client = createClient(token);
148
- return post(client, '/get_stock_data', { code: codes });
84
+ return post(client, '/get_stock_data', {code: codes});
149
85
  }
150
86
 
151
- /**
152
- * 搜索股票/行业
153
- */
154
- async function queryStock(token, keyword, type = 'stock') {
87
+ async function getGainianStock(token, gnId, type = 'ths') {
155
88
  const client = createClient(token);
156
- return post(client, '/query_stock_data', { q: keyword, type });
89
+ return post(client, '/get_gainian_stock', {gnId, type});
157
90
  }
158
91
 
159
- /**
160
- * 获取概念板块数据
161
- */
162
- async function getGnTable(token) {
92
+ async function getKline(token, code) {
163
93
  const client = createClient(token);
164
- return post(client, '/get_gn_table');
94
+ return post(client, '/get_kline', {code});
165
95
  }
166
96
 
167
- // ==================== K线数据(多数据源) ====================
168
-
169
- /**
170
- * 获取K线数据(首选腾讯,备选东方财富)
171
- * @param {string} code - 股票代码
172
- * @param {number} limit - 数据条数,默认300
173
- * @param {string} type - K线类型: day(日线), week(周线), month(月线)
174
- * @returns {Promise<Object>} K线数据
175
- */
176
- async function getKline(code, limit = 300, type = 'day') {
177
- // 首选腾讯
178
- try {
179
- const data = await getKlineFromQQ(code, limit, type);
180
- return { source: 'qq', ...data };
181
- } catch (err) {
182
- console.log('腾讯数据源失败,切换到东方财富...');
183
- }
184
-
185
- // 备选东方财富
186
- try {
187
- const data = await getKlineFromEastmoney(code, limit, type);
188
- return { source: 'eastmoney', ...data };
189
- } catch (err) {
190
- throw new Error('所有数据源均失败,请稍后重试');
191
- }
97
+ async function getZdtPool(token, type = 'zt') {
98
+ const client = createClient(token);
99
+ return post(client, '/get_zdt_pool', {type});
192
100
  }
193
101
 
194
- /**
195
- * 腾讯财经K线数据
196
- * @param {string} code - 股票代码
197
- * @param {number} limit - 数据条数
198
- * @param {string} type - K线类型
199
- */
200
- async function getKlineFromQQ(code, limit = 500, type = 'day') {
201
- const market = getMarketPrefix(code);
202
- const fullCode = `${market}${code}`;
203
-
204
- // 腾讯接口限制最大2000条
205
- if (limit > 2000) limit = 2000;
206
-
207
- const url = `https://proxy.finance.qq.com/ifzqgtimg/appstock/app/newfqkline/get?_var=&param=${fullCode},${type},,,${limit},qfq&r=0.${Date.now()}`;
208
-
209
- const { data } = await axios.get(url, { timeout: 10000 });
210
-
211
- if (!data.data || !data.data[fullCode]) {
212
- throw new Error('腾讯数据源返回异常');
213
- }
214
-
215
- const klineData = data.data[fullCode].qfqday || data.data[fullCode].day;
216
- if (!klineData || klineData.length === 0) {
217
- throw new Error('无K线数据');
218
- }
219
-
220
- const klines = klineData.map(([date, open, close, high, low, volume, , , amount]) => ({
221
- date,
222
- open: parseFloat(open),
223
- close: parseFloat(close),
224
- high: parseFloat(high),
225
- low: parseFloat(low),
226
- volume: parseFloat(volume),
227
- amount: parseFloat(amount)
228
- }));
229
-
230
- return {
231
- code,
232
- name: data.data[fullCode].qt?.[fullCode]?.[1] || code,
233
- klines,
234
- count: klines.length
235
- };
102
+ async function getSecId(token, code) {
103
+ const client = createClient(token);
104
+ return post(client, '/get_sec_id', {code});
236
105
  }
237
106
 
238
- /**
239
- * 东方财富K线数据
240
- * @param {string} code - 股票代码
241
- * @param {number} limit - 数据条数
242
- * @param {string} type - K线类型: day=101, week=102, month=103
243
- */
244
- async function getKlineFromEastmoney(code, limit = 300, type = 'day') {
245
- // K线类型映射
246
- const kltMap = { day: '101', week: '102', month: '103' };
247
- const klt = kltMap[type] || '101';
248
-
249
- // 市场代码
250
- const secid = code.startsWith('6') ? `1.${code}` : `0.${code}`;
251
-
252
- const url = `https://push2his.eastmoney.com/api/qt/stock/kline/get?` +
253
- `fields1=f1,f2,f3,f4,f5,f6&` +
254
- `fields2=f51,f52,f53,f54,f55,f56,f57,f58,f61&` +
255
- `ut=7eea3edcaed734bea9cbfc24409ed989&` +
256
- `end=29991010&` +
257
- `klt=${klt}&` +
258
- `secid=${secid}&` +
259
- `fqt=1&` +
260
- `lmt=${limit}`;
261
-
262
- const { data } = await axios.get(url, { timeout: 10000 });
263
-
264
- if (!data.data || !data.data.klines) {
265
- throw new Error('东方财富数据源返回异常');
266
- }
267
-
268
- const klines = data.data.klines.map(line => {
269
- const [date, open, close, high, low, volume, amount, , turnover] = line.split(',');
270
- return {
271
- date,
272
- open: parseFloat(open),
273
- close: parseFloat(close),
274
- high: parseFloat(high),
275
- low: parseFloat(low),
276
- volume: parseFloat(volume),
277
- amount: parseFloat(amount),
278
- turnover: parseFloat(turnover) || 0
279
- };
280
- });
281
-
282
- return {
283
- code,
284
- name: data.data.name || code,
285
- klines,
286
- count: klines.length
287
- };
107
+ async function queryStockData(token, q, type = 'stock') {
108
+ const client = createClient(token);
109
+ return post(client, '/query_stock_data', {q, type});
288
110
  }
289
111
 
290
112
  module.exports = {
291
- getIndexK,
292
113
  getMarketData,
293
- getMarketDegree,
114
+ getMarketTemp,
294
115
  getMarketStyle,
295
116
  getMarketValueData,
296
- getMarketNews,
297
- getZdtPool,
117
+ getBkData,
298
118
  getSectorData,
299
119
  getSectorRankStock,
300
- getGainianStock,
120
+ getTopStocks,
121
+ getGnHot,
301
122
  getStockData,
302
- queryStock,
303
- getGnTable,
123
+ getGainianStock,
304
124
  getKline,
305
- getKlineFromQQ,
306
- getKlineFromEastmoney
125
+ getZdtPool,
126
+ getSecId,
127
+ queryStockData
307
128
  };
package/lib/config.js CHANGED
@@ -17,30 +17,18 @@ const config = new Conf({
17
17
  schema
18
18
  });
19
19
 
20
- /**
21
- * 获取Token(优先环境变量)
22
- */
23
20
  function getToken() {
24
21
  return process.env.DAXIAPI_TOKEN || config.get('token');
25
22
  }
26
23
 
27
- /**
28
- * 获取配置项
29
- */
30
24
  function get(key) {
31
25
  return config.get(key);
32
26
  }
33
27
 
34
- /**
35
- * 设置配置项
36
- */
37
28
  function set(key, value) {
38
29
  config.set(key, value);
39
30
  }
40
31
 
41
- /**
42
- * 获取所有配置
43
- */
44
32
  function getAll() {
45
33
  return {
46
34
  token: getToken() ? '******' + getToken().slice(-4) : undefined,
@@ -48,9 +36,6 @@ function getAll() {
48
36
  };
49
37
  }
50
38
 
51
- /**
52
- * 删除配置项
53
- */
54
39
  function del(key) {
55
40
  config.delete(key);
56
41
  }