@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,786 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TpexScraper = void 0;
4
+ const _ = require("lodash");
5
+ const cheerio = require("cheerio");
6
+ const iconv = require("iconv-lite");
7
+ const numeral = require("numeral");
8
+ const luxon_1 = require("luxon");
9
+ const scraper_1 = require("./scraper");
10
+ const enums_1 = require("../enums");
11
+ const utils_1 = require("../utils");
12
+ class TpexScraper extends scraper_1.Scraper {
13
+ async fetchStocksHistorical(options) {
14
+ const { date, symbol } = options;
15
+ const query = new URLSearchParams({
16
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyy/MM/dd'),
17
+ response: 'json',
18
+ });
19
+ const url = `https://www.tpex.org.tw/www/zh-tw/afterTrading/dailyQuotes?${query}`;
20
+ const response = await this.httpService.get(url);
21
+ const json = (response.data.tables[0].totalCount > 0) && response.data;
22
+ if (!json)
23
+ return null;
24
+ const data = json.tables[0].data
25
+ .filter((row) => !(0, utils_1.isWarrant)(row[0]))
26
+ .map((row) => {
27
+ const [symbol, name, ...values] = row;
28
+ const data = {};
29
+ data.date = date,
30
+ data.exchange = enums_1.Exchange.TPEx;
31
+ data.symbol = symbol,
32
+ data.name = name.trim();
33
+ data.open = numeral(values[2]).value();
34
+ data.high = numeral(values[3]).value();
35
+ data.low = numeral(values[4]).value();
36
+ data.close = numeral(values[0]).value();
37
+ data.volume = numeral(values[6]).value();
38
+ data.turnover = numeral(values[7]).value();
39
+ data.transaction = numeral(values[8]).value();
40
+ data.change = numeral(values[1]).value();
41
+ return data;
42
+ });
43
+ return symbol ? data.find(data => data.symbol === symbol) : data;
44
+ }
45
+ async fetchStocksInstitutional(options) {
46
+ const { date, symbol } = options;
47
+ const query = new URLSearchParams({
48
+ type: 'Daily',
49
+ sect: 'EW',
50
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyy/MM/dd'),
51
+ response: 'json',
52
+ });
53
+ const url = `https://www.tpex.org.tw/www/zh-tw/insti/dailyTrade?${query}`;
54
+ const response = await this.httpService.get(url);
55
+ const json = (response.data.tables[0].totalCount > 0 || response.data.tables[1].totalCount > 0) && response.data;
56
+ if (!json)
57
+ return null;
58
+ const data = (json.tables[0].data || json.tables[1].data).map((row) => {
59
+ const [symbol, name, ...values] = row;
60
+ const data = {};
61
+ data.date = date;
62
+ data.exchange = enums_1.Exchange.TPEx;
63
+ data.symbol = symbol;
64
+ data.name = name.trim();
65
+ data.institutional = ((values) => {
66
+ switch (values.length) {
67
+ case 22: return [
68
+ {
69
+ investor: '外資及陸資(不含外資自營商)',
70
+ totalBuy: numeral(values[0]).value(),
71
+ totalSell: numeral(values[1]).value(),
72
+ difference: numeral(values[2]).value(),
73
+ },
74
+ {
75
+ investor: '外資自營商',
76
+ totalBuy: numeral(values[3]).value(),
77
+ totalSell: numeral(values[4]).value(),
78
+ difference: numeral(values[5]).value(),
79
+ },
80
+ {
81
+ investor: '外資及陸資',
82
+ totalBuy: numeral(values[6]).value(),
83
+ totalSell: numeral(values[7]).value(),
84
+ difference: numeral(values[8]).value(),
85
+ },
86
+ {
87
+ investor: '投信',
88
+ totalBuy: numeral(values[9]).value(),
89
+ totalSell: numeral(values[10]).value(),
90
+ difference: numeral(values[11]).value(),
91
+ },
92
+ {
93
+ investor: '自營商(自行買賣)',
94
+ totalBuy: numeral(values[12]).value(),
95
+ totalSell: numeral(values[13]).value(),
96
+ difference: numeral(values[14]).value(),
97
+ },
98
+ {
99
+ investor: '自營商(避險)',
100
+ totalBuy: numeral(values[15]).value(),
101
+ totalSell: numeral(values[16]).value(),
102
+ difference: numeral(values[17]).value(),
103
+ },
104
+ {
105
+ investor: '自營商',
106
+ totalBuy: numeral(values[18]).value(),
107
+ totalSell: numeral(values[19]).value(),
108
+ difference: numeral(values[20]).value(),
109
+ },
110
+ {
111
+ investor: '三大法人',
112
+ difference: numeral(values[21]).value(),
113
+ },
114
+ ];
115
+ case 14: return [
116
+ {
117
+ investor: '外資及陸資',
118
+ totalBuy: numeral(values[0]).value(),
119
+ totalSell: numeral(values[1]).value(),
120
+ difference: numeral(values[2]).value(),
121
+ },
122
+ {
123
+ investor: '投信',
124
+ totalBuy: numeral(values[3]).value(),
125
+ totalSell: numeral(values[4]).value(),
126
+ difference: numeral(values[5]).value(),
127
+ },
128
+ {
129
+ investor: '自營商',
130
+ difference: numeral(values[6]).value(),
131
+ },
132
+ {
133
+ investor: '自營商(自行買賣)',
134
+ totalBuy: numeral(values[7]).value(),
135
+ totalSell: numeral(values[8]).value(),
136
+ difference: numeral(values[9]).value(),
137
+ },
138
+ {
139
+ investor: '自營商(避險)',
140
+ totalBuy: numeral(values[10]).value(),
141
+ totalSell: numeral(values[11]).value(),
142
+ difference: numeral(values[12]).value(),
143
+ },
144
+ {
145
+ investor: '三大法人',
146
+ difference: numeral(values[13]).value(),
147
+ },
148
+ ];
149
+ }
150
+ })(values);
151
+ return data;
152
+ });
153
+ return symbol ? data.find(data => data.symbol === symbol) : data;
154
+ }
155
+ async fetchStocksFiniHoldings(options) {
156
+ const { date, symbol } = options;
157
+ const [year, month, day] = date.split('-');
158
+ const form = new URLSearchParams({
159
+ years: year,
160
+ months: month,
161
+ days: day,
162
+ bcode: '',
163
+ step: '2',
164
+ });
165
+ const url = `https://mops.twse.com.tw/server-java/t13sa150_otc`;
166
+ const response = await this.httpService.post(url, form, { responseType: 'arraybuffer' });
167
+ const page = iconv.decode(response.data, 'big5');
168
+ const $ = cheerio.load(page);
169
+ const message = $('h3').text().trim();
170
+ if (message === '查無所需資料')
171
+ return null;
172
+ const data = $('table:eq(0) tr').slice(2).map((_, el) => {
173
+ const td = $(el).find('td');
174
+ return {
175
+ date,
176
+ exchange: enums_1.Exchange.TPEx,
177
+ symbol: td.eq(0).text().trim(),
178
+ name: td.eq(1).text().trim().split('(')[0],
179
+ issuedShares: numeral(td.eq(2).text()).value(),
180
+ availableShares: numeral(td.eq(3).text()).value(),
181
+ sharesHeld: numeral(td.eq(4).text()).value(),
182
+ availablePercent: numeral(td.eq(5).text()).value(),
183
+ heldPercent: numeral(td.eq(6).text()).value(),
184
+ upperLimitPercent: numeral(td.eq(7).text()).value(),
185
+ };
186
+ }).toArray();
187
+ return symbol ? data.find(data => data.symbol === symbol) : data;
188
+ }
189
+ async fetchStocksMarginTrades(options) {
190
+ const { date, symbol } = options;
191
+ const query = new URLSearchParams({
192
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyy/MM/dd'),
193
+ response: 'json',
194
+ });
195
+ const url = `https://www.tpex.org.tw/www/zh-tw/margin/balance?${query}`;
196
+ const response = await this.httpService.get(url);
197
+ const json = (response.data.tables[0].totalCount > 0) && response.data;
198
+ if (!json)
199
+ return null;
200
+ const data = json.tables[0].data.map((row) => {
201
+ const [symbol, name, ...values] = row;
202
+ const data = {};
203
+ data.date = date;
204
+ data.exchange = enums_1.Exchange.TPEx;
205
+ data.symbol = symbol;
206
+ data.name = name.trim();
207
+ data.marginBuy = numeral(values[1]).value();
208
+ data.marginSell = numeral(values[2]).value();
209
+ data.marginRedeem = numeral(values[3]).value();
210
+ data.marginBalancePrev = numeral(values[0]).value();
211
+ data.marginBalance = numeral(values[4]).value();
212
+ data.marginQuota = numeral(values[7]).value();
213
+ data.shortBuy = numeral(values[10]).value();
214
+ data.shortSell = numeral(values[9]).value();
215
+ data.shortRedeem = numeral(values[11]).value();
216
+ data.shortBalancePrev = numeral(values[8]).value();
217
+ data.shortBalance = numeral(values[12]).value();
218
+ data.shortQuota = numeral(values[15]).value();
219
+ data.offset = numeral(values[16]).value();
220
+ data.note = values[17].trim();
221
+ return data;
222
+ });
223
+ return symbol ? data.find(data => data.symbol === symbol) : data;
224
+ }
225
+ async fetchStocksShortSales(options) {
226
+ const { date, symbol } = options;
227
+ const query = new URLSearchParams({
228
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyy/MM/dd'),
229
+ response: 'json',
230
+ });
231
+ const url = `https://www.tpex.org.tw/www/zh-tw/margin/sbl?${query}`;
232
+ const response = await this.httpService.get(url);
233
+ const json = (response.data.tables[0].totalCount > 0) && response.data;
234
+ if (!json)
235
+ return null;
236
+ const data = json.tables[0].data.map((row) => {
237
+ const [symbol, name, ...values] = row;
238
+ const data = {};
239
+ data.date = date;
240
+ data.exchange = enums_1.Exchange.TPEx;
241
+ data.symbol = symbol;
242
+ data.name = name.trim();
243
+ data.marginShortBalancePrev = numeral(values[0]).value();
244
+ data.marginShortSell = numeral(values[1]).value();
245
+ data.marginShortBuy = numeral(values[2]).value();
246
+ data.marginShortRedeem = numeral(values[3]).value();
247
+ data.marginShortBalance = numeral(values[4]).value();
248
+ data.marginShortQuota = numeral(values[5]).value();
249
+ data.sblShortBalancePrev = numeral(values[6]).value();
250
+ data.sblShortSale = numeral(values[7]).value();
251
+ data.sblShortReturn = numeral(values[8]).value();
252
+ data.sblShortAdjustment = numeral(values[9]).value();
253
+ data.sblShortBalance = numeral(values[10]).value();
254
+ data.sblShortQuota = numeral(values[11]).value();
255
+ data.note = values[12].trim();
256
+ return data;
257
+ });
258
+ return symbol ? data.find(data => data.symbol === symbol) : data;
259
+ }
260
+ async fetchStocksValues(options) {
261
+ const { date, symbol } = options;
262
+ const query = new URLSearchParams({
263
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyy/MM/dd'),
264
+ response: 'json',
265
+ });
266
+ const url = `https://www.tpex.org.tw/www/zh-tw/afterTrading/peQryDate?${query}`;
267
+ const response = await this.httpService.get(url);
268
+ const json = (response.data.tables[0].totalCount > 0) && response.data;
269
+ if (!json)
270
+ return null;
271
+ const data = json.tables[0].data.map((row) => {
272
+ const [symbol, name, ...values] = row;
273
+ const data = {};
274
+ data.date = date;
275
+ data.exchange = enums_1.Exchange.TPEx;
276
+ data.symbol = symbol;
277
+ data.name = name.trim();
278
+ data.peRatio = numeral(values[0]).value();
279
+ data.pbRatio = numeral(values[4]).value();
280
+ data.dividendYield = numeral(values[3]).value();
281
+ data.dividendYear = numeral(values[2]).add(1911).value();
282
+ return data;
283
+ });
284
+ return symbol ? data.find(data => data.symbol === symbol) : data;
285
+ }
286
+ async fetchStocksDividends(options) {
287
+ const { startDate, endDate, symbol } = options;
288
+ // Note: TPEx returns complete data directly, includeDetail parameter is ignored for compatibility
289
+ const form = new URLSearchParams({
290
+ startDate: luxon_1.DateTime.fromISO(startDate).toFormat('yyyy/MM/dd'),
291
+ endDate: luxon_1.DateTime.fromISO(endDate).toFormat('yyyy/MM/dd'),
292
+ response: 'json',
293
+ });
294
+ const url = `https://www.tpex.org.tw/www/zh-tw/bulletin/exDailyQ`;
295
+ const response = await this.httpService.post(url, form);
296
+ const json = response.data;
297
+ const data = json.tables[0].data.map((row) => {
298
+ const [date, symbol, name, ...values] = row;
299
+ const data = {};
300
+ data.date = (0, utils_1.rocToWestern)(date);
301
+ data.exchange = enums_1.Exchange.TPEx;
302
+ data.symbol = symbol;
303
+ data.name = name.trim();
304
+ data.previousClose = (0, utils_1.parseNumeric)(values[0]);
305
+ data.referencePrice = (0, utils_1.parseNumeric)(values[1]);
306
+ data.dividend = (0, utils_1.parseNumeric)(values[4]);
307
+ data.dividendType = values[5].trim().replace('除', '');
308
+ data.limitUpPrice = (0, utils_1.parseNumeric)(values[6]);
309
+ data.limitDownPrice = (0, utils_1.parseNumeric)(values[7]);
310
+ data.openingReferencePrice = (0, utils_1.parseNumeric)(values[8]);
311
+ data.exdividendReferencePrice = (0, utils_1.parseNumeric)(values[9]);
312
+ data.cashDividend = (0, utils_1.parseNumeric)(values[10]);
313
+ data.stockDividendShares = (0, utils_1.parseNumeric)(values[11]);
314
+ data.capitalIncreaseRight = values[2] || null;
315
+ data.paidCapitalIncrease = (0, utils_1.parseNumeric)(values[12]);
316
+ data.subscriptionPrice = (0, utils_1.parseNumeric)(values[13]);
317
+ data.publicOffering = (0, utils_1.parseNumeric)(values[14]);
318
+ data.employeeSubscription = (0, utils_1.parseNumeric)(values[15]);
319
+ data.existingShareholderSubscription = (0, utils_1.parseNumeric)(values[16]);
320
+ data.sharesPerThousand = (0, utils_1.parseNumeric)(values[17]);
321
+ return data;
322
+ });
323
+ return symbol ? data.filter((data) => data.symbol === symbol) : data;
324
+ }
325
+ async fetchStocksCapitalReductions(options) {
326
+ const { startDate, endDate, symbol } = options;
327
+ // Note: TPEx returns complete data directly, includeDetail parameter is ignored for compatibility
328
+ const form = new URLSearchParams({
329
+ startDate: luxon_1.DateTime.fromISO(startDate).toFormat('yyyy/MM/dd'),
330
+ endDate: luxon_1.DateTime.fromISO(endDate).toFormat('yyyy/MM/dd'),
331
+ response: 'json',
332
+ });
333
+ const url = `https://www.tpex.org.tw/www/zh-tw/bulletin/revivt`;
334
+ const response = await this.httpService.post(url, form);
335
+ const json = response.data;
336
+ const data = json.tables[0].data.map((row) => {
337
+ const [date, symbol, name, ...values] = row;
338
+ const data = {};
339
+ data.resumeDate = `${date}`.replace(/(\d{3})(\d{2})(\d{2})/, (_, year, month, day) => `${+year + 1911}-${month}-${day}`);
340
+ data.exchange = enums_1.Exchange.TPEx;
341
+ data.symbol = symbol;
342
+ data.name = name.trim();
343
+ data.previousClose = (0, utils_1.parseNumeric)(values[0]);
344
+ data.referencePrice = (0, utils_1.parseNumeric)(values[1]);
345
+ data.limitUpPrice = (0, utils_1.parseNumeric)(values[2]);
346
+ data.limitDownPrice = (0, utils_1.parseNumeric)(values[3]);
347
+ data.openingReferencePrice = (0, utils_1.parseNumeric)(values[4]);
348
+ data.exrightReferencePrice = (0, utils_1.parseNumeric)(values[5]);
349
+ data.reason = values[6].trim();
350
+ if (values[7]) {
351
+ const $ = cheerio.load(values[7]);
352
+ const haltDate = $(`th:contains('停止買賣日期')`).next('td').text().trim();
353
+ const sharesPerThousand = $(`th:contains('每壹仟股換發新股票')`).next('td').text().trim();
354
+ const refundPerShare = $(`th:contains('每股退還股款')`).next('td').text().trim();
355
+ data.haltDate = (0, utils_1.rocToWestern)(haltDate);
356
+ data.sharesPerThousand = (0, utils_1.parseNumeric)(sharesPerThousand.replace(' 股', ''));
357
+ data.refundPerShare = (0, utils_1.parseNumeric)(refundPerShare.replace(' 元/股', ''));
358
+ }
359
+ return data;
360
+ });
361
+ return symbol ? data.filter((data) => data.symbol === symbol) : data;
362
+ }
363
+ async fetchStocksSplits(options) {
364
+ const { startDate, endDate, symbol } = options;
365
+ const form = new URLSearchParams({
366
+ startDate: luxon_1.DateTime.fromISO(startDate).toFormat('yyyy/MM/dd'),
367
+ endDate: luxon_1.DateTime.fromISO(endDate).toFormat('yyyy/MM/dd'),
368
+ response: 'json',
369
+ });
370
+ const url = `https://www.tpex.org.tw/www/zh-tw/bulletin/pvChgRslt`;
371
+ const response = await this.httpService.post(url, form);
372
+ const json = response.data;
373
+ const data = json.tables[0].data.map((row) => {
374
+ const [date, symbol, name, ...values] = row;
375
+ const data = {};
376
+ data.resumeDate = `${date}`.replace(/(\d{3})(\d{2})(\d{2})/, (_, year, month, day) => `${+year + 1911}-${month}-${day}`);
377
+ data.exchange = enums_1.Exchange.TPEx;
378
+ data.symbol = symbol;
379
+ data.name = name.trim();
380
+ data.previousClose = numeral(values[0]).value();
381
+ data.referencePrice = numeral(values[1]).value();
382
+ data.limitUpPrice = numeral(values[2]).value();
383
+ data.limitDownPrice = numeral(values[3]).value();
384
+ data.openingReferencePrice = numeral(values[4]).value();
385
+ return data;
386
+ });
387
+ return symbol ? data.filter((data) => data.symbol === symbol) : data;
388
+ }
389
+ async fetchStocksEtfSplits(options) {
390
+ const { startDate, endDate, symbol } = options;
391
+ const form = new URLSearchParams({
392
+ startDate: luxon_1.DateTime.fromISO(startDate).toFormat('yyyy/MM/dd'),
393
+ endDate: luxon_1.DateTime.fromISO(endDate).toFormat('yyyy/MM/dd'),
394
+ response: 'json',
395
+ });
396
+ const url = `https://www.tpex.org.tw/www/zh-tw/bulletin/etfSplitRslt`;
397
+ const response = await this.httpService.post(url, form);
398
+ const json = response.data;
399
+ const data = json.tables[0].data.map((row) => {
400
+ const [date, symbol, name, ...values] = row;
401
+ const data = {};
402
+ data.resumeDate = `${date}`.replace(/(\d{3})(\d{2})(\d{2})/, (_, year, month, day) => `${+year + 1911}-${month}-${day}`);
403
+ data.exchange = enums_1.Exchange.TPEx;
404
+ data.symbol = symbol;
405
+ data.name = name.trim();
406
+ data.previousClose = numeral(values[0]).value();
407
+ data.referencePrice = numeral(values[1]).value();
408
+ data.limitUpPrice = numeral(values[2]).value();
409
+ data.limitDownPrice = numeral(values[3]).value();
410
+ data.openingReferencePrice = numeral(values[4]).value();
411
+ return data;
412
+ });
413
+ return symbol ? data.filter((data) => data.symbol === symbol) : data;
414
+ }
415
+ async fetchStocksEtfReverseSplits(options) {
416
+ const { startDate, endDate, symbol } = options;
417
+ const form = new URLSearchParams({
418
+ startDate: luxon_1.DateTime.fromISO(startDate).toFormat('yyyy/MM/dd'),
419
+ endDate: luxon_1.DateTime.fromISO(endDate).toFormat('yyyy/MM/dd'),
420
+ response: 'json',
421
+ });
422
+ const url = `https://www.tpex.org.tw/www/zh-tw/bulletin/etfRvsRslt`;
423
+ const response = await this.httpService.post(url, form);
424
+ const json = response.data;
425
+ const data = json.tables[0].data.map((row) => {
426
+ const [date, symbol, name, ...values] = row;
427
+ const data = {};
428
+ data.resumeDate = `${date}`.replace(/(\d{3})(\d{2})(\d{2})/, (_, year, month, day) => `${+year + 1911}-${month}-${day}`);
429
+ data.exchange = enums_1.Exchange.TPEx;
430
+ data.symbol = symbol;
431
+ data.name = name.trim();
432
+ data.previousClose = numeral(values[0]).value();
433
+ data.referencePrice = numeral(values[1]).value();
434
+ data.limitUpPrice = numeral(values[2]).value();
435
+ data.limitDownPrice = numeral(values[3]).value();
436
+ data.openingReferencePrice = numeral(values[4]).value();
437
+ return data;
438
+ });
439
+ return symbol ? data.filter((data) => data.symbol === symbol) : data;
440
+ }
441
+ async fetchIndicesHistorical(options) {
442
+ const { date, symbol } = options;
443
+ const query = new URLSearchParams({
444
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyy/MM/dd'),
445
+ response: 'json',
446
+ });
447
+ const url = `https://www.tpex.org.tw/www/zh-tw/indexInfo/sectinx?${query}`;
448
+ const response = await this.httpService.get(url);
449
+ const json = (response.data.tables[0].totalCount > 0) && response.data;
450
+ if (!json)
451
+ return null;
452
+ const data = json.tables[0].data.map((row) => {
453
+ const [_, ...values] = row;
454
+ const name = (row[0] !== '櫃買指數') ? `櫃買${row[0].replace('類', '')}類指數` : row[0];
455
+ const data = {};
456
+ data.date = date,
457
+ data.exchange = enums_1.Exchange.TPEx;
458
+ data.symbol = (0, utils_1.asIndex)(name),
459
+ data.name = name;
460
+ data.open = numeral(values[2]).value();
461
+ data.high = numeral(values[3]).value();
462
+ data.low = numeral(values[4]).value();
463
+ data.close = numeral(values[0]).value();
464
+ data.change = numeral(values[1]).value();
465
+ return data;
466
+ });
467
+ return symbol ? data.find(data => data.symbol === symbol) : data;
468
+ }
469
+ async fetchIndicesTrades(options) {
470
+ const { date, symbol } = options;
471
+ const query = new URLSearchParams({
472
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyy/MM/dd'),
473
+ response: 'json',
474
+ });
475
+ const url = `https://www.tpex.org.tw/www/zh-tw/afterTrading/sectRatio?${query}`;
476
+ const response = await this.httpService.get(url);
477
+ const json = (response.data.tables[0].totalCount > 0) && response.data;
478
+ if (!json)
479
+ return null;
480
+ let data = json.tables[0].data.map((values) => {
481
+ const index = `櫃買${values[0]}類指數`;
482
+ const data = {};
483
+ data.date = date,
484
+ data.exchange = enums_1.Exchange.TPEx;
485
+ data.symbol = (0, utils_1.asIndex)(index);
486
+ data.name = index;
487
+ data.tradeVolume = numeral(values[3]).value();
488
+ data.tradeValue = numeral(values[1]).value();
489
+ data.tradeWeight = numeral(values[2]).value();
490
+ return data;
491
+ });
492
+ if (!data.find(row => row.symbol === 'IX0047')) {
493
+ const electronics = [
494
+ 'IX0053', 'IX0054', 'IX0055', 'IX0056',
495
+ 'IX0057', 'IX0058', 'IX0059', 'IX0099',
496
+ ];
497
+ const [electronic] = _(data)
498
+ .filter(data => electronics.includes(data.symbol))
499
+ .groupBy(_ => 'IX0047')
500
+ .map((data, symbol) => ({
501
+ date,
502
+ exchange: enums_1.Exchange.TPEx,
503
+ symbol,
504
+ name: '櫃買電子類指數',
505
+ tradeVolume: _.sumBy(data, 'tradeVolume'),
506
+ tradeValue: _.sumBy(data, 'tradeValue'),
507
+ tradeWeight: +numeral(_.sumBy(data, 'tradeWeight')).format('0.00'),
508
+ }))
509
+ .value();
510
+ data = [...data, electronic];
511
+ }
512
+ data = data.filter(index => index.symbol);
513
+ return symbol ? data.find(data => data.symbol === symbol) : data;
514
+ }
515
+ async fetchMarketTrades(options) {
516
+ const { date } = options;
517
+ const query = new URLSearchParams({
518
+ type: 'Daily',
519
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyy/MM/dd'),
520
+ response: 'json',
521
+ });
522
+ const url = `https://www.tpex.org.tw/www/zh-tw/afterTrading/marketStats?${query}`;
523
+ const response = await this.httpService.get(url);
524
+ const json = (response.data.tables[0].data.length) && response.data;
525
+ if (!json)
526
+ return null;
527
+ const [_, ...values] = json.tables[0].summary[1];
528
+ const data = {};
529
+ data.date = date,
530
+ data.exchange = enums_1.Exchange.TPEx;
531
+ data.tradeVolume = numeral(values[1]).value();
532
+ data.tradeValue = numeral(values[0]).value();
533
+ data.transaction = numeral(values[2]).value();
534
+ return data;
535
+ }
536
+ async fetchMarketBreadth(options) {
537
+ const { date } = options;
538
+ const query = new URLSearchParams({
539
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyy/MM/dd'),
540
+ response: 'json',
541
+ });
542
+ const url = `https://www.tpex.org.tw/www/zh-tw/afterTrading/highlight?${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 data = {};
548
+ data.date = date;
549
+ data.exchange = enums_1.Exchange.TPEx;
550
+ data.up = numeral(json.tables[0].data[0][7]).value();
551
+ data.limitUp = numeral(json.tables[0].data[0][8]).value();
552
+ data.down = numeral(json.tables[0].data[0][9]).value();
553
+ data.limitDown = numeral(json.tables[0].data[0][10]).value();
554
+ data.unchanged = numeral(json.tables[0].data[0][11]).value();
555
+ data.unmatched = numeral(json.tables[0].data[0][12]).value();
556
+ return data;
557
+ }
558
+ async fetchMarketInstitutional(options) {
559
+ const { date } = options;
560
+ const query = new URLSearchParams({
561
+ type: 'Daily',
562
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyy/MM/dd'),
563
+ response: 'json',
564
+ });
565
+ const url = `https://www.tpex.org.tw/www/zh-tw/insti/summary?${query}`;
566
+ const response = await this.httpService.get(url);
567
+ const json = (response.data.tables[0].data.length) && response.data;
568
+ if (!json)
569
+ return null;
570
+ const data = {};
571
+ data.date = date;
572
+ data.exchange = enums_1.Exchange.TPEx,
573
+ data.institutional = json.tables[0].data.map((row) => ({
574
+ investor: row[0].trim(),
575
+ totalBuy: numeral(row[1]).value(),
576
+ totalSell: numeral(row[2]).value(),
577
+ difference: numeral(row[3]).value(),
578
+ }));
579
+ return data;
580
+ }
581
+ async fetchMarketMarginTrades(options) {
582
+ const { date } = options;
583
+ const query = new URLSearchParams({
584
+ date: luxon_1.DateTime.fromISO(date).toFormat('yyyy/MM/dd'),
585
+ response: 'json',
586
+ });
587
+ const url = `https://www.tpex.org.tw/www/zh-tw/margin/balance?${query}`;
588
+ const response = await this.httpService.get(url);
589
+ const json = (response.data.tables[0].totalCount > 0) && response.data;
590
+ if (!json)
591
+ return null;
592
+ const values = [...json.tables[0].summary[0].slice(2, -5), ...json.tables[0].summary[1].slice(2)];
593
+ const data = {};
594
+ data.date = date;
595
+ data.exchange = enums_1.Exchange.TPEx;
596
+ data.marginBuy = numeral(values[1]).value();
597
+ data.marginSell = numeral(values[2]).value();
598
+ data.marginRedeem = numeral(values[3]).value();
599
+ data.marginBalancePrev = numeral(values[0]).value();
600
+ data.marginBalance = numeral(values[4]).value();
601
+ data.shortBuy = numeral(values[10]).value();
602
+ data.shortSell = numeral(values[9]).value();
603
+ data.shortRedeem = numeral(values[11]).value();
604
+ data.shortBalancePrev = numeral(values[8]).value();
605
+ data.shortBalance = numeral(values[12]).value();
606
+ data.marginBuyValue = numeral(values[14]).value();
607
+ data.marginSellValue = numeral(values[15]).value();
608
+ data.marginRedeemValue = numeral(values[16]).value();
609
+ data.marginBalancePrevValue = numeral(values[13]).value();
610
+ data.marginBalanceValue = numeral(values[17]).value();
611
+ return data;
612
+ }
613
+ async fetchStocksDividendsAnnouncement(options) {
614
+ var _a, _b;
615
+ const { symbol: filterSymbol } = options || {};
616
+ const form = new URLSearchParams({
617
+ id: filterSymbol || '',
618
+ response: 'json',
619
+ });
620
+ const url = 'https://www.tpex.org.tw/www/zh-tw/bulletin/prePost';
621
+ const response = await this.httpService.post(url, form);
622
+ const json = (((_b = (_a = response.data.tables) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.totalCount) > 0) && response.data;
623
+ if (!json)
624
+ return [];
625
+ const data = json.tables[0].data
626
+ .filter((row) => !row[1].endsWith('B')) // Filter out bonds (symbols ending with 'B')
627
+ .map((row) => {
628
+ const [exdividendDate, symbol, name, dividendType, stockDividendRatio, cashCapitalIncreaseRatio, subscriptionPrice, cashDividend, publicOffering, employeeSubscription, existingShareholderSubscription, sharesPerThousand,] = row;
629
+ return {
630
+ symbol,
631
+ name: name.trim(),
632
+ exchange: enums_1.Exchange.TPEx,
633
+ exdividendDate: (0, utils_1.rocToWestern)(exdividendDate),
634
+ dividendType: dividendType.trim().replace('除', ''),
635
+ stockDividendRatio: (0, utils_1.parseNumeric)(stockDividendRatio),
636
+ cashCapitalIncreaseRatio: (0, utils_1.parseNumeric)(cashCapitalIncreaseRatio),
637
+ subscriptionPrice: (0, utils_1.parseNumeric)(subscriptionPrice),
638
+ cashDividend: (0, utils_1.parseNumeric)(cashDividend),
639
+ publicOffering: (0, utils_1.parseNumeric)(publicOffering),
640
+ employeeSubscription: (0, utils_1.parseNumeric)(employeeSubscription),
641
+ existingShareholderSubscription: (0, utils_1.parseNumeric)(existingShareholderSubscription),
642
+ sharesPerThousand: (0, utils_1.parseNumeric)(sharesPerThousand),
643
+ };
644
+ });
645
+ return filterSymbol
646
+ ? data.filter(d => d.symbol === filterSymbol)
647
+ : data;
648
+ }
649
+ async fetchStocksCapitalReductionAnnouncement(options) {
650
+ var _a, _b;
651
+ const { symbol: filterSymbol } = options || {};
652
+ const form = new URLSearchParams({
653
+ id: filterSymbol || '',
654
+ response: 'json',
655
+ });
656
+ const url = 'https://www.tpex.org.tw/www/zh-tw/bulletin/decap';
657
+ const response = await this.httpService.post(url, form);
658
+ const json = (((_b = (_a = response.data.tables) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.totalCount) > 0) && response.data;
659
+ if (!json)
660
+ return [];
661
+ const data = json.tables[0].data.map((row) => {
662
+ const [symbol, name, haltDate, reductionRatio, refundPerShare, reason, resumeDate, cashIncreaseRatioAfterReduction, subscriptionPrice,] = row;
663
+ return {
664
+ symbol,
665
+ name: name.trim(),
666
+ exchange: enums_1.Exchange.TPEx,
667
+ haltDate: (0, utils_1.rocToWestern)(haltDate),
668
+ resumeDate: (0, utils_1.rocToWestern)(resumeDate),
669
+ reductionRatio: (0, utils_1.parseNumeric)(reductionRatio),
670
+ reason: reason.trim(),
671
+ refundPerShare: (0, utils_1.parseNumeric)(refundPerShare),
672
+ cashIncreaseRatioAfterReduction: cashIncreaseRatioAfterReduction === '不適用' ? null : (0, utils_1.parseNumeric)(cashIncreaseRatioAfterReduction),
673
+ subscriptionPrice: subscriptionPrice === '不適用' ? null : (0, utils_1.parseNumeric)(subscriptionPrice),
674
+ };
675
+ });
676
+ return filterSymbol
677
+ ? data.filter(d => d.symbol === filterSymbol)
678
+ : data;
679
+ }
680
+ async fetchStocksSplitAnnouncement(options) {
681
+ var _a, _b;
682
+ const { symbol: filterSymbol } = options || {};
683
+ const form = new URLSearchParams({
684
+ id: filterSymbol || '',
685
+ response: 'json',
686
+ });
687
+ const url = 'https://www.tpex.org.tw/www/zh-tw/bulletin/pvChgAnn';
688
+ const response = await this.httpService.post(url, form);
689
+ const json = (((_b = (_a = response.data.tables) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.totalCount) > 0) && response.data;
690
+ if (!json)
691
+ return [];
692
+ const data = json.tables[0].data.map((row) => {
693
+ const [symbol, name, haltDate, splitRatio, oldFaceValue, newFaceValue, resumeDate,] = row;
694
+ return {
695
+ symbol,
696
+ name: name.trim(),
697
+ exchange: enums_1.Exchange.TPEx,
698
+ haltDate: (0, utils_1.rocToWestern)(haltDate),
699
+ resumeDate: (0, utils_1.rocToWestern)(resumeDate),
700
+ splitRatio: (0, utils_1.parseNumeric)(splitRatio),
701
+ oldFaceValue: (0, utils_1.parseNumeric)(oldFaceValue),
702
+ newFaceValue: (0, utils_1.parseNumeric)(newFaceValue),
703
+ };
704
+ });
705
+ return filterSymbol
706
+ ? data.filter(d => d.symbol === filterSymbol)
707
+ : data;
708
+ }
709
+ async fetchStocksEtfSplitAnnouncement(options) {
710
+ return this.fetchEtfSplitAnnouncementBase('/bulletin/etfSplit', '分割', options === null || options === void 0 ? void 0 : options.symbol);
711
+ }
712
+ async fetchStocksEtfReverseSplitAnnouncement(options) {
713
+ return this.fetchEtfSplitAnnouncementBase('/bulletin/etfRvs', '反分割', options === null || options === void 0 ? void 0 : options.symbol);
714
+ }
715
+ async fetchEtfSplitAnnouncementBase(endpoint, splitType, filterSymbol) {
716
+ var _a, _b;
717
+ const form = new URLSearchParams({
718
+ id: filterSymbol || '',
719
+ response: 'json',
720
+ });
721
+ const url = `https://www.tpex.org.tw/www/zh-tw${endpoint}`;
722
+ const response = await this.httpService.post(url, form);
723
+ const json = (((_b = (_a = response.data.tables) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.totalCount) > 0) && response.data;
724
+ if (!json)
725
+ return [];
726
+ const data = json.tables[0].data.map((row) => {
727
+ const [symbol, name, haltDate, splitRatio, previousNav, newNav, resumeDate,] = row;
728
+ return {
729
+ symbol,
730
+ name: name.trim(),
731
+ exchange: enums_1.Exchange.TPEx,
732
+ haltDate: (0, utils_1.rocToWestern)(haltDate),
733
+ resumeDate: (0, utils_1.rocToWestern)(resumeDate),
734
+ splitType,
735
+ splitRatio: (0, utils_1.parseNumeric)(splitRatio),
736
+ previousNav: (0, utils_1.parseNumeric)(previousNav),
737
+ newNav: (0, utils_1.parseNumeric)(newNav),
738
+ };
739
+ });
740
+ return filterSymbol
741
+ ? data.filter(d => d.symbol === filterSymbol)
742
+ : data;
743
+ }
744
+ async fetchStocksListingApplicants(options) {
745
+ var _a, _b;
746
+ const { symbol: filterSymbol, year } = options || {};
747
+ const form = new URLSearchParams({
748
+ code: filterSymbol || '',
749
+ date: year ? (year === 'ALL' ? 'ALL' : year.toString()) : 'ALL',
750
+ id: '',
751
+ response: 'json',
752
+ });
753
+ const url = 'https://www.tpex.org.tw/www/zh-tw/company/applicant';
754
+ const response = await this.httpService.post(url, form, {
755
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
756
+ });
757
+ const json = ((_b = (_a = response.data.tables) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.totalCount) > 0 && response.data;
758
+ if (!json)
759
+ return [];
760
+ const data = json.tables[0].data.map((row) => {
761
+ const [, // index
762
+ symbol, name, applicationDate, chairman, capitalAtApplication, reviewCommitteeDate, boardApprovalDate, contractApprovalDate, // TPEx specific: 櫃買同意上櫃契約日期
763
+ listedDate, underwriter, underwritingPrice, remarks,
764
+ // Ignore the last 2 fields (URL fields)
765
+ ] = row;
766
+ const data = {};
767
+ data.symbol = symbol.trim();
768
+ data.name = name.trim();
769
+ data.exchange = enums_1.Exchange.TPEx;
770
+ data.applicationDate = (0, utils_1.rocToWestern)(applicationDate);
771
+ data.chairman = chairman.trim();
772
+ data.capitalAtApplication = (0, utils_1.parseNumeric)(capitalAtApplication);
773
+ data.reviewCommitteeDate = reviewCommitteeDate ? (0, utils_1.rocToWestern)(reviewCommitteeDate) : null;
774
+ data.boardApprovalDate = boardApprovalDate ? (0, utils_1.rocToWestern)(boardApprovalDate) : null;
775
+ data.contractApprovalDate = contractApprovalDate ? (0, utils_1.rocToWestern)(contractApprovalDate) : null;
776
+ data.listedDate = listedDate ? (0, utils_1.rocToWestern)(listedDate) : null;
777
+ data.underwriter = underwriter.trim() || null;
778
+ data.underwritingPrice = (0, utils_1.parseNumeric)(underwritingPrice);
779
+ data.remarks = remarks.trim();
780
+ return data;
781
+ });
782
+ // Apply client-side filtering if needed (for additional filtering beyond server-side)
783
+ return filterSymbol ? data.filter((item) => item.symbol === filterSymbol) : data;
784
+ }
785
+ }
786
+ exports.TpexScraper = TpexScraper;