@quantchat/sdk
v0.6.2
Published
TypeScript SDK for the QuantChat trading and social platform
Maintainers
Readme
@quantchat/sdk
The framework-neutral TypeScript SDK for QuantChat. It is the canonical client transport, protobuf contract, and domain API layer for web, React Native, Node, CLI, and third-party integrations.
The SDK lives in its own repository and is distributed as a package. Consumers must not copy generated contracts or maintain a parallel HTTP transport.
Installation
npm install @quantchat/sdkThe package is ESM, has no framework dependency, and uses the host runtime's
standard fetch, URL, Headers, AbortSignal, and TextDecoder APIs.
Node, CLI, And Server Quick Start
import { createClient } from '@quantchat/sdk'
const qc = createClient({
apiKey: 'qc_live_...',
// baseUrl: 'http://localhost:4000', // local development
})
const health = await qc.app.health()
const me = await qc.users.me()
const accounts = await qc.accounts.myAccounts()
console.log(health.status, me.handle, accounts.length)The root import is a convenience aggregate for server and command-line consumers. It exposes every domain module on one client.
Resource Semantics
Mutable resources use the predictable CRUD vocabulary create, getById,
list, update, and delete whenever the backend supports those operations.
Natural-key lookups name the key explicitly, such as getBySlug and
getBySymbol.
Singular getters return one entity or throw QuantChatError with
code: "NOT_FOUND" and status 404. Collection list() methods return an empty
array or connection when no records match. The SDK does not call a paginated
single-page request getAll; that name is reserved for operations that truly
exhaust every page.
Optional exact decimals can be passed directly through decimal:
import { decimal } from '@quantchat/sdk'
const run = await qc.backtests.create({
// ...required fields
initialCapital: decimal(optionalInitialCapital),
})The result is DecimalString | undefined; required money fields still reject
possibly-undefined values at compile time.
Web And React Native
Browser and mobile applications should import the transport, only the domain modules they use, and protobuf contracts through granular subpaths. This keeps unused domains out of client bundles.
import { BotsModule } from '@quantchat/sdk/modules/bots'
import { createTransport } from '@quantchat/sdk/transport'
import { GetMyBotsResponse } from '@quantchat/sdk/proto/bot/bot'
const transport = createTransport({
baseUrl: 'https://api.quantchat.com',
getAccessToken: async () => auth.currentToken(),
})
const bots = new BotsModule(transport)
const page = await transport.getPb('/api/bots', GetMyBotsResponse)
const bot = await bots.getById(page.bots[0]!.id)getAccessToken is evaluated for every request, so rotating session tokens do
not require rebuilding clients. React Native needs no Node polyfills on runtimes
that implement the standard web APIs above.
Next.js
Create SDK objects lazily at module scope and pass Next.js fetch options through
init. Public and authenticated transports should be separate so public
requests remain cacheable and do not accidentally include user credentials.
import { createTransport } from '@quantchat/sdk/transport'
const publicApi = createTransport({
baseUrl: process.env.NEXT_PUBLIC_API_URL,
})
const markets = await publicApi.get('/public/markets', undefined, {
init: { next: { revalidate: 60, tags: ['markets'] } },
})Strategies
const page = await qc.strategies.list({ sort: 'recent', limit: 25 })
console.log(page.items, page.cursor)
const generated = await qc.strategies.generate({
prompt: 'Create a mean reversion strategy for large-cap US equities.',
idempotencyKey: crypto.randomUUID(),
})
if (generated.kind === 'clarification') console.log(generated.questions)
else console.log(generated.strategy)
const intentStrategy = await qc.strategies.createFromIntent({ intent })
const updated = await qc.strategies.updateIntent(intentStrategy.id, nextIntent)Bots And Backtests
const bot = await qc.bots.create({
accountId: 'account-id',
strategyId: 'strategy-id',
idempotencyKey: crypto.randomUUID(),
instrumentDefId: 'instrument-id-or-symbol',
parameters: { lookback: 20 },
initialCapital: '10000',
})
const bots = await qc.bots.list({ sort: 'recent', limit: 20 })
await qc.bots.stop(bot.id)
const backtest = await qc.backtests.create({
strategyId: 'strategy-id',
instrumentDefId: 'AAPL',
timeframe: '1h',
startTime: '2026-01-01T00:00:00Z',
endTime: '2026-02-01T00:00:00Z',
})Agents
const config = await qc.agents.launchConfig()
const agent = await qc.agents.create({
accountId: 'paper-account-id',
systemPrompt: 'Trade a small liquid universe conservatively.',
model: config.models.find((m) => m.defaultModel)?.model ?? config.models[0]!.model,
initialCapital: config.capitalDefaults.defaultInitialCapital,
allowedInstrumentDefIds: ['instrument-id'],
startNow: false,
})
await qc.agents.start(agent.id)
await qc.agents.stop(agent.id)Instruments And Market Data
const matches = await qc.marketData.searchInstruments('AAPL')
const instrument = await qc.instruments.getBySymbol('AAPL')
const bars = await qc.marketData.getBars({ symbol: 'AAPL', timeframe: '1h', limit: 100 })
const quotes = await qc.marketData.getQuotes(['AAPL', 'BTC-USD'])qc.instruments.list() returns the backend's enabled instrument-def directory. Use qc.marketData.searchInstruments() for symbol/name lookup.
Social And Utilities
const communities = await qc.communities.list()
const watchlists = await qc.watchlists.list()
const notifications = await qc.notifications.list()
const tradeCheck = await qc.tools.shouldITrade('AAPL')Protobuf Contracts
The aggregate namespace is convenient on the server:
import { proto } from '@quantchat/sdk'
const decoded = proto.bot.GetMyBotsResponse.fromBinary(bytes)Client applications should use granular contract imports so bundlers include only what the application references:
import { GetMyBotsResponse } from '@quantchat/sdk/proto/bot/bot'Configuration
const qc = createClient({
apiKey: 'qc_live_...',
bearerToken: 'user-jwt',
getAccessToken: async () => session.getToken(),
baseUrl: 'http://localhost:4000',
fetch: customFetch,
signal: abortController.signal,
})getAccessToken takes precedence over static credentials. Request helpers also
accept cancellation and arbitrary host-runtime fetch options:
await qc.get('/api/example', undefined, {
signal: requestSignal,
init: { cache: 'no-store' },
})Types
All public types are exported:
import type { Account, Agent, BacktestRun, Bot, InstrumentDef, Strategy } from '@quantchat/sdk'Money
Money-of-record fields (capital, balances, nominal P&L, credits) are typed and
delivered as exact decimal strings (e.g. bot.currentCapital === "10245.51"),
matching the wire (proto string). The SDK never converts these to floats —
consumers choose their own decimal handling (e.g. Decimal.js, Number() for
display only). Genuine ratios (profitPercent, dailyChangePercent) are proto
double and stay JS number.
Breaking in 0.4.0: money-of-record fields on Bot, Agent, Account, and
AgentLaunchConfig changed from number to string to match the actual wire
type — code doing arithmetic directly on these fields (e.g. bot.profitNominal * 2)
must convert explicitly first.
License
MIT
