@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.
- package/LICENSE +21 -0
- package/README.md +1837 -0
- package/lib/enums/exchange.enum.d.ts +5 -0
- package/lib/enums/exchange.enum.js +9 -0
- package/lib/enums/futopt.enum.d.ts +34 -0
- package/lib/enums/futopt.enum.js +38 -0
- package/lib/enums/index.d.ts +6 -0
- package/lib/enums/index.enum.d.ts +67 -0
- package/lib/enums/index.enum.js +71 -0
- package/lib/enums/index.js +22 -0
- package/lib/enums/industry.enum.d.ts +40 -0
- package/lib/enums/industry.enum.js +44 -0
- package/lib/enums/market.enum.d.ts +5 -0
- package/lib/enums/market.enum.js +9 -0
- package/lib/enums/scraper.enum.d.ts +10 -0
- package/lib/enums/scraper.enum.js +14 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +18 -0
- package/lib/interfaces/futopt-exchange-rates.interface.d.ts +13 -0
- package/lib/interfaces/futopt-exchange-rates.interface.js +2 -0
- package/lib/interfaces/futopt-historical.interface.d.ts +23 -0
- package/lib/interfaces/futopt-historical.interface.js +2 -0
- package/lib/interfaces/futopt-institutional.interface.d.ts +21 -0
- package/lib/interfaces/futopt-institutional.interface.js +2 -0
- package/lib/interfaces/futopt-large-traders.interface.d.ts +16 -0
- package/lib/interfaces/futopt-large-traders.interface.js +2 -0
- package/lib/interfaces/futopt-mxf-retail-position.interface.d.ts +7 -0
- package/lib/interfaces/futopt-mxf-retail-position.interface.js +2 -0
- package/lib/interfaces/futopt-quote.interface.d.ts +31 -0
- package/lib/interfaces/futopt-quote.interface.js +2 -0
- package/lib/interfaces/futopt-tmf-retail-position.interface.d.ts +7 -0
- package/lib/interfaces/futopt-tmf-retail-position.interface.js +2 -0
- package/lib/interfaces/futopt-txo-put-call-ratio.interface.d.ts +9 -0
- package/lib/interfaces/futopt-txo-put-call-ratio.interface.js +2 -0
- package/lib/interfaces/futopt.interface.d.ts +7 -0
- package/lib/interfaces/futopt.interface.js +2 -0
- package/lib/interfaces/index-historical.interface.d.ts +11 -0
- package/lib/interfaces/index-historical.interface.js +2 -0
- package/lib/interfaces/index-quote.interface.d.ts +13 -0
- package/lib/interfaces/index-quote.interface.js +2 -0
- package/lib/interfaces/index-trades.interface.d.ts +9 -0
- package/lib/interfaces/index-trades.interface.js +2 -0
- package/lib/interfaces/index.d.ts +37 -0
- package/lib/interfaces/index.js +53 -0
- package/lib/interfaces/market-breadth.interface.d.ts +11 -0
- package/lib/interfaces/market-breadth.interface.js +2 -0
- package/lib/interfaces/market-institutional.interface.d.ts +10 -0
- package/lib/interfaces/market-institutional.interface.js +2 -0
- package/lib/interfaces/market-margin-trades.interface.d.ts +19 -0
- package/lib/interfaces/market-margin-trades.interface.js +2 -0
- package/lib/interfaces/market-trades.interface.d.ts +7 -0
- package/lib/interfaces/market-trades.interface.js +2 -0
- package/lib/interfaces/rate-limit-options.interface.d.ts +4 -0
- package/lib/interfaces/rate-limit-options.interface.js +2 -0
- package/lib/interfaces/stock-capital-reductions.interface.d.ts +23 -0
- package/lib/interfaces/stock-capital-reductions.interface.js +2 -0
- package/lib/interfaces/stock-dividends.interface.d.ts +27 -0
- package/lib/interfaces/stock-dividends.interface.js +2 -0
- package/lib/interfaces/stock-eps.interface.d.ts +8 -0
- package/lib/interfaces/stock-eps.interface.js +2 -0
- package/lib/interfaces/stock-fini-holdings.interface.d.ts +12 -0
- package/lib/interfaces/stock-fini-holdings.interface.js +2 -0
- package/lib/interfaces/stock-futopt.interface.d.ts +8 -0
- package/lib/interfaces/stock-futopt.interface.js +2 -0
- package/lib/interfaces/stock-historical.interface.d.ts +14 -0
- package/lib/interfaces/stock-historical.interface.js +2 -0
- package/lib/interfaces/stock-institutional.interface.d.ts +12 -0
- package/lib/interfaces/stock-institutional.interface.js +2 -0
- package/lib/interfaces/stock-margin-trades.interface.d.ts +20 -0
- package/lib/interfaces/stock-margin-trades.interface.js +2 -0
- package/lib/interfaces/stock-quote.interface.d.ts +19 -0
- package/lib/interfaces/stock-quote.interface.js +2 -0
- package/lib/interfaces/stock-revenue.interface.d.ts +8 -0
- package/lib/interfaces/stock-revenue.interface.js +2 -0
- package/lib/interfaces/stock-shareholders.interface.d.ts +11 -0
- package/lib/interfaces/stock-shareholders.interface.js +2 -0
- package/lib/interfaces/stock-short-sales.interface.d.ts +19 -0
- package/lib/interfaces/stock-short-sales.interface.js +2 -0
- package/lib/interfaces/stock-splits.interface.d.ts +12 -0
- package/lib/interfaces/stock-splits.interface.js +2 -0
- package/lib/interfaces/stock-values.interface.d.ts +10 -0
- package/lib/interfaces/stock-values.interface.js +2 -0
- package/lib/interfaces/stock.interface.d.ts +8 -0
- package/lib/interfaces/stock.interface.js +2 -0
- package/lib/interfaces/stocks-capital-reduction-announcement.interface.d.ts +19 -0
- package/lib/interfaces/stocks-capital-reduction-announcement.interface.js +2 -0
- package/lib/interfaces/stocks-dividends-announcement.interface.d.ts +21 -0
- package/lib/interfaces/stocks-dividends-announcement.interface.js +2 -0
- package/lib/interfaces/stocks-etf-split-announcement.interface.d.ts +11 -0
- package/lib/interfaces/stocks-etf-split-announcement.interface.js +2 -0
- package/lib/interfaces/stocks-listing-application.interface.d.ts +16 -0
- package/lib/interfaces/stocks-listing-application.interface.js +2 -0
- package/lib/interfaces/stocks-split-announcement.interface.d.ts +11 -0
- package/lib/interfaces/stocks-split-announcement.interface.js +2 -0
- package/lib/interfaces/ticker.interface.d.ts +10 -0
- package/lib/interfaces/ticker.interface.js +2 -0
- package/lib/scrapers/index.d.ts +10 -0
- package/lib/scrapers/index.js +26 -0
- package/lib/scrapers/isin-scraper.d.ts +13 -0
- package/lib/scrapers/isin-scraper.js +76 -0
- package/lib/scrapers/mis-taifex-scraper.d.ts +17 -0
- package/lib/scrapers/mis-taifex-scraper.js +126 -0
- package/lib/scrapers/mis-twse-scraper.d.ts +15 -0
- package/lib/scrapers/mis-twse-scraper.js +97 -0
- package/lib/scrapers/mops-scraper.d.ts +17 -0
- package/lib/scrapers/mops-scraper.js +64 -0
- package/lib/scrapers/scraper-factory.d.ts +25 -0
- package/lib/scrapers/scraper-factory.js +62 -0
- package/lib/scrapers/scraper.d.ts +6 -0
- package/lib/scrapers/scraper.js +20 -0
- package/lib/scrapers/taifex-scraper.d.ts +44 -0
- package/lib/scrapers/taifex-scraper.js +406 -0
- package/lib/scrapers/tdcc-scraper.d.ts +11 -0
- package/lib/scrapers/tdcc-scraper.js +82 -0
- package/lib/scrapers/tpex-scraper.d.ts +95 -0
- package/lib/scrapers/tpex-scraper.js +786 -0
- package/lib/scrapers/twse-scraper.d.ts +110 -0
- package/lib/scrapers/twse-scraper.js +967 -0
- package/lib/twstock.d.ts +229 -0
- package/lib/twstock.js +507 -0
- package/lib/utils/as-exchange.util.d.ts +2 -0
- package/lib/utils/as-exchange.util.js +15 -0
- package/lib/utils/as-index.util.d.ts +1 -0
- package/lib/utils/as-index.util.js +178 -0
- package/lib/utils/as-industry.util.d.ts +2 -0
- package/lib/utils/as-industry.util.js +49 -0
- package/lib/utils/date-converter.util.d.ts +47 -0
- package/lib/utils/date-converter.util.js +113 -0
- package/lib/utils/html-cleaner.util.d.ts +29 -0
- package/lib/utils/html-cleaner.util.js +45 -0
- package/lib/utils/index.d.ts +7 -0
- package/lib/utils/index.js +23 -0
- package/lib/utils/is-warrant.util.d.ts +1 -0
- package/lib/utils/is-warrant.util.js +25 -0
- package/lib/utils/parse-numeric.util.d.ts +21 -0
- package/lib/utils/parse-numeric.util.js +58 -0
- package/package.json +70 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Scraper } from './scraper';
|
|
2
|
+
import { FutOpt, Stock } from '../interfaces';
|
|
3
|
+
export declare class IsinScraper extends Scraper {
|
|
4
|
+
fetchListed(options: {
|
|
5
|
+
symbol: string;
|
|
6
|
+
}): Promise<Stock[]>;
|
|
7
|
+
fetchListedStocks(options: {
|
|
8
|
+
exchange: 'TWSE' | 'TPEx';
|
|
9
|
+
}): Promise<Stock[]>;
|
|
10
|
+
fetchListedFutOpt(options?: {
|
|
11
|
+
type?: 'F' | 'O';
|
|
12
|
+
}): Promise<FutOpt[]>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IsinScraper = void 0;
|
|
4
|
+
const cheerio = require("cheerio");
|
|
5
|
+
const iconv = require("iconv-lite");
|
|
6
|
+
const luxon_1 = require("luxon");
|
|
7
|
+
const scraper_1 = require("./scraper");
|
|
8
|
+
const utils_1 = require("../utils");
|
|
9
|
+
class IsinScraper extends scraper_1.Scraper {
|
|
10
|
+
async fetchListed(options) {
|
|
11
|
+
const { symbol } = options;
|
|
12
|
+
const url = `https://isin.twse.com.tw/isin/single_main.jsp?owncode=${symbol}`;
|
|
13
|
+
const response = await this.httpService.get(url, { responseType: 'arraybuffer' });
|
|
14
|
+
const page = iconv.decode(response.data, 'big5');
|
|
15
|
+
const $ = cheerio.load(page);
|
|
16
|
+
const data = $('.h4 tr').slice(1).map((_, el) => {
|
|
17
|
+
const td = $(el).find('td');
|
|
18
|
+
return {
|
|
19
|
+
symbol: td.eq(2).text().trim(),
|
|
20
|
+
name: td.eq(3).text().trim(),
|
|
21
|
+
exchange: (0, utils_1.asExchange)(td.eq(4).text().trim()),
|
|
22
|
+
type: td.eq(5).text().trim(),
|
|
23
|
+
industry: (0, utils_1.asIndustry)(td.eq(6).text().trim()),
|
|
24
|
+
listedDate: luxon_1.DateTime.fromFormat(td.eq(7).text().trim(), 'yyyy/MM/dd').toISODate(),
|
|
25
|
+
};
|
|
26
|
+
}).toArray();
|
|
27
|
+
return data;
|
|
28
|
+
}
|
|
29
|
+
async fetchListedStocks(options) {
|
|
30
|
+
const { exchange } = options;
|
|
31
|
+
const url = {
|
|
32
|
+
'TWSE': 'https://isin.twse.com.tw/isin/class_main.jsp?market=1',
|
|
33
|
+
'TPEx': 'https://isin.twse.com.tw/isin/class_main.jsp?market=2',
|
|
34
|
+
};
|
|
35
|
+
const response = await this.httpService.get(url[exchange], { responseType: 'arraybuffer' });
|
|
36
|
+
const page = iconv.decode(response.data, 'big5');
|
|
37
|
+
const $ = cheerio.load(page);
|
|
38
|
+
const data = $('.h4 tr').slice(1).map((_, el) => {
|
|
39
|
+
const td = $(el).find('td');
|
|
40
|
+
return {
|
|
41
|
+
symbol: td.eq(2).text().trim(),
|
|
42
|
+
name: td.eq(3).text().trim(),
|
|
43
|
+
exchange: (0, utils_1.asExchange)(td.eq(4).text().trim()),
|
|
44
|
+
type: td.eq(5).text().trim(),
|
|
45
|
+
industry: (0, utils_1.asIndustry)(td.eq(6).text().trim()),
|
|
46
|
+
listedDate: luxon_1.DateTime.fromFormat(td.eq(7).text().trim(), 'yyyy/MM/dd').toISODate(),
|
|
47
|
+
};
|
|
48
|
+
}).toArray();
|
|
49
|
+
return data;
|
|
50
|
+
}
|
|
51
|
+
async fetchListedFutOpt(options) {
|
|
52
|
+
const { type } = options !== null && options !== void 0 ? options : {};
|
|
53
|
+
const url = 'https://isin.twse.com.tw/isin/class_main.jsp?market=7';
|
|
54
|
+
const response = await this.httpService.get(url, { responseType: 'arraybuffer' });
|
|
55
|
+
const page = iconv.decode(response.data, 'big5');
|
|
56
|
+
const $ = cheerio.load(page);
|
|
57
|
+
const data = $('.h4 tr').slice(1).map((_, el) => {
|
|
58
|
+
const td = $(el).find('td');
|
|
59
|
+
return {
|
|
60
|
+
symbol: td.eq(2).text().trim(),
|
|
61
|
+
name: td.eq(3).text().trim(),
|
|
62
|
+
exchange: (0, utils_1.asExchange)(td.eq(4).text().trim()),
|
|
63
|
+
type: td.eq(5).text().trim(),
|
|
64
|
+
listedDate: luxon_1.DateTime.fromFormat(td.eq(7).text().trim(), 'yyyy/MM/dd').toISODate(),
|
|
65
|
+
};
|
|
66
|
+
}).toArray();
|
|
67
|
+
return data.filter(row => {
|
|
68
|
+
if (type === 'F')
|
|
69
|
+
return row.type.includes('期貨');
|
|
70
|
+
if (type === 'O')
|
|
71
|
+
return row.type.includes('選擇權');
|
|
72
|
+
return true;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.IsinScraper = IsinScraper;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Scraper } from './scraper';
|
|
2
|
+
import { FutOptQuote, Ticker } from '../interfaces';
|
|
3
|
+
export declare class MisTaifexScraper extends Scraper {
|
|
4
|
+
fetchListedFutOpt(options?: {
|
|
5
|
+
type?: 'F' | 'O';
|
|
6
|
+
}): Promise<any>;
|
|
7
|
+
fetchFutOptQuoteList(options: {
|
|
8
|
+
ticker: Ticker;
|
|
9
|
+
afterhours?: boolean;
|
|
10
|
+
}): Promise<FutOptQuote[] | null>;
|
|
11
|
+
fetchFutOptQuoteDetail(options: {
|
|
12
|
+
ticker: Ticker;
|
|
13
|
+
afterhours?: boolean;
|
|
14
|
+
}): Promise<FutOptQuote | null | undefined>;
|
|
15
|
+
private extractSymbolIdFromTicker;
|
|
16
|
+
private extractTypeFromCmdyDDLItem;
|
|
17
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MisTaifexScraper = void 0;
|
|
4
|
+
const numeral = require("numeral");
|
|
5
|
+
const luxon_1 = require("luxon");
|
|
6
|
+
const scraper_1 = require("./scraper");
|
|
7
|
+
const enums_1 = require("../enums");
|
|
8
|
+
class MisTaifexScraper extends scraper_1.Scraper {
|
|
9
|
+
async fetchListedFutOpt(options) {
|
|
10
|
+
const { type } = options !== null && options !== void 0 ? options : {};
|
|
11
|
+
const url = 'https://mis.taifex.com.tw/futures/api/getCmdyDDLItemByKind';
|
|
12
|
+
const body = JSON.stringify({
|
|
13
|
+
SymbolType: type,
|
|
14
|
+
});
|
|
15
|
+
const response = await this.httpService.post(url, body, {
|
|
16
|
+
headers: { 'Content-Type': 'application/json' },
|
|
17
|
+
});
|
|
18
|
+
const json = response.data;
|
|
19
|
+
const data = json.RtData.Items.map((row) => (Object.assign({ symbol: row.CID, name: row.DispCName, exchange: enums_1.Exchange.TAIFEX, type: type !== null && type !== void 0 ? type : this.extractTypeFromCmdyDDLItem(row) }, (row.SpotID && { underlying: row.SpotID }))));
|
|
20
|
+
return data;
|
|
21
|
+
}
|
|
22
|
+
async fetchFutOptQuoteList(options) {
|
|
23
|
+
const { ticker, afterhours } = options;
|
|
24
|
+
const body = JSON.stringify({
|
|
25
|
+
CID: ticker.symbol,
|
|
26
|
+
SymbolType: ticker.type,
|
|
27
|
+
MarketType: afterhours ? 1 : 0,
|
|
28
|
+
});
|
|
29
|
+
const url = `https://mis.taifex.com.tw/futures/api/getQuoteList`;
|
|
30
|
+
const response = await this.httpService.post(url, body, {
|
|
31
|
+
headers: { 'Content-Type': 'application/json' },
|
|
32
|
+
});
|
|
33
|
+
const json = (response.data.RtCode === '0') && response.data;
|
|
34
|
+
if (!json)
|
|
35
|
+
return null;
|
|
36
|
+
const data = json.RtData.QuoteList.map((row) => ({
|
|
37
|
+
symbol: row.SymbolID.split('-')[0],
|
|
38
|
+
name: row.DispCName,
|
|
39
|
+
status: row.Status,
|
|
40
|
+
openPrice: row.COpenPrice && numeral(row.COpenPrice).value(),
|
|
41
|
+
highPrice: row.CHighPrice && numeral(row.CHighPrice).value(),
|
|
42
|
+
lowPrice: row.CLowPrice && numeral(row.CLowPrice).value(),
|
|
43
|
+
lastPrice: row.CLastPrice && numeral(row.CLastPrice).value(),
|
|
44
|
+
referencePrice: row.CRefPrice && numeral(row.CRefPrice).value(),
|
|
45
|
+
limitUpPrice: row.CCeilPrice && numeral(row.CCeilPrice).value(),
|
|
46
|
+
limitDownPrice: row.CFloorPrice && numeral(row.CFloorPrice).value(),
|
|
47
|
+
settlementPrice: row.SettlementPrice && numeral(row.SettlementPrice).value(),
|
|
48
|
+
change: row.CDiff && numeral(row.CDiff).value(),
|
|
49
|
+
changePercent: row.CDiffRate && numeral(row.CDiffRate).value(),
|
|
50
|
+
amplitude: row.CAmpRate && numeral(row.CAmpRate).value(),
|
|
51
|
+
totalVoluem: row.CTotalVolume && numeral(row.CTotalVolume).value(),
|
|
52
|
+
openInterest: row.OpenInterest && numeral(row.OpenInterest).value(),
|
|
53
|
+
bestBidPrice: row.CBestBidPrice && numeral(row.CBestBidPrice).value(),
|
|
54
|
+
bestAskPrice: row.CBestAskPrice && numeral(row.CBestAskPrice).value(),
|
|
55
|
+
bestBidSize: row.CBestBidSize && numeral(row.CBestBidSize).value(),
|
|
56
|
+
bestAskSize: row.CBestAskSize && numeral(row.CBestAskSize).value(),
|
|
57
|
+
testPrice: row.CTestPrice && numeral(row.CTestPrice).value(),
|
|
58
|
+
testSize: row.CTestVolume && numeral(row.CTestVolume).value(),
|
|
59
|
+
lastUpdated: row.CTime && luxon_1.DateTime.fromFormat(`${row.CDate} ${row.CTime}`, 'yyyyMMdd hhmmss', { zone: 'Asia/Taipei' }).toMillis(),
|
|
60
|
+
}));
|
|
61
|
+
return data;
|
|
62
|
+
}
|
|
63
|
+
async fetchFutOptQuoteDetail(options) {
|
|
64
|
+
const { ticker, afterhours } = options;
|
|
65
|
+
const body = JSON.stringify({ SymbolID: [this.extractSymbolIdFromTicker(ticker, { afterhours })] });
|
|
66
|
+
const url = `https://mis.taifex.com.tw/futures/api/getQuoteDetail`;
|
|
67
|
+
const response = await this.httpService.post(url, body, {
|
|
68
|
+
headers: { 'Content-Type': 'application/json' },
|
|
69
|
+
});
|
|
70
|
+
const json = (response.data.RtCode === '0') && response.data;
|
|
71
|
+
if (!json)
|
|
72
|
+
return null;
|
|
73
|
+
const data = json.RtData.QuoteList.map((row) => ({
|
|
74
|
+
symbol: ticker.symbol,
|
|
75
|
+
name: row.DispCName,
|
|
76
|
+
status: row.Status,
|
|
77
|
+
referencePrice: row.CRefPrice && numeral(row.CRefPrice).value(),
|
|
78
|
+
limitUpPrice: row.CCeilPrice && numeral(row.CCeilPrice).value(),
|
|
79
|
+
limitDownPrice: row.CFloorPrice && numeral(row.CFloorPrice).value(),
|
|
80
|
+
openPrice: row.COpenPrice && numeral(row.COpenPrice).value(),
|
|
81
|
+
highPrice: row.CHighPrice && numeral(row.CHighPrice).value(),
|
|
82
|
+
lowPrice: row.CLowPrice && numeral(row.CLowPrice).value(),
|
|
83
|
+
lastPrice: row.CLastPrice && numeral(row.CLastPrice).value(),
|
|
84
|
+
lastSize: row.CSingleVolume && numeral(row.CSingleVolume).value(),
|
|
85
|
+
testPrice: row.CTestPrice && numeral(row.CTestPrice).value(),
|
|
86
|
+
testSize: row.CTestVolume && numeral(row.CTestVolume).value(),
|
|
87
|
+
testTime: row.CTestTime && luxon_1.DateTime.fromFormat(`${row.CDate} ${row.CTestTime}`, 'yyyyMMdd hhmmss', { zone: 'Asia/Taipei' }).toMillis(),
|
|
88
|
+
totalVoluem: row.CTotalVolume && numeral(row.CTotalVolume).value(),
|
|
89
|
+
openInterest: row.OpenInterest && numeral(row.OpenInterest).value(),
|
|
90
|
+
bidOrders: row.CBidCount && numeral(row.CBidCount).value(),
|
|
91
|
+
askOrders: row.CAskCount && numeral(row.CAskCount).value(),
|
|
92
|
+
bidVolume: row.CBidUnit && numeral(row.CBidUnit).value(),
|
|
93
|
+
askVolume: row.CAskUnit && numeral(row.CAskUnit).value(),
|
|
94
|
+
bidPrice: [row.CBidPrice1, row.CBidPrice2, row.CBidPrice3, row.CBidPrice4, row.CBidPrice5].map(price => numeral(price).value()),
|
|
95
|
+
askPrice: [row.CAskPrice1, row.CAskPrice2, row.CAskPrice3, row.CAskPrice4, row.CAskPrice5].map(price => numeral(price).value()),
|
|
96
|
+
bidSize: [row.CBidSize1, row.CBidSize2, row.CBidSize3, row.CBidSize4, row.CBidSize5].map(size => numeral(size).value()),
|
|
97
|
+
askSize: [row.CAskSize1, row.CAskSize2, row.CAskSize3, row.CAskSize4, row.CAskSize5].map(size => numeral(size).value()),
|
|
98
|
+
extBidPrice: row.CExtBidPrice && numeral(row.CExtBidPrice).value(),
|
|
99
|
+
extAskPrice: row.CExtAskPrice && numeral(row.CExtAskPrice).value(),
|
|
100
|
+
extBidSize: row.CExtBidSize && numeral(row.CExtBidSize).value(),
|
|
101
|
+
extAskSize: row.CExtAskSize && numeral(row.CExtAskSize).value(),
|
|
102
|
+
lastUpdated: luxon_1.DateTime.fromFormat(`${row.CDate} ${row.CTime}`, 'yyyyMMdd hhmmss', { zone: 'Asia/Taipei' }).toMillis(),
|
|
103
|
+
}));
|
|
104
|
+
return data.find(row => row.symbol.includes(ticker.symbol));
|
|
105
|
+
}
|
|
106
|
+
extractSymbolIdFromTicker(ticker, options) {
|
|
107
|
+
const { symbol, type } = ticker;
|
|
108
|
+
const { afterhours } = options;
|
|
109
|
+
if (type && type.includes('期貨')) {
|
|
110
|
+
return afterhours ? `${symbol}-M` : `${symbol}-F`;
|
|
111
|
+
}
|
|
112
|
+
if (type && type.includes('選擇權')) {
|
|
113
|
+
return afterhours ? `${symbol}-N` : `${symbol}-O`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
extractTypeFromCmdyDDLItem(item) {
|
|
117
|
+
const type = item.CID.charAt(2);
|
|
118
|
+
if (['F', 'O'].includes(type))
|
|
119
|
+
return type;
|
|
120
|
+
if (item.DispCName.includes('期貨'))
|
|
121
|
+
return 'F';
|
|
122
|
+
if (item.DispCName.includes('選擇權'))
|
|
123
|
+
return 'O';
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
exports.MisTaifexScraper = MisTaifexScraper;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Scraper } from './scraper';
|
|
2
|
+
import { IndexQuote, StockQuote, Ticker } from '../interfaces';
|
|
3
|
+
export declare class MisTwseScraper extends Scraper {
|
|
4
|
+
fetchListedIndices(options: {
|
|
5
|
+
exchange: 'TWSE' | 'TPEx';
|
|
6
|
+
}): Promise<any>;
|
|
7
|
+
fetchStocksQuote(options: {
|
|
8
|
+
ticker: Ticker;
|
|
9
|
+
odd?: boolean;
|
|
10
|
+
}): Promise<StockQuote | null | undefined>;
|
|
11
|
+
fetchIndicesQuote(options: {
|
|
12
|
+
ticker: Ticker;
|
|
13
|
+
}): Promise<IndexQuote | null | undefined>;
|
|
14
|
+
private extractExChFromTicker;
|
|
15
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MisTwseScraper = void 0;
|
|
4
|
+
const numeral = require("numeral");
|
|
5
|
+
const luxon_1 = require("luxon");
|
|
6
|
+
const scraper_1 = require("./scraper");
|
|
7
|
+
const utils_1 = require("../utils");
|
|
8
|
+
class MisTwseScraper extends scraper_1.Scraper {
|
|
9
|
+
async fetchListedIndices(options) {
|
|
10
|
+
const { exchange } = options;
|
|
11
|
+
const ex = { 'TWSE': 'tse', 'TPEx': 'otc' };
|
|
12
|
+
const i = { 'TWSE': 'TIDX', 'TPEx': 'OIDX' };
|
|
13
|
+
const query = new URLSearchParams({
|
|
14
|
+
ex: ex[exchange],
|
|
15
|
+
i: i[exchange],
|
|
16
|
+
});
|
|
17
|
+
const url = `https://mis.twse.com.tw/stock/api/getCategory.jsp?${query}`;
|
|
18
|
+
const response = await this.httpService.get(url);
|
|
19
|
+
const json = (response.data.rtmessage === 'OK') && response.data;
|
|
20
|
+
if (!json)
|
|
21
|
+
return null;
|
|
22
|
+
const data = json.msgArray.map((row) => {
|
|
23
|
+
var _a;
|
|
24
|
+
return ({
|
|
25
|
+
symbol: (_a = (0, utils_1.asIndex)(row.n)) !== null && _a !== void 0 ? _a : (row.ch).replace('.tw', ''),
|
|
26
|
+
name: row.n,
|
|
27
|
+
exchange,
|
|
28
|
+
ex_ch: `${row.ex}_${row.ch}`,
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
return data;
|
|
32
|
+
}
|
|
33
|
+
async fetchStocksQuote(options) {
|
|
34
|
+
const { ticker, odd } = options;
|
|
35
|
+
const query = new URLSearchParams({
|
|
36
|
+
ex_ch: this.extractExChFromTicker(ticker),
|
|
37
|
+
});
|
|
38
|
+
const url = odd
|
|
39
|
+
? `https://mis.twse.com.tw/stock/api/getOddInfo.jsp?${query}`
|
|
40
|
+
: `https://mis.twse.com.tw/stock/api/getStockInfo.jsp?${query}`;
|
|
41
|
+
const response = await this.httpService.get(url);
|
|
42
|
+
const json = (response.data.rtmessage === 'OK') && response.data;
|
|
43
|
+
if (!json)
|
|
44
|
+
return null;
|
|
45
|
+
const data = json.msgArray.map((row) => ({
|
|
46
|
+
date: luxon_1.DateTime.fromFormat(row.d, 'yyyyMMdd').toISODate(),
|
|
47
|
+
symbol: row.c,
|
|
48
|
+
name: row.n,
|
|
49
|
+
referencePrice: row.y && numeral(row.y).value(),
|
|
50
|
+
limitUpPrice: row.u && numeral(row.u).value(),
|
|
51
|
+
limitDownPrice: row.w && numeral(row.w).value(),
|
|
52
|
+
openPrice: row.o && numeral(row.o).value(),
|
|
53
|
+
highPrice: row.h && numeral(row.h).value(),
|
|
54
|
+
lowPrice: row.l && numeral(row.l).value(),
|
|
55
|
+
lastPrice: row.z && numeral(row.z).value(),
|
|
56
|
+
lastSize: row.s && numeral(row.s).value(),
|
|
57
|
+
totalVoluem: row.v && numeral(row.v).value(),
|
|
58
|
+
bidPrice: row.b && row.b.split('_').slice(0, -1).map((price) => numeral(price).value()),
|
|
59
|
+
askPrice: row.a && row.a.split('_').slice(0, -1).map((price) => numeral(price).value()),
|
|
60
|
+
bidSize: row.g && row.g.split('_').slice(0, -1).map((size) => numeral(size).value()),
|
|
61
|
+
askSize: row.f && row.f.split('_').slice(0, -1).map((size) => numeral(size).value()),
|
|
62
|
+
lastUpdated: row.tlong && numeral(row.tlong).value(),
|
|
63
|
+
}));
|
|
64
|
+
return data.find(row => row.symbol === ticker.symbol);
|
|
65
|
+
}
|
|
66
|
+
async fetchIndicesQuote(options) {
|
|
67
|
+
const { ticker } = options;
|
|
68
|
+
const query = new URLSearchParams({
|
|
69
|
+
ex_ch: this.extractExChFromTicker(ticker),
|
|
70
|
+
});
|
|
71
|
+
const url = `https://mis.twse.com.tw/stock/api/getStockInfo.jsp?${query}`;
|
|
72
|
+
const response = await this.httpService.get(url);
|
|
73
|
+
const json = (response.data.rtmessage === 'OK') && response.data;
|
|
74
|
+
if (!json)
|
|
75
|
+
return null;
|
|
76
|
+
const data = json.msgArray.map((row) => ({
|
|
77
|
+
date: luxon_1.DateTime.fromFormat(row.d, 'yyyyMMdd').toISODate(),
|
|
78
|
+
symbol: ticker.symbol,
|
|
79
|
+
name: row.n,
|
|
80
|
+
previousClose: row.y && numeral(row.y).value(),
|
|
81
|
+
open: row.o && numeral(row.o).value(),
|
|
82
|
+
high: row.h && numeral(row.h).value(),
|
|
83
|
+
low: row.l && numeral(row.l).value(),
|
|
84
|
+
close: row.z && numeral(row.z).value(),
|
|
85
|
+
volume: row.v && numeral(row.v).value(),
|
|
86
|
+
lastUpdated: row.tlong && numeral(row.tlong).value(),
|
|
87
|
+
}));
|
|
88
|
+
return data.find(row => row.symbol === ticker.symbol);
|
|
89
|
+
}
|
|
90
|
+
extractExChFromTicker(ticker) {
|
|
91
|
+
const { symbol, exchange, ex_ch } = ticker;
|
|
92
|
+
const ex = { 'TWSE': 'tse', 'TPEx': 'otc' };
|
|
93
|
+
const ch = `${symbol}.tw`;
|
|
94
|
+
return ex_ch ? ex_ch : `${ex[exchange]}_${ch}`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
exports.MisTwseScraper = MisTwseScraper;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Scraper } from './scraper';
|
|
2
|
+
import { StockEps, StockRevenue } from '../interfaces';
|
|
3
|
+
export declare class MopsScraper extends Scraper {
|
|
4
|
+
fetchStocksEps(options: {
|
|
5
|
+
exchange: string;
|
|
6
|
+
year: number;
|
|
7
|
+
quarter: number;
|
|
8
|
+
symbol?: string;
|
|
9
|
+
}): Promise<StockEps | StockEps[] | null | undefined>;
|
|
10
|
+
fetchStocksRevenue(options: {
|
|
11
|
+
exchange: string;
|
|
12
|
+
year: number;
|
|
13
|
+
month: number;
|
|
14
|
+
foreign?: boolean;
|
|
15
|
+
symbol?: string;
|
|
16
|
+
}): Promise<StockRevenue | StockRevenue[] | null | undefined>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MopsScraper = void 0;
|
|
4
|
+
const _ = require("lodash");
|
|
5
|
+
const cheerio = require("cheerio");
|
|
6
|
+
const iconv = require("iconv-lite");
|
|
7
|
+
const numeral = require("numeral");
|
|
8
|
+
const scraper_1 = require("./scraper");
|
|
9
|
+
class MopsScraper extends scraper_1.Scraper {
|
|
10
|
+
async fetchStocksEps(options) {
|
|
11
|
+
const { exchange, year, quarter, symbol } = options;
|
|
12
|
+
const type = { 'TWSE': 'sii', 'TPEx': 'otc' };
|
|
13
|
+
const form = new URLSearchParams({
|
|
14
|
+
encodeURIComponent: '1',
|
|
15
|
+
step: '1',
|
|
16
|
+
firstin: '1',
|
|
17
|
+
off: '1',
|
|
18
|
+
isQuery: 'Y',
|
|
19
|
+
TYPEK: type[exchange],
|
|
20
|
+
year: numeral(year).subtract(1911).format(),
|
|
21
|
+
season: numeral(quarter).format('00'),
|
|
22
|
+
});
|
|
23
|
+
const url = 'https://mops.twse.com.tw/mops/web/t163sb04';
|
|
24
|
+
const response = await this.httpService.post(url, form);
|
|
25
|
+
if (response.data.includes('查詢無資料!'))
|
|
26
|
+
return null;
|
|
27
|
+
const $ = cheerio.load(response.data);
|
|
28
|
+
const data = $('.even,.odd').map((_, el) => {
|
|
29
|
+
const td = $(el).find('td');
|
|
30
|
+
const symbol = td.eq(0).text().trim();
|
|
31
|
+
const name = td.eq(1).text().trim();
|
|
32
|
+
const eps = numeral(td.eq(td.length - 1).text().trim()).value();
|
|
33
|
+
return { exchange, symbol, name, eps, year, quarter };
|
|
34
|
+
}).toArray();
|
|
35
|
+
return symbol ? data.find(data => data.symbol === symbol) : _.sortBy(data, 'symbol');
|
|
36
|
+
}
|
|
37
|
+
async fetchStocksRevenue(options) {
|
|
38
|
+
const { exchange, year, month, foreign = false, symbol } = options;
|
|
39
|
+
const type = { 'TWSE': 'sii', 'TPEx': 'otc' };
|
|
40
|
+
const suffix = `${numeral(year).subtract(1911).value()}_${month}_${+foreign}`;
|
|
41
|
+
const url = `https://mops.twse.com.tw/nas/t21/${type[exchange]}/t21sc03_${suffix}.html`;
|
|
42
|
+
const response = await this.httpService.get(url, { responseType: 'arraybuffer' });
|
|
43
|
+
const page = iconv.decode(response.data, 'big5');
|
|
44
|
+
if (page.toString().includes('查無資料'))
|
|
45
|
+
return null;
|
|
46
|
+
const $ = cheerio.load(page);
|
|
47
|
+
const data = $('tr [align=right]')
|
|
48
|
+
.filter((_, el) => {
|
|
49
|
+
const th = $(el).find('th');
|
|
50
|
+
const td = $(el).find('td');
|
|
51
|
+
return (th.length === 0) && !!td.eq(0).text();
|
|
52
|
+
})
|
|
53
|
+
.map((_, el) => {
|
|
54
|
+
const td = $(el).find('td');
|
|
55
|
+
const symbol = td.eq(0).text().trim();
|
|
56
|
+
const name = td.eq(1).text().trim();
|
|
57
|
+
const revenue = numeral(td.eq(2).text().trim()).value();
|
|
58
|
+
return { exchange, symbol, name, revenue, year, month };
|
|
59
|
+
})
|
|
60
|
+
.toArray();
|
|
61
|
+
return symbol ? data.find(data => data.symbol === symbol) : _.sortBy(data, 'symbol');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.MopsScraper = MopsScraper;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { TwseScraper } from './twse-scraper';
|
|
2
|
+
import { TpexScraper } from './tpex-scraper';
|
|
3
|
+
import { TaifexScraper } from './taifex-scraper';
|
|
4
|
+
import { TdccScraper } from './tdcc-scraper';
|
|
5
|
+
import { MisTwseScraper } from './mis-twse-scraper';
|
|
6
|
+
import { MisTaifexScraper } from './mis-taifex-scraper';
|
|
7
|
+
import { MopsScraper } from './mops-scraper';
|
|
8
|
+
import { IsinScraper } from './isin-scraper';
|
|
9
|
+
import { Scraper } from './scraper';
|
|
10
|
+
import { Scraper as ScraperType } from '../enums';
|
|
11
|
+
import { RateLimitOptions } from '../interfaces';
|
|
12
|
+
export declare class ScraperFactory {
|
|
13
|
+
private readonly options?;
|
|
14
|
+
private readonly scrapers;
|
|
15
|
+
constructor(options?: RateLimitOptions | undefined);
|
|
16
|
+
get(type: ScraperType): Scraper;
|
|
17
|
+
getTwseScraper(): TwseScraper;
|
|
18
|
+
getTpexScraper(): TpexScraper;
|
|
19
|
+
getTaifexScraper(): TaifexScraper;
|
|
20
|
+
getTdccScraper(): TdccScraper;
|
|
21
|
+
getMisTwseScraper(): MisTwseScraper;
|
|
22
|
+
getMisTaifexScraper(): MisTaifexScraper;
|
|
23
|
+
getMopsScraper(): MopsScraper;
|
|
24
|
+
getIsinScraper(): IsinScraper;
|
|
25
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ScraperFactory = void 0;
|
|
4
|
+
const twse_scraper_1 = require("./twse-scraper");
|
|
5
|
+
const tpex_scraper_1 = require("./tpex-scraper");
|
|
6
|
+
const taifex_scraper_1 = require("./taifex-scraper");
|
|
7
|
+
const tdcc_scraper_1 = require("./tdcc-scraper");
|
|
8
|
+
const mis_twse_scraper_1 = require("./mis-twse-scraper");
|
|
9
|
+
const mis_taifex_scraper_1 = require("./mis-taifex-scraper");
|
|
10
|
+
const mops_scraper_1 = require("./mops-scraper");
|
|
11
|
+
const isin_scraper_1 = require("./isin-scraper");
|
|
12
|
+
const enums_1 = require("../enums");
|
|
13
|
+
class ScraperFactory {
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.options = options;
|
|
16
|
+
this.scrapers = new Map();
|
|
17
|
+
}
|
|
18
|
+
get(type) {
|
|
19
|
+
let scraper = this.scrapers.get(type);
|
|
20
|
+
if (!scraper) {
|
|
21
|
+
const scrapers = {
|
|
22
|
+
[enums_1.Scraper.Twse]: twse_scraper_1.TwseScraper,
|
|
23
|
+
[enums_1.Scraper.Tpex]: tpex_scraper_1.TpexScraper,
|
|
24
|
+
[enums_1.Scraper.Taifex]: taifex_scraper_1.TaifexScraper,
|
|
25
|
+
[enums_1.Scraper.Tdcc]: tdcc_scraper_1.TdccScraper,
|
|
26
|
+
[enums_1.Scraper.MisTwse]: mis_twse_scraper_1.MisTwseScraper,
|
|
27
|
+
[enums_1.Scraper.MisTaifex]: mis_taifex_scraper_1.MisTaifexScraper,
|
|
28
|
+
[enums_1.Scraper.Mops]: mops_scraper_1.MopsScraper,
|
|
29
|
+
[enums_1.Scraper.Isin]: isin_scraper_1.IsinScraper,
|
|
30
|
+
};
|
|
31
|
+
const ScraperClass = scrapers[type];
|
|
32
|
+
scraper = new ScraperClass(this.options);
|
|
33
|
+
this.scrapers.set(type, scraper);
|
|
34
|
+
}
|
|
35
|
+
return scraper;
|
|
36
|
+
}
|
|
37
|
+
getTwseScraper() {
|
|
38
|
+
return this.get(enums_1.Scraper.Twse);
|
|
39
|
+
}
|
|
40
|
+
getTpexScraper() {
|
|
41
|
+
return this.get(enums_1.Scraper.Tpex);
|
|
42
|
+
}
|
|
43
|
+
getTaifexScraper() {
|
|
44
|
+
return this.get(enums_1.Scraper.Taifex);
|
|
45
|
+
}
|
|
46
|
+
getTdccScraper() {
|
|
47
|
+
return this.get(enums_1.Scraper.Tdcc);
|
|
48
|
+
}
|
|
49
|
+
getMisTwseScraper() {
|
|
50
|
+
return this.get(enums_1.Scraper.MisTwse);
|
|
51
|
+
}
|
|
52
|
+
getMisTaifexScraper() {
|
|
53
|
+
return this.get(enums_1.Scraper.MisTaifex);
|
|
54
|
+
}
|
|
55
|
+
getMopsScraper() {
|
|
56
|
+
return this.get(enums_1.Scraper.Mops);
|
|
57
|
+
}
|
|
58
|
+
getIsinScraper() {
|
|
59
|
+
return this.get(enums_1.Scraper.Isin);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.ScraperFactory = ScraperFactory;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Scraper = void 0;
|
|
4
|
+
const axios_1 = require("axios");
|
|
5
|
+
const rateLimit = require("axios-rate-limit");
|
|
6
|
+
const https = require("https");
|
|
7
|
+
class Scraper {
|
|
8
|
+
constructor(options = { limit: 1, ttl: 5000 }) {
|
|
9
|
+
const maxRequests = options.limit;
|
|
10
|
+
const perMilliseconds = options.ttl;
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
this.httpService = rateLimit(axios_1.default.create({
|
|
14
|
+
httpsAgent: new https.Agent({
|
|
15
|
+
rejectUnauthorized: false,
|
|
16
|
+
}),
|
|
17
|
+
}), { maxRequests, perMilliseconds });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.Scraper = Scraper;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Scraper } from './scraper';
|
|
2
|
+
import { FutOptHistorical, FutOptInstitutional, FutOptLargeTraders, FutOptMxfRetailPosition, FutOptTmfRetailPosition, FutOptTxoPutCallRatio, StockFutOpt } from '../interfaces';
|
|
3
|
+
import { FutOptExchangeRates } from '../interfaces/futopt-exchange-rates.interface';
|
|
4
|
+
export declare class TaifexScraper extends Scraper {
|
|
5
|
+
fetchListedStockFutOpt(): Promise<StockFutOpt[]>;
|
|
6
|
+
fetchFuturesHistorical(options: {
|
|
7
|
+
date: string;
|
|
8
|
+
symbol?: string;
|
|
9
|
+
afterhours?: boolean;
|
|
10
|
+
}): Promise<FutOptHistorical[] | null>;
|
|
11
|
+
fetchOptionsHistorical(options: {
|
|
12
|
+
date: string;
|
|
13
|
+
symbol?: string;
|
|
14
|
+
afterhours?: boolean;
|
|
15
|
+
}): Promise<FutOptHistorical[] | null>;
|
|
16
|
+
fetchFuturesInstitutional(options: {
|
|
17
|
+
date: string;
|
|
18
|
+
symbol: string;
|
|
19
|
+
}): Promise<FutOptInstitutional | null>;
|
|
20
|
+
fetchOptionsInstitutional(options: {
|
|
21
|
+
date: string;
|
|
22
|
+
symbol: string;
|
|
23
|
+
}): Promise<FutOptInstitutional | null>;
|
|
24
|
+
fetchFuturesLargeTraders(options: {
|
|
25
|
+
date: string;
|
|
26
|
+
symbol: string;
|
|
27
|
+
}): Promise<FutOptLargeTraders | null>;
|
|
28
|
+
fetchOptionsLargeTraders(options: {
|
|
29
|
+
date: string;
|
|
30
|
+
symbol: string;
|
|
31
|
+
}): Promise<FutOptLargeTraders | null>;
|
|
32
|
+
fetchMxfRetailPosition(options: {
|
|
33
|
+
date: string;
|
|
34
|
+
}): Promise<FutOptMxfRetailPosition | null>;
|
|
35
|
+
fetchTmfRetailPosition(options: {
|
|
36
|
+
date: string;
|
|
37
|
+
}): Promise<FutOptTmfRetailPosition | null>;
|
|
38
|
+
fetchTxoPutCallRatio(options: {
|
|
39
|
+
date: string;
|
|
40
|
+
}): Promise<FutOptTxoPutCallRatio | null>;
|
|
41
|
+
fetchExchangeRates(options: {
|
|
42
|
+
date: string;
|
|
43
|
+
}): Promise<FutOptExchangeRates | null>;
|
|
44
|
+
}
|