Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/rest/futopt/historical/candles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<RestFutOptHistoricalCandlesResponse>;
return request(`historical/candles/${encodeURIComponent(symbol)}`, options) as Promise<RestFutOptHistoricalCandlesResponse>;
}
2 changes: 1 addition & 1 deletion src/rest/futopt/historical/daily.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<RestFutOptHistoricalDailyResponse>;
return request(`historical/daily/${encodeURIComponent(symbol)}`, options) as Promise<RestFutOptHistoricalDailyResponse>;
}
2 changes: 1 addition & 1 deletion src/rest/futopt/intraday/candles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<RestFutOptIntradayCandlesResponse>;
return request(`intraday/candles/${encodeURIComponent(symbol)}`, options) as Promise<RestFutOptIntradayCandlesResponse>;
}
42 changes: 36 additions & 6 deletions src/rest/futopt/intraday/quote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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<RestFutOptIntradayQuoteResponse>;
return request(`intraday/quote/${encodeURIComponent(symbol)}`, options) as Promise<RestFutOptIntradayQuoteResponse>;
}
2 changes: 1 addition & 1 deletion src/rest/futopt/intraday/ticker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<RestFutOptIntradayTickerResponse>;
return request(`intraday/ticker/${encodeURIComponent(symbol)}`, options) as Promise<RestFutOptIntradayTickerResponse>;
}
2 changes: 2 additions & 0 deletions src/rest/futopt/intraday/tickers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface RestFutOptIntradayTickersParams {
session?: 'REGULAR' | 'AFTERHOURS';
product?: string;
contractType?: 'I' | 'R' | 'B' | 'C' | 'S' | 'E';
isSpread?: boolean;
}

export interface RestFutOptIntradayTickersResponse {
Expand All @@ -14,6 +15,7 @@ export interface RestFutOptIntradayTickersResponse {
session: string;
product?: string;
contractType?: string;
isSpread?: boolean;
data: Array<{
type: string;
contractType: string;
Expand Down
3 changes: 2 additions & 1 deletion src/rest/futopt/intraday/trades.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface RestFutOptIntradayTradesParams {
session?: 'afterhours';
offset?: number;
limit?: number;
isTrial?: boolean;
}

export interface RestFutOptIntradayTradesResponse {
Expand All @@ -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<RestFutOptIntradayTradesResponse>;
return request(`intraday/trades/${encodeURIComponent(symbol)}`, options) as Promise<RestFutOptIntradayTradesResponse>;
}
2 changes: 1 addition & 1 deletion src/rest/futopt/intraday/volumes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<RestFutOptIntradayVolumesResponse>;
return request(`intraday/volumes/${encodeURIComponent(symbol)}`, options) as Promise<RestFutOptIntradayVolumesResponse>;
}
30 changes: 30 additions & 0 deletions test/rest-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' } },
);
});
});


Expand Down Expand Up @@ -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()', () => {
Expand Down Expand Up @@ -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()', () => {
Expand Down
Loading