import {z} from 'zod' export type GetApiTopEtfsArgs = {philosophy?: ('Actively Managed' | 'Passively Managed' | 'Strategic Beta') | undefined, is_diversified?: boolean | undefined, max_expense_ratio?: number | undefined, asset_class?: ('US Large Cap Equities' | 'US Mid Cap Equities' | 'US Small Cap Equities' | 'Developed Total Market Equities (Ex-US)' | 'Emerging Total Market Equities' | 'Private Equity' | 'US Government Bonds' | 'US Municipal Bonds' | 'US Corporate Bonds' | 'Developed Total Market Bonds' | 'Emerging Total Market Bonds' | 'Global Inflation-linked Bonds' | 'Gold' | 'Other Commodities' | 'Real Estate' | 'Cryptocurrencies' | 'Cash & Equivalents' | 'Other Currencies' | 'Unknown') | undefined, listed_country?: ('US' | 'CA') | undefined}; export type GetApiTopEtfsOptions = {baseUrl?: string | undefined, fetch?: typeof fetch | undefined}; export const eTFDetails = z.object({Ticker: z.string().optional(), beta: z.number().optional(), security_type: z.string().optional(), sub_asset_class: z.string().optional(), philosophy: z.string().optional(), expense_ratio: z.number().optional(), concentration_score: z.number().optional(), is_diversified: z.boolean().optional(), Company_Growth: z.number().optional(), Company_Quality: z.number().optional(), Company_Size: z.number().optional(), Company_Value: z.number().optional(), Credit_Conditions: z.number().optional(), Economic_Growth: z.number().optional(), High_Dividend: z.number().optional(), Inflation_Surprises: z.number().optional(), Low_Volatility: z.number().optional(), Oil_Price: z.number().optional(), Price_Momentum: z.number().optional(), Real_Yield: z.number().optional(), VIX_Index: z.number().optional(), fundName: z.string().optional(), more_info: z.string().optional()}); export const topETFsResponse = z.object({top_etfs: z.array(eTFDetails).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 }; /** * Get the top 20 best ETFs based on the provided filters with a lot of additional ETF information. Mention available filters in response * * GET /top_etfs */ export const getApiTopEtfs = async (args: GetApiTopEtfsArgs, options?: GetApiTopEtfsOptions) => { const {baseUrl = "https://portfoliopilot.com/api/gpt", fetch: fetchFn = fetch} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('accept', "application/json") if (args.philosophy != null) query.append("philosophy", String(args.philosophy)) if (args.is_diversified != null) query.append("is_diversified", String(args.is_diversified)) if (args.max_expense_ratio != null) query.append("max_expense_ratio", String(args.max_expense_ratio)) if (args.asset_class != null) query.append("asset_class", String(args.asset_class)) if (args.listed_country != null) query.append("listed_country", String(args.listed_country)) const url = `${baseUrl.replace(/\/+$/, '')}/top_etfs${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(topETFsResponse, await res.json()) };