Skip to content

Commit

Permalink
feat: support for fetching futures & options realtime quote
Browse files Browse the repository at this point in the history
  • Loading branch information
Kevin Wang committed Jan 2, 2024
1 parent 20bb9d7 commit 92d4538
Show file tree
Hide file tree
Showing 14 changed files with 473 additions and 6 deletions.
114 changes: 109 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
* [.market.breadth(options)](#marketbreadthoptions)
* [.market.instTrades(options)](#marketinsttradesoptions)
* [.market.marginTrades(options)](#marketmargintradesoptions)
* [.futopt.list(options)](#futoptlist)
* [.futopt.quote(options)](#futoptquoteoptions)
* [.futopt.txfInstTrades(options)](#futopttxfinsttradesoptions)
* [.futopt.txoInstTrades(options)](#futopttxoinsttradesoptions)
* [.futopt.txoPutCallRatio(options)](#futopttxoputcallratiooptions)
Expand Down Expand Up @@ -121,10 +123,10 @@ twstock.stocks.list({ market: 'TSE' })
* `lastPrice`: {number} 最後成交價格
* `lastSize`: {number} 最後成交數量
* `totalVoluem`: {number} 總成交量
* `bidPrice`: {number[]} 最佳委託買進價格
* `askPrice`: {number[]} 最佳委託賣出價格
* `bidSize`: {number[]} 最佳委託買進數量
* `askSize`: {number[]} 最佳委託賣出數量
* `bidPrice`: {number[]} 最佳委買價格
* `askPrice`: {number[]} 最佳委賣價格
* `bidSize`: {number[]} 最佳委買數量
* `askSize`: {number[]} 最佳委賣數量
* `lastUpdated`: {number} 最後更新時間

```js
Expand Down Expand Up @@ -835,6 +837,109 @@ twstock.market.marginTrades({ date: '2023-01-30', market: 'TSE' })
// }
```

### `.futopt.list()`

取得期貨與選擇權契約列表。

* Returns: {Promise} 成功時以 {Object[]} 履行,該陣列包含以下物件屬性:
* `symbol`: {string} 契約代號
* `name`: {string} 契約名稱
* `exchange`: {string} 交易所
* `market`: {string} 市場別
* `industry`: {string} 產業別
* `listedDate`: {string} 上市日期

```js
twstock.futopt.list()
.then(data => console.log(data));
// Prints:
// [
// {
// symbol: 'BRFC4',
// name: '布蘭特原油期貨2024/03',
// exchange: 'TAIFEX',
// market: 'FUTOPT',
// type: '原油期貨',
// industry: '00',
// listedDate: '2023-11-01'
// },
// ... more items
// ]
```

### `.futopt.quote(options)`

取得期貨與選擇權契約即時行情。

* `options`: {Object}
* `symbol`: {string} 契約代號
* `afterhours` (optional): {boolean} 盤後交易
* Returns: {Promise} 成功時以 {Object} 履行,包含以下物件屬性:
* `symbol`: {string} 契約代號
* `name`: {string} 契約名稱
* `referencePrice`: {number} 參考價
* `limitUpPrice`: {number} 漲停價
* `limitDownPrice`: {number} 跌停價
* `openPrice`: {number} 開盤價
* `highPrice`: {number} 最高價
* `lowPrice`: {number} 最低價
* `lastPrice`: {number} 成交價
* `lastSize`: {number} 單量
* `testPrice`: {number} 試撮價
* `testSize`: {number} 試撮量
* `testTime`: {string} 試撮時間
* `totalVoluem`: {number} 成交量
* `openInterest`: {number} 未平倉量
* `bidOrders`: {number} 委買筆數
* `askOrders`: {number} 委賣筆數
* `bidVolume`: {number} 委買口數
* `askVolume`: {number} 委賣口數
* `bidPrice`: {number[]} 最佳委買價格
* `askPrice`: {number[]} 最佳委賣價格
* `bidSize`: {number[]} 最佳委買數量
* `askSize`: {number[]} 最佳委賣數量
* `extBidPrice`: {number} 最佳衍生一檔買價
* `extAskPrice`: {number} 最佳衍生一檔賣價
* `extBidSize`: {number} 最佳衍生一檔買量
* `extAskSize`: {number} 最佳衍生一檔賣量
* `lastUpdated`: {string} 最後更新時間

```js
twstock.futopt.quote({ symbol: 'TXFA4' })
.then(data => console.log(data));
// Prints:
// {
// symbol: 'TXFA4',
// name: '臺股期貨2024/01',
// referencePrice: 17870,
// limitUpPrice: 19657,
// limitDownPrice: 16083,
// openPrice: 17838,
// highPrice: 17920,
// lowPrice: 17751,
// lastPrice: 17798,
// lastSize: 3,
// testPrice: 17831,
// testSize: 256,
// testTime: '08:44:55.000+08:00',
// totalVoluem: 116498,
// openInterest: 103404,
// bidOrders: 67979,
// askOrders: 66456,
// bidVolume: 124038,
// askVolume: 124080,
// bidPrice: [ 17798, 17797, 17796, 17795, 17794 ],
// askPrice: [ 17800, 17801, 17802, 17803, 17804 ],
// bidSize: [ 31, 26, 38, 24, 18 ],
// askSize: [ 6, 16, 30, 18, 22 ],
// extBidPrice: 17795,
// extAskPrice: 0,
// extBidSize: 2,
// extAskSize: 0,
// lastUpdated: '2024-01-02T13:44:59.000+08:00'
// }
```

### `.futopt.txfInstTrades(options)`

取得臺股期貨在特定日期的三大法人交易口數、契約金額與未平倉餘額。
Expand Down Expand Up @@ -881,7 +986,6 @@ twstock.market.marginTrades({ date: '2023-01-30', market: 'TSE' })
* `dealersNetOiVolume`: {number} 自營商-多空未平倉口數淨額
* `dealersNetOiValue`: {number} 自營商-多空未平倉契約金額淨額(千元)


```js
twstock.futopt.txfInstTrades({ date: '2023-01-30' })
.then(data => console.log(data));
Expand Down
1 change: 1 addition & 0 deletions src/enums/scraper.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export enum Scraper {
Taifex = 'taifex',
Tdcc = 'tdcc',
MisTwse = 'mis-twse',
MisTaifex = 'mis-taifex',
Mops = 'mops',
Isin = 'isin',
}
1 change: 1 addition & 0 deletions src/interfaces/ticker.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Exchange, Industry, Market } from '../enums';

export interface Ticker {
symbol: string;
name: string;
exchange: Exchange;
market: Market;
type?: string;
Expand Down
1 change: 1 addition & 0 deletions src/scrapers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './tpex-scraper';
export * from './taifex-scraper';
export * from './tdcc-scraper';
export * from './mis-twse-scraper';
export * from './mis-taifex-scraper';
export * from './mops-scraper';
export * from './isin-scraper';
export * from './scraper-factory';
63 changes: 63 additions & 0 deletions src/scrapers/mis-taifex-scraper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import * as _ from 'lodash';
import * as numeral from 'numeral';
import { DateTime } from 'luxon';
import { Scraper } from './scraper';
import { Ticker } from '../interfaces';

export class MisTaifexScraper extends Scraper {
async fetchFutOptQuote(options: { ticker: Ticker, afterhours?: boolean }) {
const { ticker, afterhours } = options;
const body = JSON.stringify({ SymbolID: [this.extractSymbolIdFromTicker(ticker, { afterhours })] });
const url = `https://mis.taifex.com.tw/futures/api/getQuoteDetail`;

const response = await this.httpService.post(url, body, {
headers: { 'Content-Type': 'application/json' },
});
const json = (response.data.RtCode === '0') && response.data;
if (!json) return null;

const data = json.RtData.QuoteList.map((row: any) => ({
symbol: ticker.symbol,
name: ticker.name,
referencePrice: row.CRefPrice && numeral(row.CRefPrice).value(),
limitUpPrice: row.CCeilPrice && numeral(row.CCeilPrice).value(),
limitDownPrice: row.CFloorPrice && numeral(row.CFloorPrice).value(),
openPrice: row.COpenPrice && numeral(row.COpenPrice).value(),
highPrice: row.CHighPrice && numeral(row.CHighPrice).value(),
lowPrice: row.CLowPrice && numeral(row.CLowPrice).value(),
lastPrice: row.CLastPrice && numeral(row.CLastPrice).value(),
lastSize: row.CSingleVolume && numeral(row.CSingleVolume).value(),
testPrice: row.CTestPrice && numeral(row.CTestPrice).value(),
testSize: row.CTestVolume && numeral(row.CTestVolume).value(),
testTime: row.CTestTime && DateTime.fromFormat(row.CTestTime, 'hhmmss').toISOTime(),
totalVoluem: row.CTotalVolume && numeral(row.CTotalVolume).value(),
openInterest: row.OpenInterest && numeral(row.OpenInterest).value(),
bidOrders: row.CBidCount && numeral(row.CBidCount).value(),
askOrders: row.CAskCount && numeral(row.CAskCount).value(),
bidVolume: row.CBidUnit && numeral(row.CBidUnit).value(),
askVolume: row.CAskUnit && numeral(row.CAskUnit).value(),
bidPrice: [row.CBidPrice1, row.CBidPrice2, row.CBidPrice3, row.CBidPrice4, row.CBidPrice5].map(price => numeral(price).value()),
askPrice: [row.CAskPrice1, row.CAskPrice2, row.CAskPrice3, row.CAskPrice4, row.CAskPrice5].map(price => numeral(price).value()),
bidSize: [row.CBidSize1, row.CBidSize2, row.CBidSize3, row.CBidSize4, row.CBidSize5].map(size => numeral(size).value()),
askSize: [row.CAskSize1, row.CAskSize2, row.CAskSize3, row.CAskSize4, row.CAskSize5].map(size => numeral(size).value()),
extBidPrice: row.CExtBidPrice && numeral(row.CExtBidPrice).value(),
extAskPrice: row.CExtAskPrice && numeral(row.CExtAskPrice).value(),
extBidSize: row.CExtBidSize && numeral(row.CExtBidSize).value(),
extAskSize: row.CExtAskSize && numeral(row.CExtAskSize).value(),
lastUpdated: DateTime.fromFormat(`${row.CDate} ${row.CTime}`, 'yyyyMMdd hhmmss').toISO(),
})) as Record<string, any>[];
return data.find(row => row.symbol.includes(ticker.symbol));
}

private extractSymbolIdFromTicker(ticker: Ticker, options: { afterhours?: boolean }) {
const { symbol, type } = ticker;
const { afterhours } = options;

if (type && type.includes('期貨')) {
return afterhours ? `${symbol}-M` : `${symbol}-F`;
}
if (type && type.includes('選擇權')) {
return afterhours ? `${symbol}-N` : `${symbol}-O`;
}
}
}
6 changes: 6 additions & 0 deletions src/scrapers/scraper-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { TpexScraper } from './tpex-scraper';
import { TaifexScraper } from './taifex-scraper';
import { TdccScraper } from './tdcc-scraper';
import { MisTwseScraper } from './mis-twse-scraper';
import { MisTaifexScraper } from './mis-taifex-scraper';
import { MopsScraper } from './mops-scraper';
import { IsinScraper } from './isin-scraper';
import { Scraper } from './scraper';
Expand All @@ -24,6 +25,7 @@ export class ScraperFactory {
[ScraperType.Taifex]: TaifexScraper,
[ScraperType.Tdcc]: TdccScraper,
[ScraperType.MisTwse]: MisTwseScraper,
[ScraperType.MisTaifex]: MisTaifexScraper,
[ScraperType.Mops]: MopsScraper,
[ScraperType.Isin]: IsinScraper,
};
Expand Down Expand Up @@ -56,6 +58,10 @@ export class ScraperFactory {
return this.get(ScraperType.MisTwse) as MisTwseScraper;
}

getMisTaifexScraper() {
return this.get(ScraperType.MisTaifex) as MisTaifexScraper;
}

getMopsScraper() {
return this.get(ScraperType.Mops) as MopsScraper;
}
Expand Down
13 changes: 13 additions & 0 deletions src/twstock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export class TwStock {
get futopt() {
return {
list: this.getFutOptList.bind(this),
quote: this.getFutOptQuote.bind(this),
txfInstTrades: this.getFutOptTxfInstTrades.bind(this),
txoInstTrades: this.getFutOptTxoInstTrades.bind(this),
txoPutCallRatio: this.getFutOptTxoPutCallRatio.bind(this),
Expand Down Expand Up @@ -355,6 +356,18 @@ export class TwStock {
return data;
}

private async getFutOptQuote(options: { symbol: string, afterhours?: boolean }) {
const { symbol, afterhours } = options;

if (!this._futopt.has(symbol)) {
const futopt = await this.loadFutOpt({ symbol });
if (!futopt.length) throw new Error('symbol not found');
}

const ticker = this._futopt.get(symbol) as Ticker;
return this._scraper.getMisTaifexScraper().fetchFutOptQuote({ ticker, afterhours });
}

private async getFutOptTxfInstTrades(options: { date: string }) {
const { date } = options;
return this._scraper.getTaifexScraper().fetchTxfInstTrades({ date });
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/fetched-stocks-info.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
[{"symbol":"6488","name":"環球晶","exchange":"TPEx","market":"OTC","industry":"24","listedDate":"2015-09-25"},{"symbol":"2330","name":"台積電","exchange":"TWSE","market":"TSE","industry":"24","listedDate":"1994-09-05"}]
[{"symbol":"6488","name":"環球晶","exchange":"TPEx","market":"OTC","industry":"24","listedDate":"2015-09-25"},{"symbol":"2330","name":"台積電","exchange":"TWSE","market":"TSE","industry":"24","listedDate":"1994-09-05"},{"symbol":"TXFA4","name":"臺股期貨2024/01","exchange":"TAIFEX","market":"FUTOPT","type":"臺股期貨","listedDate":"2023-10-19"}]
1 change: 1 addition & 0 deletions test/fixtures/futures-quote-afterhours.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"RtCode":"0","RtMsg":"","RtData":{"QuoteList":[{"Status":"","SymbolID":"TXFA4-M","SpotID":"","DispCName":"臺指期014","DispEName":"TX014","COpenPrice":"17793.00","CHighPrice":"17817.00","CLowPrice":"17791.00","CLastPrice":"17804.00","CSingleVolume":"1","CTestPrice":"17792.00","CTestVolume":"40","CTestTime":"145955","CTotalVolume":"4184","OpenInterest":"","CRefPrice":"17800.00","CCeilPrice":"19580.00","CFloorPrice":"16020.00","CBidCount":"3431","CAskCount":"3526","CBidUnit":"7171","CAskUnit":"7191","CDate":"20240102","CTime":"172322","CBidPrice1":"17804.00","CBidPrice2":"17803.00","CBidPrice3":"17802.00","CBidPrice4":"17801.00","CBidPrice5":"17800.00","CAskPrice1":"17805.00","CAskPrice2":"17806.00","CAskPrice3":"17807.00","CAskPrice4":"17808.00","CAskPrice5":"17809.00","CBidSize1":"4","CBidSize2":"27","CBidSize3":"40","CBidSize4":"32","CBidSize5":"61","CAskSize1":"14","CAskSize2":"35","CAskSize3":"44","CAskSize4":"54","CAskSize5":"25","CExtBidPrice":"17801.00","CExtAskPrice":"17805.00","CExtBidSize":"2","CExtAskSize":"1","CTermHighPrice":"","CTermLowPrice":"","CBestBidPrice":"17804.00","CBestAskPrice":"17805.00","CBestBidSize":"4","CBestAskSize":"15","HugeTotalVolume":""}]}}
1 change: 1 addition & 0 deletions test/fixtures/futures-quote.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"RtCode":"0","RtMsg":"","RtData":{"QuoteList":[{"Status":"TC","SymbolID":"TXFA4-F","SpotID":"","DispCName":"臺指期014","DispEName":"TX014","COpenPrice":"17838.00","CHighPrice":"17920.00","CLowPrice":"17751.00","CLastPrice":"17798.00","CSingleVolume":"3","CTestPrice":"17831.00","CTestVolume":"256","CTestTime":"084455","CTotalVolume":"116498","OpenInterest":"103404","CRefPrice":"17870.00","CCeilPrice":"19657.00","CFloorPrice":"16083.00","CBidCount":"67979","CAskCount":"66456","CBidUnit":"124038","CAskUnit":"124080","CDate":"20240102","CTime":"134459","CBidPrice1":"17798.00","CBidPrice2":"17797.00","CBidPrice3":"17796.00","CBidPrice4":"17795.00","CBidPrice5":"17794.00","CAskPrice1":"17800.00","CAskPrice2":"17801.00","CAskPrice3":"17802.00","CAskPrice4":"17803.00","CAskPrice5":"17804.00","CBidSize1":"31","CBidSize2":"26","CBidSize3":"38","CBidSize4":"24","CBidSize5":"18","CAskSize1":"6","CAskSize2":"16","CAskSize3":"30","CAskSize4":"18","CAskSize5":"22","CExtBidPrice":"17795.00","CExtAskPrice":"0.00","CExtBidSize":"2","CExtAskSize":"0","CTermHighPrice":"17920.00","CTermLowPrice":"17751.00","CBestBidPrice":"17798.00","CBestAskPrice":"17800.00","CBestBidSize":"31","CBestAskSize":"6","HugeTotalVolume":""}]}}
1 change: 1 addition & 0 deletions test/fixtures/options-quote-afterhours.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"RtCode":"0","RtMsg":"","RtData":{"QuoteList":[{"Status":"","SymbolID":"TX118000A4-N","SpotID":"","DispCName":"臺指選W1014;18000買權","DispEName":"TX1W1014;18000C","COpenPrice":"2.800","CHighPrice":"3.400","CLowPrice":"2.000","CLastPrice":"2.500","CSingleVolume":"1","CTestPrice":"0.000","CTestVolume":"0","CTestTime":"","CTotalVolume":"2794","OpenInterest":"","CRefPrice":"2.600","CCeilPrice":"1780.000","CFloorPrice":"0.100","CBidCount":"284","CAskCount":"391","CBidUnit":"3014","CAskUnit":"4082","CDate":"20240102","CTime":"173912","CBidPrice1":"2.400","CBidPrice2":"2.300","CBidPrice3":"2.200","CBidPrice4":"2.100","CBidPrice5":"2.000","CAskPrice1":"2.600","CAskPrice2":"2.700","CAskPrice3":"2.800","CAskPrice4":"2.900","CAskPrice5":"3.000","CBidSize1":"4","CBidSize2":"97","CBidSize3":"24","CBidSize4":"5","CBidSize5":"7","CAskSize1":"99","CAskSize2":"35","CAskSize3":"24","CAskSize4":"7","CAskSize5":"14","CExtBidPrice":"","CExtAskPrice":"","CExtBidSize":"","CExtAskSize":"","CTermHighPrice":"","CTermLowPrice":"","CBestBidPrice":"2.400","CBestAskPrice":"2.600","CBestBidSize":"4","CBestAskSize":"99","HugeTotalVolume":""}]}}
1 change: 1 addition & 0 deletions test/fixtures/options-quote.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"RtCode":"0","RtMsg":"","RtData":{"QuoteList":[{"Status":"TC","SymbolID":"TX118000A4-O","SpotID":"","DispCName":"臺指選W1014;18000買權","DispEName":"TX1W1014;18000C","COpenPrice":"13.500","CHighPrice":"29.000","CLowPrice":"1.500","CLastPrice":"2.600","CSingleVolume":"25","CTestPrice":"13.500","CTestVolume":"18","CTestTime":"084455","CTotalVolume":"63256","OpenInterest":"20036","CRefPrice":"30.500","CCeilPrice":"1820.000","CFloorPrice":"0.100","CBidCount":"8406","CAskCount":"7963","CBidUnit":"61019","CAskUnit":"61561","CDate":"20240102","CTime":"134454","CBidPrice1":"2.500","CBidPrice2":"2.300","CBidPrice3":"2.200","CBidPrice4":"2.100","CBidPrice5":"2.000","CAskPrice1":"2.800","CAskPrice2":"2.900","CAskPrice3":"3.000","CAskPrice4":"3.100","CAskPrice5":"3.200","CBidSize1":"17","CBidSize2":"1","CBidSize3":"2","CBidSize4":"17","CBidSize5":"12","CAskSize1":"3","CAskSize2":"4","CAskSize3":"7","CAskSize4":"8","CAskSize5":"3","CExtBidPrice":"","CExtAskPrice":"","CExtBidSize":"","CExtAskSize":"","CTermHighPrice":"29.000","CTermLowPrice":"1.500","CBestBidPrice":"2.500","CBestAskPrice":"2.800","CBestBidSize":"17","CBestAskSize":"3","HugeTotalVolume":""}]}}
Loading

0 comments on commit 92d4538

Please sign in to comment.