node-twstock
Version:
A client library for scraping Taiwan stock market data
661 lines (660 loc) • 31.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TwseScraper = void 0;
const _ = require("lodash");
const numeral = require("numeral");
const luxon_1 = require("luxon");
const scraper_1 = require("./scraper");
const enums_1 = require("../enums");
const utils_1 = require("../utils");
class TwseScraper extends scraper_1.Scraper {
async fetchStocksHistorical(options) {
const { date, symbol } = options;
const query = new URLSearchParams({
date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
type: 'ALLBUT0999',
response: 'json',
});
const url = `https://www.twse.com.tw/rwd/zh/afterTrading/MI_INDEX?${query}`;
const response = await this.httpService.get(url, { headers: { 'Connection': 'keep-alive' } });
const json = (response.data.stat === 'OK') && response.data;
if (!json)
return null;
const data = json.tables[8].data.map((row) => {
const [symbol, name, ...values] = row;
const data = {};
data.date = date;
data.exchange = enums_1.Exchange.TWSE;
data.symbol = symbol;
data.name = name.trim();
data.open = numeral(values[3]).value();
data.high = numeral(values[4]).value();
data.low = numeral(values[5]).value();
data.close = numeral(values[6]).value();
data.volume = numeral(values[0]).value();
data.turnover = numeral(values[2]).value();
data.transaction = numeral(values[1]).value();
data.change = values[7].includes('green') ? numeral(values[8]).multiply(-1).value() : numeral(values[8]).value();
return data;
});
return symbol ? data.find(data => data.symbol === symbol) : data;
}
async fetchStocksInstitutional(options) {
const { date, symbol } = options;
const query = new URLSearchParams({
date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
selectType: 'ALLBUT0999',
response: 'json',
});
const url = `https://www.twse.com.tw/rwd/zh/fund/T86?${query}`;
const response = await this.httpService.get(url);
const json = (response.data.stat === 'OK') && response.data;
if (!json)
return null;
const data = json.data.map((row) => {
const [symbol, name, ...values] = row;
const data = {};
data.date = date;
data.exchange = enums_1.Exchange.TWSE;
data.symbol = symbol;
data.name = name.trim();
data.institutional = ((values) => {
switch (values.length) {
case 17: return [
{
investor: '外資及陸資(不含外資自營商)',
totalBuy: numeral(values[0]).value(),
totalSell: numeral(values[1]).value(),
difference: numeral(values[2]).value(),
},
{
investor: '外資自營商',
totalBuy: numeral(values[3]).value(),
totalSell: numeral(values[4]).value(),
difference: numeral(values[5]).value(),
},
{
investor: '投信',
totalBuy: numeral(values[6]).value(),
totalSell: numeral(values[7]).value(),
difference: numeral(values[8]).value(),
},
{
investor: '自營商',
difference: numeral(values[9]).value(),
},
{
investor: '自營商(自行買賣)',
totalBuy: numeral(values[10]).value(),
totalSell: numeral(values[11]).value(),
difference: numeral(values[12]).value(),
},
{
investor: '自營商(避險)',
totalBuy: numeral(values[13]).value(),
totalSell: numeral(values[14]).value(),
difference: numeral(values[15]).value(),
},
{
investor: '三大法人',
difference: numeral(values[16]).value(),
},
];
case 14: return [
{
investor: '外資及陸資',
totalBuy: numeral(values[0]).value(),
totalSell: numeral(values[1]).value(),
difference: numeral(values[2]).value(),
},
{
investor: '投信',
totalBuy: numeral(values[3]).value(),
totalSell: numeral(values[4]).value(),
difference: numeral(values[5]).value(),
},
{
investor: '自營商',
difference: numeral(values[6]).value(),
},
{
investor: '自營商(自行買賣)',
totalBuy: numeral(values[7]).value(),
totalSell: numeral(values[8]).value(),
difference: numeral(values[9]).value(),
},
{
investor: '自營商(避險)',
totalBuy: numeral(values[10]).value(),
totalSell: numeral(values[11]).value(),
difference: numeral(values[12]).value(),
},
{
investor: '三大法人',
difference: numeral(values[13]).value(),
},
];
case 10: return [
{
investor: '外資及陸資',
totalBuy: numeral(values[0]).value(),
totalSell: numeral(values[1]).value(),
difference: numeral(values[2]).value(),
},
{
investor: '投信',
totalBuy: numeral(values[3]).value(),
totalSell: numeral(values[4]).value(),
difference: numeral(values[5]).value(),
},
{
investor: '自營商',
totalBuy: numeral(values[6]).value(),
totalSell: numeral(values[7]).value(),
difference: numeral(values[8]).value(),
},
{
investor: '三大法人',
difference: numeral(values[9]).value(),
},
];
}
})(values);
return data;
});
return symbol ? data.find(data => data.symbol === symbol) : data;
}
async fetchStocksFiniHoldings(options) {
const { date, symbol } = options;
const query = new URLSearchParams({
date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
selectType: 'ALLBUT0999',
response: 'json',
});
const url = `https://www.twse.com.tw/rwd/zh/fund/MI_QFIIS?${query}`;
const response = await this.httpService.get(url);
const json = (response.data.stat === 'OK') && response.data;
if (!json)
return null;
const data = json.data.map((row) => {
const [symbol, name, isin, ...values] = row;
const data = {};
data.date = date;
data.exchange = enums_1.Exchange.TWSE;
data.symbol = symbol;
data.name = name.trim();
data.issuedShares = numeral(values[0]).value();
data.availableShares = numeral(values[1]).value();
data.sharesHeld = numeral(values[2]).value();
data.availablePercent = numeral(values[3]).value();
data.heldPercent = numeral(values[4]).value();
data.upperLimitPercent = numeral(values[5]).value();
return data;
});
return symbol ? data.find(data => data.symbol === symbol) : data;
}
async fetchStocksMarginTrades(options) {
const { date, symbol } = options;
const query = new URLSearchParams({
date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
selectType: 'ALL',
response: 'json',
});
const url = `https://www.twse.com.tw/rwd/zh/marginTrading/MI_MARGN?${query}`;
const response = await this.httpService.get(url);
const json = (response.data.stat === 'OK') && response.data;
if (!json)
return null;
const data = json.tables[1].data.map((row) => {
const [symbol, name, ...values] = row;
const data = {};
data.date = date;
data.exchange = enums_1.Exchange.TWSE;
data.symbol = symbol;
data.name = name.trim();
data.marginBuy = numeral(values[0]).value();
data.marginSell = numeral(values[1]).value();
data.marginRedeem = numeral(values[2]).value();
data.marginBalancePrev = numeral(values[3]).value();
data.marginBalance = numeral(values[4]).value();
data.marginQuota = numeral(values[5]).value();
data.shortBuy = numeral(values[6]).value();
data.shortSell = numeral(values[7]).value();
data.shortRedeem = numeral(values[8]).value();
data.shortBalancePrev = numeral(values[9]).value();
data.shortBalance = numeral(values[10]).value();
data.shortQuota = numeral(values[11]).value();
data.offset = numeral(values[12]).value();
data.note = values[13].trim();
return data;
});
return symbol ? data.find(data => data.symbol === symbol) : data;
}
async fetchStocksShortSales(options) {
const { date, symbol } = options;
const query = new URLSearchParams({
date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
response: 'json',
});
const url = `https://www.twse.com.tw/rwd/zh/marginTrading/TWT93U?${query}`;
const response = await this.httpService.get(url);
const json = (response.data.stat === 'OK') && response.data;
if (!json.data.length)
return null;
const data = json.data.map((row) => {
const [symbol, name, ...values] = row;
const data = {};
data.date = date;
data.exchange = enums_1.Exchange.TWSE;
data.symbol = symbol;
data.name = name.trim();
data.marginShortBalancePrev = numeral(values[0]).value();
data.marginShortSell = numeral(values[1]).value();
data.marginShortBuy = numeral(values[2]).value();
data.marginShortRedeem = numeral(values[3]).value();
data.marginShortBalance = numeral(values[4]).value();
data.marginShortQuota = numeral(values[5]).value();
data.sblShortBalancePrev = numeral(values[6]).value();
data.sblShortSale = numeral(values[7]).value();
data.sblShortReturn = numeral(values[8]).value();
data.sblShortAdjustment = numeral(values[9]).value();
data.sblShortBalance = numeral(values[10]).value();
data.sblShortQuota = numeral(values[11]).value();
data.note = values[12].trim();
return data;
});
return symbol ? data.find(data => data.symbol === symbol) : data;
}
async fetchStocksValues(options) {
const { date, symbol } = options;
const query = new URLSearchParams({
date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
selectType: 'ALL',
response: 'json',
});
const url = `https://www.twse.com.tw/rwd/zh/afterTrading/BWIBBU_d?${query}`;
const response = await this.httpService.get(url);
const json = (response.data.stat === 'OK') && response.data;
if (!json)
return null;
const data = json.data.map((row) => {
const [symbol, name, ...values] = row;
const data = {};
data.date = date;
data.exchange = enums_1.Exchange.TWSE;
data.symbol = symbol;
data.name = name.trim();
data.peRatio = numeral(values[2]).value();
data.pbRatio = numeral(values[3]).value();
data.dividendYield = numeral(values[0]).value();
data.dividendYear = numeral(values[1]).add(1911).value();
return data;
});
return symbol ? data.find(data => data.symbol === symbol) : data;
}
async fetchStocksDividends(options) {
const { startDate, endDate, symbol } = options;
const query = new URLSearchParams({
startDate: luxon_1.DateTime.fromISO(startDate).toFormat('yyyyMMdd'),
endDate: luxon_1.DateTime.fromISO(endDate).toFormat('yyyyMMdd'),
response: 'json',
});
const url = `https://www.twse.com.tw/rwd/zh/exRight/TWT49U?${query}`;
const response = await this.httpService.get(url);
const json = response.data.stat === 'OK' && response.data;
if (!json)
return [];
const data = await Promise.all(json.data.map(async (row) => {
const [date, symbol, name, ...values] = row;
const formattedDate = date.replace(/(\d+)年(\d+)月(\d+)日/, (_, year, month, day) => {
const westernYear = parseInt(year) + 1911;
return `${westernYear}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
});
const data = {};
data.date = formattedDate;
data.exchange = enums_1.Exchange.TWSE;
data.symbol = symbol;
data.name = name.trim();
data.previousClose = numeral(values[0]).value();
data.referencePrice = numeral(values[1]).value();
data.dividend = numeral(values[2]).value();
data.dividendType = values[3].trim();
data.limitUpPrice = numeral(values[4]).value();
data.limitDownPrice = numeral(values[5]).value();
data.openingReferencePrice = numeral(values[6]).value();
data.exdividendReferencePrice = numeral(values[7]).value();
const [_, detailDate] = values[8].split(',');
const detail = await this.fetchStocksDividendsDetail({ symbol, date: detailDate });
return Object.assign(Object.assign({}, data), detail);
}));
return symbol ? data.filter((data) => data.symbol === symbol) : data;
}
async fetchStocksDividendsDetail(options) {
const { date, symbol } = options;
const query = new URLSearchParams({
STK_NO: symbol,
T1: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
response: 'json',
});
const url = `https://www.twse.com.tw/rwd/zh/exRight/TWT49UDetail?${query}`;
const response = await this.httpService.get(url);
const json = response.data.stat === 'ok' && response.data;
if (!json)
return null;
const [_, name, ...values] = json.data[0];
const data = {};
data.symbol = symbol;
data.name = name.trim();
data.cashDividend = values[0] && parseFloat(values[0]);
data.stockDividendShares = values[2] && parseFloat(values[2]);
return data;
}
async fetchStocksCapitalReductions(options) {
const { startDate, endDate, symbol } = options;
const query = new URLSearchParams({
startDate: luxon_1.DateTime.fromISO(startDate).toFormat('yyyyMMdd'),
endDate: luxon_1.DateTime.fromISO(endDate).toFormat('yyyyMMdd'),
response: 'json',
});
const url = `https://www.twse.com.tw/rwd/zh/reducation/TWTAUU?${query}`;
const response = await this.httpService.get(url);
const json = response.data.stat === 'OK' && response.data;
if (!json)
return [];
const data = await Promise.all(json.data.map(async (row) => {
const [date, symbol, name, ...values] = row;
const [year, month, day] = date.split('/');
const data = {};
data.resumeDate = `${+year + 1911}-${month}-${day}`;
data.exchange = enums_1.Exchange.TWSE;
data.symbol = symbol;
data.name = name.trim();
data.previousClose = numeral(values[0]).value();
data.referencePrice = numeral(values[1]).value();
data.limitUpPrice = numeral(values[2]).value();
data.limitDownPrice = numeral(values[3]).value();
data.openingReferencePrice = numeral(values[4]).value();
data.exrightReferencePrice = numeral(values[5]).value();
data.reason = values[6].trim();
const [_, detailDate] = values[7].split(',');
const detail = await this.fetchStockCapitalReductionDetail({ symbol, date: detailDate });
return Object.assign(Object.assign({}, data), detail);
}));
return symbol ? data.filter((data) => data.symbol === symbol) : data;
}
async fetchStockCapitalReductionDetail(options) {
const { date, symbol } = options;
const query = new URLSearchParams({
STK_NO: symbol,
FILE_DATE: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
response: 'json',
});
const url = `https://www.twse.com.tw/rwd/zh/reducation/TWTAVUDetail?${query}`;
const response = await this.httpService.get(url);
const json = response.data.stat === 'OK' && response.data;
if (!json)
return null;
const [_, name, ...values] = json.data[0];
const [year, month, day] = values[0].split('/');
const data = {};
data.symbol = symbol;
data.name = name.trim();
data.haltDate = `${+year + 1911}-${month}-${day}`;
data.sharesPerThousand = parseFloat(values[1]);
data.refundPerShare = parseFloat(values[2]);
return data;
}
async fetchStocksSplits(options) {
const { startDate, endDate, symbol } = options;
const query = new URLSearchParams({
startDate: luxon_1.DateTime.fromISO(startDate).toFormat('yyyyMMdd'),
endDate: luxon_1.DateTime.fromISO(endDate).toFormat('yyyyMMdd'),
response: 'json',
});
const url = `https://www.twse.com.tw/rwd/zh/change/TWTB8U?${query}`;
const response = await this.httpService.get(url);
const json = response.data.stat === 'OK' && response.data;
const data = json.data.map((row) => {
const [date, symbol, name, ...values] = row;
const [year, month, day] = date.split('/');
const data = {};
data.resumeDate = `${+year + 1911}-${month}-${day}`;
data.exchange = enums_1.Exchange.TWSE;
data.symbol = symbol;
data.name = name.trim();
data.previousClose = numeral(values[0]).value();
data.referencePrice = numeral(values[1]).value();
data.limitUpPrice = numeral(values[2]).value();
data.limitDownPrice = numeral(values[3]).value();
data.openingReferencePrice = numeral(values[4]).value();
return data;
});
return symbol ? data.filter((data) => data.symbol === symbol) : data;
}
async fetchStocksEtfSplits(options) {
const { startDate, endDate, symbol } = options;
const query = new URLSearchParams({
startDate: luxon_1.DateTime.fromISO(startDate).toFormat('yyyyMMdd'),
endDate: luxon_1.DateTime.fromISO(endDate).toFormat('yyyyMMdd'),
response: 'json',
});
const url = `https://www.twse.com.tw/rwd/zh/split/TWTCAU?${query}`;
const response = await this.httpService.get(url);
const json = response.data.stat === 'OK' && response.data;
const data = json.data.map((row) => {
const [date, symbol, name, type, ...values] = row;
const [year, month, day] = date.split('/');
const data = {};
data.resumeDate = `${+year + 1911}-${month}-${day}`;
data.exchange = enums_1.Exchange.TWSE;
data.symbol = symbol;
data.name = name.trim();
data.type = type;
data.previousClose = numeral(values[0]).value();
data.referencePrice = numeral(values[1]).value();
data.limitUpPrice = numeral(values[2]).value();
data.limitDownPrice = numeral(values[3]).value();
data.openingReferencePrice = numeral(values[4]).value();
return data;
})
.filter((row) => options.reverseSplit ? row.type === '反分割' : row.type === '分割')
.map((row) => _.omit(row, ['type']));
return symbol ? data.filter((data) => data.symbol === symbol) : data;
}
async fetchIndicesHistorical(options) {
const { date, symbol } = options;
const query = new URLSearchParams({
date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
response: 'json',
});
const url = `https://www.twse.com.tw/rwd/zh/TAIEX/MI_5MINS_INDEX?${query}`;
const response = await this.httpService.get(url);
const json = (response.data.stat === 'OK') && response.data;
if (!json)
return null;
const indices = json.fields.slice(1).map((index) => ({
symbol: (0, utils_1.asIndex)(index),
name: index,
}));
const quotes = json.data.flatMap((row) => {
const [time, ...values] = row;
return values.map((value, i) => ({
date,
time,
symbol: indices[i].symbol,
name: indices[i].name,
price: numeral(value).value(),
}));
});
const data = _(quotes).groupBy('symbol')
.map(quotes => {
const [prev, ...rows] = quotes;
const { date, symbol, name } = prev;
const data = {};
data.date = date,
data.exchange = enums_1.Exchange.TWSE;
data.symbol = symbol;
data.name = name.trim();
data.open = _.minBy(rows, 'time').price;
data.high = _.maxBy(rows, 'price').price;
data.low = _.minBy(rows, 'price').price;
data.close = _.maxBy(rows, 'time').price;
data.change = numeral(data.close).subtract(prev.price).value();
return data;
}).value();
return symbol ? data.find(data => data.symbol === symbol) : data;
}
async fetchIndicesTrades(options) {
const { date, symbol } = options;
const query = new URLSearchParams({
date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
response: 'json',
});
const url = `https://www.twse.com.tw/rwd/zh/afterTrading/BFIAMU?${query}`;
const response = await this.httpService.get(url);
const json = (response.data.stat === 'OK') && response.data;
if (!json)
return null;
const market = await this.fetchMarketTrades({ date });
if (!market)
return null;
const data = json.data.map((row) => {
const data = {};
data.date = date,
data.exchange = enums_1.Exchange.TWSE;
data.symbol = (0, utils_1.asIndex)(row[0].trim());
data.name = row[0].trim();
data.tradeVolume = numeral(row[1]).value();
data.tradeValue = numeral(row[2]).value();
data.tradeWeight = +numeral(data.tradeValue).divide(market.tradeValue).multiply(100).format('0.00');
return data;
});
const excludedSymbols = [enums_1.Index.ChemicalBiotechnologyAndMedicalCare, enums_1.Index.Electronics];
const total = data
.filter(row => !excludedSymbols.includes(row.symbol))
.reduce((total, row) => ({
tradeVolume: total.tradeVolume + row.tradeVolume,
tradeValue: total.tradeValue + row.tradeValue,
}), { tradeVolume: 0, tradeValue: 0 });
const electronics = _.find(data, { symbol: enums_1.Index.Electronics });
const finance = _.find(data, { symbol: enums_1.Index.FinancialAndInsurance });
const createIndexEntry = (symbol, name, tradeVolume, tradeValue) => ({
date,
exchange: enums_1.Exchange.TWSE,
symbol,
name,
tradeVolume,
tradeValue,
tradeWeight: +numeral(tradeValue).divide(market.tradeValue).multiply(100).format('0.00')
});
const nonFinance = createIndexEntry(enums_1.Index.NonFinance, '未含金融保險股指數', total.tradeVolume - finance.tradeVolume, total.tradeValue - finance.tradeValue);
const nonElectronics = createIndexEntry(enums_1.Index.NonElectronics, '未含電子股指數', total.tradeVolume - electronics.tradeVolume, total.tradeValue - electronics.tradeValue);
const nonFinanceNonElectronics = createIndexEntry(enums_1.Index.NonFinanceNonElectronics, '未含金融電子股指數', total.tradeVolume - (finance.tradeVolume + electronics.tradeVolume), total.tradeValue - (finance.tradeValue + electronics.tradeValue));
data.push(nonFinance, nonElectronics, nonFinanceNonElectronics);
return symbol ? data.find(data => data.symbol === symbol) : data;
}
async fetchMarketTrades(options) {
const { date } = options;
const query = new URLSearchParams({
date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
response: 'json',
});
const url = `https://www.twse.com.tw/rwd/zh/afterTrading/MI_INDEX?${query}`;
const response = await this.httpService.get(url, { headers: { 'Connection': 'keep-alive' } });
const json = (response.data.stat === 'OK') && response.data;
if (!json)
return null;
const [_, ...values] = json.tables[6].data.slice(-1)[0];
const data = {};
data.date = date,
data.exchange = enums_1.Exchange.TWSE;
data.tradeVolume = numeral(values[1]).value();
data.tradeValue = numeral(values[0]).value();
data.transaction = numeral(values[2]).value();
return data;
}
async fetchMarketBreadth(options) {
const { date } = options;
const query = new URLSearchParams({
date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
response: 'json',
});
const url = `https://www.twse.com.tw/rwd/zh/afterTrading/MI_INDEX?${query}`;
const response = await this.httpService.get(url, { headers: { 'Connection': 'keep-alive' } });
const json = (response.data.stat === 'OK') && response.data;
if (!json)
return null;
const raw = json.tables[7].data.map((row) => row[2]);
const [up, limitUp] = raw[0].replace(')', '').split('(');
const [down, limitDown] = raw[1].replace(')', '').split('(');
const [unchanged, unmatched, notApplicable] = raw.slice(2);
const data = {};
data.date = date;
data.exchange = enums_1.Exchange.TWSE,
data.up = numeral(up).value();
data.limitUp = numeral(limitUp).value();
data.down = numeral(down).value();
data.limitDown = numeral(limitDown).value();
data.unchanged = numeral(unchanged).value();
data.unmatched = numeral(unmatched).value();
data.notApplicable = numeral(notApplicable).value();
return data;
}
async fetchMarketInstitutional(options) {
const { date } = options;
const query = new URLSearchParams({
dayDate: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
type: 'day',
response: 'json',
});
const url = `https://www.twse.com.tw/rwd/zh/fund/BFI82U?${query}`;
const response = await this.httpService.get(url);
const json = (response.data.stat === 'OK') && response.data;
if (!json)
return null;
const data = {};
data.date = date;
data.exchange = enums_1.Exchange.TWSE,
data.institutional = json.data.map((row) => ({
investor: row[0] === '合計' ? '三大法人' : row[0],
totalBuy: numeral(row[1]).value(),
totalSell: numeral(row[2]).value(),
difference: numeral(row[3]).value(),
}));
return data;
}
async fetchMarketMarginTrades(options) {
const { date } = options;
const query = new URLSearchParams({
date: luxon_1.DateTime.fromISO(date).toFormat('yyyyMMdd'),
selectType: 'MS',
response: 'json',
});
const url = `https://www.twse.com.tw/rwd/zh/marginTrading/MI_MARGN?${query}`;
const response = await this.httpService.get(url);
const json = (response.data.stat === 'OK') && response.data;
if (!json)
return null;
const values = json.tables[0].data.map((row) => row.slice(1)).flat();
const data = {};
data.date = date;
data.exchange = enums_1.Exchange.TWSE,
data.marginBuy = numeral(values[0]).value();
data.marginSell = numeral(values[1]).value();
data.marginRedeem = numeral(values[2]).value();
data.marginBalancePrev = numeral(values[3]).value();
data.marginBalance = numeral(values[4]).value();
data.shortBuy = numeral(values[5]).value();
data.shortSell = numeral(values[6]).value();
data.shortRedeem = numeral(values[7]).value();
data.shortBalancePrev = numeral(values[8]).value();
data.shortBalance = numeral(values[9]).value();
data.marginBuyValue = numeral(values[10]).value();
data.marginSellValue = numeral(values[11]).value();
data.marginRedeemValue = numeral(values[12]).value();
data.marginBalancePrevValue = numeral(values[13]).value();
data.marginBalanceValue = numeral(values[14]).value();
return data;
}
}
exports.TwseScraper = TwseScraper;