@sudowealth/schwab-api
Version:
TypeScript client for Charles Schwab API with OAuth support, market data, trading functionality, and complete type safety
400 lines (399 loc) • 16 kB
JavaScript
import { z } from 'zod';
import { mergeShapes } from '../../utils/schema-utils';
// --- Enums ---
export const UnderlyingExchangeEnum = z.enum([
'IND',
'ASE',
'NYS',
'NAS',
'NAP',
'PAC',
'OPR',
'BATS',
]);
export const OptionStrategyEnum = z.enum([
'SINGLE',
'ANALYTICAL',
'COVERED',
'VERTICAL',
'CALENDAR',
'STRANGLE',
'STRADDLE',
'BUTTERFLY',
'CONDOR',
'DIAGONAL',
'COLLAR',
'ROLL',
]);
export const PutCallEnum = z.enum(['PUT', 'CALL']);
export const ExpirationTypeEnum = z.enum(['M', 'Q', 'S', 'W']);
export const SettlementTypeEnum = z.enum(['A', 'P']); // AM or PM
// --- Nested Schemas ---
export const OptionDeliverablesSchema = z.object({
symbol: z.string().describe('Deliverable symbol'),
assetType: z
.string()
.describe('Asset type of the deliverable (e.g., EQUITY, CURRENCY)'), // Consider reusing InstrumentAssetTypeEnum if applicable
deliverableUnits: z
.union([z.string(), z.number()])
.describe('Units of the deliverable'),
currencyType: z
.string()
.optional()
.describe('Currency type of the deliverable'),
});
export const UnderlyingSchema = z.object({
ask: z.number().optional().describe('Current ask price for the underlying'),
askSize: z
.number()
.int()
.optional()
.describe('Number of shares for ask for the underlying'),
bid: z.number().optional().describe('Current bid price for the underlying'),
bidSize: z
.number()
.int()
.optional()
.describe('Number of shares for bid for the underlying'),
change: z
.number()
.optional()
.describe('Current day change for the underlying'),
close: z
.number()
.optional()
.describe("Previous day's closing price for the underlying"),
delayed: z
.boolean()
.optional()
.describe('Is the quote delayed for the underlying'),
description: z.string().optional().describe('Description of the underlying'),
exchangeName: UnderlyingExchangeEnum.optional().describe('Exchange of the underlying'),
fiftyTwoWeekHigh: z
.number()
.optional()
.describe('52-week high price for the underlying'),
fiftyTwoWeekLow: z
.number()
.optional()
.describe('52-week low price for the underlying'),
highPrice: z
.number()
.optional()
.describe("Current day's high price for the underlying"),
last: z.number().optional().describe('Last trade price for the underlying'),
lowPrice: z
.number()
.optional()
.describe("Current day's low price for the underlying"),
mark: z.number().optional().describe('Mark price for the underlying'),
markChange: z.number().optional().describe('Mark change for the underlying'),
markPercentChange: z
.number()
.optional()
.describe('Mark percent change for the underlying'),
openPrice: z
.number()
.optional()
.describe("Current day's opening price for the underlying"),
percentChange: z
.number()
.optional()
.describe('Percent change for the underlying'),
quoteTime: z
.number()
.int()
.optional()
.describe('Quote time in milliseconds since Epoch for the underlying'), // int64 in image
symbol: z.string().optional().describe('Symbol of the underlying'),
totalVolume: z
.number()
.int()
.optional()
.describe('Total volume for the underlying'), // int64 in image
tradeTime: z
.number()
.int()
.optional()
.describe('Last trade time in milliseconds since Epoch for the underlying'), // int64 in image
});
export const OptionContractSchema = z.object({
putCall: PutCallEnum.describe('Indicates if the option is a PUT or CALL'),
symbol: z.string().describe('Option symbol'),
description: z
.string()
.optional()
.describe('Description of the option contract'),
exchangeName: z.string().optional().describe('Exchange name for the option'),
bidPrice: z.number().optional().describe('Current bid price for the option'),
askPrice: z.number().optional().describe('Current ask price for the option'),
lastPrice: z.number().optional().describe('Last trade price for the option'),
markPrice: z.number().optional().describe('Mark price for the option'),
bidSize: z.number().int().optional().describe('Number of contracts for bid'),
askSize: z.number().int().optional().describe('Number of contracts for ask'),
lastSize: z
.number()
.int()
.optional()
.describe('Number of contracts in last trade'),
highPrice: z.number().optional().describe("Day's high price for the option"),
lowPrice: z.number().optional().describe("Day's low price for the option"),
openPrice: z
.number()
.optional()
.describe("Day's opening price for the option"),
closePrice: z
.number()
.optional()
.describe("Previous day's closing price for the option"),
totalVolume: z
.number()
.int()
.optional()
.describe('Total volume for the option'),
tradeDate: z
.number()
.int()
.optional()
.describe('Trade date of the option (numeric, possibly a timestamp or 0 if not set/applicable).'),
quoteTimeInLong: z
.number()
.int()
.optional()
.describe('Quote time in milliseconds since Epoch'),
tradeTimeInLong: z
.number()
.int()
.optional()
.describe('Last trade time in milliseconds since Epoch'),
netChange: z.number().optional().describe('Net change for the option'),
volatility: z
.number()
.optional()
.describe('Implied volatility for the option'),
delta: z.number().optional().describe('Delta value of the option'),
gamma: z.number().optional().describe('Gamma value of the option'),
theta: z.number().optional().describe('Theta value of the option'),
vega: z.number().optional().describe('Vega value of the option'),
rho: z.number().optional().describe('Rho value of the option'),
timeValue: z.number().optional().describe('Time value of the option'),
openInterest: z
.number()
.int()
.optional()
.describe('Open interest for the option'),
isInTheMoney: z.boolean().optional().describe('Is the option in the money'),
theoreticalOptionValue: z
.number()
.optional()
.describe('Theoretical value of the option'),
theoreticalVolatility: z
.number()
.optional()
.describe('Theoretical volatility of the option'),
isMini: z.boolean().optional().describe('Is it a mini option'),
isNonStandard: z.boolean().optional().describe('Is it a non-standard option'),
optionDeliverablesList: z
.array(OptionDeliverablesSchema)
.optional()
.describe('List of deliverables for the option'),
strikePrice: z.number().describe('Strike price of the option'),
expirationDate: z
.string()
.describe('Expiration date of the option (e.g. YYYY-MM-DDTHH:mm:ssZ)'),
daysToExpiration: z
.number()
.int()
.optional()
.describe('Number of days to expiration'),
expirationType: ExpirationTypeEnum.optional().describe('Type of expiration (Monthly, Weekly, etc.)'),
lastTradingDay: z
.number()
.int()
.optional()
.describe('Last trading day in milliseconds since Epoch'), // long in image
multiplier: z.number().optional().describe('Option contract multiplier'),
settlementType: SettlementTypeEnum.optional().describe('Settlement type (AM or PM)'),
deliverableNote: z.string().optional().describe('Note about deliverables'),
isIndexOption: z.boolean().optional().describe('Is it an index option'),
percentChange: z
.number()
.optional()
.describe('Percent change for the option'),
markChange: z.number().optional().describe('Mark change for the option'),
markPercentChange: z
.number()
.optional()
.describe('Mark percent change for the option'),
isPennyPilot: z
.boolean()
.optional()
.describe('Is the option part of the penny pilot program'),
intrinsicValue: z
.number()
.optional()
.describe('Intrinsic value of the option'),
optionRoot: z.string().optional().describe('Option root symbol'),
});
// Maps strike price (string) to array of OptionContract
export const OptionContractMapSchema = z.record(z.string(), z.array(OptionContractSchema));
// Maps expiration date (string) to a map of strike prices to arrays of OptionContracts
export const OptionContractDateMapSchema = z.record(z.string(), OptionContractMapSchema);
// --- Main OptionChain Schema ---
export const OptionChainSchema = z.object({
symbol: z
.string()
.optional()
.describe('Symbol for which the option chain is requested'),
status: z
.string()
.optional()
.describe('Status of the request (e.g., SUCCESS)'),
underlying: UnderlyingSchema.optional().describe('Details of the underlying instrument'),
strategy: OptionStrategyEnum.optional().describe('Strategy used for the option chain request'),
interval: z
.number()
.optional()
.describe('Interval for the strategy if applicable'),
isDelayed: z
.boolean()
.optional()
.describe('Indicates if the data is delayed'),
isIndex: z
.boolean()
.optional()
.describe('Indicates if the underlying is an index'),
daysToExpiration: z
.number()
.optional()
.describe('Days to expiration (might be specific to a leg if strategy is complex)'),
interestRate: z.number().optional().describe('Current interest rate'),
underlyingPrice: z
.number()
.optional()
.describe('Price of the underlying security at the time of the request'),
volatility: z.number().optional().describe('Volatility of the underlying'),
callExpDateMap: OptionContractDateMapSchema.optional().describe('Map of expiration dates to call option contracts. May be omitted if no call options exist for the query.'),
putExpDateMap: OptionContractDateMapSchema.optional().describe('Map of expiration dates to put option contracts. May be omitted if no put options exist for the query.'),
});
// --- Schemas for GET /chains endpoint ---
// Enums for Query Parameters
export const OptionContractTypeQueryEnum = z.enum(['CALL', 'PUT', 'ALL']);
export const OptionRangeQueryEnum = z.enum([
'ITM', // In-the-money
'NTM', // Near-the-money
'OTM', // Out-of-the-money
'SAK', // Strikes Above Market
'SBK', // Strikes Below Market
'SNK', // Strikes Near Market
'ALL', // All Strikes
]);
export const OptionExpMonthQueryEnum = z.enum([
'JAN',
'FEB',
'MAR',
'APR',
'MAY',
'JUN',
'JUL',
'AUG',
'SEP',
'OCT',
'NOV',
'DEC',
'ALL',
]);
export const OptionTypeQueryEnum = z.enum(['S', 'NS', 'ALL']); // Standard, Non-Standard, All
export const OptionEntitlementQueryEnum = z.enum(['PN', 'NP', 'PP']); // PP-PayingPro, NP-NonPro, PN-NonPayingPro
// Path Parameters Schema for GET /chains (no path params)
export const GetOptionChainPathParams = z.object({});
// Query Parameters Schema for GET /chains
export const GetOptionChainQueryParams = z.object({
symbol: z.string().describe('Symbol for the option chain'),
contractType: OptionContractTypeQueryEnum.optional().describe('Type of contracts to retrieve. Available values: CALL, PUT, ALL'),
strikeCount: z
.number()
.int()
.optional()
.describe('Number of strikes to return above and below the at-the-money price'),
includeUnderlyingQuote: z
.boolean()
.optional()
.describe('Include quote for the underlying asset'),
strategy: OptionStrategyEnum.optional().describe('Option strategy. Available values: SINGLE, ANALYTICAL, COVERED, VERTICAL, CALENDAR, STRANGLE, STRADDLE, BUTTERFLY, CONDOR, DIAGONAL, COLLAR, ROLL'),
interval: z
.number()
.optional()
.describe('Strike interval for spread strategies'),
strike: z.number().optional().describe('Specific strike price'),
range: OptionRangeQueryEnum.optional().describe('Range of option strikes. Available values: ITM, NTM, OTM, SAK, SBK, SNK, ALL'),
fromDate: z
.string()
.optional()
.describe('Only return options with expiration dates on or after this date'),
toDate: z
.string()
.optional()
.describe('Only return options with expiration dates on or before this date'),
volatility: z
.number()
.optional()
.describe('Volatility to use in calculations'),
underlyingPrice: z
.number()
.optional()
.describe('Underlying price to use in calculations'),
interestRate: z
.number()
.optional()
.describe('Interest rate to use in calculations'),
daysToExpiration: z
.number()
.int()
.optional()
.describe('Days to expiration to use in calculations'),
expMonth: OptionExpMonthQueryEnum.optional().describe('Return only options expiring in the specified month. Available values: ALL, JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC'),
optionType: OptionTypeQueryEnum.optional().describe('Type of options to retrieve. Available values: S, NS, ALL'),
entitlement: OptionEntitlementQueryEnum.optional().describe('Account entitlement for option data. Available values: PN, NP, PP'),
});
// Request Params Schema for GET /chains (merged path + query params)
export const GetOptionChainParams = z.object(mergeShapes(GetOptionChainQueryParams.shape, GetOptionChainPathParams.shape));
// Response Body Schema for GET /chains
export const GetOptionChainResponse = OptionChainSchema;
// --- Schemas for GET /expirationchain endpoint ---
// Schema for individual expiration items in the expiration chain
export const OptionExpirationItemSchema = z.object({
expirationDate: z
.string()
.describe('Expiration date in YYYY-MM-DD format (e.g., "2025-06-06")'),
daysToExpiration: z
.number()
.int()
.describe('Number of days until expiration'),
expirationType: z
.string()
.describe('Type of expiration (e.g., "W" for weekly, "S" for standard)'),
settlementType: z
.string()
.describe('Settlement type (e.g., "P" for PM settlement)'),
optionRoots: z
.string()
.describe('Option root symbol (typically same as underlying symbol)'),
standard: z.boolean().describe('Whether this is a standard expiration'),
});
// Schema for the complete expiration chain response
export const OptionExpirationChainSchema = z.object({
expirationList: z
.array(OptionExpirationItemSchema)
.describe('List of available expiration dates and their details'),
});
// Path Parameters Schema for GET /expirationchain (no path params)
export const GetOptionExpirationChainPathParams = z.object({});
// Query Parameters Schema for GET /expirationchain
export const GetOptionExpirationChainQueryParams = z.object({
symbol: z.string().describe('Symbol for the option expiration chain'),
});
// Request Params Schema for GET /expirationchain (merged path + query params)
export const GetOptionExpirationChainParams = z.object(mergeShapes(GetOptionExpirationChainQueryParams.shape, GetOptionExpirationChainPathParams.shape));
// Response Body Schema for GET /expirationchain
export const GetOptionExpirationChainResponse = OptionExpirationChainSchema;