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

@discomedia/papertiger

v0.1.11

Published

Official type-safe TypeScript client for PaperTiger paper trading and historical prediction-market data.

Readme

@discomedia/papertiger

Official type-safe TypeScript client for the PaperTiger paper-trading API.

PaperTiger simulates orders against public Polymarket and Kalshi order books. Limit fills conservatively attribute at most 30% of each executable displayed level to the paper order and prevent an unchanged GTC book from being consumed more than once per one-minute observation window. It never submits a real-money order.

Install

Use the package manager already used by your project:

pnpm add @discomedia/papertiger
npm install @discomedia/papertiger

The package has no runtime dependencies and includes ESM, CommonJS, TypeScript declarations, the complete API reference, and LLM-oriented discovery documentation.

Quick start

Create an API key in the PaperTiger dashboard, then keep it in a server-side environment variable:

export PAPERTIGER_API_KEY="pt_key_replace_me"
import { PaperTigerClient } from "@discomedia/papertiger";

const apiKey = process.env.PAPERTIGER_API_KEY;
if (apiKey === undefined) {
  throw new Error("PAPERTIGER_API_KEY is required.");
}

const paperTiger = new PaperTigerClient({
  apiKey,
});

const wallets = await paperTiger.listWallets();
const { markets, warnings } = await paperTiger.searchMarkets({
  q: "bitcoin",
  venue: "all",
});

console.log({ wallets, markets, warnings });

New keys initially access only the wallet selected when they are created. Wallet-scoped calls can omit walletId and walletName to use the editable default wallet.

Do not embed API keys in browser bundles. Browser applications can use the session returned by login() or call PaperTiger through their own server.

New accounts must explicitly acknowledge PaperTiger's 13+ eligibility rule and the parental or guardian permission requirement that applies to users under their local age of majority:

await paperTiger.register({
  email: "[email protected]",
  password: "a-long-unique-password",
  eligibilityAcknowledged: true,
});

Place a retry-safe paper order

Use canonical market and outcome identifiers returned by market search:

const market = markets.at(0);
const outcome = market?.outcomes.at(0);
if (market === undefined || outcome === undefined) {
  throw new Error("A tradable market outcome is required.");
}

const order = await paperTiger.placeOrder(
  {
    venue: market.venue,
    marketId: market.marketId,
    outcomeId: outcome.id,
    side: "buy",
    quantity: 10,
    type: "limit",
    timeInForce: "gtc",
    limitPricePercent1: 0.54,
    executionFloorPricePercent1: 0.5,
    clientOrderId: "strategy-a-2026-07-27-001",
  },
  {
    idempotencyKey: crypto.randomUUID(),
  },
);

console.log(order.status);

Reuse an idempotencyKey only when retrying the identical order request. clientOrderId is separately wallet-unique and is intended for reconciling your strategy state.

For a buy execution band, set executionFloorPricePercent1 alongside a limit. For example, a 0.90 floor and 0.98 buy limit accept only consumed book levels from 90c through 98c. This is enforced during the execution engine's fill walk, including each later GTC evaluation—not by an advisory client-side book check. Floors are buy-only and cannot exceed the limit.

Use maxSpendDollars instead of quantity to cap a buy at an all-in dollar budget, including the simulated venue fee:

const dollarSizedOrder = await paperTiger.placeOrder({
  venue: market.venue,
  marketId: market.marketId,
  outcomeId: outcome.id,
  side: "buy",
  maxSpendDollars: 100,
  type: "market",
  maxSlippagePercent100: 4,
});

Bulk order actions

bulkOrders() sends 1–50 place, replace, and cancel actions in one request. Actions run in order and return independent success or failure results:

const bulkResult = await paperTiger.bulkOrders([
  {
    action: "place",
    idempotencyKey: crypto.randomUUID(),
    order: {
      venue: market.venue,
      marketId: market.marketId,
      outcomeId: outcome.id,
      side: "buy",
      maxSpendDollars: 50,
      type: "limit",
      timeInForce: "gtc",
      limitPricePercent1: 0.48,
    },
  },
  {
    action: "cancel",
    orderId: "existing-order-id",
  },
]);

for (const result of bulkResult.results) {
  if (result.status === "failed") {
    console.error(result.index, result.error.code, result.error.message);
  }
}

The server validates the entire bulk payload before executing it. Execution is best-effort rather than atomic, so one item failure does not roll back earlier items or prevent later items.

Retrieve trading history by time

Order, position, and account-activity list methods accept inclusive ISO 8601 start and end bounds. Orders are bounded by creation time, aggregate positions by their original creation time, and activities by occurrence time:

const start = "2026-07-01T00:00:00.000Z";
const end = "2026-07-31T23:59:59.999Z";

const [{ orders }, { positions }, { activities }] = await Promise.all([
  paperTiger.listOrders({ start, end }),
  paperTiger.listPositions("wallet-id", { status: "all", start, end }),
  paperTiger.listActivities("wallet-id", { start, end }),
]);

Keep the same bounds and other filters when following an order or activity nextCursor.

Query historical market data

Historical pages use stable cursors and include provenance and coverage quality. Market-level coverage start/end values apply to the row's highest reported fidelity, so use replayable_l2 rows when selecting an executable backtest window. Market rows include title, inferred underlying/duration, and scheduled close metadata so callers can select coverage for an exact rolling family. Coverage may also report collectionMode: "sampled" and a scheduledNextCaptureAt value during cost-bounded debug collection; do not treat separate sampled intervals as continuous. Keep all filters unchanged when following nextCursor:

const coverage = await paperTiger.getMarketDataCoverage();
const trades = await paperTiger.listHistoricalTrades({
  venue: "polymarket",
  marketId: "venue-market-id",
  start: "2026-07-01T00:00:00.000Z",
  end: "2026-07-28T00:00:00.000Z",
  limit: 10_000,
});

const book = await paperTiger.getHistoricalOrderBook(
  "polymarket",
  "venue-market-id",
  "2026-07-28T00:00:00.000Z",
  "venue-outcome-id",
);

console.log({ coverage, trades: trades.data.length, bids: book.bids });

The market-data client methods are:

| Method | Data | | --- | --- | | getMarketDataCoverage() | Ranges, fidelity, quality, freshness, and known gaps | | listHistoricalMarkets() | Current or point-in-time definitions, optionally bounded by scheduled close time | | getHistoricalMarket() | One current or point-in-time definition | | listHistoricalTrades() | Exact normalized public trades | | listHistoricalQuotes() | Change-only best bid/ask observations | | listHistoricalCandles() | One-minute price, quote, volume, and open-interest candles | | getHistoricalOrderBook() | Causally reconstructed replayable L2 depth | | createMarketDataExport() | Start a bounded Parquet export | | getMarketDataExport() | Read export status and obtain a temporary download URL |

Generate and backtest a strategy

generateStrategy() uses PaperTiger's server-side gpt-5.6-luna configuration to turn a natural-language idea into an editable, validated version-two weighted L2 strategy. It also returns provider token and cost usage:

const generated = await paperTiger.generateStrategy(
  "Buy Polymarket BTC 5-minute Up only when the weighted L2 score persists for 750ms, Chainlink agrees, both books are fresh, ephemeral liquidity stays low, and executable net edge after fees and slippage is at least 0.25%.",
);

console.log(generated.strategy, generated.usage);

V2 strategies support hard entry safeguards in addition to weighted signals: score persistence, reference-direction agreement, maximum book age, ephemeral-liquidity abstention, and an extra expected-edge slippage buffer. Known collection gaps always fail closed.

Review the definition, then run a bounded point-in-time simulation:

const result = await paperTiger.runBacktest({
  strategy: generated.strategy,
  start: "2026-07-27T00:00:00.000Z",
  end: "2026-07-28T00:00:00.000Z",
  initialCapitalDollars: 1_000,
});

console.log({
  returnPercent100: result.metrics.returnPercent100,
  maxDrawdownPercent100: result.metrics.maxDrawdownPercent100,
  trades: result.trades.length,
  equityPoints: result.equityCurve.length,
  retainedDecisions: result.decisions?.length ?? 0,
  firstTradeDiagnostics: result.trades[0]?.diagnostics,
});

Interactive runs span at most 31 days and 500,000 market-data events. V1 remains accepted. V2 processes full snapshots, absolute-size deltas, and public trades only after observedAt; maintains both outcome books; walks asks and bids after configured latency; applies per-level depth participation, slippage, and venue fee curves; and fails closed across known gaps. Each completed trade includes score components, executable book and flow state, expected net edge, rejection context, and 250 ms/1 s/3 s/10 s executable-bid markouts. Every run persists its immutable strategy version and frozen data watermarks.

Track experiments and forward paper runs

Use the experiment control plane when a hypothesis must remain reproducible across strategy revisions and historical or forward evaluations:

const experiment = await paperTiger.createExperiment({
  name: "BTC five-minute depth pressure",
  hypothesis: "Positive executable depth imbalance predicts the Up outcome.",
  successCriteria: { minimumCompletedTrades: 30 },
  guardrails: { paperOnly: true },
});

const version = await paperTiger.createExperimentStrategyVersion(
  experiment.id,
  {
    definition: generated.strategy,
    changeNote: "Initial causal V2 definition.",
    engineVersion: "backtest-l2-v2",
  },
);

const historical = await paperTiger.runExperimentBacktest(experiment.id, {
  strategyVersionId: version.versionId,
  start: "2026-07-27T00:00:00.000Z",
  end: "2026-07-28T00:00:00.000Z",
  initialCapitalDollars: 1_000,
});

const analysis = await paperTiger.analyzeExperimentRun(historical.run.id);
const bundle = await paperTiger.exportExperimentResearchBundle(
  historical.run.id,
);

Managed forward-paper runs use a dedicated wallet. Externally hosted bots use createExperimentRun() with mode: "external_paper" and submit retry-safe causal decisions through ingestExperimentDecisions(). Use compareExperimentRuns() only for compatible universes, windows, capital, and execution assumptions.

To page without changing the query snapshot:

const query = {
  venue: "polymarket" as const,
  marketId: "venue-market-id",
  start: "2026-07-01T00:00:00.000Z",
  end: "2026-07-28T00:00:00.000Z",
  limit: 1_000,
};

const firstPage = await paperTiger.listHistoricalQuotes(query);
const secondPage =
  firstPage.nextCursor === null
    ? null
    : await paperTiger.listHistoricalQuotes({
        ...query,
        cursor: firstPage.nextCursor,
      });

getHistoricalOrderBook() returns only causally replayable depth. Gapped or unavailable depth throws PaperTigerApiError with code INSUFFICIENT_MARKET_DATA; trades and proxy candles are never substituted.

Create a bounded Parquet export and poll it until complete:

const exportJob = await paperTiger.createMarketDataExport({
  dataset: "trades",
  start: "2026-07-01T00:00:00.000Z",
  end: "2026-07-28T00:00:00.000Z",
});

const currentExport = await paperTiger.getMarketDataExport(exportJob.id);
console.log(currentExport.status, currentExport.downloadUrl);

Exports may cover at most 90 days and expire after 24 hours. A completed job returns a private downloadUrl valid for 15 minutes. Fetch that URL directly; do not send the PaperTiger API key to the storage host.

Errors

Non-successful HTTP responses throw PaperTigerApiError:

import {
  PaperTigerApiError,
  PaperTigerClient,
} from "@discomedia/papertiger";

try {
  await paperTiger.getPortfolio("wallet-id");
} catch (error: unknown) {
  if (error instanceof PaperTigerApiError) {
    console.error({
      status: error.status,
      code: error.code,
      requestId: error.requestId,
      details: error.details,
    });
  }
}

Network errors and caller-triggered aborts remain standard Fetch API errors. The client does not automatically retry trading mutations.

Authentication

Server integrations should normally construct the client with apiKey. Account applications can use sessionToken. login() and confirmEmail() automatically activate the returned session on that client instance:

const accountClient = new PaperTigerClient();

const { user, sessionToken } = await accountClient.login({
  email: "[email protected]",
  password: "replace-with-a-secure-password",
});

The package never persists credentials. Store sessionToken using the secure storage appropriate to your runtime and pass it back to a new client when needed.

API coverage

PaperTigerClient covers health, contact, account authentication, wallets, portfolio history, activity, market discovery, URL resolution, order books, historical definitions/trades/quotes/candles/L2 replay/Parquet exports, single and bulk orders, fills, positions, strategy generation, historical backtests, experiments, immutable strategy versions, managed/external paper runs, decision ingestion, deterministic run analysis/comparison/export, and API-key management. Every method accepts an optional AbortSignal.

Account clients can call updateApiKeyDefaultWallet() to change the wallet used when a key-authenticated request omits a wallet selector, and updateApiKeyAccess() to switch between default-wallet-only and all-wallet access. updateApiKeyLevel() grants or removes account-management authority, and updateApiKey() can change the settings together. Account-level keys are always forced to all-wallet access.

Create an account-level key, then use it to manage paper accounts:

const created = await accountClient.createApiKey({
  name: "Account operator",
  walletId: "existing-wallet-id",
  level: "account",
});

const accountApi = new PaperTigerClient({ apiKey: created.key });
const paperAccount = await accountApi.createAccount({
  name: "Research",
  startingBalanceDollars: 10_000,
});
await accountApi.updateAccount(paperAccount.id, { name: "Research v2" });
await accountApi.resetAccount(paperAccount.id);
await accountApi.deleteAccount(paperAccount.id);

deleteAccount() permanently removes all trading history. The last paper account cannot be deleted, and an active API key's default account must be changed or revoked before that account can be destroyed.

The public request<Response>() method supports newly released endpoints before a dedicated convenience method is available.

Included documentation

  • Complete API reference: package export @discomedia/papertiger/api.md
  • Historical market-data guide: package export @discomedia/papertiger/market-data.md
  • LLM discovery guide: package export @discomedia/papertiger/llms.txt
  • Rendered current documentation: papertigerapp.com/docs
  • Canonical Markdown: papertigerapp.com/api.md

The package build copies the canonical API and LLM files into the published tarball, so installed-package documentation cannot silently diverge from the website source.