diff --git a/README.md b/README.md index 5e1c154..d10aa1b 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,26 @@ stock.intraday.quote({ symbol: '2330' }) .then(data => console.log(data)); ``` +#### Futures & Options spread contracts + +Spread (combination) contract symbols contain a `/` separator (e.g. `MXFA6/C6`). +The symbol is URL-encoded automatically, so you can pass it as-is. Their quotes +may carry **negative** prices. + +```js +// List tradable spread contracts +futopt.intraday.tickers({ type: 'FUTURE', isSpread: true }) + .then(data => console.log(data)); + +// Quote a spread contract +futopt.intraday.quote({ symbol: 'MXFA6/C6' }) + .then(data => console.log(data)); +``` + +The futopt quote response includes trial-matching (試搓) fields `lastTrial` and +`isTrial` (both `null`/`false` outside the trial session). You can also fetch +trial-matching trade ticks with `futopt.intraday.trades({ symbol, isTrial: true })`. + ### WebSocket API ```js @@ -58,6 +78,10 @@ stock.on('message', (message) => { }); ``` +The futopt `books` channel may include an extended 6th order-book level as +`derivedBid` / `derivedAsk` (present only when the exchange sends DERIVED-FLAG), +and trial-session messages carry an `isTrial` flag. + ## License [MIT](LICENSE) diff --git a/src/rest/futopt/historical/candles.ts b/src/rest/futopt/historical/candles.ts index 663b7ed..eb4d59e 100644 --- a/src/rest/futopt/historical/candles.ts +++ b/src/rest/futopt/historical/candles.ts @@ -29,5 +29,5 @@ export interface RestFutOptHistoricalCandlesResponse { export const candles = (request: RestClientRequest, params: RestFutOptHistoricalCandlesParams) => { const { symbol, ...options } = params; - return request(`historical/candles/${symbol}`, options) as Promise; + return request(`historical/candles/${encodeURIComponent(symbol)}`, options) as Promise; } diff --git a/src/rest/futopt/historical/daily.ts b/src/rest/futopt/historical/daily.ts index 84a5cdd..79b25ad 100644 --- a/src/rest/futopt/historical/daily.ts +++ b/src/rest/futopt/historical/daily.ts @@ -28,5 +28,5 @@ export interface RestFutOptHistoricalDailyResponse { export const daily = (request: RestClientRequest, params: RestFutOptHistoricalDailyParams) => { const { symbol, ...options } = params; - return request(`historical/daily/${symbol}`, options) as Promise; + return request(`historical/daily/${encodeURIComponent(symbol)}`, options) as Promise; } diff --git a/src/rest/futopt/intraday/candles.ts b/src/rest/futopt/intraday/candles.ts index c3d2beb..cd797c2 100644 --- a/src/rest/futopt/intraday/candles.ts +++ b/src/rest/futopt/intraday/candles.ts @@ -25,5 +25,5 @@ export interface RestFutOptIntradayCandlesResponse { export const candles = (request: RestClientRequest, params: RestFutOptIntradayCandlesParams) => { const { symbol, ...options } = params; - return request(`intraday/candles/${symbol}`, options) as Promise; + return request(`intraday/candles/${encodeURIComponent(symbol)}`, options) as Promise; } diff --git a/src/rest/futopt/intraday/quote.ts b/src/rest/futopt/intraday/quote.ts index 439ba52..b5ba4bd 100644 --- a/src/rest/futopt/intraday/quote.ts +++ b/src/rest/futopt/intraday/quote.ts @@ -9,9 +9,8 @@ export interface RestFutOptIntradayQuoteResponse { date: string; type: string; exchange: string; + market: string; symbol: string; - name: string; - previousClose: number; openPrice: number; openTime: number; highPrice: number; @@ -21,11 +20,11 @@ export interface RestFutOptIntradayQuoteResponse { closePrice: number; closeTime: number; lastPrice: number; - lastSize: number; avgPrice: number; change: number; changePercent: number; amplitude: number; + lastSize: number; bids: Array<{ price: number; size: number; @@ -35,19 +34,50 @@ export interface RestFutOptIntradayQuoteResponse { size: number; }>; total: { + tradeValue: number; tradeVolume: number; - totalBidMatch: number; - totalAskMatch: number; + tradeVolumeAtBid: number; + tradeVolumeAtAsk: number; + transaction: number; + time: number; + }; + priceLimits: { + price: number; + bid: number; + ask: number; + curb: number; }; lastTrade: { + bid: number; + ask: number; price: number; size: number; time: number; + serial: number; + }; + lastTrial: { + bid: number; + ask: number; + price: number; + size: number; + time: number; + serial: number; + }; + tradingHalt: { + isHalted: boolean; + time: number; }; + isTrial: boolean; + isDelayedOpen: boolean; + isDelayedClose: boolean; + isContinuous: boolean; + isOpen: boolean; + isClose: boolean; + serial: number; lastUpdated: number; } export const quote = (request: RestClientRequest, params: RestFutOptIntradayQuoteParams) => { const { symbol, ...options } = params; - return request(`intraday/quote/${symbol}`, options) as Promise; + return request(`intraday/quote/${encodeURIComponent(symbol)}`, options) as Promise; } diff --git a/src/rest/futopt/intraday/ticker.ts b/src/rest/futopt/intraday/ticker.ts index 2bb62ab..e80b336 100644 --- a/src/rest/futopt/intraday/ticker.ts +++ b/src/rest/futopt/intraday/ticker.ts @@ -19,5 +19,5 @@ export interface RestFutOptIntradayTickerResponse { export const ticker = (request: RestClientRequest, params: RestFutOptIntradayTickerParams) => { const { symbol, ...options } = params; - return request(`intraday/ticker/${symbol}`, options) as Promise; + return request(`intraday/ticker/${encodeURIComponent(symbol)}`, options) as Promise; } diff --git a/src/rest/futopt/intraday/tickers.ts b/src/rest/futopt/intraday/tickers.ts index e0dac53..4465cb0 100644 --- a/src/rest/futopt/intraday/tickers.ts +++ b/src/rest/futopt/intraday/tickers.ts @@ -6,6 +6,7 @@ export interface RestFutOptIntradayTickersParams { session?: 'REGULAR' | 'AFTERHOURS'; product?: string; contractType?: 'I' | 'R' | 'B' | 'C' | 'S' | 'E'; + isSpread?: boolean; } export interface RestFutOptIntradayTickersResponse { @@ -14,6 +15,7 @@ export interface RestFutOptIntradayTickersResponse { session: string; product?: string; contractType?: string; + isSpread?: boolean; data: Array<{ type: string; contractType: string; diff --git a/src/rest/futopt/intraday/trades.ts b/src/rest/futopt/intraday/trades.ts index 0fe208d..d417600 100644 --- a/src/rest/futopt/intraday/trades.ts +++ b/src/rest/futopt/intraday/trades.ts @@ -5,6 +5,7 @@ export interface RestFutOptIntradayTradesParams { session?: 'afterhours'; offset?: number; limit?: number; + isTrial?: boolean; } export interface RestFutOptIntradayTradesResponse { @@ -21,5 +22,5 @@ export interface RestFutOptIntradayTradesResponse { export const trades = (request: RestClientRequest, params: RestFutOptIntradayTradesParams) => { const { symbol, ...options } = params; - return request(`intraday/trades/${symbol}`, options) as Promise; + return request(`intraday/trades/${encodeURIComponent(symbol)}`, options) as Promise; } diff --git a/src/rest/futopt/intraday/volumes.ts b/src/rest/futopt/intraday/volumes.ts index 1dde6ff..b29f95d 100644 --- a/src/rest/futopt/intraday/volumes.ts +++ b/src/rest/futopt/intraday/volumes.ts @@ -18,5 +18,5 @@ export interface RestFutOptIntradayVolumesResponse { export const volumes = (request: RestClientRequest, params: RestFutOptIntradayVolumesParams) => { const { symbol, ...options } = params; - return request(`intraday/volumes/${symbol}`, options) as Promise; + return request(`intraday/volumes/${encodeURIComponent(symbol)}`, options) as Promise; } diff --git a/test/rest-client.spec.ts b/test/rest-client.spec.ts index b69c049..b0d2946 100644 --- a/test/rest-client.spec.ts +++ b/test/rest-client.spec.ts @@ -686,6 +686,16 @@ describe('RestClient', () => { { headers: { 'Authorization': 'Bearer bearer-token' } }, ); }); + + it('should request spread tickers with isSpread filter', async () => { + const client = new RestClient({ apiKey: 'api-key' }); + const futopt = client.futopt as RestFutOptClient; + await futopt.intraday.tickers({ type: 'FUTURE', isSpread: true }); + expect(fetch).toBeCalledWith( + 'https://api.fugle.tw/marketdata/v1.0/futopt/intraday/tickers?isSpread=true&type=FUTURE', + { headers: { 'X-API-KEY': 'api-key' } }, + ); + }); }); @@ -731,6 +741,16 @@ describe('RestClient', () => { { headers: { 'Authorization': 'Bearer bearer-token' } }, ); }); + + it('should url-encode spread contract symbols', async () => { + const client = new RestClient({ apiKey: 'api-key' }); + const futopt = client.futopt as RestFutOptClient; + await futopt.intraday.quote({ symbol: 'MXFA6/C6' }); + expect(fetch).toBeCalledWith( + 'https://api.fugle.tw/marketdata/v1.0/futopt/intraday/quote/MXFA6%2FC6', + { headers: { 'X-API-KEY': 'api-key' } }, + ); + }); }); describe('.candles()', () => { @@ -775,6 +795,16 @@ describe('RestClient', () => { { headers: { 'Authorization': 'Bearer bearer-token' } }, ); }); + + it('should request trial trades with isTrial filter', async () => { + const client = new RestClient({ apiKey: 'api-key' }); + const futopt = client.futopt as RestFutOptClient; + await futopt.intraday.trades({ symbol: 'TXFH4', isTrial: true }); + expect(fetch).toBeCalledWith( + 'https://api.fugle.tw/marketdata/v1.0/futopt/intraday/trades/TXFH4?isTrial=true', + { headers: { 'X-API-KEY': 'api-key' } }, + ); + }); }); describe('.volumes()', () => {