@sudowealth/schwab-api
Version:
TypeScript client for Charles Schwab API with OAuth support, market data, trading functionality, and complete type safety
310 lines (309 loc) • 11.6 kB
JavaScript
import { z } from 'zod';
import { isoDateTimeSchema } from '../../utils/date-utils';
import { mergeShapes } from '../../utils/schema-utils';
import { assetType } from '../shared';
export const TransactionType = z.enum([
'TRADE',
'RECEIVE_AND_DELIVER',
'DIVIDEND_OR_INTEREST',
'ACH_RECEIPT',
'ACH_DISBURSEMENT',
'CASH_RECEIPT',
'CASH_DISBURSEMENT',
'ELECTRONIC_FUND',
'WIRE_OUT',
'WIRE_IN',
'JOURNAL',
'MEMORANDUM',
'MARGIN_CALL',
'MONEY_MARKET',
'SMA_ADJUSTMENT',
]);
// Create a flexible date schema for query parameters
const flexibleDateSchema = (daysOffset, description) => z
.preprocess((val) => {
// Handle empty strings and undefined
if (val === '' || val === undefined || val === null) {
// Generate default date in full ISO format
const date = new Date();
date.setDate(date.getDate() + daysOffset);
// For start dates, set to beginning of day; for end dates, set to end of day
if (daysOffset < 0) {
// Start date - beginning of day
date.setHours(0, 0, 0, 0);
}
else {
// End date - end of day
date.setHours(23, 59, 59, 999);
}
return date.toISOString();
}
// Handle YYYY-MM-DD format - convert to full ISO datetime
if (typeof val === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(val)) {
const date = new Date(val + 'T00:00:00.000Z');
return date.toISOString();
}
// Handle full ISO datetime - ensure proper format
if (typeof val === 'string' && val.includes('T')) {
return new Date(val).toISOString();
}
return val;
}, z.string().describe(description))
.optional();
const UserDetails = z.object({
cdDomainId: z.string(),
login: z.string(),
type: z.enum([
'ADVISOR_USER',
'BROKER_USER',
'CLIENT_USER',
'SYSTEM_USER',
'UNKNOWN',
]),
userId: z.number().int(), // ($int64)
systemUserName: z.string(),
firstName: z.string(),
lastName: z.string(),
brokerRepCode: z.string(),
});
// Recreate TransactionBaseInstrument
const TransactionBaseInstrument = z.object({
assetType: assetType,
cusip: z.string(), // Removed optional
symbol: z.string(), // Removed optional
description: z.string(), // Description often optional
instrumentId: z.number().int().optional(), // Removed optional, kept int ($int64)
netChange: z.number().optional(),
});
// Define Transaction Instrument Types
const TransactionCashEquivalent = TransactionBaseInstrument.extend({
assetType: z.literal('CASH_EQUIVALENT'),
symbol: z.string(), // Required per spec
description: z.string(), // Required per spec
type: z.enum(['SWEEP_VEHICLE', 'SAVINGS', 'MONEY_MARKET_FUND', 'UNKNOWN']),
});
const CollectiveInvestment = TransactionBaseInstrument.extend({
assetType: z.literal('COLLECTIVE_INVESTMENT'),
cusip: z.string(), // Required per spec
symbol: z.string(), // Required per spec
instrumentId: z.any(), // [...] definition unclear
type: z.any(), // [...] definition unclear
});
const Currency = TransactionBaseInstrument.extend({
assetType: z.literal('CURRENCY'),
symbol: z.string(), // Required per spec
});
const TransactionEquity = TransactionBaseInstrument.extend({
assetType: z.literal('EQUITY'),
cusip: z.string(), // Required per spec
symbol: z.string(), // Required per spec
type: z.enum([
'COMMON_STOCK',
'PREFERRED_STOCK',
'DEPOSITORY_RECEIPT',
'PREFERRED_DEPOSITORY_RECEIPT',
'RESTRICTED_STOCK',
'COMPONENT_UNIT',
'RIGHT',
'WARRANT',
'CONVERTIBLE_PREFERRED_STOCK',
'CONVERTIBLE_STOCK',
'LIMITED_PARTNERSHIP',
'WHEN_ISSUED',
'UNKNOWN',
]),
});
const TransactionFixedIncome = TransactionBaseInstrument.extend({
assetType: z.literal('FIXED_INCOME'),
cusip: z.string(), // Required per spec
symbol: z.string(), // Required per spec
type: z.enum([
'BOND_UNIT',
'CERTIFICATE_OF_DEPOSIT',
'CONVERTIBLE_BOND',
'COLLATERALIZED_MORTGAGE_OBLIGATION',
'CORPORATE_BOND',
'GOVERNMENT_MORTGAGE',
'GNMA_BONDS',
'MUNICIPAL_ASSESSMENT_DISTRICT',
'MUNICIPAL_BOND',
'OTHER_GOVERNMENT',
'SHORT_TERM_PAPER',
'US_TREASURY_BOND',
'US_TREASURY_BILL',
'US_TREASURY_NOTE',
'US_TREASURY_ZERO_COUPON',
'AGENCY_BOND',
'WHEN_AS_AND_IF_ISSUED_BOND',
'ASSET_BACKED_SECURITY',
'UNKNOWN',
]),
maturityDate: isoDateTimeSchema,
factor: z.number(), // Not marked required
multiplier: z.number(), // Not marked required
variableRate: z.number(), // Not marked required
});
const Forex = TransactionBaseInstrument.extend({
assetType: z.literal('FOREX'),
symbol: z.string(), // Required per spec
type: z.enum(['STANDARD', 'NBBO', 'UNKNOWN']),
baseCurrency: z.lazy(() => Currency), // Assuming Currency is defined
counterCurrency: z.lazy(() => Currency),
});
// Define Future and Index based on interpretation (ignoring incorrect oneOf)
const Future = TransactionBaseInstrument.extend({
assetType: z.literal('FUTURE'), // Assuming FUTURE based on name
symbol: z.string(), // Required per spec
activeContract: z.boolean().default(false),
type: z.enum(['STANDARD', 'UNKNOWN']),
expirationDate: isoDateTimeSchema,
lastTradingDate: isoDateTimeSchema, // Assuming optional
firstNoticeDate: isoDateTimeSchema, // Assuming optional
multiplier: z.number(),
});
const Index = TransactionBaseInstrument.extend({
assetType: z.literal('INDEX'), // Assuming INDEX based on name
symbol: z.string(), // Required per spec
activeContract: z.boolean().default(false),
type: z.string(), // Enum Array [ 3 ] unclear
});
const TransactionMutualFund = TransactionBaseInstrument.extend({
assetType: z.literal('MUTUAL_FUND'),
cusip: z.string(), // Required per spec
symbol: z.string(), // Required per spec
fundFamilyName: z.string(), // Not marked required
fundFamilySymbol: z.string(), // Not marked required
fundGroup: z.string(), // Not marked required
type: z.enum([
'NOT_APPLICABLE',
'OPEN_END_NON_TAXABLE',
'OPEN_END_TAXABLE',
'NO_LOAD_NON_TAXABLE',
'NO_LOAD_TAXABLE',
'UNKNOWN',
]),
exchangeCutoffTime: isoDateTimeSchema, // Not marked required
purchaseCutoffTime: isoDateTimeSchema, // Not marked required
redemptionCutoffTime: isoDateTimeSchema, // Not marked required
});
const TransactionAPIOptionDeliverable = z.object({
rootSymbol: z.string(),
strikePercent: z.number().int(),
deliverableNumber: z.number().int(),
deliverableUnits: z.number(),
deliverable: z.any(), // Changed to z.any() for now
assetType: assetType,
});
const TransactionOption = TransactionBaseInstrument.extend({
assetType: z.literal('OPTION'), // Corrected to z.literal for discriminated union
cusip: z.string(),
symbol: z.string(),
description: z.string(),
instrumentId: z.number().int().optional(),
netChange: z.number().optional(),
expirationDate: isoDateTimeSchema,
optionDeliverables: z.array(TransactionAPIOptionDeliverable),
optionPremiumMultiplier: z.number().int(),
putCall: z.enum(['PUT', 'CALL', 'UNKNOWN']),
strikePrice: z.number(),
type: z.enum(['VANILLA', 'BINARY', 'BARRIER', 'UNKNOWN']),
underlyingSymbol: z.string(),
underlyingCusip: z.string(),
deliverable: z.any(), // Changed to z.any() for now
});
const Product = TransactionBaseInstrument.extend({
assetType: z.literal('PRODUCT'),
symbol: z.string(), // Required per spec
type: z.enum(['TBD', 'UNKNOWN']),
});
// Replace placeholder with the correct discriminated union
const TransactionInstrument = z.discriminatedUnion('assetType', [
TransactionCashEquivalent,
CollectiveInvestment,
Currency,
TransactionEquity,
TransactionFixedIncome,
Forex,
Future,
Index,
TransactionMutualFund,
TransactionOption,
Product,
]);
const TransferItem = z.object({
instrument: TransactionInstrument, // References the placeholder TransactionInstrument
amount: z.number(),
cost: z.number(),
price: z.number(),
feeType: z.enum([
'COMMISSION',
'SEC_FEE',
'STR_FEE',
'R_FEE',
'CDSC_FEE',
'OPT_REG_FEE',
'ADDITIONAL_FEE',
'MISCELLANEOUS_FEE',
'FUTURES_EXCHANGE_FEE',
'LOW_PROCEEDS_COMMISSION',
'BASE_CHARGE',
'GENERAL_CHARGE',
'GST_FEE',
'TAF_FEE',
'INDEX_OPTION_FEE',
'UNKNOWN',
]),
positionEffect: z.enum(['OPENING', 'CLOSING', 'AUTOMATIC', 'UNKNOWN']),
});
// Define Transaction after its dependencies UserDetails and TransferItem
const Transaction = z.object({
activityId: z.number().int(),
time: isoDateTimeSchema,
user: UserDetails,
description: z.string(),
accountNumber: z.string(),
type: TransactionType,
status: z.enum(['VALID', 'INVALID', 'PENDING', 'UNKNOWN']),
subAccount: z.enum(['CASH', 'MARGIN', 'SHORT', 'DIV', 'INCOME', 'UNKNOWN']),
tradeDate: isoDateTimeSchema,
settlementDate: isoDateTimeSchema,
positionId: z.number().int(),
orderId: z.number().int(),
netAmount: z.number(),
activityType: z.enum([
'ACTIVITY_CORRECTION',
'EXECUTION',
'ORDER_ACTION',
'TRANSFER',
'UNKNOWN',
]),
transferItems: z.array(TransferItem),
});
// --- GET /accounts/{accountNumber}/transactions endpoint schemas ---
// Path Parameters Schema for GET /accounts/{accountNumber}/transactions
export const GetTransactionsPathParams = z.object({
accountNumber: z.string().describe('Encrypted account number'),
});
// Query Parameters Schema for GET /accounts/{accountNumber}/transactions
export const GetTransactionsQueryParams = z.object({
startDate: flexibleDateSchema(-30, 'Start date for transaction search'),
endDate: flexibleDateSchema(0, 'End date for transaction search'),
symbol: z.string().optional().describe('Symbol to filter transactions'),
types: TransactionType.describe('Transaction type to filter'),
});
// Request Params Schema for GET /accounts/{accountNumber}/transactions (merged path + query params)
export const GetTransactionsParams = z.object(mergeShapes(GetTransactionsQueryParams.shape, GetTransactionsPathParams.shape));
// Response Body Schema for GET /accounts/{accountNumber}/transactions
export const GetTransactionsResponse = z.array(Transaction);
// --- GET /accounts/{accountNumber}/transactions/{transactionId} endpoint schemas ---
// Path Parameters Schema for GET /accounts/{accountNumber}/transactions/{transactionId}
export const GetTransactionByIdPathParams = z.object({
accountNumber: z.string().describe('Encrypted account number'),
transactionId: z.number().int().describe('Transaction ID'),
});
// Query Parameters Schema for GET /accounts/{accountNumber}/transactions/{transactionId}
export const GetTransactionByIdQueryParams = z.object({});
// Request Params Schema for GET /accounts/{accountNumber}/transactions/{transactionId} (merged path + query params)
export const GetTransactionByIdParams = z.object(mergeShapes(GetTransactionByIdQueryParams.shape, GetTransactionByIdPathParams.shape));
// Response Body Schema for GET /accounts/{accountNumber}/transactions/{transactionId}
export const GetTransactionByIdResponse = Transaction;