@fugle/node-twstock 2.2.1-alpha.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.
Files changed (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1837 -0
  3. package/lib/enums/exchange.enum.d.ts +5 -0
  4. package/lib/enums/exchange.enum.js +9 -0
  5. package/lib/enums/futopt.enum.d.ts +34 -0
  6. package/lib/enums/futopt.enum.js +38 -0
  7. package/lib/enums/index.d.ts +6 -0
  8. package/lib/enums/index.enum.d.ts +67 -0
  9. package/lib/enums/index.enum.js +71 -0
  10. package/lib/enums/index.js +22 -0
  11. package/lib/enums/industry.enum.d.ts +40 -0
  12. package/lib/enums/industry.enum.js +44 -0
  13. package/lib/enums/market.enum.d.ts +5 -0
  14. package/lib/enums/market.enum.js +9 -0
  15. package/lib/enums/scraper.enum.d.ts +10 -0
  16. package/lib/enums/scraper.enum.js +14 -0
  17. package/lib/index.d.ts +2 -0
  18. package/lib/index.js +18 -0
  19. package/lib/interfaces/futopt-exchange-rates.interface.d.ts +13 -0
  20. package/lib/interfaces/futopt-exchange-rates.interface.js +2 -0
  21. package/lib/interfaces/futopt-historical.interface.d.ts +23 -0
  22. package/lib/interfaces/futopt-historical.interface.js +2 -0
  23. package/lib/interfaces/futopt-institutional.interface.d.ts +21 -0
  24. package/lib/interfaces/futopt-institutional.interface.js +2 -0
  25. package/lib/interfaces/futopt-large-traders.interface.d.ts +16 -0
  26. package/lib/interfaces/futopt-large-traders.interface.js +2 -0
  27. package/lib/interfaces/futopt-mxf-retail-position.interface.d.ts +7 -0
  28. package/lib/interfaces/futopt-mxf-retail-position.interface.js +2 -0
  29. package/lib/interfaces/futopt-quote.interface.d.ts +31 -0
  30. package/lib/interfaces/futopt-quote.interface.js +2 -0
  31. package/lib/interfaces/futopt-tmf-retail-position.interface.d.ts +7 -0
  32. package/lib/interfaces/futopt-tmf-retail-position.interface.js +2 -0
  33. package/lib/interfaces/futopt-txo-put-call-ratio.interface.d.ts +9 -0
  34. package/lib/interfaces/futopt-txo-put-call-ratio.interface.js +2 -0
  35. package/lib/interfaces/futopt.interface.d.ts +7 -0
  36. package/lib/interfaces/futopt.interface.js +2 -0
  37. package/lib/interfaces/index-historical.interface.d.ts +11 -0
  38. package/lib/interfaces/index-historical.interface.js +2 -0
  39. package/lib/interfaces/index-quote.interface.d.ts +13 -0
  40. package/lib/interfaces/index-quote.interface.js +2 -0
  41. package/lib/interfaces/index-trades.interface.d.ts +9 -0
  42. package/lib/interfaces/index-trades.interface.js +2 -0
  43. package/lib/interfaces/index.d.ts +37 -0
  44. package/lib/interfaces/index.js +53 -0
  45. package/lib/interfaces/market-breadth.interface.d.ts +11 -0
  46. package/lib/interfaces/market-breadth.interface.js +2 -0
  47. package/lib/interfaces/market-institutional.interface.d.ts +10 -0
  48. package/lib/interfaces/market-institutional.interface.js +2 -0
  49. package/lib/interfaces/market-margin-trades.interface.d.ts +19 -0
  50. package/lib/interfaces/market-margin-trades.interface.js +2 -0
  51. package/lib/interfaces/market-trades.interface.d.ts +7 -0
  52. package/lib/interfaces/market-trades.interface.js +2 -0
  53. package/lib/interfaces/rate-limit-options.interface.d.ts +4 -0
  54. package/lib/interfaces/rate-limit-options.interface.js +2 -0
  55. package/lib/interfaces/stock-capital-reductions.interface.d.ts +23 -0
  56. package/lib/interfaces/stock-capital-reductions.interface.js +2 -0
  57. package/lib/interfaces/stock-dividends.interface.d.ts +27 -0
  58. package/lib/interfaces/stock-dividends.interface.js +2 -0
  59. package/lib/interfaces/stock-eps.interface.d.ts +8 -0
  60. package/lib/interfaces/stock-eps.interface.js +2 -0
  61. package/lib/interfaces/stock-fini-holdings.interface.d.ts +12 -0
  62. package/lib/interfaces/stock-fini-holdings.interface.js +2 -0
  63. package/lib/interfaces/stock-futopt.interface.d.ts +8 -0
  64. package/lib/interfaces/stock-futopt.interface.js +2 -0
  65. package/lib/interfaces/stock-historical.interface.d.ts +14 -0
  66. package/lib/interfaces/stock-historical.interface.js +2 -0
  67. package/lib/interfaces/stock-institutional.interface.d.ts +12 -0
  68. package/lib/interfaces/stock-institutional.interface.js +2 -0
  69. package/lib/interfaces/stock-margin-trades.interface.d.ts +20 -0
  70. package/lib/interfaces/stock-margin-trades.interface.js +2 -0
  71. package/lib/interfaces/stock-quote.interface.d.ts +19 -0
  72. package/lib/interfaces/stock-quote.interface.js +2 -0
  73. package/lib/interfaces/stock-revenue.interface.d.ts +8 -0
  74. package/lib/interfaces/stock-revenue.interface.js +2 -0
  75. package/lib/interfaces/stock-shareholders.interface.d.ts +11 -0
  76. package/lib/interfaces/stock-shareholders.interface.js +2 -0
  77. package/lib/interfaces/stock-short-sales.interface.d.ts +19 -0
  78. package/lib/interfaces/stock-short-sales.interface.js +2 -0
  79. package/lib/interfaces/stock-splits.interface.d.ts +12 -0
  80. package/lib/interfaces/stock-splits.interface.js +2 -0
  81. package/lib/interfaces/stock-values.interface.d.ts +10 -0
  82. package/lib/interfaces/stock-values.interface.js +2 -0
  83. package/lib/interfaces/stock.interface.d.ts +8 -0
  84. package/lib/interfaces/stock.interface.js +2 -0
  85. package/lib/interfaces/stocks-capital-reduction-announcement.interface.d.ts +19 -0
  86. package/lib/interfaces/stocks-capital-reduction-announcement.interface.js +2 -0
  87. package/lib/interfaces/stocks-dividends-announcement.interface.d.ts +21 -0
  88. package/lib/interfaces/stocks-dividends-announcement.interface.js +2 -0
  89. package/lib/interfaces/stocks-etf-split-announcement.interface.d.ts +11 -0
  90. package/lib/interfaces/stocks-etf-split-announcement.interface.js +2 -0
  91. package/lib/interfaces/stocks-listing-application.interface.d.ts +16 -0
  92. package/lib/interfaces/stocks-listing-application.interface.js +2 -0
  93. package/lib/interfaces/stocks-split-announcement.interface.d.ts +11 -0
  94. package/lib/interfaces/stocks-split-announcement.interface.js +2 -0
  95. package/lib/interfaces/ticker.interface.d.ts +10 -0
  96. package/lib/interfaces/ticker.interface.js +2 -0
  97. package/lib/scrapers/index.d.ts +10 -0
  98. package/lib/scrapers/index.js +26 -0
  99. package/lib/scrapers/isin-scraper.d.ts +13 -0
  100. package/lib/scrapers/isin-scraper.js +76 -0
  101. package/lib/scrapers/mis-taifex-scraper.d.ts +17 -0
  102. package/lib/scrapers/mis-taifex-scraper.js +126 -0
  103. package/lib/scrapers/mis-twse-scraper.d.ts +15 -0
  104. package/lib/scrapers/mis-twse-scraper.js +97 -0
  105. package/lib/scrapers/mops-scraper.d.ts +17 -0
  106. package/lib/scrapers/mops-scraper.js +64 -0
  107. package/lib/scrapers/scraper-factory.d.ts +25 -0
  108. package/lib/scrapers/scraper-factory.js +62 -0
  109. package/lib/scrapers/scraper.d.ts +6 -0
  110. package/lib/scrapers/scraper.js +20 -0
  111. package/lib/scrapers/taifex-scraper.d.ts +44 -0
  112. package/lib/scrapers/taifex-scraper.js +406 -0
  113. package/lib/scrapers/tdcc-scraper.d.ts +11 -0
  114. package/lib/scrapers/tdcc-scraper.js +82 -0
  115. package/lib/scrapers/tpex-scraper.d.ts +95 -0
  116. package/lib/scrapers/tpex-scraper.js +786 -0
  117. package/lib/scrapers/twse-scraper.d.ts +110 -0
  118. package/lib/scrapers/twse-scraper.js +967 -0
  119. package/lib/twstock.d.ts +229 -0
  120. package/lib/twstock.js +507 -0
  121. package/lib/utils/as-exchange.util.d.ts +2 -0
  122. package/lib/utils/as-exchange.util.js +15 -0
  123. package/lib/utils/as-index.util.d.ts +1 -0
  124. package/lib/utils/as-index.util.js +178 -0
  125. package/lib/utils/as-industry.util.d.ts +2 -0
  126. package/lib/utils/as-industry.util.js +49 -0
  127. package/lib/utils/date-converter.util.d.ts +47 -0
  128. package/lib/utils/date-converter.util.js +113 -0
  129. package/lib/utils/html-cleaner.util.d.ts +29 -0
  130. package/lib/utils/html-cleaner.util.js +45 -0
  131. package/lib/utils/index.d.ts +7 -0
  132. package/lib/utils/index.js +23 -0
  133. package/lib/utils/is-warrant.util.d.ts +1 -0
  134. package/lib/utils/is-warrant.util.js +25 -0
  135. package/lib/utils/parse-numeric.util.d.ts +21 -0
  136. package/lib/utils/parse-numeric.util.js +58 -0
  137. package/package.json +70 -0
@@ -0,0 +1,967 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TwseScraper = void 0;
4
+ const _ = require("lodash");
5
+ const numeral = require("numeral");
6
+ const luxon_1 = require("luxon");
7
+ const scraper_1 = require("./scraper");
8
+ const enums_1 = require("../enums");
9
+ const utils_1 = require("../utils");
10
+ class TwseScraper extends scraper_1.Scraper {
11
+ async fetchStocksHistorical(options) {
12
+ const { date, symbol } = options;
13
+ const query = new URLSearchParams({
14
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
15
+ type: 'ALLBUT0999',
16
+ response: 'json',
17
+ });
18
+ const url = `https://www.twse.com.tw/rwd/zh/afterTrading/MI_INDEX?${query}`;
19
+ const response = await this.httpService.get(url, { headers: { 'Connection': 'keep-alive' } });
20
+ const json = (response.data.stat === 'OK') && response.data;
21
+ if (!json)
22
+ return null;
23
+ const data = json.tables[8].data.map((row) => {
24
+ const [symbol, name, ...values] = row;
25
+ const data = {};
26
+ data.date = date;
27
+ data.exchange = enums_1.Exchange.TWSE;
28
+ data.symbol = symbol;
29
+ data.name = name.trim();
30
+ data.open = numeral(values[3]).value();
31
+ data.high = numeral(values[4]).value();
32
+ data.low = numeral(values[5]).value();
33
+ data.close = numeral(values[6]).value();
34
+ data.volume = numeral(values[0]).value();
35
+ data.turnover = numeral(values[2]).value();
36
+ data.transaction = numeral(values[1]).value();
37
+ data.change = values[7].includes('green') ? numeral(values[8]).multiply(-1).value() : numeral(values[8]).value();
38
+ return data;
39
+ });
40
+ return symbol ? data.find(data => data.symbol === symbol) : data;
41
+ }
42
+ async fetchStocksInstitutional(options) {
43
+ const { date, symbol } = options;
44
+ const query = new URLSearchParams({
45
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
46
+ selectType: 'ALLBUT0999',
47
+ response: 'json',
48
+ });
49
+ const url = `https://www.twse.com.tw/rwd/zh/fund/T86?${query}`;
50
+ const response = await this.httpService.get(url);
51
+ const json = (response.data.stat === 'OK') && response.data;
52
+ if (!json)
53
+ return null;
54
+ const data = json.data.map((row) => {
55
+ const [symbol, name, ...values] = row;
56
+ const data = {};
57
+ data.date = date;
58
+ data.exchange = enums_1.Exchange.TWSE;
59
+ data.symbol = symbol;
60
+ data.name = name.trim();
61
+ data.institutional = ((values) => {
62
+ switch (values.length) {
63
+ case 17: return [
64
+ {
65
+ investor: '外資及陸資(不含外資自營商)',
66
+ totalBuy: numeral(values[0]).value(),
67
+ totalSell: numeral(values[1]).value(),
68
+ difference: numeral(values[2]).value(),
69
+ },
70
+ {
71
+ investor: '外資自營商',
72
+ totalBuy: numeral(values[3]).value(),
73
+ totalSell: numeral(values[4]).value(),
74
+ difference: numeral(values[5]).value(),
75
+ },
76
+ {
77
+ investor: '投信',
78
+ totalBuy: numeral(values[6]).value(),
79
+ totalSell: numeral(values[7]).value(),
80
+ difference: numeral(values[8]).value(),
81
+ },
82
+ {
83
+ investor: '自營商',
84
+ difference: numeral(values[9]).value(),
85
+ },
86
+ {
87
+ investor: '自營商(自行買賣)',
88
+ totalBuy: numeral(values[10]).value(),
89
+ totalSell: numeral(values[11]).value(),
90
+ difference: numeral(values[12]).value(),
91
+ },
92
+ {
93
+ investor: '自營商(避險)',
94
+ totalBuy: numeral(values[13]).value(),
95
+ totalSell: numeral(values[14]).value(),
96
+ difference: numeral(values[15]).value(),
97
+ },
98
+ {
99
+ investor: '三大法人',
100
+ difference: numeral(values[16]).value(),
101
+ },
102
+ ];
103
+ case 14: return [
104
+ {
105
+ investor: '外資及陸資',
106
+ totalBuy: numeral(values[0]).value(),
107
+ totalSell: numeral(values[1]).value(),
108
+ difference: numeral(values[2]).value(),
109
+ },
110
+ {
111
+ investor: '投信',
112
+ totalBuy: numeral(values[3]).value(),
113
+ totalSell: numeral(values[4]).value(),
114
+ difference: numeral(values[5]).value(),
115
+ },
116
+ {
117
+ investor: '自營商',
118
+ difference: numeral(values[6]).value(),
119
+ },
120
+ {
121
+ investor: '自營商(自行買賣)',
122
+ totalBuy: numeral(values[7]).value(),
123
+ totalSell: numeral(values[8]).value(),
124
+ difference: numeral(values[9]).value(),
125
+ },
126
+ {
127
+ investor: '自營商(避險)',
128
+ totalBuy: numeral(values[10]).value(),
129
+ totalSell: numeral(values[11]).value(),
130
+ difference: numeral(values[12]).value(),
131
+ },
132
+ {
133
+ investor: '三大法人',
134
+ difference: numeral(values[13]).value(),
135
+ },
136
+ ];
137
+ case 10: return [
138
+ {
139
+ investor: '外資及陸資',
140
+ totalBuy: numeral(values[0]).value(),
141
+ totalSell: numeral(values[1]).value(),
142
+ difference: numeral(values[2]).value(),
143
+ },
144
+ {
145
+ investor: '投信',
146
+ totalBuy: numeral(values[3]).value(),
147
+ totalSell: numeral(values[4]).value(),
148
+ difference: numeral(values[5]).value(),
149
+ },
150
+ {
151
+ investor: '自營商',
152
+ totalBuy: numeral(values[6]).value(),
153
+ totalSell: numeral(values[7]).value(),
154
+ difference: numeral(values[8]).value(),
155
+ },
156
+ {
157
+ investor: '三大法人',
158
+ difference: numeral(values[9]).value(),
159
+ },
160
+ ];
161
+ }
162
+ })(values);
163
+ return data;
164
+ });
165
+ return symbol ? data.find(data => data.symbol === symbol) : data;
166
+ }
167
+ async fetchStocksFiniHoldings(options) {
168
+ const { date, symbol } = options;
169
+ const query = new URLSearchParams({
170
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
171
+ selectType: 'ALLBUT0999',
172
+ response: 'json',
173
+ });
174
+ const url = `https://www.twse.com.tw/rwd/zh/fund/MI_QFIIS?${query}`;
175
+ const response = await this.httpService.get(url);
176
+ const json = (response.data.stat === 'OK') && response.data;
177
+ if (!json)
178
+ return null;
179
+ const data = json.data.map((row) => {
180
+ const [symbol, name, isin, ...values] = row;
181
+ const data = {};
182
+ data.date = date;
183
+ data.exchange = enums_1.Exchange.TWSE;
184
+ data.symbol = symbol;
185
+ data.name = name.trim();
186
+ data.issuedShares = numeral(values[0]).value();
187
+ data.availableShares = numeral(values[1]).value();
188
+ data.sharesHeld = numeral(values[2]).value();
189
+ data.availablePercent = numeral(values[3]).value();
190
+ data.heldPercent = numeral(values[4]).value();
191
+ data.upperLimitPercent = numeral(values[5]).value();
192
+ return data;
193
+ });
194
+ return symbol ? data.find(data => data.symbol === symbol) : data;
195
+ }
196
+ async fetchStocksMarginTrades(options) {
197
+ const { date, symbol } = options;
198
+ const query = new URLSearchParams({
199
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
200
+ selectType: 'ALL',
201
+ response: 'json',
202
+ });
203
+ const url = `https://www.twse.com.tw/rwd/zh/marginTrading/MI_MARGN?${query}`;
204
+ const response = await this.httpService.get(url);
205
+ const json = (response.data.stat === 'OK') && response.data;
206
+ if (!json)
207
+ return null;
208
+ const data = json.tables[1].data.map((row) => {
209
+ const [symbol, name, ...values] = row;
210
+ const data = {};
211
+ data.date = date;
212
+ data.exchange = enums_1.Exchange.TWSE;
213
+ data.symbol = symbol;
214
+ data.name = name.trim();
215
+ data.marginBuy = numeral(values[0]).value();
216
+ data.marginSell = numeral(values[1]).value();
217
+ data.marginRedeem = numeral(values[2]).value();
218
+ data.marginBalancePrev = numeral(values[3]).value();
219
+ data.marginBalance = numeral(values[4]).value();
220
+ data.marginQuota = numeral(values[5]).value();
221
+ data.shortBuy = numeral(values[6]).value();
222
+ data.shortSell = numeral(values[7]).value();
223
+ data.shortRedeem = numeral(values[8]).value();
224
+ data.shortBalancePrev = numeral(values[9]).value();
225
+ data.shortBalance = numeral(values[10]).value();
226
+ data.shortQuota = numeral(values[11]).value();
227
+ data.offset = numeral(values[12]).value();
228
+ data.note = values[13].trim();
229
+ return data;
230
+ });
231
+ return symbol ? data.find(data => data.symbol === symbol) : data;
232
+ }
233
+ async fetchStocksShortSales(options) {
234
+ const { date, symbol } = options;
235
+ const query = new URLSearchParams({
236
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
237
+ response: 'json',
238
+ });
239
+ const url = `https://www.twse.com.tw/rwd/zh/marginTrading/TWT93U?${query}`;
240
+ const response = await this.httpService.get(url);
241
+ const json = (response.data.stat === 'OK') && response.data;
242
+ if (!json.data.length)
243
+ return null;
244
+ const data = json.data.map((row) => {
245
+ const [symbol, name, ...values] = row;
246
+ const data = {};
247
+ data.date = date;
248
+ data.exchange = enums_1.Exchange.TWSE;
249
+ data.symbol = symbol;
250
+ data.name = name.trim();
251
+ data.marginShortBalancePrev = numeral(values[0]).value();
252
+ data.marginShortSell = numeral(values[1]).value();
253
+ data.marginShortBuy = numeral(values[2]).value();
254
+ data.marginShortRedeem = numeral(values[3]).value();
255
+ data.marginShortBalance = numeral(values[4]).value();
256
+ data.marginShortQuota = numeral(values[5]).value();
257
+ data.sblShortBalancePrev = numeral(values[6]).value();
258
+ data.sblShortSale = numeral(values[7]).value();
259
+ data.sblShortReturn = numeral(values[8]).value();
260
+ data.sblShortAdjustment = numeral(values[9]).value();
261
+ data.sblShortBalance = numeral(values[10]).value();
262
+ data.sblShortQuota = numeral(values[11]).value();
263
+ data.note = values[12].trim();
264
+ return data;
265
+ });
266
+ return symbol ? data.find(data => data.symbol === symbol) : data;
267
+ }
268
+ async fetchStocksValues(options) {
269
+ const { date, symbol } = options;
270
+ const query = new URLSearchParams({
271
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
272
+ selectType: 'ALL',
273
+ response: 'json',
274
+ });
275
+ const url = `https://www.twse.com.tw/rwd/zh/afterTrading/BWIBBU_d?${query}`;
276
+ const response = await this.httpService.get(url);
277
+ const json = (response.data.stat === 'OK') && response.data;
278
+ if (!json)
279
+ return null;
280
+ const data = json.data.map((row) => {
281
+ const [symbol, name, ...values] = row;
282
+ const data = {};
283
+ data.date = date;
284
+ data.exchange = enums_1.Exchange.TWSE;
285
+ data.symbol = symbol;
286
+ data.name = name.trim();
287
+ data.peRatio = numeral(values[2]).value();
288
+ data.pbRatio = numeral(values[3]).value();
289
+ data.dividendYield = numeral(values[0]).value();
290
+ data.dividendYear = numeral(values[1]).add(1911).value();
291
+ return data;
292
+ });
293
+ return symbol ? data.find(data => data.symbol === symbol) : data;
294
+ }
295
+ async fetchStocksDividends(options) {
296
+ const { startDate, endDate, symbol, includeDetail = true } = options;
297
+ const query = new URLSearchParams({
298
+ startDate: luxon_1.DateTime.fromISO(startDate).toFormat('yyyyMMdd'),
299
+ endDate: luxon_1.DateTime.fromISO(endDate).toFormat('yyyyMMdd'),
300
+ response: 'json',
301
+ });
302
+ const url = `https://www.twse.com.tw/rwd/zh/exRight/TWT49U?${query}`;
303
+ const response = await this.httpService.get(url);
304
+ const json = response.data.stat === 'OK' && response.data;
305
+ if (!json)
306
+ return [];
307
+ const data = await Promise.all(json.data.map(async (row) => {
308
+ const [date, symbol, name, ...values] = row;
309
+ const formattedDate = (0, utils_1.rocToWestern)(date);
310
+ const data = {};
311
+ data.date = formattedDate;
312
+ data.exchange = enums_1.Exchange.TWSE;
313
+ data.symbol = symbol;
314
+ data.name = name.trim();
315
+ data.previousClose = (0, utils_1.parseNumeric)(values[0]);
316
+ data.referencePrice = (0, utils_1.parseNumeric)(values[1]);
317
+ data.dividend = (0, utils_1.parseNumeric)(values[2]);
318
+ data.dividendType = values[3].trim();
319
+ data.limitUpPrice = (0, utils_1.parseNumeric)(values[4]);
320
+ data.limitDownPrice = (0, utils_1.parseNumeric)(values[5]);
321
+ data.openingReferencePrice = (0, utils_1.parseNumeric)(values[6]);
322
+ data.exdividendReferencePrice = (0, utils_1.parseNumeric)(values[7]);
323
+ data.latestFinancialReportDate = (0, utils_1.parseFinancialReportDate)(values[9]);
324
+ data.latestNetAssetValuePerShare = (0, utils_1.parseNumeric)(values[10]);
325
+ data.latestEarningsPerShare = (0, utils_1.parseNumeric)(values[11]);
326
+ // Include detail API call if requested (default: true for backward compatibility)
327
+ if (includeDetail) {
328
+ try {
329
+ const [, detailDate] = values[8].split(',');
330
+ const detail = await this.fetchStocksDividendsDetail({ symbol, date: detailDate });
331
+ return Object.assign(Object.assign({}, data), detail);
332
+ }
333
+ catch (error) {
334
+ // If detail fetch fails, return main data without detail
335
+ console.warn(`Failed to fetch dividend detail for ${symbol}:`, error);
336
+ return data;
337
+ }
338
+ }
339
+ return data;
340
+ }));
341
+ return symbol ? data.filter((data) => data.symbol === symbol) : data;
342
+ }
343
+ async fetchStocksDividendsDetail(options) {
344
+ const { date, symbol } = options;
345
+ const query = new URLSearchParams({
346
+ STK_NO: symbol,
347
+ T1: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
348
+ response: 'json',
349
+ });
350
+ const url = `https://www.twse.com.tw/rwd/zh/exRight/TWT49UDetail?${query}`;
351
+ const response = await this.httpService.get(url);
352
+ const json = response.data.stat === 'ok' && response.data;
353
+ if (!json)
354
+ return null;
355
+ const [, name, ...values] = json.data[0];
356
+ const data = {};
357
+ data.symbol = symbol;
358
+ data.name = name.trim();
359
+ data.cashDividend = (0, utils_1.parseNumeric)(values[0]);
360
+ data.stockDividendShares = (0, utils_1.parseNumeric)(values[2]);
361
+ data.employeeBonusShares = (0, utils_1.parseNumeric)(values[3]);
362
+ data.paidCapitalIncrease = (0, utils_1.parseNumeric)(values[4]);
363
+ data.subscriptionPrice = (0, utils_1.parseNumeric)(values[5]);
364
+ data.publicOffering = (0, utils_1.parseNumeric)(values[6]);
365
+ data.employeeSubscription = (0, utils_1.parseNumeric)(values[7]);
366
+ data.existingShareholderSubscription = (0, utils_1.parseNumeric)(values[8]);
367
+ data.sharesPerThousand = (0, utils_1.parseNumeric)(values[9]);
368
+ return data;
369
+ }
370
+ async fetchStocksDividendsAnnouncement(options) {
371
+ var _a;
372
+ const { symbol: filterSymbol, includeDetail = false } = options || {};
373
+ const query = new URLSearchParams({
374
+ response: 'json',
375
+ _: Date.now().toString(),
376
+ });
377
+ const url = `https://www.twse.com.tw/rwd/zh/exRight/TWT48U?${query}`;
378
+ const response = await this.httpService.get(url);
379
+ const json = ((_a = response.data) === null || _a === void 0 ? void 0 : _a.stat) === 'OK' && response.data;
380
+ if (!json || !json.data)
381
+ return [];
382
+ const data = await Promise.all(json.data.map(async (row) => {
383
+ const [date, symbol, name, dividendType, stockDividendRatio, cashCapitalIncreaseRatio, subscriptionPrice, cashDividend, , // Skip field 8: detail reference
384
+ , // Skip field 9: reference price calculation
385
+ latestFinancialReportDate, latestNetAssetValuePerShare, latestEarningsPerShare,] = row;
386
+ const data = {};
387
+ data.symbol = symbol.trim();
388
+ data.name = name.trim();
389
+ data.exchange = enums_1.Exchange.TWSE;
390
+ data.exdividendDate = (0, utils_1.rocToWestern)(date);
391
+ data.dividendType = dividendType.trim();
392
+ data.stockDividendRatio = (0, utils_1.parseNumeric)(stockDividendRatio);
393
+ data.cashCapitalIncreaseRatio = (0, utils_1.parseNumeric)(cashCapitalIncreaseRatio);
394
+ data.subscriptionPrice = (0, utils_1.parseNumeric)((0, utils_1.stripHtmlTags)(subscriptionPrice));
395
+ data.cashDividend = (0, utils_1.parseNumeric)((0, utils_1.stripHtmlTags)(cashDividend));
396
+ // Add financial information fields from main API
397
+ data.latestFinancialReportDate = (0, utils_1.parseFinancialReportDate)(latestFinancialReportDate);
398
+ data.latestNetAssetValuePerShare = (0, utils_1.parseNumeric)(latestNetAssetValuePerShare);
399
+ data.latestEarningsPerShare = (0, utils_1.parseNumeric)(latestEarningsPerShare);
400
+ // Include detail API call if requested (default: false for announcements)
401
+ if (includeDetail) {
402
+ try {
403
+ const detail = await this.fetchStocksDividendsAnnouncementDetail({
404
+ symbol: data.symbol,
405
+ date: data.exdividendDate
406
+ });
407
+ return Object.assign(Object.assign({}, data), detail);
408
+ }
409
+ catch (error) {
410
+ // If detail fetch fails, return main data without detail
411
+ console.warn(`Failed to fetch dividend announcement detail for ${data.symbol}:`, error);
412
+ return data;
413
+ }
414
+ }
415
+ return data;
416
+ }));
417
+ return filterSymbol ? data.filter((item) => item.symbol === filterSymbol) : data;
418
+ }
419
+ async fetchStocksCapitalReductions(options) {
420
+ const { startDate, endDate, symbol } = options;
421
+ // Note: Always fetch detail for consistency with TPEx, includeDetail parameter is ignored
422
+ const query = new URLSearchParams({
423
+ startDate: luxon_1.DateTime.fromISO(startDate).toFormat('yyyyMMdd'),
424
+ endDate: luxon_1.DateTime.fromISO(endDate).toFormat('yyyyMMdd'),
425
+ response: 'json',
426
+ });
427
+ const url = `https://www.twse.com.tw/rwd/zh/reducation/TWTAUU?${query}`;
428
+ const response = await this.httpService.get(url);
429
+ const json = response.data.stat === 'OK' && response.data;
430
+ if (!json)
431
+ return [];
432
+ const data = await Promise.all(json.data.map(async (row) => {
433
+ const [date, symbol, name, ...values] = row;
434
+ const data = {};
435
+ data.resumeDate = (0, utils_1.rocToWestern)(date);
436
+ data.exchange = enums_1.Exchange.TWSE;
437
+ data.symbol = symbol;
438
+ data.name = name.trim();
439
+ data.previousClose = (0, utils_1.parseNumeric)(values[0]);
440
+ data.referencePrice = (0, utils_1.parseNumeric)(values[1]);
441
+ data.limitUpPrice = (0, utils_1.parseNumeric)(values[2]);
442
+ data.limitDownPrice = (0, utils_1.parseNumeric)(values[3]);
443
+ data.openingReferencePrice = (0, utils_1.parseNumeric)(values[4]);
444
+ data.exrightReferencePrice = (0, utils_1.parseNumeric)(values[5]);
445
+ data.reason = values[6].trim();
446
+ try {
447
+ const [, detailDate] = values[7].split(',');
448
+ const detail = await this.fetchStockCapitalReductionDetail({ symbol, date: detailDate });
449
+ return Object.assign(Object.assign({}, data), detail);
450
+ }
451
+ catch (error) {
452
+ // If detail fetch fails, return main data without detail
453
+ console.warn(`Failed to fetch capital reduction detail for ${symbol}:`, error);
454
+ return data;
455
+ }
456
+ }));
457
+ return symbol ? data.filter((data) => data.symbol === symbol) : data;
458
+ }
459
+ async fetchStockCapitalReductionDetail(options) {
460
+ const { date, symbol } = options;
461
+ const query = new URLSearchParams({
462
+ STK_NO: symbol,
463
+ FILE_DATE: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
464
+ response: 'json',
465
+ });
466
+ const url = `https://www.twse.com.tw/rwd/zh/reducation/TWTAVUDetail?${query}`;
467
+ const response = await this.httpService.get(url);
468
+ const json = response.data.stat === 'OK' && response.data;
469
+ if (!json)
470
+ return null;
471
+ const [, name, ...values] = json.data[0];
472
+ const data = {};
473
+ data.symbol = symbol;
474
+ data.name = name.trim();
475
+ data.haltDate = (0, utils_1.rocToWestern)(values[0]);
476
+ data.sharesPerThousand = (0, utils_1.parseNumeric)(values[1]);
477
+ data.refundPerShare = (0, utils_1.parseNumeric)(values[2]);
478
+ return data;
479
+ }
480
+ async fetchStocksDividendsAnnouncementDetail(options) {
481
+ const { date, symbol } = options;
482
+ const query = new URLSearchParams({
483
+ STK_NO: symbol,
484
+ T1: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
485
+ response: 'json',
486
+ });
487
+ const url = `https://www.twse.com.tw/rwd/zh/exRight/TWT49UDetail?${query}`;
488
+ const response = await this.httpService.get(url);
489
+ const json = response.data.stat === 'ok' && response.data;
490
+ if (!json)
491
+ return null;
492
+ const [, name, ...values] = json.data[0];
493
+ const data = {};
494
+ data.symbol = symbol;
495
+ data.name = name.trim();
496
+ data.cashDividend = (0, utils_1.parseNumeric)(values[0]);
497
+ data.stockDividendShares = (0, utils_1.parseNumeric)(values[2]);
498
+ data.employeeBonusShares = (0, utils_1.parseNumeric)(values[3]);
499
+ data.paidCapitalIncrease = (0, utils_1.parseNumeric)(values[4]);
500
+ data.subscriptionPrice = (0, utils_1.parseNumeric)(values[5]);
501
+ data.publicOffering = (0, utils_1.parseNumeric)(values[6]);
502
+ data.employeeSubscription = (0, utils_1.parseNumeric)(values[7]);
503
+ data.existingShareholderSubscription = (0, utils_1.parseNumeric)(values[8]);
504
+ data.sharesPerThousand = (0, utils_1.parseNumeric)(values[9]);
505
+ return data;
506
+ }
507
+ async fetchStocksCapitalReductionAnnouncementDetail(options) {
508
+ const { date, symbol } = options;
509
+ const query = new URLSearchParams({
510
+ STK_NO: symbol,
511
+ FILE_DATE: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
512
+ response: 'json',
513
+ });
514
+ const url = `https://www.twse.com.tw/rwd/zh/reducation/TWTAVUDetail?${query}`;
515
+ const response = await this.httpService.get(url);
516
+ const json = response.data.stat === 'OK' && response.data;
517
+ if (!json)
518
+ return null;
519
+ const [, name, ...values] = json.data[0];
520
+ const data = {};
521
+ data.symbol = symbol;
522
+ data.name = name.trim();
523
+ data.haltDate = (0, utils_1.rocToWestern)(values[0]);
524
+ data.sharesPerThousand = (0, utils_1.parseNumeric)(values[1]);
525
+ data.refundPerShare = (0, utils_1.parseNumeric)(values[2]);
526
+ data.cashDividendPerShare = (0, utils_1.parseNumeric)(values[3]);
527
+ data.paidCapitalIncrease = (0, utils_1.parseNumeric)(values[4]);
528
+ data.subscriptionPrice = (0, utils_1.parseNumeric)(values[5]);
529
+ data.publicOffering = (0, utils_1.parseNumeric)(values[6]);
530
+ data.employeeSubscription = (0, utils_1.parseNumeric)(values[7]);
531
+ data.existingShareholderSubscription = (0, utils_1.parseNumeric)(values[8]);
532
+ data.sharesPerThousandSubscription = (0, utils_1.parseNumeric)(values[9]);
533
+ return data;
534
+ }
535
+ async fetchStocksSplitAnnouncementDetail(options) {
536
+ const { date, symbol } = options;
537
+ const query = new URLSearchParams({
538
+ STK_NO: symbol,
539
+ FILE_DATE: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
540
+ response: 'json',
541
+ });
542
+ const url = `https://www.twse.com.tw/rwd/zh/change/TWTB7UDetail?${query}`;
543
+ const response = await this.httpService.get(url);
544
+ const json = response.data.stat === 'OK' && response.data;
545
+ if (!json)
546
+ return null;
547
+ const [, name, ...values] = json.data[0];
548
+ const data = {};
549
+ data.symbol = symbol;
550
+ data.name = name.trim();
551
+ data.haltDate = (0, utils_1.rocToWestern)(values[0]);
552
+ data.sharesPerOldShare = (0, utils_1.parseNumeric)(values[1]);
553
+ data.oldFaceValue = (0, utils_1.parseNumeric)(values[2]);
554
+ data.newFaceValue = (0, utils_1.parseNumeric)(values[3]);
555
+ return data;
556
+ }
557
+ async fetchStocksCapitalReductionAnnouncement(options) {
558
+ var _a;
559
+ const { symbol: filterSymbol, includeDetail = false } = options || {};
560
+ const query = new URLSearchParams({
561
+ response: 'json',
562
+ _: Date.now().toString(),
563
+ });
564
+ const url = `https://www.twse.com.tw/rwd/zh/reducation/TWTAVU?${query}`;
565
+ const response = await this.httpService.get(url);
566
+ const json = ((_a = response.data) === null || _a === void 0 ? void 0 : _a.stat) === 'OK' && response.data;
567
+ if (!json || !json.data)
568
+ return [];
569
+ const data = await Promise.all(json.data.map(async (row) => {
570
+ const [haltDate, symbol, name, resumeDate, reductionRatio, reason, refundPerShare, cashIncreaseRatioAfterReduction, subscriptionPrice] = row;
571
+ const data = {};
572
+ data.symbol = symbol.trim();
573
+ data.name = name.trim();
574
+ data.exchange = enums_1.Exchange.TWSE;
575
+ data.haltDate = (0, utils_1.rocToWestern)(haltDate);
576
+ data.resumeDate = (0, utils_1.rocToWestern)(resumeDate);
577
+ data.reductionRatio = (0, utils_1.parseNumeric)(reductionRatio);
578
+ data.reason = reason.trim();
579
+ data.refundPerShare = (0, utils_1.parseNumeric)(refundPerShare);
580
+ data.cashIncreaseRatioAfterReduction = (0, utils_1.parseNumeric)(cashIncreaseRatioAfterReduction);
581
+ data.subscriptionPrice = (0, utils_1.parseNumeric)(subscriptionPrice);
582
+ // Include detail API call if requested (default: false for announcements)
583
+ if (includeDetail) {
584
+ try {
585
+ const detail = await this.fetchStocksCapitalReductionAnnouncementDetail({
586
+ symbol: data.symbol,
587
+ date: data.haltDate
588
+ });
589
+ return Object.assign(Object.assign({}, data), detail);
590
+ }
591
+ catch (error) {
592
+ // If detail fetch fails, return main data without detail
593
+ console.warn(`Failed to fetch capital reduction announcement detail for ${data.symbol}:`, error);
594
+ return data;
595
+ }
596
+ }
597
+ return data;
598
+ }));
599
+ return filterSymbol ? data.filter((item) => item.symbol === filterSymbol) : data;
600
+ }
601
+ async fetchStocksSplits(options) {
602
+ const { startDate, endDate, symbol } = options;
603
+ const query = new URLSearchParams({
604
+ startDate: luxon_1.DateTime.fromISO(startDate).toFormat('yyyyMMdd'),
605
+ endDate: luxon_1.DateTime.fromISO(endDate).toFormat('yyyyMMdd'),
606
+ response: 'json',
607
+ });
608
+ const url = `https://www.twse.com.tw/rwd/zh/change/TWTB8U?${query}`;
609
+ const response = await this.httpService.get(url);
610
+ const json = response.data.stat === 'OK' && response.data;
611
+ const data = json.data.map((row) => {
612
+ const [date, symbol, name, ...values] = row;
613
+ const data = {};
614
+ data.resumeDate = (0, utils_1.rocToWestern)(date);
615
+ data.exchange = enums_1.Exchange.TWSE;
616
+ data.symbol = symbol;
617
+ data.name = name.trim();
618
+ data.previousClose = (0, utils_1.parseNumeric)(values[0]);
619
+ data.referencePrice = (0, utils_1.parseNumeric)(values[1]);
620
+ data.limitUpPrice = (0, utils_1.parseNumeric)(values[2]);
621
+ data.limitDownPrice = (0, utils_1.parseNumeric)(values[3]);
622
+ data.openingReferencePrice = (0, utils_1.parseNumeric)(values[4]);
623
+ // Parse halt date from detail field: "6531,20211007,20211018"
624
+ if (values[5]) {
625
+ const detailParts = values[5].split(',');
626
+ if (detailParts.length >= 3) {
627
+ const [, haltDateStr] = detailParts;
628
+ // haltDateStr is in YYYYMMDD format, convert to YYYY-MM-DD
629
+ if (haltDateStr && haltDateStr.length === 8) {
630
+ data.haltDate = `${haltDateStr.substring(0, 4)}-${haltDateStr.substring(4, 6)}-${haltDateStr.substring(6, 8)}`;
631
+ }
632
+ }
633
+ }
634
+ return data;
635
+ });
636
+ return symbol ? data.filter((data) => data.symbol === symbol) : data;
637
+ }
638
+ async fetchStocksEtfSplits(options) {
639
+ const { startDate, endDate, symbol } = options;
640
+ const query = new URLSearchParams({
641
+ startDate: luxon_1.DateTime.fromISO(startDate).toFormat('yyyyMMdd'),
642
+ endDate: luxon_1.DateTime.fromISO(endDate).toFormat('yyyyMMdd'),
643
+ response: 'json',
644
+ });
645
+ const url = `https://www.twse.com.tw/rwd/zh/split/TWTCAU?${query}`;
646
+ const response = await this.httpService.get(url);
647
+ const json = response.data.stat === 'OK' && response.data;
648
+ const data = json.data.map((row) => {
649
+ const [date, symbol, name, type, ...values] = row;
650
+ const data = {};
651
+ data.resumeDate = (0, utils_1.rocToWestern)(date);
652
+ data.exchange = enums_1.Exchange.TWSE;
653
+ data.symbol = symbol;
654
+ data.name = name.trim();
655
+ data.type = type;
656
+ data.previousClose = (0, utils_1.parseNumeric)(values[0]);
657
+ data.referencePrice = (0, utils_1.parseNumeric)(values[1]);
658
+ data.limitUpPrice = (0, utils_1.parseNumeric)(values[2]);
659
+ data.limitDownPrice = (0, utils_1.parseNumeric)(values[3]);
660
+ data.openingReferencePrice = (0, utils_1.parseNumeric)(values[4]);
661
+ return data;
662
+ })
663
+ .filter((row) => options.reverseSplit ? row.type === '反分割' : row.type === '分割')
664
+ .map((row) => _.omit(row, ['type']));
665
+ return symbol ? data.filter((data) => data.symbol === symbol) : data;
666
+ }
667
+ async fetchStocksSplitAnnouncement(options) {
668
+ var _a;
669
+ const { symbol: filterSymbol, includeDetail = false } = options || {};
670
+ const query = new URLSearchParams({
671
+ response: 'json',
672
+ _: Date.now().toString(),
673
+ });
674
+ const url = `https://www.twse.com.tw/rwd/zh/change/TWTB7U?${query}`;
675
+ const response = await this.httpService.get(url);
676
+ const json = ((_a = response.data) === null || _a === void 0 ? void 0 : _a.stat) === 'OK' && response.data;
677
+ if (!json || !json.data)
678
+ return [];
679
+ const data = await Promise.all(json.data.map(async (row) => {
680
+ const [haltDate, symbol, name, resumeDate, splitRatio, oldFaceValue, newFaceValue] = row;
681
+ const data = {};
682
+ data.symbol = symbol.trim();
683
+ data.name = name.trim();
684
+ data.exchange = enums_1.Exchange.TWSE;
685
+ data.haltDate = (0, utils_1.rocToWestern)(haltDate);
686
+ data.resumeDate = (0, utils_1.rocToWestern)(resumeDate);
687
+ data.splitRatio = (0, utils_1.parseNumeric)(splitRatio);
688
+ data.oldFaceValue = (0, utils_1.parseNumeric)(oldFaceValue);
689
+ data.newFaceValue = (0, utils_1.parseNumeric)(newFaceValue);
690
+ // Include detail API call if requested (default: false for announcements)
691
+ if (includeDetail) {
692
+ try {
693
+ const detail = await this.fetchStocksSplitAnnouncementDetail({
694
+ symbol: data.symbol,
695
+ date: data.haltDate
696
+ });
697
+ return Object.assign(Object.assign({}, data), detail);
698
+ }
699
+ catch (error) {
700
+ // If detail fetch fails, return main data without detail
701
+ console.warn(`Failed to fetch split announcement detail for ${data.symbol}:`, error);
702
+ return data;
703
+ }
704
+ }
705
+ return data;
706
+ }));
707
+ return filterSymbol ? data.filter((item) => item.symbol === filterSymbol) : data;
708
+ }
709
+ async fetchStocksEtfSplitAnnouncement(options) {
710
+ var _a;
711
+ const query = new URLSearchParams({
712
+ response: 'json',
713
+ _: Date.now().toString(),
714
+ });
715
+ const url = `https://www.twse.com.tw/rwd/zh/split/TWTC9U?${query}`;
716
+ const response = await this.httpService.get(url);
717
+ const json = ((_a = response.data) === null || _a === void 0 ? void 0 : _a.stat) === 'OK' && response.data;
718
+ if (!json || !json.data)
719
+ return [];
720
+ const data = json.data.map((row) => {
721
+ const [haltDate, symbol, name, splitType, resumeDate, splitRatio] = row;
722
+ const data = {};
723
+ data.symbol = symbol.trim();
724
+ data.name = name.trim();
725
+ data.exchange = enums_1.Exchange.TWSE;
726
+ data.haltDate = (0, utils_1.rocToWestern)(haltDate);
727
+ data.resumeDate = (0, utils_1.rocToWestern)(resumeDate);
728
+ data.splitType = splitType.trim();
729
+ data.splitRatio = (0, utils_1.parseNumeric)(splitRatio);
730
+ return data;
731
+ });
732
+ return (options === null || options === void 0 ? void 0 : options.symbol) ? data.filter((item) => item.symbol === options.symbol) : data;
733
+ }
734
+ async fetchStocksListingApplicants(options) {
735
+ var _a;
736
+ const { symbol: filterSymbol, year } = options || {};
737
+ const query = {
738
+ response: 'json',
739
+ _: Date.now().toString(),
740
+ };
741
+ if (year) {
742
+ query.date = `${year}0101`;
743
+ }
744
+ const queryParams = new URLSearchParams(query);
745
+ const url = `https://www.twse.com.tw/rwd/zh/company/applylisting?${queryParams}`;
746
+ const response = await this.httpService.get(url);
747
+ const json = ((_a = response.data) === null || _a === void 0 ? void 0 : _a.stat) === 'ok' && response.data;
748
+ if (!json || !json.data)
749
+ return [];
750
+ const data = json.data.map((row) => {
751
+ const [, // index
752
+ symbol, name, applicationDate, chairman, capitalAtApplication, reviewCommitteeDate, boardApprovalDate, contractFilingDate, listedDate, underwriter, underwritingPrice, remarks,] = row;
753
+ const data = {};
754
+ data.symbol = symbol.trim();
755
+ data.name = name.trim();
756
+ data.exchange = enums_1.Exchange.TWSE;
757
+ data.applicationDate = (0, utils_1.rocToWestern)(applicationDate);
758
+ data.chairman = chairman.trim();
759
+ data.capitalAtApplication = (0, utils_1.parseNumeric)(capitalAtApplication);
760
+ data.reviewCommitteeDate = reviewCommitteeDate ? (0, utils_1.rocToWestern)(reviewCommitteeDate) : null;
761
+ data.boardApprovalDate = boardApprovalDate ? (0, utils_1.rocToWestern)(boardApprovalDate) : null;
762
+ data.contractFilingDate = contractFilingDate ? (0, utils_1.rocToWestern)(contractFilingDate) : null;
763
+ data.listedDate = listedDate ? (0, utils_1.rocToWestern)(listedDate) : null;
764
+ data.underwriter = underwriter.trim() || null;
765
+ data.underwritingPrice = (0, utils_1.parseNumeric)((0, utils_1.stripHtmlTags)(underwritingPrice));
766
+ data.remarks = remarks.trim();
767
+ return data;
768
+ });
769
+ return filterSymbol ? data.filter((item) => item.symbol === filterSymbol) : data;
770
+ }
771
+ async fetchIndicesHistorical(options) {
772
+ const { date, symbol } = options;
773
+ const query = new URLSearchParams({
774
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
775
+ response: 'json',
776
+ });
777
+ const url = `https://www.twse.com.tw/rwd/zh/TAIEX/MI_5MINS_INDEX?${query}`;
778
+ const response = await this.httpService.get(url);
779
+ const json = (response.data.stat === 'OK') && response.data;
780
+ if (!json)
781
+ return null;
782
+ const indices = json.fields.slice(1).map((index) => ({
783
+ symbol: (0, utils_1.asIndex)(index),
784
+ name: index,
785
+ }));
786
+ const quotes = json.data.flatMap((row) => {
787
+ const [time, ...values] = row;
788
+ return values.map((value, i) => ({
789
+ date,
790
+ time,
791
+ symbol: indices[i].symbol,
792
+ name: indices[i].name,
793
+ price: numeral(value).value(),
794
+ }));
795
+ });
796
+ const data = _(quotes).groupBy('symbol')
797
+ .map(quotes => {
798
+ const [prev, ...rows] = quotes;
799
+ const { date, symbol, name } = prev;
800
+ const data = {};
801
+ data.date = date,
802
+ data.exchange = enums_1.Exchange.TWSE;
803
+ data.symbol = symbol;
804
+ data.name = name.trim();
805
+ data.open = _.minBy(rows, 'time').price;
806
+ data.high = _.maxBy(rows, 'price').price;
807
+ data.low = _.minBy(rows, 'price').price;
808
+ data.close = _.maxBy(rows, 'time').price;
809
+ data.change = numeral(data.close).subtract(prev.price).value();
810
+ return data;
811
+ }).value();
812
+ return symbol ? data.find(data => data.symbol === symbol) : data;
813
+ }
814
+ async fetchIndicesTrades(options) {
815
+ const { date, symbol } = options;
816
+ const query = new URLSearchParams({
817
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
818
+ response: 'json',
819
+ });
820
+ const url = `https://www.twse.com.tw/rwd/zh/afterTrading/BFIAMU?${query}`;
821
+ const response = await this.httpService.get(url);
822
+ const json = (response.data.stat === 'OK') && response.data;
823
+ if (!json)
824
+ return null;
825
+ const market = await this.fetchMarketTrades({ date });
826
+ if (!market)
827
+ return null;
828
+ const data = json.data.map((row) => {
829
+ const data = {};
830
+ data.date = date,
831
+ data.exchange = enums_1.Exchange.TWSE;
832
+ data.symbol = (0, utils_1.asIndex)(row[0].trim());
833
+ data.name = row[0].trim();
834
+ data.tradeVolume = numeral(row[1]).value();
835
+ data.tradeValue = numeral(row[2]).value();
836
+ data.tradeWeight = +numeral(data.tradeValue).divide(market.tradeValue).multiply(100).format('0.00');
837
+ return data;
838
+ });
839
+ const excludedSymbols = [enums_1.Index.ChemicalBiotechnologyAndMedicalCare, enums_1.Index.Electronics];
840
+ const total = data
841
+ .filter(row => !excludedSymbols.includes(row.symbol))
842
+ .reduce((total, row) => ({
843
+ tradeVolume: total.tradeVolume + row.tradeVolume,
844
+ tradeValue: total.tradeValue + row.tradeValue,
845
+ }), { tradeVolume: 0, tradeValue: 0 });
846
+ const electronics = _.find(data, { symbol: enums_1.Index.Electronics });
847
+ const finance = _.find(data, { symbol: enums_1.Index.FinancialAndInsurance });
848
+ const createIndexEntry = (symbol, name, tradeVolume, tradeValue) => ({
849
+ date,
850
+ exchange: enums_1.Exchange.TWSE,
851
+ symbol,
852
+ name,
853
+ tradeVolume,
854
+ tradeValue,
855
+ tradeWeight: +numeral(tradeValue).divide(market.tradeValue).multiply(100).format('0.00')
856
+ });
857
+ const nonFinance = createIndexEntry(enums_1.Index.NonFinance, '未含金融保險股指數', total.tradeVolume - finance.tradeVolume, total.tradeValue - finance.tradeValue);
858
+ const nonElectronics = createIndexEntry(enums_1.Index.NonElectronics, '未含電子股指數', total.tradeVolume - electronics.tradeVolume, total.tradeValue - electronics.tradeValue);
859
+ const nonFinanceNonElectronics = createIndexEntry(enums_1.Index.NonFinanceNonElectronics, '未含金融電子股指數', total.tradeVolume - (finance.tradeVolume + electronics.tradeVolume), total.tradeValue - (finance.tradeValue + electronics.tradeValue));
860
+ data.push(nonFinance, nonElectronics, nonFinanceNonElectronics);
861
+ return symbol ? data.find(data => data.symbol === symbol) : data;
862
+ }
863
+ async fetchMarketTrades(options) {
864
+ const { date } = options;
865
+ const query = new URLSearchParams({
866
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
867
+ response: 'json',
868
+ });
869
+ const url = `https://www.twse.com.tw/rwd/zh/afterTrading/MI_INDEX?${query}`;
870
+ const response = await this.httpService.get(url, { headers: { 'Connection': 'keep-alive' } });
871
+ const json = (response.data.stat === 'OK') && response.data;
872
+ if (!json)
873
+ return null;
874
+ const [_, ...values] = json.tables[6].data.slice(-1)[0];
875
+ const data = {};
876
+ data.date = date,
877
+ data.exchange = enums_1.Exchange.TWSE;
878
+ data.tradeVolume = numeral(values[1]).value();
879
+ data.tradeValue = numeral(values[0]).value();
880
+ data.transaction = numeral(values[2]).value();
881
+ return data;
882
+ }
883
+ async fetchMarketBreadth(options) {
884
+ const { date } = options;
885
+ const query = new URLSearchParams({
886
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
887
+ response: 'json',
888
+ });
889
+ const url = `https://www.twse.com.tw/rwd/zh/afterTrading/MI_INDEX?${query}`;
890
+ const response = await this.httpService.get(url, { headers: { 'Connection': 'keep-alive' } });
891
+ const json = (response.data.stat === 'OK') && response.data;
892
+ if (!json)
893
+ return null;
894
+ const raw = json.tables[7].data.map((row) => row[2]);
895
+ const [up, limitUp] = raw[0].replace(')', '').split('(');
896
+ const [down, limitDown] = raw[1].replace(')', '').split('(');
897
+ const [unchanged, unmatched, notApplicable] = raw.slice(2);
898
+ const data = {};
899
+ data.date = date;
900
+ data.exchange = enums_1.Exchange.TWSE,
901
+ data.up = numeral(up).value();
902
+ data.limitUp = numeral(limitUp).value();
903
+ data.down = numeral(down).value();
904
+ data.limitDown = numeral(limitDown).value();
905
+ data.unchanged = numeral(unchanged).value();
906
+ data.unmatched = numeral(unmatched).value();
907
+ data.notApplicable = numeral(notApplicable).value();
908
+ return data;
909
+ }
910
+ async fetchMarketInstitutional(options) {
911
+ const { date } = options;
912
+ const query = new URLSearchParams({
913
+ dayDate: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
914
+ type: 'day',
915
+ response: 'json',
916
+ });
917
+ const url = `https://www.twse.com.tw/rwd/zh/fund/BFI82U?${query}`;
918
+ const response = await this.httpService.get(url);
919
+ const json = (response.data.stat === 'OK') && response.data;
920
+ if (!json)
921
+ return null;
922
+ const data = {};
923
+ data.date = date;
924
+ data.exchange = enums_1.Exchange.TWSE,
925
+ data.institutional = json.data.map((row) => ({
926
+ investor: row[0] === '合計' ? '三大法人' : row[0],
927
+ totalBuy: numeral(row[1]).value(),
928
+ totalSell: numeral(row[2]).value(),
929
+ difference: numeral(row[3]).value(),
930
+ }));
931
+ return data;
932
+ }
933
+ async fetchMarketMarginTrades(options) {
934
+ const { date } = options;
935
+ const query = new URLSearchParams({
936
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
937
+ selectType: 'MS',
938
+ response: 'json',
939
+ });
940
+ const url = `https://www.twse.com.tw/rwd/zh/marginTrading/MI_MARGN?${query}`;
941
+ const response = await this.httpService.get(url);
942
+ const json = (response.data.stat === 'OK') && response.data;
943
+ if (!json)
944
+ return null;
945
+ const values = json.tables[0].data.map((row) => row.slice(1)).flat();
946
+ const data = {};
947
+ data.date = date;
948
+ data.exchange = enums_1.Exchange.TWSE,
949
+ data.marginBuy = numeral(values[0]).value();
950
+ data.marginSell = numeral(values[1]).value();
951
+ data.marginRedeem = numeral(values[2]).value();
952
+ data.marginBalancePrev = numeral(values[3]).value();
953
+ data.marginBalance = numeral(values[4]).value();
954
+ data.shortBuy = numeral(values[5]).value();
955
+ data.shortSell = numeral(values[6]).value();
956
+ data.shortRedeem = numeral(values[7]).value();
957
+ data.shortBalancePrev = numeral(values[8]).value();
958
+ data.shortBalance = numeral(values[9]).value();
959
+ data.marginBuyValue = numeral(values[10]).value();
960
+ data.marginSellValue = numeral(values[11]).value();
961
+ data.marginRedeemValue = numeral(values[12]).value();
962
+ data.marginBalancePrevValue = numeral(values[13]).value();
963
+ data.marginBalanceValue = numeral(values[14]).value();
964
+ return data;
965
+ }
966
+ }
967
+ exports.TwseScraper = TwseScraper;