w-dwdata-hub 1.0.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 (135) hide show
  1. package/.editorconfig +9 -0
  2. package/.eslintignore +3 -0
  3. package/.eslintrc.js +55 -0
  4. package/.jsdoc +25 -0
  5. package/LICENSE +21 -0
  6. package/README.md +168 -0
  7. package/SECURITY.md +5 -0
  8. package/babel.config.js +16 -0
  9. package/dist/w-dwdata-hub.umd.js +7 -0
  10. package/dist/w-dwdata-hub.umd.js.map +1 -0
  11. package/docs/WDwdataHub.mjs.html +136 -0
  12. package/docs/decodeEntities.mjs.html +129 -0
  13. package/docs/extractDivContent.mjs.html +147 -0
  14. package/docs/fetchAiNewsAggregator.mjs.html +155 -0
  15. package/docs/fetchAisixiang.mjs.html +1038 -0
  16. package/docs/fetchCnyes.mjs.html +260 -0
  17. package/docs/fetchGuancha.mjs.html +1007 -0
  18. package/docs/fetchHackerNews.mjs.html +189 -0
  19. package/docs/fetchMoneydj.mjs.html +251 -0
  20. package/docs/fetchMops.mjs.html +385 -0
  21. package/docs/fetchRSS.mjs.html +160 -0
  22. package/docs/fetchStatementdog.mjs.html +213 -0
  23. package/docs/fetchTaifex.mjs.html +513 -0
  24. package/docs/fetchTpex3insti.mjs.html +278 -0
  25. package/docs/fetchTpexMargin.mjs.html +248 -0
  26. package/docs/fetchTpexStock.mjs.html +199 -0
  27. package/docs/fetchTwDataHoliday.mjs.html +236 -0
  28. package/docs/fetchTwseMargin.mjs.html +253 -0
  29. package/docs/fetchTwseStock.mjs.html +188 -0
  30. package/docs/fetchTwseT86.mjs.html +208 -0
  31. package/docs/fetchWithRetry.mjs.html +376 -0
  32. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  33. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  34. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  35. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  36. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  37. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  38. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  39. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  40. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  41. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
  42. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  43. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  44. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  45. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  46. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
  47. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  48. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  49. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  50. package/docs/getOptFetch.mjs.html +185 -0
  51. package/docs/global.html +22649 -0
  52. package/docs/htmlToMarkdown.mjs.html +168 -0
  53. package/docs/index.html +84 -0
  54. package/docs/isYmd.mjs.html +114 -0
  55. package/docs/parseIntComma.mjs.html +100 -0
  56. package/docs/safeFilename.mjs.html +102 -0
  57. package/docs/scripts/collapse.js +39 -0
  58. package/docs/scripts/commonNav.js +28 -0
  59. package/docs/scripts/linenumber.js +25 -0
  60. package/docs/scripts/nav.js +12 -0
  61. package/docs/scripts/polyfill.js +4 -0
  62. package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
  63. package/docs/scripts/prettify/lang-css.js +2 -0
  64. package/docs/scripts/prettify/prettify.js +28 -0
  65. package/docs/scripts/search.js +99 -0
  66. package/docs/styles/jsdoc.css +776 -0
  67. package/docs/styles/prettify.css +80 -0
  68. package/docs/toDatetimeUTC8.mjs.html +143 -0
  69. package/docs/toRocDate.mjs.html +107 -0
  70. package/g.mjs +73 -0
  71. package/package.json +31 -0
  72. package/script.txt +17 -0
  73. package/src/WDwdataHub.mjs +64 -0
  74. package/src/decodeEntities.mjs +57 -0
  75. package/src/extractDivContent.mjs +75 -0
  76. package/src/fetchAiNewsAggregator.mjs +83 -0
  77. package/src/fetchAisixiang.mjs +966 -0
  78. package/src/fetchCnyes.mjs +188 -0
  79. package/src/fetchGuancha.mjs +935 -0
  80. package/src/fetchHackerNews.mjs +117 -0
  81. package/src/fetchMoneydj.mjs +179 -0
  82. package/src/fetchMops.mjs +313 -0
  83. package/src/fetchRSS.mjs +88 -0
  84. package/src/fetchStatementdog.mjs +141 -0
  85. package/src/fetchTaifex.mjs +441 -0
  86. package/src/fetchTpex3insti.mjs +206 -0
  87. package/src/fetchTpexMargin.mjs +176 -0
  88. package/src/fetchTpexStock.mjs +127 -0
  89. package/src/fetchTwDataHoliday.mjs +164 -0
  90. package/src/fetchTwseMargin.mjs +181 -0
  91. package/src/fetchTwseStock.mjs +116 -0
  92. package/src/fetchTwseT86.mjs +136 -0
  93. package/src/fetchWithRetry.mjs +304 -0
  94. package/src/getOptFetch.mjs +113 -0
  95. package/src/htmlToMarkdown.mjs +96 -0
  96. package/src/isYmd.mjs +42 -0
  97. package/src/parseIntComma.mjs +28 -0
  98. package/src/safeFilename.mjs +30 -0
  99. package/src/toDatetimeUTC8.mjs +71 -0
  100. package/src/toRocDate.mjs +35 -0
  101. package/test/WDwdataHub.test.mjs +63 -0
  102. package/test/decodeEntities.test.mjs +42 -0
  103. package/test/extractDivContent.test.mjs +48 -0
  104. package/test/fetchAiNewsAggregator.test.mjs +79 -0
  105. package/test/fetchAisixiang.test.mjs +225 -0
  106. package/test/fetchCnyes.test.mjs +61 -0
  107. package/test/fetchGuancha.test.mjs +190 -0
  108. package/test/fetchHackerNews.test.mjs +78 -0
  109. package/test/fetchMoneydj.test.mjs +82 -0
  110. package/test/fetchMops.test.mjs +106 -0
  111. package/test/fetchRSS.test.mjs +85 -0
  112. package/test/fetchStatementdog.test.mjs +69 -0
  113. package/test/fetchTaifex.test.mjs +99 -0
  114. package/test/fetchTpex3insti.test.mjs +90 -0
  115. package/test/fetchTpexMargin.test.mjs +94 -0
  116. package/test/fetchTpexStock.test.mjs +68 -0
  117. package/test/fetchTwDataHoliday.test.mjs +100 -0
  118. package/test/fetchTwseMargin.test.mjs +81 -0
  119. package/test/fetchTwseStock.test.mjs +80 -0
  120. package/test/fetchTwseT86.test.mjs +76 -0
  121. package/test/fetchWithRetry.test.mjs +174 -0
  122. package/test/htmlToMarkdown.test.mjs +71 -0
  123. package/test/isYmd.test.mjs +56 -0
  124. package/test/parseIntComma.test.mjs +38 -0
  125. package/test/safeFilename.test.mjs +35 -0
  126. package/test/toDatetimeUTC8.test.mjs +65 -0
  127. package/test/toRocDate.test.mjs +38 -0
  128. package/test/tools/encodeBig5.mjs +80 -0
  129. package/test/tools/fixtures.mjs +539 -0
  130. package/test/tools/hasChrome.mjs +39 -0
  131. package/test/tools/serverForTest.mjs +397 -0
  132. package/toolg/addVersion.mjs +4 -0
  133. package/toolg/cleanFolder.mjs +5 -0
  134. package/toolg/gDistRollup.mjs +49 -0
  135. package/toolg/modifyReadme.mjs +4 -0
@@ -0,0 +1,253 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>fetchTwseMargin.mjs - Documentation</title>
7
+
8
+
9
+ <script src="scripts/prettify/prettify.js"></script>
10
+ <script src="scripts/prettify/lang-css.js"></script>
11
+ <!--[if lt IE 9]>
12
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
13
+ <![endif]-->
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16
+ <script src="scripts/nav.js" defer></script>
17
+
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ </head>
20
+ <body>
21
+
22
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
23
+ <label for="nav-trigger" class="navicon-button x">
24
+ <div class="navicon"></div>
25
+ </label>
26
+
27
+ <label for="nav-trigger" class="overlay"></label>
28
+
29
+ <nav >
30
+
31
+
32
+ <h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#KNOWN_TOPICS">KNOWN_TOPICS</a></li><li><a href="global.html#WDwdataHub">WDwdataHub</a></li><li><a href="global.html#assertTpexFieldShape">assertTpexFieldShape</a></li><li><a href="global.html#decodeEntities">decodeEntities</a></li><li><a href="global.html#extractAuthorFromContent">extractAuthorFromContent</a></li><li><a href="global.html#extractDivContent">extractDivContent</a></li><li><a href="global.html#extractHtmlTitle">extractHtmlTitle</a></li><li><a href="global.html#extractPubTime">extractPubTime</a></li><li><a href="global.html#extractTitle">extractTitle</a></li><li><a href="global.html#fetchAiNewsAggregator">fetchAiNewsAggregator</a></li><li><a href="global.html#fetchAisixiang">fetchAisixiang</a></li><li><a href="global.html#fetchAllListPages">fetchAllListPages</a></li><li><a href="global.html#fetchAllSearchPages">fetchAllSearchPages</a></li><li><a href="global.html#fetchArticle">fetchArticle</a></li><li><a href="global.html#fetchAuthorArticles">fetchAuthorArticles</a></li><li><a href="global.html#fetchAuthorsList">fetchAuthorsList</a></li><li><a href="global.html#fetchCnyes">fetchCnyes</a></li><li><a href="global.html#fetchFuturesData">fetchFuturesData</a></li><li><a href="global.html#fetchGuancha">fetchGuancha</a></li><li><a href="global.html#fetchHackerNews">fetchHackerNews</a></li><li><a href="global.html#fetchHtml">fetchHtml</a></li><li><a href="global.html#fetchInstitutionalData">fetchInstitutionalData</a></li><li><a href="global.html#fetchKeywordArticles">fetchKeywordArticles</a></li><li><a href="global.html#fetchMoneydj">fetchMoneydj</a></li><li><a href="global.html#fetchMops">fetchMops</a></li><li><a href="global.html#fetchOnce">fetchOnce</a></li><li><a href="global.html#fetchPCRatio">fetchPCRatio</a></li><li><a href="global.html#fetchRSS">fetchRSS</a></li><li><a href="global.html#fetchStatementdog">fetchStatementdog</a></li><li><a href="global.html#fetchTaifex">fetchTaifex</a></li><li><a href="global.html#fetchTargetWithRetry">fetchTargetWithRetry</a></li><li><a href="global.html#fetchTitleArticles">fetchTitleArticles</a></li><li><a href="global.html#fetchTopicArticles">fetchTopicArticles</a></li><li><a href="global.html#fetchTopicsList">fetchTopicsList</a></li><li><a href="global.html#fetchTpex3insti">fetchTpex3insti</a></li><li><a href="global.html#fetchTpexMargin">fetchTpexMargin</a></li><li><a href="global.html#fetchTpexStock">fetchTpexStock</a></li><li><a href="global.html#fetchTwDataHoliday">fetchTwDataHoliday</a></li><li><a href="global.html#fetchTwseMargin">fetchTwseMargin</a></li><li><a href="global.html#fetchTwseStock">fetchTwseStock</a></li><li><a href="global.html#fetchTwseT86">fetchTwseT86</a></li><li><a href="global.html#fetchWithRetry">fetchWithRetry</a></li><li><a href="global.html#getCfg">getCfg</a></li><li><a href="global.html#getOptFetch">getOptFetch</a></li><li><a href="global.html#getPayload">getPayload</a></li><li><a href="global.html#htmlToMarkdown">htmlToMarkdown</a></li><li><a href="global.html#isHomepageHtml">isHomepageHtml</a></li><li><a href="global.html#isNonHolidayEntry">isNonHolidayEntry</a></li><li><a href="global.html#isRetryableError">isRetryableError</a></li><li><a href="global.html#isYmd">isYmd</a></li><li><a href="global.html#lookupAuthor">lookupAuthor</a></li><li><a href="global.html#lookupTopic">lookupTopic</a></li><li><a href="global.html#parseAuthorColumnPage">parseAuthorColumnPage</a></li><li><a href="global.html#parseAuthorIndex">parseAuthorIndex</a></li><li><a href="global.html#parseCSV">parseCSV</a></li><li><a href="global.html#parseCSVLine">parseCSVLine</a></li><li><a href="global.html#parseIntComma">parseIntComma</a></li><li><a href="global.html#parseListPage">parseListPage</a></li><li><a href="global.html#parseNum">parseNum</a></li><li><a href="global.html#parseSearchResults">parseSearchResults</a></li><li><a href="global.html#parseSearchTotalPages">parseSearchTotalPages</a></li><li><a href="global.html#parseZhuantiArticles">parseZhuantiArticles</a></li><li><a href="global.html#pickPint">pickPint</a></li><li><a href="global.html#rocToWestern">rocToWestern</a></li><li><a href="global.html#safeFilename">safeFilename</a></li><li><a href="global.html#toDatetimeUTC8">toDatetimeUTC8</a></li><li><a href="global.html#toRocDate">toRocDate</a></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">fetchTwseMargin.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>import get from 'lodash-es/get.js'
49
+ import isestr from 'wsemi/src/isestr.mjs'
50
+ import isearr from 'wsemi/src/isearr.mjs'
51
+ import fetchWithRetry from './fetchWithRetry.mjs'
52
+ import getOptFetch from './getOptFetch.mjs'
53
+ import isYmd from './isYmd.mjs'
54
+ import parseIntComma from './parseIntComma.mjs'
55
+
56
+
57
+ //證交所網站根網址
58
+ let BASE_URL = 'https://www.twse.com.tw'
59
+
60
+
61
+ //本函數之預設抓取設定
62
+ let DFLT = {
63
+ timeout: 15000,
64
+ maxRetries: 10,
65
+ baseDelayMs: 5000,
66
+ maxDelayMs: 30000,
67
+ label: 'fetch-twse-margin',
68
+ }
69
+
70
+
71
+ //融資融券彙總表之文件化欄數, 下方固定index解析讀至row[15], 故欄數不足即視為API改版
72
+ let MIN_FIELDS = 16
73
+
74
+
75
+ /**
76
+ * 抓取證交所(TWSE)上市融資融券
77
+ *
78
+ * 抓取指定日之上市個股融資融券餘額並轉為結構化資料, 可指定股票代號陣列過濾;
79
+ * 選表先以標題「融資融券彙總」精確比對, 失敗則退而以欄位同時含「融資」與「融券」比對, 提升schema改版容忍度
80
+ *
81
+ * @param {String} dateStr 輸入日期YYYYMMDD字串
82
+ * @param {Array} [stockCodes] 輸入股票代號字串陣列,省略或空陣列表示全市場
83
+ * @param {Object} [opt={}] 輸入設定物件,預設{}
84
+ * @param {String} [opt.baseUrl='https://www.twse.com.tw'] 輸入證交所根網址字串,供測試或改指向鏡像時覆寫
85
+ * @param {Integer} [opt.timeout=15000] 輸入單次請求逾時毫秒整數,預設15000
86
+ * @param {Integer} [opt.maxRetries=10] 輸入最大重試次數整數,含初始共執行maxRetries+1次,預設10
87
+ * @param {Integer} [opt.baseDelayMs=5000] 輸入重試之線性退避基礎毫秒整數,預設5000
88
+ * @param {Integer} [opt.maxDelayMs=30000] 輸入重試之線性退避上限毫秒整數,預設30000
89
+ * @param {Boolean} [opt.showLog=true] 輸入是否顯示過程訊息布林值,預設true
90
+ * @returns {Promise} 回傳Promise,resolve回傳結果物件{source,date,count,data},日期無效、API錯誤或指定個股查無資料時reject回傳錯誤物件
91
+ * @example
92
+ *
93
+ * import fetchTwseMargin from './src/fetchTwseMargin.mjs'
94
+ *
95
+ * let test = async () => {
96
+ *
97
+ * let r = await fetchTwseMargin('20260807', ['2330'])
98
+ * console.log(r.source, r.count, r.data[0].code, r.data[0].marginBalance)
99
+ * // => 'twse_margin' 1 '2330' 12345
100
+ *
101
+ * }
102
+ * await test()
103
+ * .catch((err) => {
104
+ * console.log(err)
105
+ * })
106
+ *
107
+ */
108
+ async function fetchTwseMargin(dateStr, stockCodes, opt = {}) {
109
+
110
+ //check
111
+ if (!isestr(dateStr) || !/^\d{8}$/.test(dateStr)) {
112
+ throw new Error(`日期參數無效:格式須為 YYYYMMDD (收到 "${dateStr}")`)
113
+ }
114
+ if (!isYmd(dateStr)) {
115
+ throw new Error(`日期參數無效:不合法的日期 (${dateStr})`)
116
+ }
117
+
118
+ //baseUrl
119
+ let baseUrl = get(opt, 'baseUrl')
120
+ if (!isestr(baseUrl)) {
121
+ baseUrl = BASE_URL
122
+ }
123
+ baseUrl = baseUrl.replace(/\/+$/, '')
124
+
125
+ //optFetch
126
+ let optFetch = getOptFetch(opt, DFLT)
127
+ let showLog = optFetch.showLog
128
+
129
+ //targetCodes
130
+ let targetCodes = isearr(stockCodes) ? stockCodes.filter(Boolean) : []
131
+
132
+ //url
133
+ let url = `${baseUrl}/rwd/zh/marginTrading/MI_MARGN?date=${dateStr}&amp;selectType=ALL&amp;response=json`
134
+
135
+ if (showLog) {
136
+ console.log(`Fetching TWSE margin data: ${dateStr}`)
137
+ console.log(`Target: ${targetCodes.length > 0 ? targetCodes.join(', ') : 'All Market'}`)
138
+ console.log(`URL: ${url}`)
139
+ }
140
+
141
+ //data
142
+ let data = await fetchWithRetry(url, optFetch)
143
+
144
+ //check
145
+ if (get(data, 'stat') !== 'OK') {
146
+ throw new Error(`TWSE MI_MARGN API returned: ${get(data, 'stat')}`)
147
+ }
148
+
149
+ //detailTable, 先以title精確比對, 失敗則退而以fields雙重比對
150
+ //fields: ["代號","名稱","買進","賣出","現金償還","前日餘額","今日餘額","次一營業日限額",
151
+ // "買進","賣出","現券償還","前日餘額","今日餘額","次一營業日限額","資券互抵","註記"]
152
+ //前8欄為融資, idx 8-13為融券, idx 14=資券互抵, idx 15=註記
153
+ let tables = Array.isArray(data.tables) ? data.tables : []
154
+ let detailTable = tables.find((t) => get(t, 'data.length', 0) > 0 &amp;&amp; get(t, 'title', '').includes('融資融券彙總'))
155
+ if (!detailTable) {
156
+ detailTable = tables.find((t) => {
157
+ return get(t, 'data.length', 0) > 0 &amp;&amp;
158
+ Array.isArray(t.fields) &amp;&amp;
159
+ t.fields.some((f) => String(f).includes('融資')) &amp;&amp;
160
+ t.fields.some((f) => String(f).includes('融券'))
161
+ })
162
+ }
163
+ if (!detailTable || !isearr(get(detailTable, 'data'))) {
164
+ throw new Error('TWSE MI_MARGN: 找不到融資融券彙總資料表')
165
+ }
166
+
167
+ //shape sanity, 固定index解析讀至row[15], 驗證欄數避免fallback抓到結構不同的表後靜默解析錯欄
168
+ if (!Array.isArray(detailTable.fields) || detailTable.fields.length &lt; MIN_FIELDS) {
169
+ throw new Error(`TWSE 融資融券資料欄數不符預期(fields ${get(detailTable, 'fields.length', 'N/A')} 欄,應 >= ${MIN_FIELDS}),可能 API 格式變更`)
170
+ }
171
+
172
+ //rows
173
+ let rows = detailTable.data
174
+ if (targetCodes.length > 0) {
175
+ rows = rows.filter((row) => targetCodes.includes(String(row[0] || '').trim()))
176
+ if (rows.length === 0) {
177
+ throw new Error(`指定個股 ${targetCodes.join(',')} 不在上市融資融券資料中(可能為上櫃股或代碼有誤)`)
178
+ }
179
+ }
180
+
181
+ //parsedData
182
+ let parsedData = rows.map((row) => {
183
+
184
+ let marginBuy = parseIntComma(row[2])
185
+ let marginSell = parseIntComma(row[3])
186
+ let marginBalance = parseIntComma(row[6])
187
+ let marginPrev = parseIntComma(row[5])
188
+ let shortBuy = parseIntComma(row[8]) //融券買進(回補)
189
+ let shortSell = parseIntComma(row[9]) //融券賣出(新增放空)
190
+ let shortBalance = parseIntComma(row[12])
191
+ let shortPrev = parseIntComma(row[11])
192
+
193
+ return {
194
+ code: String(row[0] || '').trim(),
195
+ name: String(row[1] || '').trim(),
196
+ marginBuy,
197
+ marginSell,
198
+ marginCashRepay: parseIntComma(row[4]),
199
+ marginPrevBalance: marginPrev,
200
+ marginBalance,
201
+ marginChange: marginBalance - marginPrev,
202
+ marginLimit: parseIntComma(row[7]),
203
+ shortSell,
204
+ shortBuy,
205
+ shortCashRepay: parseIntComma(row[10]),
206
+ shortPrevBalance: shortPrev,
207
+ shortBalance,
208
+ shortChange: shortBalance - shortPrev,
209
+ shortLimit: parseIntComma(row[13]),
210
+ offset: parseIntComma(row[14]),
211
+ note: String(row[15] || '').trim(),
212
+ }
213
+ })
214
+
215
+ if (showLog) {
216
+ console.log(`Fetched ${parsedData.length} records.`)
217
+ }
218
+
219
+ return {
220
+ source: 'twse_margin',
221
+ date: dateStr,
222
+ count: parsedData.length,
223
+ data: parsedData,
224
+ }
225
+ }
226
+
227
+
228
+ export default fetchTwseMargin
229
+ </code></pre>
230
+ </article>
231
+ </section>
232
+
233
+
234
+
235
+
236
+
237
+
238
+ </div>
239
+
240
+ <br class="clear">
241
+
242
+ <footer>
243
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 07 2026 12:04:51 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
244
+ </footer>
245
+
246
+ <script>prettyPrint();</script>
247
+ <script src="scripts/polyfill.js"></script>
248
+ <script src="scripts/linenumber.js"></script>
249
+
250
+
251
+
252
+ </body>
253
+ </html>
@@ -0,0 +1,188 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>fetchTwseStock.mjs - Documentation</title>
7
+
8
+
9
+ <script src="scripts/prettify/prettify.js"></script>
10
+ <script src="scripts/prettify/lang-css.js"></script>
11
+ <!--[if lt IE 9]>
12
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
13
+ <![endif]-->
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16
+ <script src="scripts/nav.js" defer></script>
17
+
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ </head>
20
+ <body>
21
+
22
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
23
+ <label for="nav-trigger" class="navicon-button x">
24
+ <div class="navicon"></div>
25
+ </label>
26
+
27
+ <label for="nav-trigger" class="overlay"></label>
28
+
29
+ <nav >
30
+
31
+
32
+ <h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#KNOWN_TOPICS">KNOWN_TOPICS</a></li><li><a href="global.html#WDwdataHub">WDwdataHub</a></li><li><a href="global.html#assertTpexFieldShape">assertTpexFieldShape</a></li><li><a href="global.html#decodeEntities">decodeEntities</a></li><li><a href="global.html#extractAuthorFromContent">extractAuthorFromContent</a></li><li><a href="global.html#extractDivContent">extractDivContent</a></li><li><a href="global.html#extractHtmlTitle">extractHtmlTitle</a></li><li><a href="global.html#extractPubTime">extractPubTime</a></li><li><a href="global.html#extractTitle">extractTitle</a></li><li><a href="global.html#fetchAiNewsAggregator">fetchAiNewsAggregator</a></li><li><a href="global.html#fetchAisixiang">fetchAisixiang</a></li><li><a href="global.html#fetchAllListPages">fetchAllListPages</a></li><li><a href="global.html#fetchAllSearchPages">fetchAllSearchPages</a></li><li><a href="global.html#fetchArticle">fetchArticle</a></li><li><a href="global.html#fetchAuthorArticles">fetchAuthorArticles</a></li><li><a href="global.html#fetchAuthorsList">fetchAuthorsList</a></li><li><a href="global.html#fetchCnyes">fetchCnyes</a></li><li><a href="global.html#fetchFuturesData">fetchFuturesData</a></li><li><a href="global.html#fetchGuancha">fetchGuancha</a></li><li><a href="global.html#fetchHackerNews">fetchHackerNews</a></li><li><a href="global.html#fetchHtml">fetchHtml</a></li><li><a href="global.html#fetchInstitutionalData">fetchInstitutionalData</a></li><li><a href="global.html#fetchKeywordArticles">fetchKeywordArticles</a></li><li><a href="global.html#fetchMoneydj">fetchMoneydj</a></li><li><a href="global.html#fetchMops">fetchMops</a></li><li><a href="global.html#fetchOnce">fetchOnce</a></li><li><a href="global.html#fetchPCRatio">fetchPCRatio</a></li><li><a href="global.html#fetchRSS">fetchRSS</a></li><li><a href="global.html#fetchStatementdog">fetchStatementdog</a></li><li><a href="global.html#fetchTaifex">fetchTaifex</a></li><li><a href="global.html#fetchTargetWithRetry">fetchTargetWithRetry</a></li><li><a href="global.html#fetchTitleArticles">fetchTitleArticles</a></li><li><a href="global.html#fetchTopicArticles">fetchTopicArticles</a></li><li><a href="global.html#fetchTopicsList">fetchTopicsList</a></li><li><a href="global.html#fetchTpex3insti">fetchTpex3insti</a></li><li><a href="global.html#fetchTpexMargin">fetchTpexMargin</a></li><li><a href="global.html#fetchTpexStock">fetchTpexStock</a></li><li><a href="global.html#fetchTwDataHoliday">fetchTwDataHoliday</a></li><li><a href="global.html#fetchTwseMargin">fetchTwseMargin</a></li><li><a href="global.html#fetchTwseStock">fetchTwseStock</a></li><li><a href="global.html#fetchTwseT86">fetchTwseT86</a></li><li><a href="global.html#fetchWithRetry">fetchWithRetry</a></li><li><a href="global.html#getCfg">getCfg</a></li><li><a href="global.html#getOptFetch">getOptFetch</a></li><li><a href="global.html#getPayload">getPayload</a></li><li><a href="global.html#htmlToMarkdown">htmlToMarkdown</a></li><li><a href="global.html#isHomepageHtml">isHomepageHtml</a></li><li><a href="global.html#isNonHolidayEntry">isNonHolidayEntry</a></li><li><a href="global.html#isRetryableError">isRetryableError</a></li><li><a href="global.html#isYmd">isYmd</a></li><li><a href="global.html#lookupAuthor">lookupAuthor</a></li><li><a href="global.html#lookupTopic">lookupTopic</a></li><li><a href="global.html#parseAuthorColumnPage">parseAuthorColumnPage</a></li><li><a href="global.html#parseAuthorIndex">parseAuthorIndex</a></li><li><a href="global.html#parseCSV">parseCSV</a></li><li><a href="global.html#parseCSVLine">parseCSVLine</a></li><li><a href="global.html#parseIntComma">parseIntComma</a></li><li><a href="global.html#parseListPage">parseListPage</a></li><li><a href="global.html#parseNum">parseNum</a></li><li><a href="global.html#parseSearchResults">parseSearchResults</a></li><li><a href="global.html#parseSearchTotalPages">parseSearchTotalPages</a></li><li><a href="global.html#parseZhuantiArticles">parseZhuantiArticles</a></li><li><a href="global.html#pickPint">pickPint</a></li><li><a href="global.html#rocToWestern">rocToWestern</a></li><li><a href="global.html#safeFilename">safeFilename</a></li><li><a href="global.html#toDatetimeUTC8">toDatetimeUTC8</a></li><li><a href="global.html#toRocDate">toRocDate</a></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">fetchTwseStock.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>import get from 'lodash-es/get.js'
49
+ import isestr from 'wsemi/src/isestr.mjs'
50
+ import fetchWithRetry from './fetchWithRetry.mjs'
51
+ import getOptFetch from './getOptFetch.mjs'
52
+ import isYmd from './isYmd.mjs'
53
+ import toRocDate from './toRocDate.mjs'
54
+
55
+
56
+ //證交所網站根網址
57
+ let BASE_URL = 'https://www.twse.com.tw'
58
+
59
+
60
+ //本函數之預設抓取設定
61
+ let DFLT = {
62
+ timeout: 10000,
63
+ maxRetries: 10,
64
+ baseDelayMs: 5000,
65
+ maxDelayMs: 30000,
66
+ label: 'fetch-twse-stock',
67
+ }
68
+
69
+
70
+ /**
71
+ * 抓取證交所(TWSE)上市股價
72
+ *
73
+ * 指定stockCode時走STOCK_DAY取個股日成交資訊(該API回傳整月資料, 本函數過濾為指定日單筆);
74
+ * 未指定或指定為'all'時走MI_INDEX取全市場收盤資料
75
+ *
76
+ * @param {String} dateStr 輸入日期YYYYMMDD字串
77
+ * @param {String} [stockCode] 輸入股票代號字串,例如'2330',省略或給'all'表示全市場
78
+ * @param {Object} [opt={}] 輸入設定物件,預設{}
79
+ * @param {String} [opt.baseUrl='https://www.twse.com.tw'] 輸入證交所根網址字串,供測試或改指向鏡像時覆寫
80
+ * @param {Integer} [opt.timeout=10000] 輸入單次請求逾時毫秒整數,預設10000
81
+ * @param {Integer} [opt.maxRetries=10] 輸入最大重試次數整數,含初始共執行maxRetries+1次,預設10
82
+ * @param {Integer} [opt.baseDelayMs=5000] 輸入重試之線性退避基礎毫秒整數,預設5000
83
+ * @param {Integer} [opt.maxDelayMs=30000] 輸入重試之線性退避上限毫秒整數,預設30000
84
+ * @param {Boolean} [opt.showLog=true] 輸入是否顯示過程訊息布林值,預設true
85
+ * @returns {Promise} 回傳Promise,resolve回傳證交所API原始資料物件,內含fields與data等欄位,日期無效、API回傳非OK或指定日無交易資料時reject回傳錯誤物件
86
+ * @example
87
+ *
88
+ * import fetchTwseStock from './src/fetchTwseStock.mjs'
89
+ *
90
+ * let test = async () => {
91
+ *
92
+ * let r = await fetchTwseStock('20260807', '2330')
93
+ * console.log(r.stat, r.data.length)
94
+ * // => 'OK' 1
95
+ *
96
+ * }
97
+ * await test()
98
+ * .catch((err) => {
99
+ * console.log(err)
100
+ * })
101
+ *
102
+ */
103
+ async function fetchTwseStock(dateStr, stockCode, opt = {}) {
104
+
105
+ //check, 函數入口驗日期, 令程式化呼叫不繞過CLI之檢核
106
+ if (!isYmd(dateStr)) {
107
+ throw new Error(`dateStr 須為合法之 YYYYMMDD 字串,得到: ${dateStr}`)
108
+ }
109
+
110
+ //baseUrl
111
+ let baseUrl = get(opt, 'baseUrl')
112
+ if (!isestr(baseUrl)) {
113
+ baseUrl = BASE_URL
114
+ }
115
+ baseUrl = baseUrl.replace(/\/+$/, '')
116
+
117
+ //optFetch
118
+ let optFetch = getOptFetch(opt, DFLT)
119
+ let showLog = optFetch.showLog
120
+
121
+ //isSingleStock
122
+ let isSingleStock = isestr(stockCode) &amp;&amp; stockCode.toLowerCase() !== 'all'
123
+ let stockNo = isSingleStock ? stockCode : 'ALLBUT0999'
124
+
125
+ //url
126
+ let url = ''
127
+ if (isSingleStock) {
128
+ url = `${baseUrl}/exchangeReport/STOCK_DAY?response=json&amp;date=${dateStr}&amp;stockNo=${stockNo}`
129
+ }
130
+ else {
131
+ url = `${baseUrl}/exchangeReport/MI_INDEX?response=json&amp;date=${dateStr}&amp;type=ALLBUT0999`
132
+ }
133
+
134
+ if (showLog) {
135
+ console.log(`Fetching TWSE data: ${dateStr}, Stock: ${stockNo}`)
136
+ console.log(`URL: ${url}`)
137
+ }
138
+
139
+ //data
140
+ let data = await fetchWithRetry(url, optFetch)
141
+
142
+ //check
143
+ if (get(data, 'stat') !== 'OK') {
144
+ throw new Error(`TWSE API returned: ${get(data, 'stat')}`)
145
+ }
146
+
147
+ //STOCK_DAY回傳整月資料, 呼叫者既已指定單一日期則過濾為該日單筆
148
+ //保留原欄位結構(fields/data/title等), 僅替換data為篩選後陣列
149
+ if (isSingleStock &amp;&amp; Array.isArray(data.data)) {
150
+ let rocDate = toRocDate(dateStr)
151
+ let filtered = data.data.filter((row) => row[0] === rocDate)
152
+ data.data = filtered
153
+ if (filtered.length === 0) {
154
+ //整月有資料但指定日無 → 當日停盤、假日或未開市
155
+ throw new Error(`TWSE 個股 ${stockCode} 於 ${dateStr} 無交易資料(可能為假日或停盤)`)
156
+ }
157
+ }
158
+
159
+ return data
160
+ }
161
+
162
+
163
+ export default fetchTwseStock
164
+ </code></pre>
165
+ </article>
166
+ </section>
167
+
168
+
169
+
170
+
171
+
172
+
173
+ </div>
174
+
175
+ <br class="clear">
176
+
177
+ <footer>
178
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 07 2026 12:04:51 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
179
+ </footer>
180
+
181
+ <script>prettyPrint();</script>
182
+ <script src="scripts/polyfill.js"></script>
183
+ <script src="scripts/linenumber.js"></script>
184
+
185
+
186
+
187
+ </body>
188
+ </html>