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.
pump-anomaly is a black box for detecting synchronized pump signals in a stream of Telegram trading recommendations and turning that detection into a ready-to-execute trade plan. It solves three problems at once: separating real capital inflow (several independent authors hitting the same ticker in sync) from single-actor manipulation across anonymous channels, separating a genuine pump from a stop-hunting trap (a liquidation cascade that wipes out leveraged longs or shorts), and producing a trade plan with trained exit parameters tuned per source. All three decisions — detect, classify the cascade, resolve the exit — are made inside the library. Prod code just executes s.direction with s.exit.
The Detection Pipeline
The core of matrix mode is a five-layer signal processing chain. Each layer narrows the candidate set, passing only what the next layer needs.selfTuneLag
Estimates the characteristic lag τ between sibling channels from the histogram of pairwise delays. No magic constants — the burst window is derived from τ. See Detector Layers for the full algorithm.
jaccardScreen
Coarse Jaccard similarity sieve over a sliding window of raw timestamps. Discards channel pairs whose co-occurrence is too sparse to investigate further.
lagXCorr
Directed cross-correlation graph of “who follows whom.” A sharp peak in the delay distribution confirms a sibling relationship; a smeared background is dropped.
clusterAuthors
Union-find merges every channel belonging to the same author into one cluster. An author running ten channels still counts as one independent voice.
singleChannelSignals. See Detector Layers for a full deep-dive into each layer.
Entry-Selection Modes
The mode controls the entry condition, but the exit is not shared — it is tuned separately per cell of the exit tensor regardless of mode.| Mode | Entry condition | When used |
|---|---|---|
matrix | Synchronous burst across ≥ minClusters independent author clusters | 2+ viable channels with a real correlation |
single | Every post is an entry; trained exit decides the outcome | 1 channel or low matrix viability |
auto | matrix if viable and the matrix produced a signal; otherwise single | Default |
auto is the safe choice for a production pipeline: it falls back to single rather than emitting a false signal from a noisy two-channel coincidence. The usedMode and viability fields give you a machine-readable explanation of which branch was taken.
Matrix Viability
Two channels do not guarantee matrix mode. If their overlap is noisy — Jaccard crossed the threshold on one or two events, no sharp edges, a trivial graph —viability.viable is false and auto falls back to single rather than emitting a false signal. The strict default criterion (DEFAULT_VIABILITY) requires all four conditions simultaneously:
ViabilityReport returned in result.viability tells you exactly why the decision was made:
viability to fit or predict. All conditions must hold simultaneously — there is no partial credit.
Training Labels — Path-Aware Replay
The training label comes from an exact replay of your production exit on 1m candles (replayExit), not close-to-close. This is the most important property of the library: the optimizer sees the actual path the price took, not just two endpoints.
moonbag / gravebag
For a long (
moonbag), the hard stop fires when the low of a 1m candle drops hardStop % below entry. For a short (gravebag), it fires when the high rises hardStop % above entry. Both are honest realized losses.Trailing take
Once
currentProfit >= 0, the peak is tracked. When the close pulls back trailingTake % from the peak, the position exits at the peak PnL — not at the pullback close.Peak staleness
If the peak exceeds
stalenessSinceProfit % profit but no new high is made for stalenessSinceMinutes minutes, the position exits at the stale peak. The price may never reach a classic TP target at all.Life-cap
staleMinutes is the ceiling on position lifetime — the empirical impact horizon tuned by the grid. The position exits at the close of the last candle in the window, realizing whatever PnL (positive or negative) is there.trailingTake because the pullback hits the hard stop. The label is negative even if close[t+H] happens to be positive. The optimizer sees the risk of stops directly in the training labels.
An earlier version of the library rolled the realized PnL back to the last positive peak on a stop-out, which meant a stop-out never showed a loss and silently inflated PnL and risk-reward. This is now fixed — hard-stop always returns -hardStop % as the realized PnL.
Look-Ahead Prevention
The candle that contains the signal is still forming when the signal arrives. Its close, high, and low are only known at the end of that minute. Entering it would be peeking ahead into the future. The library avoids this withentryStartTs: the entry search starts at the next fully-closed candle after the signal. A signal that lands exactly on a candle boundary is tradeable (that candle is already closed) and is not skipped.
plan() (live mode), getCandles is called for candles strictly before the signal — no look-ahead by construction. backtest() replays forward over already-closed history.
plan() vs backtest() — these two methods have different semantics:plan(items, source)is the live decision. It measures the cascade using candles before the signal (squeezePressureBefore), returns aTradeSignal(decision only — no result), and never requests a candle withts ≥ entryStartTs.backtest(items, source)replays forward over closed history. It measures the cascade using candles after the entry, runsreplayExiton the 1m path, and returns aBacktestSignalwith aresultthat has realized PnL, exit reason, and entry/exit prices.
signals() uses neither — cascade is not evaluated and every verdict is enter.Stationarity Window
On five months of data, statistics accumulate over incomparable regimes. Channels appear and go quiet, sibling pairs break up, and the author matrix built in January still remembers those connections in May. The fix is a local window:selfTuneLag and clusterAuthors are computed only over the most recent stationarityWindowMs of data ending at the latest event, not over the whole history. The window size is a grid axis tuned by cross-validation:
Infinity (the default) uses the entire history. On a long horizon a finite window typically wins — it drops stale connections that no longer reflect the current regime. The stationarity window affects only matrix mode; single mode is independent of it.