Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/tripolskypetr/pump-anomaly/llms.txt

Use this file to discover all available pages before exploring further.

This guide walks you from a fresh install to a live-ready trade plan in a single file. You will install the package, define the getCandles adapter that connects the library to your exchange data source, train a model on historical ParserItem[] signals, verify the statistical certificate, persist the model to disk, and call plan() to get executable TradeSignal[] objects. The whole flow — including serialization — takes under 100 lines of TypeScript.
1

Install the package

Add pump-anomaly to your project with your preferred package manager.
npm install pump-anomaly
2

Understand the ParserItem data shape

Every signal in your Telegram history must conform to the ParserItem interface. Only channel, symbol, direction, and ts are required by the detector — all other fields are passed through untouched.
import type { ParserItem } from "pump-anomaly";

// The interface from types.ts — all fields the library reads:
interface ParserItem {
  channel: string;           // Telegram channel identifier (required)
  symbol: string;            // e.g. "SOLUSDT"
  direction: "long" | "short";
  ts: number;                // unix time of publication, milliseconds
  entryFromPrice?: number;   // lower bound of entry zone (optional)
  entryToPrice?: number;     // upper bound of entry zone (optional)
  id?: string | number;      // optional source post id — threaded to dump() and origin
  [extra: string]: unknown;  // targets/stoploss/etc. are allowed and ignored
}

// Minimal valid example:
const item: ParserItem = {
  channel: "alpha_calls",
  symbol: "SOLUSDT",
  direction: "long",
  ts: 1_700_000_000_000,
};
3

Implement the getCandles adapter

pump-anomaly needs an async function that fetches 1-minute OHLCV candles from your exchange. The signature is fixed — implement it once and reuse it for training, live, and backtest.
import type { GetCandles, ICandleData } from "pump-anomaly";

// The full type from candle.ts:
// type CandleInterval = "1m"|"3m"|"5m"|"15m"|"30m"|"1h"|"2h"|"4h"|"6h"|"8h"|"1d";
//
// type GetCandles = (
//   symbol: string,
//   interval: CandleInterval,
//   limit?: number,
//   sDate?: number,   // inclusive, unix ms
//   eDate?: number,   // exclusive, unix ms
// ) => Promise<ICandleData[]>;

// Minimal ccxt / Binance example:
import ccxt from "ccxt";

const exchange = new ccxt.binance({
  enableRateLimit: true,
  options: { defaultType: "spot" },
});
await exchange.loadMarkets();

const getCandles: GetCandles = async (symbol, interval, limit, sDate, eDate) => {
  const rows = await exchange.fetchOHLCV(
    symbol,
    interval,
    sDate,           // since (ms) — undefined is fine for ccxt
    limit,
  );
  return rows.map(([timestamp, open, high, low, close, volume]) => ({
    timestamp,   // unix ms, candle OPEN time
    open,
    high,
    low,
    close,
    volume,
  })) as ICandleData[];
};
The library internally chunks large candle requests with fetchCandlesChunked — your adapter does not need to paginate. If your adapter throws (e.g. unknown symbol, rate-limit), that candidate is skipped and training continues rather than crashing.
4

Train the model with PumpMatrix.fit()

Pass your full ParserItem[] history and the getCandles adapter. Training replays each candidate burst on 1m candles, runs a K-fold grid search, and selects the most conservative configuration within one standard error of the CV maximum.
import { PumpMatrix, silentProgress } from "pump-anomaly";
import type { ParserItem } from "pump-anomaly";
import * as fs from "fs";

// Load your historical channel signals (e.g. from a JSON file):
const history: ParserItem[] = JSON.parse(
  fs.readFileSync("assets/parser-items.json", "utf8")
);

// Train — this is the slow step (IO-bound candle labeling):
const model = await PumpMatrix.fit(history, getCandles, {
  // Silence the stdout progress bar in scripts; omit for the default bar:
  onProgress: silentProgress,
});

console.log("Mode:", model.mode);          // "matrix" | "single"
console.log("Reliable:", model.reliable);  // did training have enough data?
console.log("Confidence:", model.confidence); // 0..1
A stdout progress bar renders by default across three phases — label (candle replay, slow), score (grid evaluation from cache), and nested (one tick per outer nested-CV fold):
[██████████████░░░░░░░░░░░░░░░░] 47% (42/90) label TRXUSDT
[██████████████████████████████] 100% (27/27) score 5|0.4|0.6|all
5

Check model.certification.certified

Before saving or trading, verify that the five statistical barriers all passed. A grid search always finds a “best” configuration — even on pure noise. The certificate is the independent judge that confirms the edge is real.
const cert = model.certification;

if (!cert.certified) {
  console.warn("Model NOT certified — do not trade.");
  console.warn("Reasons:", cert.reasons);
  // cert.reasons might contain entries like:
  //   "DSR 0.82 < threshold 0.95"
  //   "PBO 0.23 > threshold 0.10"
  //   "SPA p=0.12 > threshold 0.05"
  //   "actualN 28 < minTRL 35"
  //   "nestedScore -0.003 ≤ 0"
  process.exit(1);
}

console.log("Certified ✓");
console.log("  DSR:", cert.dsr);           // Deflated Sharpe Ratio (≥ 0.95)
console.log("  PBO:", cert.pbo);           // Probability of Backtest Overfitting (≤ 0.10)
console.log("  SPA p-value:", cert.spaPValue); // ≤ 0.05
console.log("  N:", cert.actualN, "≥ minTRL:", cert.minTRL);
console.log("  Nested OOS score:", cert.nestedScore); // > 0
A certified: false model still has an argmax winner from the grid. That winner is a brute-force artifact, not a real edge. The reliable flag alone cannot detect this — only the certificate can. Do not trade until certified: true.
6

Save the model and reload it in production

Serialize the trained model to a JSON string with model.save(). In production, restore it instantly with PumpMatrix.load() — no retraining, no candle requests.
// Save (after training):
const json = model.save(); // JSON string — includes exit tensor, policy, history, cert
fs.writeFileSync("assets/model-weights.json", json, "utf8");
console.log("Saved", json.length, "bytes");

// Load in production (fast, no getCandles needed):
const saved = fs.readFileSync("assets/model-weights.json", "utf8");
const prodModel = PumpMatrix.load(saved);

// Alternatively, load from an already-parsed object:
const weights = JSON.parse(saved);
const prodModel2 = PumpMatrix.load(weights);

console.log("Loaded model, mode:", prodModel.mode);
console.log("Impact horizon:", prodModel.impactHorizonMinutes, "min");
console.log("Lookback needed for plan():", prodModel.lookbackMinutes, "min");
7

Execute live signals with model.plan()

In production, call model.plan(liveItems, getCandles) to get cascade-aware executable signals. Each TradeSignal has direction already resolved (inversion applied if needed), an entry zone, and a flat exit plan ready to pass to your broker adapter.
import type { TradeSignal } from "pump-anomaly";

// liveItems = the latest ParserItem[] from your Telegram parser
const trades: TradeSignal[] = await prodModel.plan(liveItems, getCandles);

for (const s of trades) {
  console.log(
    s.symbol,
    s.direction,          // already inverted if squeezePolicy="invert"
    s.action,             // "enter" | "tighten" | "invert"
    s.exit.trailingTake,  // trailing take %, already tightened if action="tighten"
    s.exit.hardStop,      // hard stop %
    s.exit.impactHorizonMinutes, // max holding period
  );

  // Open the position — no if-statements about veto or inversion needed:
  openPosition(
    s.symbol,
    s.direction,
    { from: s.entryFromPrice, to: s.entryToPrice },
    s.exit,
  );
}

Complete End-to-End Example

import { PumpMatrix, silentProgress } from "pump-anomaly";
import type { ParserItem, GetCandles, ICandleData, TradeSignal } from "pump-anomaly";
import ccxt from "ccxt";
import * as fs from "fs";

// ── 1. Build the getCandles adapter ──────────────────────────────────────────
const exchange = new ccxt.binance({ enableRateLimit: true });
await exchange.loadMarkets();

const getCandles: GetCandles = async (symbol, interval, limit, sDate) => {
  const rows = await exchange.fetchOHLCV(symbol, interval, sDate, limit);
  return rows.map(([timestamp, open, high, low, close, volume]) => ({
    timestamp, open, high, low, close, volume,
  })) as ICandleData[];
};

// ── 2. Load history ───────────────────────────────────────────────────────────
const history: ParserItem[] = JSON.parse(
  fs.readFileSync("assets/parser-items.json", "utf8")
);

// ── 3. Train ──────────────────────────────────────────────────────────────────
const model = await PumpMatrix.fit(history, getCandles, {
  onProgress: silentProgress,
});

// ── 4. Check certification ────────────────────────────────────────────────────
if (!model.certification.certified) {
  console.error("Not certified:", model.certification.reasons);
  process.exit(1);
}

// ── 5. Save ───────────────────────────────────────────────────────────────────
fs.writeFileSync("assets/model-weights.json", model.save(), "utf8");

// ── 6. Load in prod (skip training) ──────────────────────────────────────────
const prodModel = PumpMatrix.load(
  fs.readFileSync("assets/model-weights.json", "utf8")
);

// ── 7. Get live signals ───────────────────────────────────────────────────────
const liveItems: ParserItem[] = [
  { channel: "alpha_calls", symbol: "SOLUSDT", direction: "long", ts: Date.now() },
];

// plan() adds liquidation-cascade detection from pre-signal candles (no look-ahead):
const trades: TradeSignal[] = await prodModel.plan(liveItems, getCandles);

for (const s of trades) {
  openPosition(
    s.symbol,
    s.direction,
    { from: s.entryFromPrice, to: s.entryToPrice },
    s.exit,
  );
}

Fast path: signals() with no candles. model.signals(liveItems) skips candle fetching entirely. Because no candles are available, the liquidation-cascade detector cannot run — every outcome is "enter". Use this path when latency is critical and you are willing to accept that stop-hunt traps are not filtered at runtime. The trained exit parameters still apply.
// Synchronous, no network calls, cascade not evaluated:
const trades = prodModel.signals(liveItems);
The DEFAULT_GRID covers a broad search space suitable for most crypto assets. For asset-specific grids (HYPE, Solana, BTC, DOGE, and more), see /guides/training for the full grid rationale and per-asset TrainGrid configuration options.

Build docs developers (and LLMs) love