import {z} from 'zod' export type GetApiTopStocksArgs = {sector?: ('Healthcare' | 'Energy' | 'Technology' | 'Financial Services' | 'Basic Materials' | 'Industrials' | 'Real Estate' | 'Consumer Defensive' | 'Manufacturing' | 'Consumer Cyclical' | 'Communication Services' | 'Information' | 'Retail Trade' | 'Professional, Scientific, and Technical Services' | 'Transportation and Warehousing' | 'Finance and Insurance' | 'Health Care and Social Assistance' | 'Utilities' | 'Educational Services' | 'Mining, Quarrying, and Oil and Gas Extraction' | 'Administrative and Support and Waste Management and Remediation Services' | 'Agriculture, Forestry, Fishing and Hunting' | 'Construction' | 'Wholesale Trade' | 'Arts, Entertainment, and Recreation' | 'Real Estate and Rental and Leasing' | 'Accommodation and Food Services' | 'Management of Companies and Enterprises') | undefined, min_marketcap?: number | undefined, country?: ('US' | 'CA') | undefined, sort_by?: ('expected_sharpe' | 'ai_sentiment_score' | 'expected_return' | 'market_cap') | undefined, sort_order?: ('descending' | 'ascending') | undefined}; export type GetApiTopStocksOptions = {baseUrl?: string | undefined, fetch?: typeof fetch | undefined}; export const stockDetails = z.object({Ticker: z.string().optional(), beta: z.number().optional(), security_type: z.string().optional(), sub_asset_class: z.string().optional(), name: z.string().optional(), country: z.string().optional(), exchange: z.string().optional(), detailed_info: z.string().optional(), sector: z.string().optional(), industry: z.string().optional(), marketcap: z.number().optional(), employees: z.number().optional(), ceo: z.string().optional(), similar: z.string().optional(), logo: z.string().optional(), url: z.string().optional(), tags: z.string().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(), seriesName: z.string().optional(), more_info: z.string().optional()}); export const topStocksResponse = z.object({top_stocks: z.array(stockDetails).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 stocks with the highest expected Sharpe based on the provided filters. * * GET /top_stocks */ export const getApiTopStocks = async (args: GetApiTopStocksArgs, options?: GetApiTopStocksOptions) => { 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.sector != null) query.append("sector", String(args.sector)) if (args.min_marketcap != null) query.append("min_marketcap", String(args.min_marketcap)) if (args.country != null) query.append("country", String(args.country)) if (args.sort_by != null) query.append("sort_by", String(args.sort_by)) if (args.sort_order != null) query.append("sort_order", String(args.sort_order)) const url = `${baseUrl.replace(/\/+$/, '')}/top_stocks${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(topStocksResponse, await res.json()) };