daxiapi-cli 2.3.3 → 2.4.2
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 +13 -0
- package/bin/index.js +1 -0
- package/commands/market.js +17 -0
- package/commands/news.js +96 -0
- package/commands/stock.js +32 -1
- package/lib/api.js +132 -25
- package/lib/caibao.js +37 -39
- package/lib/dividendUtils.js +10 -3
- package/lib/output.js +4 -1
- package/package.json +4 -5
package/README.md
CHANGED
|
@@ -220,6 +220,19 @@ daxiapi report finance 600036
|
|
|
220
220
|
daxiapi report finance 000001
|
|
221
221
|
```
|
|
222
222
|
|
|
223
|
+
### 新闻数据
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
# 获取个股舆情(内部自动将 code 转为 secid)
|
|
227
|
+
daxiapi news sentiment -c 600031 -p 20
|
|
228
|
+
|
|
229
|
+
# 获取个股公告
|
|
230
|
+
daxiapi news notice -c 600031 -p 20 -i 1
|
|
231
|
+
|
|
232
|
+
# 获取个股研报(支持时间区间)
|
|
233
|
+
daxiapi news report -c 600031 -p 25 -i 1 -b 2026-01-01 -e 2026-04-08
|
|
234
|
+
```
|
|
235
|
+
|
|
223
236
|
### 工具
|
|
224
237
|
|
|
225
238
|
```bash
|
package/bin/index.js
CHANGED
package/commands/market.js
CHANGED
|
@@ -31,7 +31,24 @@ module.exports = function (program) {
|
|
|
31
31
|
process.exit(1);
|
|
32
32
|
}
|
|
33
33
|
});
|
|
34
|
+
marketCmd.command('compass')
|
|
35
|
+
.description('获取市场整合趋势结构、估值、情绪三层指标,用于A股市场三维结构分析系统,给出市场当前状态的综合判断与操作建议')
|
|
36
|
+
.action(async () => {
|
|
37
|
+
try {
|
|
38
|
+
const token = config.getToken();
|
|
39
|
+
if (!token) {
|
|
40
|
+
const error = new Error('未配置 API Token');
|
|
41
|
+
error.response = {status: 401};
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
34
44
|
|
|
45
|
+
const data = await api.getCompassData(token);
|
|
46
|
+
output(data);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
handleError(error);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
35
52
|
marketCmd
|
|
36
53
|
.command('temp')
|
|
37
54
|
.description(
|
package/commands/news.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const api = require('../lib/api');
|
|
2
|
+
const {handleError, createParameterError} = require('../lib/error');
|
|
3
|
+
const {encode} = require('../lib/output');
|
|
4
|
+
const {getSecid} = require('../lib/utils');
|
|
5
|
+
|
|
6
|
+
function toPositiveInt(value, fieldName, examples) {
|
|
7
|
+
const number = Number.parseInt(value, 10);
|
|
8
|
+
if (!Number.isInteger(number) || number <= 0) {
|
|
9
|
+
throw createParameterError('参数无效', [`参数 '${fieldName}' 必须是大于0的整数`], examples);
|
|
10
|
+
}
|
|
11
|
+
return number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function validateDate(value, fieldName, examples) {
|
|
15
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
16
|
+
throw createParameterError('参数无效', [`参数 '${fieldName}' 必须是 YYYY-MM-DD 格式`], examples);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = function (program) {
|
|
21
|
+
const newsCmd = program.command('news').description('获取个股舆情、公告、研报数据,用于跟踪个股信息面变化。');
|
|
22
|
+
|
|
23
|
+
newsCmd
|
|
24
|
+
.command('sentiment')
|
|
25
|
+
.description('获取个股舆情列表。')
|
|
26
|
+
.option('-c, --code <code>', '股票代码(6位),如600031', '600031')
|
|
27
|
+
.option('-p, --page-size <pageSize>', '每页条数,默认20', '20')
|
|
28
|
+
.action(async options => {
|
|
29
|
+
try {
|
|
30
|
+
if (!options.code) {
|
|
31
|
+
throw createParameterError('参数无效', ["参数 'code' 不能为空"], ['daxiapi news sentiment -c600031 -p20']);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const secid = getSecid(options.code);
|
|
35
|
+
const pageSize = toPositiveInt(options.pageSize, 'pageSize', ['daxiapi news sentiment -c600031 -p20']);
|
|
36
|
+
const data = await api.getNewsSentiment(secid, pageSize);
|
|
37
|
+
console.log('```toon\n' + encode(data) + '\n```');
|
|
38
|
+
} catch (error) {
|
|
39
|
+
handleError(error);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
newsCmd
|
|
45
|
+
.command('notice')
|
|
46
|
+
.description('获取个股公告列表。')
|
|
47
|
+
.option('-c, --code <code>', '股票代码(6位),如600031', '600031')
|
|
48
|
+
.option('-p, --page-size <pageSize>', '每页条数,默认20', '20')
|
|
49
|
+
.option('-i, --page-index <pageIndex>', '页码,默认1', '1')
|
|
50
|
+
.action(async options => {
|
|
51
|
+
try {
|
|
52
|
+
if (!options.code) {
|
|
53
|
+
throw createParameterError('参数无效', ["参数 'code' 不能为空"], ['daxiapi news notice -c600031 -p20 -i1']);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const pageSize = toPositiveInt(options.pageSize, 'pageSize', ['daxiapi news notice -c600031 -p20 -i1']);
|
|
57
|
+
const pageIndex = toPositiveInt(options.pageIndex, 'pageIndex', ['daxiapi news notice -c600031 -p20 -i1']);
|
|
58
|
+
const data = await api.getNewsNotice(options.code, pageSize, pageIndex);
|
|
59
|
+
console.log('```toon\n' + encode(data) + '\n```');
|
|
60
|
+
} catch (error) {
|
|
61
|
+
handleError(error);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
newsCmd
|
|
67
|
+
.command('report')
|
|
68
|
+
.description('获取个股研报列表。')
|
|
69
|
+
.option('-c, --code <code>', '股票代码(6位),如600031', '600031')
|
|
70
|
+
.option('-p, --page-size <pageSize>', '每页条数,默认25', '25')
|
|
71
|
+
.option('-i, --page-index <pageIndex>', '页码,默认1', '1')
|
|
72
|
+
.option('-b, --begin-time <beginTime>', '开始日期,格式 YYYY-MM-DD', '2026-01-01')
|
|
73
|
+
.option('-e, --end-time <endTime>', '结束日期,格式 YYYY-MM-DD')
|
|
74
|
+
.action(async options => {
|
|
75
|
+
try {
|
|
76
|
+
if (!options.code) {
|
|
77
|
+
throw createParameterError('参数无效', ["参数 'code' 不能为空"], [
|
|
78
|
+
'daxiapi news report -c600031 -p25 -i1 -b2026-01-01 -e2026-04-08'
|
|
79
|
+
]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
validateDate(options.beginTime, 'beginTime', ['daxiapi news report -b2026-01-01']);
|
|
83
|
+
const endTime = options.endTime || new Date().toISOString().slice(0, 10);
|
|
84
|
+
validateDate(endTime, 'endTime', ['daxiapi news report -e2026-04-08']);
|
|
85
|
+
|
|
86
|
+
const pageSize = toPositiveInt(options.pageSize, 'pageSize', ['daxiapi news report -p25']);
|
|
87
|
+
const pageIndex = toPositiveInt(options.pageIndex, 'pageIndex', ['daxiapi news report -i1']);
|
|
88
|
+
|
|
89
|
+
const data = await api.getNewsReport(options.code, pageSize, pageIndex, options.beginTime, endTime);
|
|
90
|
+
console.log('```toon\n' + encode(data) + '\n```');
|
|
91
|
+
} catch (error) {
|
|
92
|
+
handleError(error);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
};
|
package/commands/stock.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const config = require('../lib/config');
|
|
2
2
|
const api = require('../lib/api');
|
|
3
3
|
const {handleError, createParameterError} = require('../lib/error');
|
|
4
|
-
const {output} = require('../lib/output');
|
|
4
|
+
const {output, encode} = require('../lib/output');
|
|
5
5
|
|
|
6
6
|
const SUPPORTED_PATTERNS = [
|
|
7
7
|
'gxl',
|
|
@@ -142,4 +142,35 @@ module.exports = function (program) {
|
|
|
142
142
|
process.exit(1);
|
|
143
143
|
}
|
|
144
144
|
});
|
|
145
|
+
|
|
146
|
+
stockCmd
|
|
147
|
+
.command('capital-flow <code>')
|
|
148
|
+
.description(
|
|
149
|
+
'获取个股主力资金流向数据,返回最近N天的资金动向。' +
|
|
150
|
+
'包括主力净流入、主力净流入占比、五日主力净流入、板块资金流向等详细数据。' +
|
|
151
|
+
'支持6位数字代码,自动识别沪深市场。可用于分析主力资金动向和资金面分析。'
|
|
152
|
+
)
|
|
153
|
+
.option('--days <days>', '返回天数(默认5天)', '5')
|
|
154
|
+
.action(async (code, options) => {
|
|
155
|
+
try {
|
|
156
|
+
if (!code) {
|
|
157
|
+
throw createParameterError('参数无效', ["参数 'code' 不能为空"], ['daxiapi stock capital-flow 000001']);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const days = parseInt(options.days, 10);
|
|
161
|
+
if (isNaN(days) || days < 1 || days > 30) {
|
|
162
|
+
throw createParameterError(
|
|
163
|
+
'参数无效',
|
|
164
|
+
["参数 'days' 必须是1-30之间的整数"],
|
|
165
|
+
['daxiapi stock capital-flow 000001 --days 5', 'daxiapi stock capital-flow 000001 --days 10']
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const data = await api.getCapitalFlow(code, days);
|
|
170
|
+
console.log('```toon\n' + encode(data) + '\n```');
|
|
171
|
+
} catch (error) {
|
|
172
|
+
handleError(error);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
145
176
|
};
|
package/lib/api.js
CHANGED
|
@@ -107,6 +107,10 @@ async function getSecId(token, code) {
|
|
|
107
107
|
const client = createClient(token);
|
|
108
108
|
return post(client, '/get_sec_id', {code});
|
|
109
109
|
}
|
|
110
|
+
async function getCompassData(token, ) {
|
|
111
|
+
const client = createClient(token);
|
|
112
|
+
return get(client, '/get_market_compass');
|
|
113
|
+
}
|
|
110
114
|
|
|
111
115
|
async function queryStockData(token, q, type = 'stock') {
|
|
112
116
|
const client = createClient(token);
|
|
@@ -146,8 +150,8 @@ async function getDividendScore(token, code) {
|
|
|
146
150
|
scores: recentScores.map(item => ({
|
|
147
151
|
date: item.date,
|
|
148
152
|
score: item.totalScore,
|
|
149
|
-
cs: item.cs.toFixed(2),
|
|
150
|
-
rsi: item.rsi.toFixed(2)
|
|
153
|
+
cs: item.cs != null ? item.cs.toFixed(2) : null,
|
|
154
|
+
rsi: item.rsi != null ? item.rsi.toFixed(2) : null
|
|
151
155
|
}))
|
|
152
156
|
};
|
|
153
157
|
}
|
|
@@ -156,8 +160,6 @@ async function getStockRank(type = 'hour', listType = 'normal') {
|
|
|
156
160
|
try {
|
|
157
161
|
const params = {stock_type: 'a', list_type: listType};
|
|
158
162
|
|
|
159
|
-
// 只有 normal 和 skyrocket 才有 type 参数,默认为 hour
|
|
160
|
-
// 其他榜单类型固定为 day
|
|
161
163
|
if (listType === 'normal' || listType === 'skyrocket') {
|
|
162
164
|
params.type = type;
|
|
163
165
|
} else {
|
|
@@ -204,19 +206,6 @@ async function getPlateRank(type = 'concept') {
|
|
|
204
206
|
const data = extractData(response, 'plate_list');
|
|
205
207
|
|
|
206
208
|
return data.map(d => {
|
|
207
|
-
// - code: "881160"
|
|
208
|
-
// rise_and_fall: 1.4869
|
|
209
|
-
// etf_rise_and_fall: -0.1488
|
|
210
|
-
// hot_rank_chg: 0
|
|
211
|
-
// market_id: 48
|
|
212
|
-
// hot_tag: 连续11天上榜
|
|
213
|
-
// etf_product_id: "159766"
|
|
214
|
-
// rate: "10366.5"
|
|
215
|
-
// etf_name: 旅游ETF富国
|
|
216
|
-
// name: 旅游及酒店
|
|
217
|
-
// tag: 1家涨停
|
|
218
|
-
// etf_market_id: 36
|
|
219
|
-
// order: 20
|
|
220
209
|
return {
|
|
221
210
|
code: d.code,
|
|
222
211
|
name: d.name,
|
|
@@ -270,19 +259,15 @@ async function getTurnoverData(type = 'day') {
|
|
|
270
259
|
const currentTurnover = latest[1];
|
|
271
260
|
const prevTurnover = previous[1];
|
|
272
261
|
const diff = currentTurnover - prevTurnover;
|
|
273
|
-
const currentYi = currentTurnover / 100000000;
|
|
274
|
-
const currentWanYi = (currentYi / 10000).toFixed(2);
|
|
275
|
-
const diffYi = (Math.abs(diff) / 100000000).toFixed(2);
|
|
262
|
+
const currentYi = currentTurnover / 100000000;
|
|
263
|
+
const currentWanYi = (currentYi / 10000).toFixed(2);
|
|
264
|
+
const diffYi = (Math.abs(diff) / 100000000).toFixed(2);
|
|
276
265
|
const formattedData = {
|
|
277
266
|
当前成交额: currentWanYi + '万亿',
|
|
278
267
|
变化量: (diff > 0 ? '增加' : '减少') + diffYi + '亿',
|
|
279
268
|
较上日: diff > 0 ? '增加' : '减少'
|
|
280
269
|
};
|
|
281
270
|
const isTrading = minuteData.isTrading;
|
|
282
|
-
// '0': { val: 1623545300000, name: '当日成交额', key: 'turnover' },
|
|
283
|
-
// '1': { val: 1668872000000, name: '昨日成交额', key: 'turnover_pre' },
|
|
284
|
-
// '2': { val: -45326700000, name: '较昨日变动', key: 'turnover_change' },
|
|
285
|
-
// '3': { val: 1623545300000, name: '预测全天成交额', key: 'predict_turnover' },
|
|
286
271
|
const rs = {};
|
|
287
272
|
minuteData.header.forEach((item, index) => {
|
|
288
273
|
rs[item.name] = item.val;
|
|
@@ -339,9 +324,128 @@ async function getTurnoverDataByMinute() {
|
|
|
339
324
|
}
|
|
340
325
|
}
|
|
341
326
|
|
|
327
|
+
function normalizeHttps(url) {
|
|
328
|
+
if (!url) {
|
|
329
|
+
return '';
|
|
330
|
+
}
|
|
331
|
+
return String(url).replace(/^http:\/\//, 'https://');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function getNewsSentiment(secid, pageSize = 20) {
|
|
335
|
+
const response = await axios.get('https://np-listapi.eastmoney.com/comm/web/getListInfo', {
|
|
336
|
+
params: {
|
|
337
|
+
client: 'web',
|
|
338
|
+
biz: 'web_voice',
|
|
339
|
+
mTypeAndCode: secid,
|
|
340
|
+
pageSize,
|
|
341
|
+
type: 1,
|
|
342
|
+
req_trace: '3d4e684212bee1464c1b44611236955b',
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const payload = response.data || {};
|
|
347
|
+
const list = payload.data?.list || [];
|
|
348
|
+
return {
|
|
349
|
+
pageIndex: payload.data?.page_index || 1,
|
|
350
|
+
pageSize: payload.data?.page_size || pageSize,
|
|
351
|
+
total: payload.data?.totle_hits || 0,
|
|
352
|
+
list: list.map((item) => ({
|
|
353
|
+
title: item.Art_Title,
|
|
354
|
+
showTime: item.Art_ShowTime,
|
|
355
|
+
artCode: item.Art_Code,
|
|
356
|
+
url: normalizeHttps(item.Art_Url),
|
|
357
|
+
originUrl: normalizeHttps(item.Art_OriginUrl),
|
|
358
|
+
})),
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async function getNewsNotice(code, pageSize = 20, pageIndex = 1) {
|
|
363
|
+
const response = await axios.get('https://np-anotice-stock.eastmoney.com/api/security/ann', {
|
|
364
|
+
params: {
|
|
365
|
+
sr: -1,
|
|
366
|
+
page_size: pageSize,
|
|
367
|
+
page_index: pageIndex,
|
|
368
|
+
ann_type: 'A',
|
|
369
|
+
client_source: 'web',
|
|
370
|
+
stock_list: code,
|
|
371
|
+
f_node: 0,
|
|
372
|
+
s_node: 0,
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const payload = response.data || {};
|
|
377
|
+
const data = payload.data || {};
|
|
378
|
+
const list = data.list || [];
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
pageIndex: data.page_index || pageIndex,
|
|
382
|
+
pageSize: data.page_size || pageSize,
|
|
383
|
+
total: data.total_hits || 0,
|
|
384
|
+
list: list.map((item) => {
|
|
385
|
+
const stockCode = item.codes?.[0]?.stock_code || code;
|
|
386
|
+
return {
|
|
387
|
+
title: item.title,
|
|
388
|
+
noticeDate: item.notice_date,
|
|
389
|
+
displayTime: item.display_time,
|
|
390
|
+
artCode: item.art_code,
|
|
391
|
+
stockCode,
|
|
392
|
+
url: `https://data.eastmoney.com/notices/detail/${stockCode}/${item.art_code}.html`,
|
|
393
|
+
columns: (item.columns || []).map((column) => column.column_name),
|
|
394
|
+
};
|
|
395
|
+
}),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async function getNewsReport(code, pageSize = 25, pageIndex = 1, beginTime = '2026-01-01', endTime) {
|
|
400
|
+
const response = await axios.get('https://reportapi.eastmoney.com/report/list', {
|
|
401
|
+
params: {
|
|
402
|
+
pageNo: pageIndex,
|
|
403
|
+
pageSize,
|
|
404
|
+
code,
|
|
405
|
+
industryCode: '*',
|
|
406
|
+
industry: '*',
|
|
407
|
+
rating: '*',
|
|
408
|
+
ratingchange: '*',
|
|
409
|
+
beginTime,
|
|
410
|
+
endTime,
|
|
411
|
+
fields: '',
|
|
412
|
+
qType: 0,
|
|
413
|
+
sort: 'publishDate,desc',
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const payload = response.data || {};
|
|
418
|
+
const list = payload.data || [];
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
pageIndex: payload.pageNo || pageIndex,
|
|
422
|
+
pageSize: payload.size || pageSize,
|
|
423
|
+
total: payload.hits || 0,
|
|
424
|
+
list: list.map((item) => ({
|
|
425
|
+
title: item.title,
|
|
426
|
+
stockCode: item.stockCode,
|
|
427
|
+
stockName: item.stockName,
|
|
428
|
+
publishDate: item.publishDate,
|
|
429
|
+
orgName: item.orgName,
|
|
430
|
+
rating: item.emRatingName,
|
|
431
|
+
infoCode: item.infoCode,
|
|
432
|
+
predictNextTwoYearEps: item.predictNextTwoYearEps,
|
|
433
|
+
predictNextTwoYearPe: item.predictNextTwoYearPe,
|
|
434
|
+
predictNextYearEps: item.predictNextYearEps,
|
|
435
|
+
predictNextYearPe: item.predictNextYearPe,
|
|
436
|
+
predictThisYearEps: item.predictThisYearEps,
|
|
437
|
+
predictThisYearPe: item.predictThisYearPe,
|
|
438
|
+
predictLastYearEps: item.predictLastYearEps,
|
|
439
|
+
predictLastYearPe: item.predictLastYearPe,
|
|
440
|
+
url: `https://data.eastmoney.com/report/info/${item.infoCode}.html`,
|
|
441
|
+
})),
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
342
445
|
module.exports = {
|
|
343
446
|
getMarketData,
|
|
344
447
|
getMarketTemp,
|
|
448
|
+
getCompassData,
|
|
345
449
|
getMarketStyle,
|
|
346
450
|
getMarketValueData,
|
|
347
451
|
getBkData,
|
|
@@ -361,5 +465,8 @@ module.exports = {
|
|
|
361
465
|
getPlateRank,
|
|
362
466
|
getTurnoverData,
|
|
363
467
|
getTurnoverDataByMinute,
|
|
364
|
-
getFinanceReportDetail
|
|
468
|
+
getFinanceReportDetail,
|
|
469
|
+
getNewsSentiment,
|
|
470
|
+
getNewsNotice,
|
|
471
|
+
getNewsReport,
|
|
365
472
|
};
|
package/lib/caibao.js
CHANGED
|
@@ -2,59 +2,57 @@
|
|
|
2
2
|
// const vm = require('vm');
|
|
3
3
|
|
|
4
4
|
// ?ut=&invt=2&fltt=2&fields=&secid=1.600862&cb=&_=1601168994587
|
|
5
|
+
const axios = require('axios');
|
|
5
6
|
const picker = require('@ksky521/html-picker');
|
|
6
7
|
const iconv = require('./iconv');
|
|
7
8
|
|
|
8
9
|
const {getHeader} = require('./thsUtils');
|
|
9
10
|
|
|
10
|
-
const request = require('request-promise-native');
|
|
11
|
-
|
|
12
11
|
const API_URL = 'http://basic.10jqka.com.cn/';
|
|
13
12
|
|
|
14
|
-
function getDetail(stockId) {
|
|
13
|
+
async function getDetail(stockId) {
|
|
15
14
|
const prefix = `${API_URL}${stockId}`;
|
|
16
15
|
const url = `${prefix}/finance.html`;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
encoding: null,
|
|
16
|
+
const response = await axios.get(url, {
|
|
17
|
+
responseType: 'arraybuffer',
|
|
18
|
+
timeout: 30000,
|
|
21
19
|
headers: getHeader({Referrer: prefix})
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const html = iconv(response.data);
|
|
23
|
+
let rs = {};
|
|
24
|
+
picker(html, {
|
|
25
|
+
data: {
|
|
26
|
+
selector: 'p#main',
|
|
27
|
+
handler($node) {
|
|
28
|
+
rs = JSON.parse($node.html());
|
|
31
29
|
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (content[index]) {
|
|
46
|
-
report[title] = content[i][index];
|
|
47
|
-
} else {
|
|
48
|
-
// console.log(report[title]);
|
|
49
|
-
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
const titles = rs.title;
|
|
33
|
+
const content = rs.report;
|
|
34
|
+
const reports = [];
|
|
35
|
+
for (let index = 0; index < content[0].length; index++) {
|
|
36
|
+
const report = {};
|
|
37
|
+
for (let i = 0; i < titles.length; i++) {
|
|
38
|
+
let title = titles[i];
|
|
39
|
+
if (typeof title === 'string') {
|
|
40
|
+
title = title.trim();
|
|
41
|
+
} else if (Array.isArray(title)) {
|
|
42
|
+
title = title[0];
|
|
50
43
|
}
|
|
51
|
-
if (
|
|
52
|
-
|
|
44
|
+
if (content[index]) {
|
|
45
|
+
report[title] = content[i][index];
|
|
46
|
+
} else {
|
|
47
|
+
// console.log(report[title]);
|
|
53
48
|
}
|
|
54
49
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
if (Object.keys(report).length > 0) {
|
|
51
|
+
reports.push(report);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return reports;
|
|
58
56
|
}
|
|
59
57
|
|
|
60
58
|
module.exports = getDetail;
|
package/lib/dividendUtils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const DIVIDEND_SCORE_CONSTANTS = {
|
|
2
2
|
ROLLING_WINDOW: 440,
|
|
3
|
-
PERCENTILE_LOW:
|
|
4
|
-
PERCENTILE_HIGH:
|
|
3
|
+
PERCENTILE_LOW: 0,
|
|
4
|
+
PERCENTILE_HIGH: 100,
|
|
5
5
|
SCORE_MA_PERIOD: 5,
|
|
6
6
|
EMA_PERIOD: 20,
|
|
7
7
|
MA_PERIOD: 80,
|
|
@@ -140,6 +140,7 @@ function calculateScores(data) {
|
|
|
140
140
|
|
|
141
141
|
const csValues = dataCopy.map(d => d.cs);
|
|
142
142
|
const ma80BiasValues = dataCopy.map(d => d.ma80Bias);
|
|
143
|
+
const rsiValues = dataCopy.map(d => d.rsi);
|
|
143
144
|
|
|
144
145
|
for (let i = 0; i < dataCopy.length; i++) {
|
|
145
146
|
const current = dataCopy[i];
|
|
@@ -155,6 +156,7 @@ function calculateScores(data) {
|
|
|
155
156
|
const startIdx = Math.max(0, i - DIVIDEND_SCORE_CONSTANTS.ROLLING_WINDOW + 1);
|
|
156
157
|
const csHistory = csValues.slice(startIdx, i + 1);
|
|
157
158
|
const ma80History = ma80BiasValues.slice(startIdx, i + 1);
|
|
159
|
+
const rsiHistory = rsiValues.slice(startIdx, i + 1);
|
|
158
160
|
|
|
159
161
|
current.csScore = calculateRollingScore(
|
|
160
162
|
current.cs,
|
|
@@ -168,7 +170,12 @@ function calculateScores(data) {
|
|
|
168
170
|
DIVIDEND_SCORE_CONSTANTS.PERCENTILE_LOW,
|
|
169
171
|
DIVIDEND_SCORE_CONSTANTS.PERCENTILE_HIGH
|
|
170
172
|
);
|
|
171
|
-
current.rsiScore =
|
|
173
|
+
current.rsiScore = calculateRollingScore(
|
|
174
|
+
current.rsi,
|
|
175
|
+
rsiHistory,
|
|
176
|
+
DIVIDEND_SCORE_CONSTANTS.PERCENTILE_LOW,
|
|
177
|
+
DIVIDEND_SCORE_CONSTANTS.PERCENTILE_HIGH
|
|
178
|
+
);
|
|
172
179
|
|
|
173
180
|
if (current.csScore !== null && current.ma80Score !== null && current.rsiScore !== null) {
|
|
174
181
|
current.totalScore = parseFloat(
|
package/lib/output.js
CHANGED
|
@@ -8,7 +8,10 @@ function output(data) {
|
|
|
8
8
|
console.log('[60]{"日期","分数","cs值","rsi值"}:');
|
|
9
9
|
|
|
10
10
|
data.scores.forEach(item => {
|
|
11
|
-
|
|
11
|
+
const score = item.score != null ? item.score : '-';
|
|
12
|
+
const cs = item.cs != null ? item.cs : '-';
|
|
13
|
+
const rsi = item.rsi != null ? item.rsi : '-';
|
|
14
|
+
console.log(` ${item.date},${score},${cs},${rsi}`);
|
|
12
15
|
});
|
|
13
16
|
|
|
14
17
|
console.log('```');
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "daxiapi-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.2",
|
|
4
4
|
"description": "大虾皮金融数据API命令行工具",
|
|
5
5
|
"bin": {
|
|
6
6
|
"daxiapi": "./bin/index.js",
|
|
7
7
|
"dxp": "./bin/index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"test": "node
|
|
10
|
+
"test": "node --test test/*.test.js",
|
|
11
|
+
"test:coverage": "node --test --experimental-test-coverage --test-coverage-branches=80 --test-coverage-functions=80 --test-coverage-lines=80 test/*.test.js"
|
|
11
12
|
},
|
|
12
13
|
"keywords": [
|
|
13
14
|
"stock",
|
|
@@ -20,8 +21,6 @@
|
|
|
20
21
|
"license": "MIT",
|
|
21
22
|
"dependencies": {
|
|
22
23
|
"iconv-lite": "^0.5.0",
|
|
23
|
-
"request": "^2.88.0",
|
|
24
|
-
"request-promise-native": "^1.0.7",
|
|
25
24
|
"@ksky521/html-picker": "^1.0.0",
|
|
26
25
|
"@toon-format/toon": "^2.1.0",
|
|
27
26
|
"axios": "^1.6.0",
|
|
@@ -38,4 +37,4 @@
|
|
|
38
37
|
"lib",
|
|
39
38
|
"README.md"
|
|
40
39
|
]
|
|
41
|
-
}
|
|
40
|
+
}
|