import { type Static } from "@sinclair/typebox"; /** * Discrete signal types emitted by the emerging-movers scanner (v4). * * Each type corresponds to a distinct market behaviour pattern detected in * the SM (smart-money) leaderboard. They are listed in priority order — when * multiple patterns fire for the same token, the highest-priority type wins. * * - **FIRST_JUMP** — Token rockets 10+ ranks from deep in the list and was * either absent from the previous scan or ranked >= 30. This is the earliest * possible entry signal; it fires before confirmation to catch moves early. * * - **CONTRIB_EXPLOSION** — Top-trader contribution increased 3x+ in a single * scan, indicating a sudden concentration of smart-money interest. * * - **IMMEDIATE_MOVER** — 10+ rank jump from >= #25 in one scan, but the token * was already tracked in the previous top-N (ruling out FIRST_JUMP). * * - **NEW_ENTRY_DEEP** — Token appears in the top-N for the first time and * lands directly in the top 20, suggesting strong initial positioning. * * - **DEEP_CLIMBER** — Moderate rank improvement (>= minRankJump) from a deep * starting position (>= #25). Catch-all for significant but non-explosive moves. */ export type EmergingMoverSignalType = "FIRST_JUMP" | "CONTRIB_EXPLOSION" | "IMMEDIATE_MOVER" | "NEW_ENTRY_DEEP" | "DEEP_CLIMBER"; declare const emergingMoversConfigSchema: import("@sinclair/typebox").TObject<{ topN: import("@sinclair/typebox").TNumber; minRankJump: import("@sinclair/typebox").TNumber; minTopTradersGain: import("@sinclair/typebox").TNumber; minScansBeforeSignals: import("@sinclair/typebox").TNumber; historyLimit: import("@sinclair/typebox").TNumber; immediateJumpThreshold: import("@sinclair/typebox").TOptional; deepClimbRankThreshold: import("@sinclair/typebox").TOptional; contribExplosionMultiplier: import("@sinclair/typebox").TOptional; contribAccelThreshold: import("@sinclair/typebox").TOptional; minVelocityForDeepClimber: import("@sinclair/typebox").TOptional; erraticReversalThreshold: import("@sinclair/typebox").TOptional; climbStreakScans: import("@sinclair/typebox").TOptional; rankClimbThreshold: import("@sinclair/typebox").TOptional; newEntryDeepMaxRank: import("@sinclair/typebox").TOptional; newEntryMaxRank: import("@sinclair/typebox").TOptional; firstJumpMinPrevRank: import("@sinclair/typebox").TOptional; /** Enable regime-aware score gating via market-regime artifact. Default: false (disabled). */ regimeScoreGating: import("@sinclair/typebox").TOptional; /** Score threshold when market regime is NEUTRAL. Only used when regimeScoreGating is true. */ regimeScoreNeutral: import("@sinclair/typebox").TOptional; /** Score threshold when market regime is BULLISH or BEARISH. Only used when regimeScoreGating is true. */ regimeScoreDefault: import("@sinclair/typebox").TOptional; /** Minimum asset max leverage to emit signals. 0 = disabled (no filtering). */ minLeverage: import("@sinclair/typebox").TOptional; /** Token blacklist — excluded from detection pipeline before topN selection. Case-insensitive; supports plain tokens (`DOGE`) and dex-qualified keys (`xyz:DOGE`). */ blacklist: import("@sinclair/typebox").TOptional>; }>; export type EmergingMoversConfig = Static; /** * Config with all optional v4 fields guaranteed present. * Created by {@link resolveConfig} at the start of each scan so detection * rules never need to handle `undefined`. */ export type ResolvedEmergingMoversConfig = Required; /** * Persisted state carried across scan runs. * * The scanner is stateful: it must compare the current leaderboard snapshot * against previous ones to detect rank movements and contribution trends. * This state is saved to disk via `ctx.setState()` after every scan. */ export interface EmergingMoversState { /** Total number of completed scans. Used to enforce the warm-up gate * (`minScansBeforeSignals`) — no signals are emitted until enough * history has accumulated to make comparisons meaningful. */ scans: number; /** Per-token rank history (keyed by {@link tokenKey}). * Each entry is a sliding window of the token's leaderboard rank * across recent scans, capped at `historyLimit`. Enables multi-scan * climb detection, streak analysis, and erratic-history filtering. */ rankHistory: Record; /** Per-token contribution history (keyed by {@link tokenKey}). * Mirrors `rankHistory` but tracks `pctOfTopTradersGain` values. * Used for contribution velocity, acceleration, and explosion detection. */ contribHistory: Record; /** Token keys present in the previous scan's top-N. * Compared against the current scan to distinguish "first appearance" * signals (FIRST_JUMP, NEW_ENTRY_DEEP) from rank movements within * an already-tracked set. */ prevTopTokens: string[]; } /** * Fully enriched alert candidate produced by {@link analyzeMarket}. * * Combines the market's raw data, accumulated detection flags, computed * velocity, and history windows. Passed to {@link classifySignal} for * type resolution and to {@link alertToSignal} for Signal construction. */ interface AlertCandidate { /** Token symbol (e.g. "SOL", "WIF"). */ token: string; /** Optional DEX identifier (e.g. "xyz"). When present, the token is * qualified as `xyz:` in the emitted signal's `asset` field. */ dex: string | undefined; /** Market direction string from the SM data (e.g. "up", "short"). */ direction: string; /** 1-based rank in the current scan's top-N. */ currentRank: number; /** Raw `pctOfTopTradersGain` in the current scan. */ currentContrib: number; /** 4-hour token price change percentage from the SM data source. */ priceChg4h: number; /** Number of tracked traders for this token. */ traderCount: number; /** Human-readable reason strings from every triggered detection rule. */ reasons: string[]; /** See {@link DetectionFlags.isDeepClimber}. */ isDeepClimber: boolean; /** See {@link DetectionFlags.isImmediate}. */ isImmediate: boolean; /** See {@link DetectionFlags.isFirstJump}. */ isFirstJump: boolean; /** See {@link DetectionFlags.isContribExplosion}. */ isContribExplosion: boolean; /** See {@link DetectionFlags.rankJumpThisScan}. */ rankJumpThisScan: number; /** Average contribution change per scan over the recent window. * Positive = contribution is accelerating; used by the velocity gate. */ contribVelocity: number; /** Last <=5 rank values plus the current rank (for metadata / erratic check). */ rankHistoryWindow: (number | null)[]; /** Last <=5 contribution values plus the current one (as percentages, for metadata). */ contribHistoryWindow: (number | null)[]; } /** * Detects zigzag rank patterns that indicate market noise rather than a * genuine trend. * * Scans consecutive rank deltas for direction reversals that exceed the * threshold (e.g. improving 10 ranks then dropping 8). Such zigzags suggest * the token is bouncing rather than sustainably climbing, and are used to * downgrade IMMEDIATE_MOVER signals to the lower-priority DEEP_CLIMBER. * * @param excludeLast - When `true`, the final entry (this scan's rank) is * removed before checking. This is critical for big-jump signals: the * jump itself is the signal, not noise, so only the *pre-jump* history * should be evaluated for erratic behaviour. */ export declare function isErraticHistory(rankHistory: (number | null)[], erraticReversalThreshold: number, excludeLast?: boolean): boolean; /** * Computes average contribution change per scan over the given window. * * Uses (last - first) / (length - 1) rather than summing individual deltas, * which is algebraically identical but avoids floating-point accumulation. * A positive result means contribution is trending up; the velocity gate * uses this to filter out tokens whose rank improvement is not backed by * growing smart-money concentration. */ export declare function computeContribVelocity(contribs: number[]): number; /** * Classifies an alert candidate into a signal type and applies quality * filters (erratic history, velocity gate). * * The classification pipeline is: * 1. **Resolve signal type** from detection flags (priority order). * 2. **Check erratic history** — zigzag rank patterns indicate noise. * 3. **Check velocity gate** — contribution velocity must be positive * (IMMEDIATE/FIRST_JUMP) or above a minimum (DEEP_CLIMBER). * 4. **Apply downgrades** — IMMEDIATE_MOVER is demoted to DEEP_CLIMBER * when erratic or low-velocity. FIRST_JUMP and CONTRIB_EXPLOSION are * immune to downgrades because they represent decisive, one-scan events * where historical noise is irrelevant. * * @returns Classification result, or `null` if the candidate has no reasons * (should not happen if called from {@link analyzeMarket}). */ export declare function classifySignal(alert: AlertCandidate, config: ResolvedEmergingMoversConfig): { signalType: EmergingMoverSignalType; priority: number; conviction: number; erratic: boolean; lowVelocity: boolean; } | null; /** * Creates the emerging-movers scanner instance (v4). * * This is the entry point registered in the scanner registry. It produces a * stateful scanner that: * 1. Ranks markets by `pctOfTopTradersGain` and tracks the top N. * 2. Maintains per-token rank and contribution history across scans. * 3. Runs 7 detection rules per market to identify emerging movers. * 4. Classifies alerts into 5 signal types with conviction scoring. * 5. Applies erratic-history and velocity-gate filters to reduce noise. */ export declare function emergingMoversScanner(): import("../index.js").Scanner<{ immediateJumpThreshold?: number | undefined; deepClimbRankThreshold?: number | undefined; contribExplosionMultiplier?: number | undefined; contribAccelThreshold?: number | undefined; minVelocityForDeepClimber?: number | undefined; erraticReversalThreshold?: number | undefined; climbStreakScans?: number | undefined; rankClimbThreshold?: number | undefined; newEntryDeepMaxRank?: number | undefined; newEntryMaxRank?: number | undefined; firstJumpMinPrevRank?: number | undefined; regimeScoreGating?: boolean | undefined; regimeScoreNeutral?: number | undefined; regimeScoreDefault?: number | undefined; minLeverage?: number | undefined; blacklist?: string[] | undefined; topN: number; minRankJump: number; minTopTradersGain: number; minScansBeforeSignals: number; historyLimit: number; }, EmergingMoversState>; export {}; //# sourceMappingURL=emerging-movers.d.ts.map