import {z} from 'zod' export type GetApiV113fFlowsArgs = {dimension?: string | undefined, reportDate?: string | undefined, groupBy?: string | undefined, bucket?: string | undefined}; export type GetApiV113fFlowsOptions = {baseUrl?: string | undefined, ApiKey?: string | undefined, fetch?: typeof fetch | undefined}; export const holderSectorFlowDto = z.object({group: z.string().nullable().optional(), name: z.string().nullable().optional(), boughtValue: z.number().optional(), soldValue: z.number().optional(), netValue: z.number().optional()}); export const holderFlowBucketDto = z.object({token: z.string().nullable().optional(), label: z.string().nullable().optional(), filers: z.number().int().optional(), comparedFilers: z.number().int().optional(), equityAum: z.number().int().optional(), priorEquityAum: z.number().int().optional(), boughtValue: z.number().optional(), soldValue: z.number().optional(), netValue: z.number().optional(), netPercentOfPriorBook: z.number().nullable().optional(), netBuyers: z.number().int().optional(), netSellers: z.number().int().optional(), unpricedTrades: z.number().int().optional(), sectors: z.array(holderSectorFlowDto).nullable().optional()}); export const holderFlowsResponse = z.object({reportDate: z.string().optional(), previousReportDate: z.string().optional(), dimension: z.string().nullable().optional(), dimensionLabel: z.string().nullable().optional(), groupBy: z.string().nullable().optional(), sectorSplitAvailable: z.boolean().optional(), computedAt: z.string().optional(), buckets: z.array(holderFlowBucketDto).nullable().optional(), total: holderFlowBucketDto.optional(), availableReportDates: z.array(z.string()).nullable().optional()}); export const validateResponse = async ( schema: { readonly '~standard': { readonly validate: (value: unknown) => ({ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }) | Promise<{ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }> } }, value: unknown ): Promise => { const result = await schema['~standard'].validate(value) if (result.issues !== undefined) { const detail = result.issues .map(({ message, path }) => path?.length ? `${path.map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')}: ${message}` : message ) .join('; ') throw new Error(`Response does not match the schema: ${detail}`, { cause: { issues: result.issues } }) } return result.value }; /** * Market-wide 13F holder flows for a closed quarter: what each kind of filer bought and sold of * 13F common shares, its net flow, net flow as a percentage of its prior 13F book, and net * buyers against net sellers. Trades are each filer's share changes priced at the security's * average close over the quarter, else its quarter-end value per share (an exit at the quarter * before's); options and principal amounts are left out, and a share change from a corporate * action reads as a trade. Only filers with a 13F the quarter before are compared. Holder type * is each filer's current regulatory profile, never its name; client base and private funds * come from the Form ADV roster current at the quarter end (rosters from December 2019); * style, turnover, horizon and book size are measured from its own 13F book that quarter. * With groupBy=sector each bucket's flow is split by its issuers' sectors, with exchange-traded * products apart. Quarters still in their 13F filing window are not computed. * * GET /v1/13f/flows */ export const getApiV113fFlows = async (args: GetApiV113fFlowsArgs, options?: GetApiV113fFlowsOptions) => { const {baseUrl = "https://api.equibles.com", fetch: fetchFn = fetch, ApiKey} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('accept', "application/json") if (args.dimension != null) query.append("dimension", String(args.dimension)) if (args.reportDate != null) query.append("reportDate", String(args.reportDate)) if (args.groupBy != null) query.append("groupBy", String(args.groupBy)) if (args.bucket != null) query.append("bucket", String(args.bucket)) if (ApiKey !== undefined) headers.set('authorization', `Bearer ${ApiKey}`) const url = `${baseUrl.replace(/\/+$/, '')}/v1/13f/flows${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "GET", headers}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(holderFlowsResponse, await res.json()) };