@sudowealth/schwab-api
Version:
TypeScript client for Charles Schwab API with OAuth support, market data, trading functionality, and complete type safety
274 lines (273 loc) • 11.7 kB
JavaScript
import { z } from 'zod';
import { mergeShapes } from '../../utils/schema-utils';
import { assetType, AccountAPIOptionDeliverable } from '../shared';
export const AccountsBaseInstrument = z.object({
assetType: assetType,
cusip: z.string().optional(), // Made optional based on some TransactionInstrument variations
symbol: z.string(),
description: z.string().optional(),
instrumentId: z.number().int().optional(),
netChange: z.number().optional(),
});
const AccountCashEquivalent = AccountsBaseInstrument.extend({
assetType: z.literal(assetType.Enum.CASH_EQUIVALENT),
type: z
.enum(['SWEEP_VEHICLE', 'SAVINGS', 'MONEY_MARKET_FUND', 'UNKNOWN'])
.optional(),
underlyingSymbol: z.string().optional(),
});
const AccountEquity = AccountsBaseInstrument.extend({
assetType: z.literal(assetType.Enum.EQUITY),
});
const AccountFixedIncome = AccountsBaseInstrument.extend({
assetType: z.literal(assetType.Enum.FIXED_INCOME),
maturityDate: z.string().datetime().optional(),
factor: z.number().optional(),
variableRate: z.number().optional(),
});
const AccountMutualFund = AccountsBaseInstrument.extend({
assetType: z.literal(assetType.Enum.MUTUAL_FUND),
});
const AccountOption = AccountsBaseInstrument.extend({
assetType: z.literal(assetType.Enum.OPTION),
optionDeliverables: z
.array(z.lazy(() => AccountAPIOptionDeliverable))
.optional(),
putCall: z.enum(['PUT', 'CALL', 'UNKNOWN']).optional(),
optionMultiplier: z.number().int().optional(),
type: z.enum(['VANILLA', 'BINARY', 'BARRIER', 'UNKNOWN']).optional(),
underlyingSymbol: z.string().optional(), // Added as it's common for options
});
const AccountFuture = AccountsBaseInstrument.extend({
assetType: z.literal(assetType.Enum.FUTURE),
expirationDate: z.string().datetime().optional(),
activeContract: z.boolean().default(false).optional(),
});
const AccountForex = AccountsBaseInstrument.extend({
assetType: z.literal(assetType.Enum.FOREX),
});
const AccountIndex = AccountsBaseInstrument.extend({
assetType: z.literal(assetType.Enum.INDEX),
});
const AccountProduct = AccountsBaseInstrument.extend({
assetType: z.literal(assetType.Enum.PRODUCT), // Assuming PRODUCT is in AssetType enum
});
const AccountCurrency = AccountsBaseInstrument.extend({
assetType: z.literal(assetType.Enum.CURRENCY),
});
const AccountCollectiveInvestment = AccountsBaseInstrument.extend({
assetType: z.literal(assetType.Enum.COLLECTIVE_INVESTMENT),
});
const AccountsInstrument = z.discriminatedUnion('assetType', [
AccountCashEquivalent,
AccountEquity,
AccountFixedIncome,
AccountMutualFund,
AccountOption,
AccountFuture,
AccountForex,
AccountIndex,
AccountProduct,
AccountCurrency,
AccountCollectiveInvestment,
]);
// --- Position Schema ---
const Position = z.object({
shortQuantity: z.number().optional(),
averagePrice: z.number().optional(),
currentDayProfitLoss: z.number().optional(),
currentDayProfitLossPercentage: z.number().optional(),
longQuantity: z.number().optional(),
settledLongQuantity: z.number().optional(),
settledShortQuantity: z.number().optional(),
agedQuantity: z.number().optional(),
instrument: z.lazy(() => AccountsInstrument), // Defined above
marketValue: z.number().optional(),
maintenanceRequirement: z.number().optional(),
averageLongPrice: z.number().optional(),
averageShortPrice: z.number().optional(),
taxLotAverageLongPrice: z.number().optional(),
taxLotAverageShortPrice: z.number().optional(),
longOpenProfitLoss: z.number().optional(),
shortOpenProfitLoss: z.number().optional(),
previousSessionLongQuantity: z.number().optional(),
previousSessionShortQuantity: z.number().optional(),
currentDayCost: z.number().optional(),
});
// --- Account Balance Schemas ---
const MarginInitialBalance = z.object({
accruedInterest: z.number().optional(),
availableFundsNonMarginableTrade: z.number().optional(),
bondValue: z.number().optional(),
buyingPower: z.number().optional(),
cashBalance: z.number().optional(),
cashAvailableForTrading: z.number().optional(),
cashReceipts: z.number().optional(),
dayTradingBuyingPower: z.number().optional(),
dayTradingBuyingPowerCall: z.number().optional(),
dayTradingEquityCall: z.number().optional(),
equity: z.number().optional(),
equityPercentage: z.number().optional(),
liquidationValue: z.number().optional(),
longMarginValue: z.number().optional(),
longOptionMarketValue: z.number().optional(),
longStockValue: z.number().optional(),
maintenanceCall: z.number().optional(),
maintenanceRequirement: z.number().optional(),
margin: z.number().optional(),
marginEquity: z.number().optional(),
moneyMarketFund: z.number().optional(),
mutualFundValue: z.number().optional(),
regTCall: z.number().optional(),
shortMarginValue: z.number().optional(),
shortOptionMarketValue: z.number().optional(),
shortStockValue: z.number().optional(),
totalCash: z.number().optional(),
isInCall: z.boolean().optional(),
unsettledCash: z.number().optional(),
pendingDeposits: z.number().optional(),
marginBalance: z.number().optional(),
shortBalance: z.number().optional(),
accountValue: z.number().optional(),
});
const MarginBalance = z.object({
availableFunds: z.number().optional(),
availableFundsNonMarginableTrade: z.number().optional(),
buyingPower: z.number().optional(),
buyingPowerNonMarginableTrade: z.number().optional(),
dayTradingBuyingPower: z.number().optional(),
dayTradingBuyingPowerCall: z.number().optional(),
equity: z.number().optional(),
equityPercentage: z.number().optional(),
longMarginValue: z.number().optional(),
maintenanceCall: z.number().optional(),
maintenanceRequirement: z.number().optional(),
marginBalance: z.number().optional(),
regTCall: z.number().optional(),
shortBalance: z.number().optional(),
shortMarginValue: z.number().optional(),
sma: z.number().optional(),
isInCall: z.boolean().optional(),
stockBuyingPower: z.number().optional(),
optionBuyingPower: z.number().optional(),
});
const CashInitialBalance = z.object({
accruedInterest: z.number().optional(),
cashAvailableForTrading: z.number().optional(),
cashAvailableForWithdrawal: z.number().optional(),
cashBalance: z.number().optional(),
bondValue: z.number().optional(),
cashReceipts: z.number().optional(),
liquidationValue: z.number().optional(),
longOptionMarketValue: z.number().optional(),
longStockValue: z.number().optional(),
moneyMarketFund: z.number().optional(),
mutualFundValue: z.number().optional(),
shortOptionMarketValue: z.number().optional(),
shortStockValue: z.number().optional(),
isInCall: z.boolean().optional(),
unsettledCash: z.number().optional(),
cashDebitCallValue: z.number().optional(),
pendingDeposits: z.number().optional(),
accountValue: z.number().optional(),
});
const CashBalance = z.object({
cashAvailableForTrading: z.number().optional(),
cashAvailableForWithdrawal: z.number().optional(),
cashCall: z.number().optional(),
longNonMarginableMarketValue: z.number().optional(),
totalCash: z.number().optional(),
cashDebitCallValue: z.number().optional(),
unsettledCash: z.number().optional(),
accruedInterest: z.number().optional(),
cashBalance: z.number().optional(),
cashReceipts: z.number().optional(),
longOptionMarketValue: z.number().optional(),
liquidationValue: z.number().optional(),
longMarketValue: z.number().optional(),
moneyMarketFund: z.number().optional(),
savings: z.number().optional(),
shortMarketValue: z.number().optional(),
pendingDeposits: z.number().optional(),
mutualFundValue: z.number().optional(),
bondValue: z.number().optional(),
shortOptionMarketValue: z.number().optional(),
});
// --- Account Core Schemas ---
const SecuritiesAccountBase = z.object({
type: z.string(), // Will be 'MARGIN' or 'CASH' in extending types
accountNumber: z.string(),
roundTrips: z.number().int().optional(),
isDayTrader: z.boolean().default(false).optional(),
isClosingOnlyRestricted: z.boolean().default(false).optional(),
pfcbFlag: z.boolean().default(false).optional(),
positions: z.array(Position).default([]).optional(),
});
const MarginAccount = SecuritiesAccountBase.extend({
type: z.literal('MARGIN'),
initialBalances: MarginInitialBalance.optional(),
currentBalances: MarginBalance.optional(),
projectedBalances: MarginBalance.optional(), // MCP used MarginBalance here
});
const CashAccount = SecuritiesAccountBase.extend({
type: z.literal('CASH'),
initialBalances: CashInitialBalance.optional(),
currentBalances: CashBalance.optional(),
projectedBalances: z
.object({
// MCP had a specific structure here
cashAvailableForTrading: z.number().optional(),
cashAvailableForWithdrawal: z.number().optional(),
})
.optional(),
});
const SecuritiesAccount = z.discriminatedUnion('type', [
MarginAccount,
CashAccount,
]);
// --- GET /accounts endpoint schemas ---
// Path Parameters Schema for GET /accounts (no path params)
export const GetAccountsPathParams = z.object({});
// Query Parameters Schema for GET /accounts
export const GetAccountsQueryParams = z.object({
fields: z
.string()
.optional()
.describe('A comma-separated string of fields to return for each account. Valid values include: positions'),
});
// Request Params Schema for GET /accounts (merged path + query params)
export const GetAccountsParams = z.object(mergeShapes(GetAccountsQueryParams.shape, GetAccountsPathParams.shape));
// Response Body Schema for GET /accounts
export const GetAccountsResponse = z.array(z.object({
securitiesAccount: SecuritiesAccount,
}));
// --- GET /accounts/{accountNumber} endpoint schemas ---
// Path Parameters Schema for GET /accounts/{accountNumber}
export const GetAccountByNumberPathParams = z.object({
accountNumber: z.string().describe('Encrypted account number'),
});
// Query Parameters Schema for GET /accounts/{accountNumber}
export const GetAccountByNumberQueryParams = z.object({
fields: z
.string()
.optional()
.describe('A comma-separated string of fields to return. Valid values include: positions'),
});
// Request Params Schema for GET /accounts/{accountNumber} (merged path + query params)
export const GetAccountByNumberParams = z.object(mergeShapes(GetAccountByNumberQueryParams.shape, GetAccountByNumberPathParams.shape));
// Response Body Schema for GET /accounts/{accountNumber}
export const GetAccountByNumberResponse = z.object({
securitiesAccount: SecuritiesAccount,
});
// --- GET /accountNumbers endpoint schemas ---
// Path Parameters Schema for GET /accountNumbers (no path params)
export const GetAccountNumbersPathParams = z.object({});
// Query Parameters Schema for GET /accountNumbers (no query params)
export const GetAccountNumbersQueryParams = z.object({});
// Request Params Schema for GET /accountNumbers (merged path + query params)
export const GetAccountNumbersParams = z.object(mergeShapes(GetAccountNumbersQueryParams.shape, GetAccountNumbersPathParams.shape));
// Response Body Schema for GET /accountNumbers
export const GetAccountNumbersResponse = z.array(z.object({
accountNumber: z.string().describe('Encrypted account number'),
hashValue: z.string().describe('Hash value for the account'),
}));