@kamuira/stock-analyzer 1.0.5 → 1.2.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/stock.js DELETED
@@ -1,181 +0,0 @@
1
- /**
2
- * A股数据获取工具
3
- * - 实时行情:新浪财经接口
4
- * - 历史K线:腾讯财经接口(前复权日K)
5
- *
6
- * 用法:
7
- * node stock.js realtime [sz002049,sh603893]
8
- * node stock.js history [sz002049] [days=60]
9
- * node stock.js all
10
- */
11
-
12
- const http = require('http');
13
- const https = require('https');
14
-
15
- // 监控列表
16
- const WATCH_LIST = {
17
- 'sz002049': '紫光国微',
18
- 'sh603893': '瑞芯微',
19
- };
20
-
21
- // ============ 实时行情(新浪财经) ============
22
-
23
- function fetchRealtime(codes) {
24
- return new Promise((resolve, reject) => {
25
- const codesStr = codes.join(',');
26
- const options = {
27
- hostname: 'hq.sinajs.cn',
28
- path: `/list=${codesStr}`,
29
- headers: {
30
- 'Referer': 'http://finance.sina.com.cn',
31
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
32
- },
33
- };
34
-
35
- http.get(options, (res) => {
36
- const chunks = [];
37
- res.on('data', c => chunks.push(c));
38
- res.on('end', () => {
39
- const buf = Buffer.concat(chunks);
40
- const decoder = new TextDecoder('gbk');
41
- const text = decoder.decode(buf);
42
- resolve(parseRealtimeData(text));
43
- });
44
- }).on('error', reject);
45
- });
46
- }
47
-
48
- function parseRealtimeData(raw) {
49
- const results = [];
50
- const lines = raw.trim().split('\n');
51
-
52
- for (const line of lines) {
53
- const match = line.match(/var hq_str_(\w+)="(.*)";?/);
54
- if (!match || !match[2]) continue;
55
-
56
- const code = match[1];
57
- const fields = match[2].split(',');
58
- if (fields.length < 32) continue;
59
-
60
- const name = fields[0];
61
- const open = parseFloat(fields[1]);
62
- const yesterdayClose = parseFloat(fields[2]);
63
- const price = parseFloat(fields[3]);
64
- const high = parseFloat(fields[4]);
65
- const low = parseFloat(fields[5]);
66
- const volume = parseFloat(fields[8]); // 股
67
- const amount = parseFloat(fields[9]); // 元
68
- const date = fields[30];
69
- const time = fields[31];
70
-
71
- const change = price - yesterdayClose;
72
- const changePct = yesterdayClose > 0 ? (change / yesterdayClose * 100) : 0;
73
-
74
- results.push({
75
- code, name, price, open, high, low,
76
- yesterdayClose,
77
- change: +change.toFixed(2),
78
- changePct: +changePct.toFixed(2),
79
- volume: Math.round(volume / 100), // 手
80
- amount: +(amount / 10000).toFixed(2), // 万元
81
- time: `${date} ${time}`,
82
- });
83
- }
84
- return results;
85
- }
86
-
87
- // ============ 历史K线(腾讯财经) ============
88
-
89
- function fetchHistory(code, days = 60) {
90
- return new Promise((resolve, reject) => {
91
- const url = `https://web.ifzq.gtimg.cn/appstock/app/fqkline/get?param=${code},day,,,${days},qfq`;
92
-
93
- https.get(url, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } }, (res) => {
94
- // 处理重定向
95
- if (res.statusCode === 301 || res.statusCode === 302) {
96
- const redirect = res.headers.location;
97
- const client = redirect.startsWith('https') ? https : http;
98
- client.get(redirect, { headers: { 'User-Agent': 'Mozilla/5.0' } }, (res2) => {
99
- collectResponse(res2, code, resolve, reject);
100
- }).on('error', reject);
101
- return;
102
- }
103
- collectResponse(res, code, resolve, reject);
104
- }).on('error', reject);
105
- });
106
- }
107
-
108
- function collectResponse(res, code, resolve, reject) {
109
- const chunks = [];
110
- res.on('data', c => chunks.push(c));
111
- res.on('end', () => {
112
- try {
113
- const json = JSON.parse(Buffer.concat(chunks).toString('utf-8'));
114
- resolve(parseHistoryData(json, code));
115
- } catch (e) {
116
- reject(e);
117
- }
118
- });
119
- }
120
-
121
- function parseHistoryData(json, code) {
122
- if (!json.data || !json.data[code]) return [];
123
-
124
- // 腾讯接口返回 qfqday(前复权日K)
125
- const klines = json.data[code].qfqday || json.data[code].day || [];
126
-
127
- return klines.map(item => {
128
- // [日期, 开盘, 收盘, 最高, 最低, 成交量(手)]
129
- const date = item[0];
130
- const open = parseFloat(item[1]);
131
- const close = parseFloat(item[2]);
132
- const high = parseFloat(item[3]);
133
- const low = parseFloat(item[4]);
134
- const volume = parseInt(item[5]) || 0;
135
-
136
- const change = open > 0 ? close - open : 0;
137
- const changePct = open > 0 ? ((close - open) / open * 100) : 0;
138
-
139
- return {
140
- date,
141
- open,
142
- close,
143
- high,
144
- low,
145
- volume, // 手
146
- change: +change.toFixed(2),
147
- changePct: +changePct.toFixed(2),
148
- };
149
- });
150
- }
151
-
152
- // ============ 主程序 ============
153
-
154
- async function main() {
155
- const args = process.argv.slice(2);
156
- const command = args[0] || 'realtime';
157
-
158
- if (command === 'realtime') {
159
- const codes = args[1] ? args[1].split(',') : Object.keys(WATCH_LIST);
160
- const data = await fetchRealtime(codes);
161
- console.log(JSON.stringify(data, null, 2));
162
- } else if (command === 'history') {
163
- const code = args[1] || Object.keys(WATCH_LIST)[0];
164
- const days = parseInt(args[2]) || 60;
165
- const data = await fetchHistory(code, days);
166
- console.log(JSON.stringify(data, null, 2));
167
- } else if (command === 'all') {
168
- // 获取所有监控股票的实时+历史数据
169
- const codes = Object.keys(WATCH_LIST);
170
- const realtime = await fetchRealtime(codes);
171
- const histories = {};
172
- for (const code of codes) {
173
- histories[code] = await fetchHistory(code, 60);
174
- }
175
- console.log(JSON.stringify({ realtime, histories }, null, 2));
176
- } else {
177
- console.log('用法: node stock.js [realtime|history|all] [codes] [days]');
178
- }
179
- }
180
-
181
- main().catch(e => console.error('Error:', e.message));