From 7366d0d71ea09d52f5a7dcc7693b1a1b4483aa3a Mon Sep 17 00:00:00 2001 From: ZackFan Date: Thu, 20 Aug 2026 15:40:27 +0800 Subject: [PATCH 1/2] feat: add stock ownership institutionalTrades, directorHoldings & tdccDistribution REST endpoints Wraps the v1.0 /stock/ownership/{institutional-trades,director-holdings,tdcc-distribution}/:symbol endpoints (fugle-realtime issues 623 / 642 / 641). Same from/to/sort query contract as etfHoldings. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01DSkx8KegwNXgz4EDgdEcQa --- src/rest/stock/client.ts | 6 ++ src/rest/stock/ownership/director-holdings.ts | 38 +++++++ .../stock/ownership/institutional-trades.ts | 33 +++++++ src/rest/stock/ownership/tdcc-distribution.ts | 31 ++++++ test/rest-client.spec.ts | 99 +++++++++++++++++++ 5 files changed, 207 insertions(+) create mode 100644 src/rest/stock/ownership/director-holdings.ts create mode 100644 src/rest/stock/ownership/institutional-trades.ts create mode 100644 src/rest/stock/ownership/tdcc-distribution.ts diff --git a/src/rest/stock/client.ts b/src/rest/stock/client.ts index 70e32ef..eba12af 100644 --- a/src/rest/stock/client.ts +++ b/src/rest/stock/client.ts @@ -19,6 +19,9 @@ import { RestStockCorporateActionsCapitalChangesParams, capitalChanges } from '. import { RestStockCorporateActionsDividendsParams, dividends } from './corporate-actions/dividends'; import { RestStockCorporateActionsListingApplicantsParams, listingApplicants } from './corporate-actions/listing-applicants'; import { RestStockOwnershipEtfHoldingsParams, etfHoldings } from './ownership/etf-holdings'; +import { RestStockOwnershipInstitutionalTradesParams, institutionalTrades } from './ownership/institutional-trades'; +import { RestStockOwnershipDirectorHoldingsParams, directorHoldings } from './ownership/director-holdings'; +import { RestStockOwnershipTdccDistributionParams, tdccDistribution } from './ownership/tdcc-distribution'; export class RestStockClient extends RestClient { get intraday() { @@ -74,6 +77,9 @@ export class RestStockClient extends RestClient { const request = this.request; return { etfHoldings: (params: RestStockOwnershipEtfHoldingsParams) => etfHoldings(request, params), + institutionalTrades: (params: RestStockOwnershipInstitutionalTradesParams) => institutionalTrades(request, params), + directorHoldings: (params: RestStockOwnershipDirectorHoldingsParams) => directorHoldings(request, params), + tdccDistribution: (params: RestStockOwnershipTdccDistributionParams) => tdccDistribution(request, params), }; } } diff --git a/src/rest/stock/ownership/director-holdings.ts b/src/rest/stock/ownership/director-holdings.ts new file mode 100644 index 0000000..66d27fe --- /dev/null +++ b/src/rest/stock/ownership/director-holdings.ts @@ -0,0 +1,38 @@ +import { RestClientRequest } from '../../client'; + +export interface RestStockOwnershipDirectorHoldingsParams { + symbol: string; + from?: string; + to?: string; + sort?: 'asc' | 'desc'; +} + +export interface RestStockOwnershipDirectorHoldingsDirector { + order: number; + title: string; + name: string; + electedShares: number; + heldShares: number; + pledgedShares: number; + pledgeRatio: number; + relatedHeldShares: number; + relatedPledgedShares: number; + relatedPledgeRatio: number; +} + +export interface RestStockOwnershipDirectorHoldingsResponse { + type?: string; + exchange?: string; + market?: string; + symbol: string; + data: Array<{ + /** yyyy-MM (monthly) */ + date: string; + directors: RestStockOwnershipDirectorHoldingsDirector[]; + }>; +} + +export const directorHoldings = (request: RestClientRequest, params: RestStockOwnershipDirectorHoldingsParams) => { + const { symbol, ...options } = params; + return request(`ownership/director-holdings/${symbol}`, options) as Promise; +} diff --git a/src/rest/stock/ownership/institutional-trades.ts b/src/rest/stock/ownership/institutional-trades.ts new file mode 100644 index 0000000..1eecbff --- /dev/null +++ b/src/rest/stock/ownership/institutional-trades.ts @@ -0,0 +1,33 @@ +import { RestClientRequest } from '../../client'; + +export interface RestStockOwnershipInstitutionalTradesParams { + symbol: string; + from?: string; + to?: string; + sort?: 'asc' | 'desc'; +} + +export interface RestStockOwnershipInstitutionalTradesInvestor { + buy: number; + sell: number; + net: number; +} + +export interface RestStockOwnershipInstitutionalTradesResponse { + type?: string; + exchange?: string; + market?: string; + symbol: string; + data: Array<{ + date: string; + foreign: RestStockOwnershipInstitutionalTradesInvestor; + trust: RestStockOwnershipInstitutionalTradesInvestor; + dealer: RestStockOwnershipInstitutionalTradesInvestor; + total: number; + }>; +} + +export const institutionalTrades = (request: RestClientRequest, params: RestStockOwnershipInstitutionalTradesParams) => { + const { symbol, ...options } = params; + return request(`ownership/institutional-trades/${symbol}`, options) as Promise; +} diff --git a/src/rest/stock/ownership/tdcc-distribution.ts b/src/rest/stock/ownership/tdcc-distribution.ts new file mode 100644 index 0000000..1a8a6ef --- /dev/null +++ b/src/rest/stock/ownership/tdcc-distribution.ts @@ -0,0 +1,31 @@ +import { RestClientRequest } from '../../client'; + +export interface RestStockOwnershipTdccDistributionParams { + symbol: string; + from?: string; + to?: string; + sort?: 'asc' | 'desc'; +} + +export interface RestStockOwnershipTdccDistributionLevel { + range: string; + holders: number; + shares: number; + proportion: number; +} + +export interface RestStockOwnershipTdccDistributionResponse { + type?: string; + exchange?: string; + market?: string; + symbol: string; + data: Array<{ + date: string; + distributions: RestStockOwnershipTdccDistributionLevel[]; + }>; +} + +export const tdccDistribution = (request: RestClientRequest, params: RestStockOwnershipTdccDistributionParams) => { + const { symbol, ...options } = params; + return request(`ownership/tdcc-distribution/${symbol}`, options) as Promise; +} diff --git a/test/rest-client.spec.ts b/test/rest-client.spec.ts index 88206c6..95e7f15 100644 --- a/test/rest-client.spec.ts +++ b/test/rest-client.spec.ts @@ -312,6 +312,9 @@ describe('RestClient', () => { const stock = client.stock as RestStockClient; expect(stock.ownership).toBeDefined(); expect(stock.ownership).toHaveProperty('etfHoldings'); + expect(stock.ownership).toHaveProperty('institutionalTrades'); + expect(stock.ownership).toHaveProperty('directorHoldings'); + expect(stock.ownership).toHaveProperty('tdccDistribution'); }); describe('.etfHoldings()', () => { @@ -345,6 +348,102 @@ describe('RestClient', () => { ); }); }); + + describe('.institutionalTrades()', () => { + it('should request with api key', async () => { + const client = new RestClient({ apiKey: 'api-key' }); + const stock = client.stock as RestStockClient; + await stock.ownership.institutionalTrades({ symbol: '2330' }); + expect(fetch).toBeCalledWith( + 'https://api.fugle.tw/marketdata/v1.0/stock/ownership/institutional-trades/2330', + { headers: { 'X-API-KEY': 'api-key' } }, + ); + }); + + it('should request with bearer token', async () => { + const client = new RestClient({ bearerToken: 'bearer-token' }); + const stock = client.stock as RestStockClient; + await stock.ownership.institutionalTrades({ symbol: '2330' }); + expect(fetch).toBeCalledWith( + 'https://api.fugle.tw/marketdata/v1.0/stock/ownership/institutional-trades/2330', + { headers: { 'Authorization': 'Bearer bearer-token' } }, + ); + }); + + it('should request with query params', async () => { + const client = new RestClient({ apiKey: 'api-key' }); + const stock = client.stock as RestStockClient; + await stock.ownership.institutionalTrades({ symbol: '2330', from: '2026-07-01', to: '2026-07-31', sort: 'desc' }); + expect(fetch).toBeCalledWith( + 'https://api.fugle.tw/marketdata/v1.0/stock/ownership/institutional-trades/2330?from=2026-07-01&sort=desc&to=2026-07-31', + { headers: { 'X-API-KEY': 'api-key' } }, + ); + }); + }); + + describe('.directorHoldings()', () => { + it('should request with api key', async () => { + const client = new RestClient({ apiKey: 'api-key' }); + const stock = client.stock as RestStockClient; + await stock.ownership.directorHoldings({ symbol: '2330' }); + expect(fetch).toBeCalledWith( + 'https://api.fugle.tw/marketdata/v1.0/stock/ownership/director-holdings/2330', + { headers: { 'X-API-KEY': 'api-key' } }, + ); + }); + + it('should request with bearer token', async () => { + const client = new RestClient({ bearerToken: 'bearer-token' }); + const stock = client.stock as RestStockClient; + await stock.ownership.directorHoldings({ symbol: '2330' }); + expect(fetch).toBeCalledWith( + 'https://api.fugle.tw/marketdata/v1.0/stock/ownership/director-holdings/2330', + { headers: { 'Authorization': 'Bearer bearer-token' } }, + ); + }); + + it('should request with query params', async () => { + const client = new RestClient({ apiKey: 'api-key' }); + const stock = client.stock as RestStockClient; + await stock.ownership.directorHoldings({ symbol: '2330', from: '2026-01-01', to: '2026-05-31', sort: 'desc' }); + expect(fetch).toBeCalledWith( + 'https://api.fugle.tw/marketdata/v1.0/stock/ownership/director-holdings/2330?from=2026-01-01&sort=desc&to=2026-05-31', + { headers: { 'X-API-KEY': 'api-key' } }, + ); + }); + }); + + describe('.tdccDistribution()', () => { + it('should request with api key', async () => { + const client = new RestClient({ apiKey: 'api-key' }); + const stock = client.stock as RestStockClient; + await stock.ownership.tdccDistribution({ symbol: '2330' }); + expect(fetch).toBeCalledWith( + 'https://api.fugle.tw/marketdata/v1.0/stock/ownership/tdcc-distribution/2330', + { headers: { 'X-API-KEY': 'api-key' } }, + ); + }); + + it('should request with bearer token', async () => { + const client = new RestClient({ bearerToken: 'bearer-token' }); + const stock = client.stock as RestStockClient; + await stock.ownership.tdccDistribution({ symbol: '2330' }); + expect(fetch).toBeCalledWith( + 'https://api.fugle.tw/marketdata/v1.0/stock/ownership/tdcc-distribution/2330', + { headers: { 'Authorization': 'Bearer bearer-token' } }, + ); + }); + + it('should request with query params', async () => { + const client = new RestClient({ apiKey: 'api-key' }); + const stock = client.stock as RestStockClient; + await stock.ownership.tdccDistribution({ symbol: '2330', from: '2026-06-01', to: '2026-07-03', sort: 'desc' }); + expect(fetch).toBeCalledWith( + 'https://api.fugle.tw/marketdata/v1.0/stock/ownership/tdcc-distribution/2330?from=2026-06-01&sort=desc&to=2026-07-03', + { headers: { 'X-API-KEY': 'api-key' } }, + ); + }); + }); }); describe('.snapshot', () => { From ebf42a4f1bed7077b9e4af7fe55a7b3ff34a7a35 Mon Sep 17 00:00:00 2001 From: ZackFan Date: Thu, 20 Aug 2026 15:54:56 +0800 Subject: [PATCH 2/2] chore(release): 1.6.0-rc.1 --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1221941..36aea65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +# [1.6.0-rc.1](https://github.com/fugle-dev/fugle-marketdata-node/compare/v1.5.0...v1.6.0-rc.1) (2026-08-20) + + +### Features + +* add stock ownership institutionalTrades, directorHoldings & tdccDistribution REST endpoints ([7366d0d](https://github.com/fugle-dev/fugle-marketdata-node/commit/7366d0d71ea09d52f5a7dcc7693b1a1b4483aa3a)) + # [1.5.0](https://github.com/fugle-dev/fugle-marketdata-node/compare/v1.4.2...v1.5.0) (2026-08-14) diff --git a/package-lock.json b/package-lock.json index 2c48e8f..a1f32f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@fugle/marketdata", - "version": "1.5.0", + "version": "1.6.0-rc.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@fugle/marketdata", - "version": "1.5.0", + "version": "1.6.0-rc.1", "license": "MIT", "dependencies": { "isomorphic-fetch": "^3.0.0", diff --git a/package.json b/package.json index e63b1ed..fa56466 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fugle/marketdata", - "version": "1.5.0", + "version": "1.6.0-rc.1", "description": "Fugle MarketData API client library for Node.js", "main": "lib/index.js", "types": "lib/index.d.ts",