npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@qtsurfer/sdk

v0.2.0

Published

Opinionated TypeScript SDK for QTSurfer: workflow orchestration, domain objects, normalized errors

Readme

@qtsurfer/sdk

Opinionated TypeScript SDK for QTSurfer, built on top of @qtsurfer/api-client.

Where @qtsurfer/api-client gives you one typed function per API endpoint, @qtsurfer/sdk adds workflow orchestration, normalized errors, and cancellation — run a backtest with a single await.

Installation

npm install @qtsurfer/sdk
# or
pnpm add @qtsurfer/sdk

Quick start

import { QTSurfer } from '@qtsurfer/sdk';
import { readFileSync } from 'node:fs';

const qts = new QTSurfer({
  baseUrl: 'https://api.qtsurfer.com/v1',
  token: process.env.QTSURFER_TOKEN,
});

const controller = new AbortController();

const result = await qts.backtest(
  {
    strategy: readFileSync('./MyStrategy.java', 'utf8'),
    exchangeId: 'binance',
    instrument: 'BTC/USDT',
    from: '2024-01-01',
    to: '2024-12-31',
    storeSignals: true,
  },
  {
    signal: controller.signal,
    onProgress: (p) => console.log(`[${p.stage}] ${p.percent?.toFixed(1) ?? '-'}%`),
    pollIntervalMs: 500,
    maxPollIntervalMs: 5000,
    timeoutMs: 10 * 60 * 1000,
  },
);

console.log('PnL:', result.pnlTotal);
console.log('Trades:', result.totalTrades);
console.log('Signals:', result.signalsUrl);

What backtest() does

Orchestrates the full four-step workflow that the raw API exposes:

  1. Compile the strategy (POST /strategy in async mode) and poll GET /strategy/{id} until Completed.
  2. Prepare the data range (POST /backtest/{exchange}/ticker/prepare) and poll until Completed.
  3. Execute the backtest (POST /backtest/{exchange}/ticker/execute) and poll GET /backtest/.../execute/{jobId} until Completed.
  4. Return the ResultMap (pnlTotal, totalTrades, sharpeRatio, signalsUrl, …).

Polling uses exponential backoff (intervalMs * 1.5, capped at maxIntervalMs) with per-stage timeout.

Progress is emitted on every stage transition and after each poll whose size > 0.

Hourly tickers/klines downloads

Stream one hour of raw ticker or kline data for an instrument. The default wire format is Lastra (application/vnd.lastra); pass format: 'parquet' for on-the-fly Parquet conversion.

// Lastra (default)
const blob = await qts.tickers({
  exchangeId: 'binance',
  base: 'BTC',
  quote: 'USDT',
  hour: '2026-01-15T10',
});
await Bun.write('BTC_USDT_2026-01-15_h10.lastra', await blob.arrayBuffer());

// Parquet
const klines = await qts.klines({
  exchangeId: 'binance',
  base: 'BTC',
  quote: 'USDT',
  hour: '2026-01-15T10',
  format: 'parquet',
});

HTTP errors surface as QTSDownloadError (subclass of QTSError).

Error hierarchy

All SDK errors extend QTSError so you can catch them generically or match by subclass.

import {
  QTSError,
  QTSStrategyCompileError,
  QTSPreparationError,
  QTSExecutionError,
  QTSDownloadError,
  QTSTimeoutError,
  QTSCanceledError,
} from '@qtsurfer/sdk';

try {
  await qts.backtest(req);
} catch (e) {
  if (e instanceof QTSStrategyCompileError) {
    console.error('Compile failed:', e.message);
  } else if (e instanceof QTSPreparationError) {
    console.error('Data prep failed:', e.message);
  } else if (e instanceof QTSExecutionError) {
    console.error('Execution failed:', e.message);
  } else if (e instanceof QTSDownloadError) {
    console.error('Download failed:', e.message);
  } else if (e instanceof QTSTimeoutError) {
    console.error('Stage timed out');
  } else if (e instanceof QTSCanceledError) {
    console.error('Canceled by signal');
  }
}

Cancellation

Pass an AbortSignal. The SDK stops polling immediately and, if execution has already started server-side, best-effort calls cancelExecution on the QTSurfer API.

const controller = new AbortController();
setTimeout(() => controller.abort(), 60_000);
await qts.backtest(req, { signal: controller.signal });

Under the hood

Polling, retry, backoff, timeout, and cancellation are delegated to cockatiel. Each workflow stage composes a retry policy (exponential backoff on in-progress statuses) with an optional timeout policy. If you need advanced resilience primitives (circuit breakers, bulkheads, fallbacks), import them directly from cockatiel.

Roadmap

v0.1 — Core workflow ✅

  • [x] QTSurfer client over @qtsurfer/api-client
  • [x] qts.backtest() orchestrating compile → prepare → execute
  • [x] Backoff, timeout, and AbortSignal cancellation via cockatiel policies
  • [x] Error hierarchy: QTSError, QTSStrategyCompileError, QTSPreparationError, QTSExecutionError, QTSTimeoutError, QTSCanceledError

v0.2 — Domain objects

  • [ ] Strategy class with .backtest(), .status()
  • [ ] BacktestJob class with .wait(), .cancel(), .stream()
  • [ ] TTL cache for exchanges / instruments

v0.3 — Streaming progress

  • [ ] job.stream() returns AsyncIterator<BacktestProgress>
  • [ ] Server-side hooks (when the backend exposes SSE/WebSocket)

v0.4 — Ecosystem integration

  • [ ] Helpers to load signalsUrl Parquet into DuckDB / Lastra
  • [ ] Framework adapters (@qtsurfer/sdk-react, @qtsurfer/sdk-svelte)

Project layout

src/
├── index.ts              # public exports
├── client.ts             # QTSurfer class
├── errors.ts             # QTSError hierarchy
└── workflows/
    ├── backtest.ts       # compile → prepare → execute (cockatiel policies)
    └── downloads.ts      # hourly tickers/klines as Lastra/Parquet blobs

Development

| Script | Description | | ------ | ----------- | | npm run lint | Type-check without emitting | | npm run build | Bundle to dist/ via tsup | | npm test | Run unit tests | | npm run test:integration | Run the integration test (requires JWT_API_TOKEN). Set QTSURFER_TEST_VERBOSE=1 to stream progress + final result | | npm run changeset | Record a changeset for the next release | | npm run changeset:version | Consume pending changesets: bump package.json and update CHANGELOG.md | | npm run changeset:publish | Publish released packages to npm (used by CI) |

Releasing

Versioning and changelogs are managed with changesets:

  1. Create a changeset describing your change: npm run changeset
  2. Commit the generated .changeset/<slug>.md with your PR.
  3. When ready to release, run npm run changeset:version locally. It bumps package.json and appends to CHANGELOG.md.
  4. Commit the version bump, tag vX.Y.Z, and push the tag; the Publish to npm workflow handles the rest.

License

Apache-2.0 — see LICENSE.