daxiapi-cli 2.2.0 → 2.3.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,216 @@
1
+ const DIVIDEND_SCORE_CONSTANTS = {
2
+ ROLLING_WINDOW: 440,
3
+ PERCENTILE_LOW: 5,
4
+ PERCENTILE_HIGH: 95,
5
+ SCORE_MA_PERIOD: 5,
6
+ EMA_PERIOD: 20,
7
+ MA_PERIOD: 80,
8
+ RSI_PERIOD: 20,
9
+ MIN_VALID_VALUES: 10,
10
+ MIN_SCORE: 0,
11
+ MAX_SCORE: 100,
12
+ CS_WEIGHT: 0.35,
13
+ MA80_WEIGHT: 0.35,
14
+ RSI_WEIGHT: 0.3
15
+ };
16
+
17
+ function calculateEMA(period, data) {
18
+ const closes = data.map(d => d.close);
19
+ const cs = [];
20
+ let ema = closes[0];
21
+ const multiplier = 2 / (period + 1);
22
+
23
+ for (let i = 0; i < closes.length; i++) {
24
+ if (i === 0) {
25
+ ema = closes[i];
26
+ } else {
27
+ ema = (closes[i] - ema) * multiplier + ema;
28
+ }
29
+ cs.push(((closes[i] - ema) / ema) * 100);
30
+ }
31
+
32
+ return {cs};
33
+ }
34
+
35
+ function calculateMA(period, data) {
36
+ const closes = data.map(d => d.close);
37
+ const ma = [];
38
+ const maBias = [];
39
+
40
+ for (let i = 0; i < closes.length; i++) {
41
+ if (i < period - 1) {
42
+ ma.push('-');
43
+ maBias.push('-');
44
+ } else {
45
+ let sum = 0;
46
+ for (let j = 0; j < period; j++) {
47
+ sum += closes[i - j];
48
+ }
49
+ const avg = sum / period;
50
+ ma.push(avg);
51
+ maBias.push(((closes[i] - avg) / avg) * 100);
52
+ }
53
+ }
54
+
55
+ return [ma, maBias];
56
+ }
57
+
58
+ function calculateRSI(closes, period = 20) {
59
+ const rsiValues = [];
60
+ let gains = 0;
61
+ let losses = 0;
62
+
63
+ for (let i = 0; i < closes.length; i++) {
64
+ if (i < period) {
65
+ rsiValues.push(null);
66
+ continue;
67
+ }
68
+
69
+ const change = closes[i] - closes[i - 1];
70
+ if (i === period) {
71
+ let sumGain = 0;
72
+ let sumLoss = 0;
73
+ for (let j = 1; j <= period; j++) {
74
+ const c = closes[j] - closes[j - 1];
75
+ if (c > 0) {
76
+ sumGain += c;
77
+ } else {
78
+ sumLoss += Math.abs(c);
79
+ }
80
+ }
81
+ gains = sumGain / period;
82
+ losses = sumLoss / period;
83
+ } else {
84
+ const currentGain = change > 0 ? change : 0;
85
+ const currentLoss = change < 0 ? Math.abs(change) : 0;
86
+ gains = (gains * (period - 1) + currentGain) / period;
87
+ losses = (losses * (period - 1) + currentLoss) / period;
88
+ }
89
+
90
+ if (gains + losses === 0) {
91
+ rsiValues.push(50);
92
+ } else {
93
+ const rs = gains / losses;
94
+ rsiValues.push(parseFloat((100 - 100 / (1 + rs)).toFixed(2)));
95
+ }
96
+ }
97
+
98
+ return rsiValues;
99
+ }
100
+
101
+ function percentile(p, arr) {
102
+ if (!arr.length) {
103
+ return 0;
104
+ }
105
+ const sorted = [...arr].sort((a, b) => a - b);
106
+ const index = Math.ceil((p / 100) * sorted.length) - 1;
107
+ return sorted[Math.max(0, index)];
108
+ }
109
+
110
+ function calculateRollingScore(value, historyValues, lowPercentile, highPercentile) {
111
+ const validValues = historyValues.filter(v => v !== null && !isNaN(v));
112
+ if (validValues.length < DIVIDEND_SCORE_CONSTANTS.MIN_VALID_VALUES || value === null) {
113
+ return null;
114
+ }
115
+
116
+ const low = percentile(lowPercentile, validValues);
117
+ const high = percentile(highPercentile, validValues);
118
+
119
+ if (low === high) {
120
+ return 50;
121
+ }
122
+
123
+ let score = ((value - low) / (high - low)) * 100;
124
+ score = Math.max(DIVIDEND_SCORE_CONSTANTS.MIN_SCORE, Math.min(DIVIDEND_SCORE_CONSTANTS.MAX_SCORE, score));
125
+ return parseFloat(score.toFixed(2));
126
+ }
127
+
128
+ function calculateScores(data) {
129
+ const dataCopy = data.map(item => ({...item}));
130
+
131
+ const closes = dataCopy.map(d => d.close);
132
+ const {cs} = calculateEMA(DIVIDEND_SCORE_CONSTANTS.EMA_PERIOD, dataCopy);
133
+ const [_, ma80Bias] = calculateMA(DIVIDEND_SCORE_CONSTANTS.MA_PERIOD, dataCopy);
134
+ const rsi = calculateRSI(closes, DIVIDEND_SCORE_CONSTANTS.RSI_PERIOD);
135
+ for (let i = 0; i < dataCopy.length; i++) {
136
+ dataCopy[i].cs = cs[i] === '-' ? null : parseFloat(cs[i]);
137
+ dataCopy[i].ma80Bias = ma80Bias[i] === '-' ? null : parseFloat(ma80Bias[i]);
138
+ dataCopy[i].rsi = rsi[i];
139
+ }
140
+
141
+ const csValues = dataCopy.map(d => d.cs);
142
+ const ma80BiasValues = dataCopy.map(d => d.ma80Bias);
143
+
144
+ for (let i = 0; i < dataCopy.length; i++) {
145
+ const current = dataCopy[i];
146
+ if (current.cs === null || current.ma80Bias === null || current.rsi === null) {
147
+ current.csScore = null;
148
+ current.ma80Score = null;
149
+ current.rsiScore = null;
150
+ current.totalScore = null;
151
+ current.scoreMA = null;
152
+ continue;
153
+ }
154
+
155
+ const startIdx = Math.max(0, i - DIVIDEND_SCORE_CONSTANTS.ROLLING_WINDOW + 1);
156
+ const csHistory = csValues.slice(startIdx, i + 1);
157
+ const ma80History = ma80BiasValues.slice(startIdx, i + 1);
158
+
159
+ current.csScore = calculateRollingScore(
160
+ current.cs,
161
+ csHistory,
162
+ DIVIDEND_SCORE_CONSTANTS.PERCENTILE_LOW,
163
+ DIVIDEND_SCORE_CONSTANTS.PERCENTILE_HIGH
164
+ );
165
+ current.ma80Score = calculateRollingScore(
166
+ current.ma80Bias,
167
+ ma80History,
168
+ DIVIDEND_SCORE_CONSTANTS.PERCENTILE_LOW,
169
+ DIVIDEND_SCORE_CONSTANTS.PERCENTILE_HIGH
170
+ );
171
+ current.rsiScore = current.rsi;
172
+
173
+ if (current.csScore !== null && current.ma80Score !== null && current.rsiScore !== null) {
174
+ current.totalScore = parseFloat(
175
+ (
176
+ current.csScore * DIVIDEND_SCORE_CONSTANTS.CS_WEIGHT +
177
+ current.ma80Score * DIVIDEND_SCORE_CONSTANTS.MA80_WEIGHT +
178
+ current.rsiScore * DIVIDEND_SCORE_CONSTANTS.RSI_WEIGHT
179
+ ).toFixed(2)
180
+ );
181
+ } else {
182
+ current.totalScore = null;
183
+ }
184
+ }
185
+
186
+ for (let i = 0; i < dataCopy.length; i++) {
187
+ const current = dataCopy[i];
188
+ if (current.totalScore === null) {
189
+ current.scoreMA = null;
190
+ continue;
191
+ }
192
+
193
+ const scoreHistory = dataCopy
194
+ .slice(Math.max(0, i - DIVIDEND_SCORE_CONSTANTS.SCORE_MA_PERIOD + 1), i + 1)
195
+ .filter(d => d.totalScore !== null)
196
+ .map(d => d.totalScore);
197
+
198
+ if (scoreHistory.length >= DIVIDEND_SCORE_CONSTANTS.SCORE_MA_PERIOD) {
199
+ current.scoreMA = parseFloat((scoreHistory.reduce((a, b) => a + b, 0) / scoreHistory.length).toFixed(2));
200
+ } else {
201
+ current.scoreMA = current.totalScore;
202
+ }
203
+ }
204
+
205
+ return dataCopy;
206
+ }
207
+
208
+ module.exports = {
209
+ DIVIDEND_SCORE_CONSTANTS,
210
+ calculateEMA,
211
+ calculateMA,
212
+ calculateRSI,
213
+ percentile,
214
+ calculateRollingScore,
215
+ calculateScores
216
+ };
package/lib/iconv.js ADDED
@@ -0,0 +1,4 @@
1
+ const iconv = require('iconv-lite');
2
+
3
+ module.exports = (content, encoding = 'gbk') =>
4
+ iconv.decode(Buffer.isBuffer(content) ? content : Buffer.from(content), encoding).toString('binary');
package/lib/output.js CHANGED
@@ -1,3 +1,4 @@
1
+ const {encode} = require('@toon-format/toon');
1
2
  function output(data) {
2
3
  // 检查是否是dividend score结果
3
4
  if (data && data.scores && Array.isArray(data.scores)) {
@@ -5,11 +6,11 @@ function output(data) {
5
6
  console.log('');
6
7
  console.log('```toon');
7
8
  console.log('[60]{"日期","分数","cs值","rsi值"}:');
8
-
9
+
9
10
  data.scores.forEach(item => {
10
11
  console.log(` ${item.date},${item.score || '-'},${item.cs || '-'},${item.rsi || '-'}`);
11
12
  });
12
-
13
+
13
14
  console.log('```');
14
15
  } else {
15
16
  console.log(data);
@@ -17,5 +18,6 @@ function output(data) {
17
18
  }
18
19
 
19
20
  module.exports = {
21
+ encode,
20
22
  output
21
23
  };
package/lib/request.js ADDED
@@ -0,0 +1,44 @@
1
+ const axios = require('axios');
2
+
3
+ const API_DOMAINS = {
4
+ JQKA: 'https://dq.10jqka.com.cn'
5
+ };
6
+
7
+ const API_PATHS = {
8
+ HOT_LIST: '/fuyao/hot_list_data/out/hot_list/v1',
9
+ MARKET_ANALYSIS: '/fuyao/market_analysis/data/out/v1'
10
+ };
11
+
12
+ async function get(path, params = {}, options = {}) {
13
+ const domain = options.domain || 'JQKA';
14
+ const baseUrl = API_DOMAINS[domain] || API_DOMAINS.JQKA;
15
+ const url = `${baseUrl}${path}`;
16
+
17
+ try {
18
+ const response = await axios.get(url, {
19
+ params,
20
+ timeout: 30000,
21
+ headers: {
22
+ 'User-Agent':
23
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
24
+ Referer: 'https://www.10jqka.com.cn/',
25
+ Accept: 'application/json, text/plain, */*'
26
+ }
27
+ });
28
+
29
+ return response.data;
30
+ } catch (error) {
31
+ if (error.response) {
32
+ const err = new Error(`HTTP ${error.response.status}: ${error.response.statusText}`);
33
+ err.response = error.response;
34
+ throw err;
35
+ }
36
+ throw error;
37
+ }
38
+ }
39
+
40
+ module.exports = {
41
+ get,
42
+ API_DOMAINS,
43
+ API_PATHS
44
+ };
@@ -0,0 +1,148 @@
1
+ class S {
2
+ constructor(startTime, base_fileds = [4, 4, 4, 4, 1, 1, 1, 3, 2, 2, 2, 2, 2, 2, 2, 4, 2, 1]) {
3
+ let endTime;
4
+ if (startTime && startTime > 10000000) {
5
+ endTime = startTime + 3600 * 3;
6
+ } else {
7
+ const now = new Date();
8
+ const date = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
9
+ const s = Date.parse(`${date} ${now.getHours()}:03:23`) / 1000;
10
+ startTime = parseInt(s, 10);
11
+ endTime = parseInt(s + 3600 * 3, 10);
12
+ }
13
+ const data = {
14
+ 0: 33554944,
15
+ 1: startTime,
16
+ 2: endTime,
17
+ 3: 3086956040,
18
+ 4: 7,
19
+ 5: 10,
20
+ 6: 0,
21
+ 7: 0,
22
+ 8: 0,
23
+ 9: 0,
24
+ 10: 0,
25
+ 11: 0,
26
+ 12: 0,
27
+ 13: 4064,
28
+ 14: 0,
29
+ 15: 0,
30
+ 16: 0,
31
+ 17: 3
32
+ };
33
+ for (let n = 0, t = base_fileds.length; t > n; n++) {
34
+ this[n] = data[n];
35
+ }
36
+ this.base_fileds = base_fileds;
37
+ }
38
+ random() {
39
+ return (Math.random() * parseInt(4294967295, 10)) >>> 0;
40
+ }
41
+ toBuffer() {
42
+ for (var r = this.base_fileds, n = [], t = -1, e = 0, a = r.length; a > e; e++) {
43
+ for (let u = this[e], f = r[e], d = (t += f); (n[d] = u & parseInt('11111111', 2)), --f != 0; ) {
44
+ --d;
45
+ u >>= parseInt(10, 8);
46
+ }
47
+ }
48
+ return n;
49
+ }
50
+ getSession() {
51
+ let r = this.toBuffer();
52
+ return this.encode(r);
53
+ }
54
+ encode(r) {
55
+ let n = T(r);
56
+ let t = [2, n];
57
+ P(r, 0, t, 2, n);
58
+ return N(t);
59
+ }
60
+
61
+ decodeBuffer(r) {
62
+ let n = dr;
63
+ let t = this.base_fileds;
64
+ let e = 0;
65
+ n = B;
66
+ for (let a = 0, o = t.length; o > a; a++) {
67
+ let i = t[a];
68
+ let u = 0;
69
+ do {
70
+ u = (u << parseInt(1000, 2)) + r[e++];
71
+ } while (--i > 0);
72
+ this[a] = u >>> 0;
73
+ }
74
+ }
75
+ strhash(t) {
76
+ for (var o = 0, i = 0, u = t.length; u > i; i++) {
77
+ ((o = (o << 5) - o + t.charCodeAt(i)), (o >>>= 0));
78
+ }
79
+ return o;
80
+ }
81
+ updateServerTime(stime, ua) {
82
+ // """更新服务器时间"""
83
+ const ts = parseInt(Date.now() / 1000, 10);
84
+ const random = parseInt(this.random(), 10);
85
+ this[0] = random;
86
+ this[1] = parseInt(stime, 10);
87
+ this[2] = ts;
88
+ this[3] = this.strhash(ua);
89
+ return this.getSession();
90
+ }
91
+ }
92
+
93
+ function T(r) {
94
+ for (var n = 0, t = 0, e = r.length; e > t; t++) {
95
+ n = (n << 5) - n + r[t];
96
+ }
97
+ return n & 255;
98
+ }
99
+
100
+ function P(r, n, t, e, a) {
101
+ for (let f = r.length; f > n; ) {
102
+ t[e++] = r[n++] ^ (a & 255);
103
+ a = ~(a * parseInt(203, 8));
104
+ }
105
+ }
106
+
107
+ function N(r) {
108
+ const S = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
109
+ for (var E = 0, A = r.length, b = []; A > E; ) {
110
+ let B = (r[E++] << parseInt('10000', 2)) | (r[E++] << 8) | r[E++];
111
+ b.push(
112
+ S.charAt(B >> parseInt('10010', 2)),
113
+ S.charAt((B >> parseInt('14', 8)) & parseInt('77', 8)),
114
+ S.charAt((B >> 6) & parseInt('3f', 16)),
115
+ S.charAt(B & parseInt('3f', 16))
116
+ );
117
+ }
118
+ return b.join('');
119
+ }
120
+ const s = new S();
121
+
122
+ const getHeader = (options = {}) => {
123
+ let headers = {
124
+ 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7',
125
+ Pragma: 'no-cache',
126
+ Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
127
+ 'Cache-Control': 'no-cache',
128
+ 'Upgrade-Insecure-Requests': '1',
129
+ Referer: 'https://basic.10jqka.com.cn/601633',
130
+ 'User-Agent':
131
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36',
132
+ ...options
133
+ };
134
+ const ss = s.updateServerTime(parseInt(Date.now() / 1000, 10), headers['User-Agent']);
135
+ headers.Cookie = `v=${ss}`;
136
+ return headers;
137
+ };
138
+ module.exports = {
139
+ getHeader
140
+ };
141
+ // const s = new S();
142
+ // console.log(s.getSession());
143
+ // AnR2o5Q9lSuShAFn00OHkARhQznmTZg32nEsew7VAP-CeRsjNl1oxyqB_Apd
144
+ // AkhKJwjh3Gs-RbDvsuvGsWTNGrVa8az7jlWAfwL5lEO23epHqgF8i95lUA9Q
145
+ // 2,0,2,0,93,140,24,229,93,140,122,126,183,255,54,8,7,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,224,0,0,0,0,0,0,0,1,3
146
+ // 2,0,2,0,93,140,24,229,93,140,123,170,183,255,54,8,7,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,224,0,0,0,0,0,0,0,1,3
147
+ // 2,0,2,0,93,140,24,229,93,140,123,44,183,255,54,8,7,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,224,0,0,0,0,0,0,0,1,3
148
+ // 2,0,2,0,93,140,24,229,93,140,124,95,183,255,54,8,7,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,224,0,0,0,0,0,0,0,1,3
package/lib/utils.js CHANGED
@@ -1,3 +1,27 @@
1
+ const isWeekday = date => {
2
+ const t = new Date(date).getDay();
3
+ return t >= 1 && t <= 5;
4
+ };
5
+ // 交易时间配置
6
+ const TRADING_HOURS = {
7
+ MORNING_START: 570, // 9:30 (分钟)
8
+ MORNING_END: 690, // 11:30
9
+ AFTERNOON_START: 780, // 13:00
10
+ AFTERNOON_END: 900 // 15:00
11
+ };
12
+ const isTradingTime = date => {
13
+ const t = new Date(date);
14
+ const n = 60 * t.getHours() + t.getMinutes();
15
+ return (
16
+ (n >= TRADING_HOURS.MORNING_START && n <= TRADING_HOURS.MORNING_END) ||
17
+ (n >= TRADING_HOURS.AFTERNOON_START && n <= TRADING_HOURS.AFTERNOON_END)
18
+ );
19
+ };
20
+
21
+ const isTradingNow = e => {
22
+ const t = new Date();
23
+ return !(!isWeekday(t) || !isTradingTime(t) || (null != e && e > 0 && t.valueOf() - e > 6e5));
24
+ };
1
25
  function getSecid(code) {
2
26
  code = String(code);
3
27
  if (code.indexOf('.') !== -1) {
@@ -25,7 +49,39 @@ function getSecid(code) {
25
49
  }
26
50
  return `0.${code}`;
27
51
  }
28
-
52
+ const formatThsVolumeTime = e => {
53
+ let t;
54
+ let n;
55
+ let r;
56
+ let l;
57
+ let a;
58
+ let s;
59
+ let i = (null == e || null == (t = e.charts) ? void 0 : t.point_list) || [];
60
+ let o = (null == e || null == (n = e.charts) ? void 0 : n.header) || [];
61
+ let c = null;
62
+ let d = null;
63
+ let u = null;
64
+ let h = null;
65
+ let m = o.find(e => 'turnover' === e.key || '当日成交额' === e.name);
66
+ c = null != (r = null == m ? void 0 : m.val) ? r : null;
67
+ const p = o.find(e => 'turnover_change' === e.key || '较昨日变动' === e.name);
68
+ d = null != (l = null == p ? void 0 : p.val) ? l : null;
69
+ const f = o.find(e => 'predict_turnover' === e.key || '预测全天成交额' === e.name);
70
+ if (((u = null != (a = null == f ? void 0 : f.val) ? a : null), i.length > 0)) {
71
+ const e = i[i.length - 1];
72
+ e && (h = null != (s = e[0]) ? s : null);
73
+ }
74
+ return {
75
+ currentTurnover: c,
76
+ changeAmount: d,
77
+ predictTurnover: u,
78
+ dataTimestamp: h
79
+ };
80
+ };
29
81
  module.exports = {
82
+ isWeekday,
83
+ isTradingTime,
84
+ isTradingNow,
85
+ formatThsVolumeTime,
30
86
  getSecid
31
- };
87
+ };
package/package.json CHANGED
@@ -1,36 +1,39 @@
1
1
  {
2
- "name": "daxiapi-cli",
3
- "version": "2.2.0",
4
- "description": "大虾皮金融数据API命令行工具",
5
- "bin": {
6
- "daxiapi": "./bin/index.js",
7
- "dxp": "./bin/index.js"
8
- },
9
- "scripts": {
10
- "test": "node bin/index.js --help"
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
- },
27
- "engines": {
28
- "node": ">=14.0.0"
29
- },
30
- "files": [
31
- "bin",
32
- "commands",
33
- "lib",
34
- "README.md"
35
- ]
36
- }
2
+ "name": "daxiapi-cli",
3
+ "version": "2.3.0",
4
+ "description": "大虾皮金融数据API命令行工具",
5
+ "bin": {
6
+ "daxiapi": "./bin/index.js",
7
+ "dxp": "./bin/index.js"
8
+ },
9
+ "scripts": {
10
+ "test": "node bin/index.js --help"
11
+ },
12
+ "keywords": [
13
+ "stock",
14
+ "api",
15
+ "daxiapi",
16
+ "cli",
17
+ "finance"
18
+ ],
19
+ "author": "daxiapi.com",
20
+ "license": "MIT",
21
+ "dependencies": {
22
+ "iconv-lite": "^0.5.0",
23
+ "@ksky521/html-picker": "^1.0.0",
24
+ "@toon-format/toon": "^2.1.0",
25
+ "axios": "^1.6.0",
26
+ "chalk": "^4.1.2",
27
+ "commander": "^11.1.0",
28
+ "conf": "^10.2.0"
29
+ },
30
+ "engines": {
31
+ "node": ">=14.0.0"
32
+ },
33
+ "files": [
34
+ "bin",
35
+ "commands",
36
+ "lib",
37
+ "README.md"
38
+ ]
39
+ }