Back to AlphaAll Docs & help
METHODOLOGY

How Alpha scoring works

Every number on the Alpha screens comes from the formulas on this page, rendered from the same constants the scorers run on, so what you read here is what's in force. Nothing is predicted, hand-picked, or reordered by engagement.

§1

Principles

Alpha is a screen, not a recommendation. Each tab answers one narrow question with a weighted composite of disclosed, objective signals, and every card shows its full component breakdown so you can see why it ranked where it did. Thresholds exist to filter noise (dust volume, near-zero caps, spam duplicates), not to imply quality.

The underlying question every screen is really asking is “is this worth a closer look, or is it more likely to be a dead-cat bounce, a rug in progress, or an abandoned shell?”, never “will this go up.” No formula can answer that; markets this young and this thin are dominated by noise. What the signals below can do is separate real, broadly-held, currently-active trading from common look-alikes: a finished pump-and-dump (impressive totals, no present: caught by last-hour volume and momentum) and a wash-traded mint (volume with no distinct buyers: caught by holders paid and on-chain distribution).

No fabricated data
A missing input is treated as “unknown” (a neutral default) or excludes the token. It's never scored as zero when zero would be a claim.
Recency beats history
Wherever a real-time signal exists, it outweighs cumulative totals: a finished pump-and-dump has impressive lifetime numbers and none of the present ones.

§2

Shared math

logScore(value, floor, ceiling)

Maps a raw number onto 0–100 on a logarithmic scale: floor → 0, ceiling → 100, clamped beyond either end. Log scaling means going from $1k to $10k of volume is worth as much as $10k to $100k, the right shape for quantities that span orders of magnitude.

logScore⁡(v, floor, ceil)  =  100×clamp⁡ ⁣(ln⁡(v / floor)ln⁡(ceil / floor), 0, 1)\operatorname{logScore}(v,\ \text{floor},\ \text{ceil}) \;=\; 100 \times \operatorname{clamp}\!\left(\dfrac{\ln(v \,/\, \text{floor})}{\ln(\text{ceil} \,/\, \text{floor})},\ 0,\ 1\right)
(1)

Weighted average

Every component is 0–100; the score is the weight-normalised average, so adding an optional component (like “Graduation pace” for bonding-curve tokens) never inflates or deflates a score by itself.

score  =  ∑ivaluei wi∑iwi\text{score} \;=\; \dfrac{\displaystyle\sum_i \text{value}_i \, w_i}{\displaystyle\sum_i w_i}
(2)

momentumScore(recentRate, olderRate)

Compares one window's per-hour volume with the token's own earlier per-hour rate over that same window. 50 means steady; above 50 is accelerating; below is fading. Symmetric on a log scale, clamped to a 4× band either way, so 4× faster and 4× slower sit the same distance from centre. If there is no earlier volume at all, any recent volume scores 100.

r=clamp⁡ ⁣(recentolder, 0.25, 4)momentum=100×ln⁡(r / 0.25)ln⁡(4 / 0.25)\begin{aligned} r &= \operatorname{clamp}\!\left(\dfrac{\text{recent}}{\text{older}},\ 0.25,\ 4\right) \\ \text{momentum} &= 100 \times \dfrac{\ln(r \,/\, 0.25)}{\ln(4 \,/\, 0.25)} \end{aligned}
(3)

computeMomentum(activity, hours): the blend

Every screen that scores momentum runs this same function on two independent windows: the hourly trend (the last hour vs. the rest of the lookback the token has actually existed for) and the 15-minute pulse (the last completed 15-minute candle vs. the one before it). The hourly window needs at least 15 minutes of “before” to compare against (so effectively age ≳ 1h15m); the 15-minute window only needs the token to be at least 30 minutes old, independent of the hourly one. When both exist, the score used for ranking is the average weighted 2:1 toward the hourly read, steadier, but slower to notice a turn than the immediate pulse. When only one exists, that one is used directly. When neither does (a token under 30 minutes old), there is no Momentum component at all: not a guessed 50.

The reason text is built from the same two windows, not the blended number: when they agree, the sentence quotes whichever window's ratio is more extreme (“Accelerating: 3.2x its earlier pace”); when they disagree (the hourly trend still looks fine but the last 15 minutes have gone quiet, or the reverse), that disagreement is treated as the more decision-relevant fact and leads instead (“Cooling in just the last 15 minutes, after a strong hour overall”). A token that's steady on every timeframe gets no momentum line at all: nothing worth saying isn't forced into a sentence.

75Hourly trendweight ×2
9015-min pulseweight ×1
80Blendedround((75×2 + 90) ÷ 3)
Worked example · hourly trend contributes twice as much

distributionScore(top10Pct): on-chain concentration

This reads live from chain (not from StonkFun's snapshot) via the same holder scan the token page's Holders panel uses. It is piecewise-linear through the exact thresholds that panel colours amber and red at, so the score and holder view cannot disagree about what counts as concentrated. Higher means the top 10 real wallets hold less supply.

distributionScore⁡:0%↦100,  20%↦70,  50%↦30,  ≥ 90%↦0\operatorname{distributionScore}: 0\% \mapsto 100,\ \ 20\% \mapsto 70,\ \ 50\% \mapsto 30,\ \ {\ge}\,90\% \mapsto 0
(4)
distributionScore (top-10 wallets)
05010020%50%% of supply
Top-10 concentration score curve

An unreadable scan (RPC unavailable, refused, or simply hasn't run for that mint yet) scores 50, a neutral middle, never a penalty. A token this screen hasn't checked at all (the first, free-signals pass) gets no Distribution component whatsoever, the same “didn't check” vs. “checked, unknown” distinction used everywhere else on this page.

§3

New launches gaining traction

Question: among tokens created in the last 6 / 24 hours, which ones are actually being traded right now, by real people, and still holding what they built?

Candidate pool

The newest-sorted StonkFun list, 10 pages for 6h, 25 pages for 24h (100 tokens per page). StonkFun launches ~100 tokens per 50 minutes, so the pool has to scale with the window or every setting would see the same few dozen mints. Duplicate name+symbol launches (spam clusters) collapse to their best-scoring instance.

Hard exclusions

  • 24h volume under $5,000 or market cap under $15,000: a mint nobody but the deployer has touched.
  • After the chart check: no trade in the last 120 minutes, or under $250 of volume in the last hour. The second rule is what removes a launch that did $150k early and $40 since.
  • More than 75% below its own peak market cap. A dump prints a lot of volume; that volume is not traction, and a weight can't outvote it: only a gate can.
The shortlist guarantees the youngest candidates a real check
The free-signal first pass (see “Two-pass pipeline”) ranks candidates on cumulative size: a token can only have a lot of 24h volume or market cap once it's already been trading a while. Left alone, that means a token in its first few minutes never gets a real-time chart check at all once enough already-larger launches fill the size-ranked shortlist first, so the only launches anyone sees have already run most of the way up. On top of the size-ranked cut, the 15 youngest candidates by age always get a real chart check regardless of their current size, so recency itself can never be gatekept by not being big yet.

Components (total weight 23)

ComponentWeightShareSignal0 → 100 scaling
Last hour volumesecond pass only14%Dollar volume in the trailing 60 minutes (Jupiter candles)logScore(v, 250, 50,000)
Holders paid522%Distinct wallets paid a reward so far (reward ledger)logScore(n, 5, 500); 35 if no ledger row yet
Distributionsecond pass only29%Top-10 real-wallet share of supply, read live from chaindistributionScore(pct), see below; 50 if the scan hasn't run yet
Market cap14%Current market caplogScore(mc, 5,000, 5,000,000)
Holding its peakwhen a peak is known313%Current market cap ÷ peak market capround(ratio × 100), clamped 0–100
Still tradingsecond pass only522%Minutes since the last candle with volume100 × (1 − minutes / 120)
Momentumsecond pass only14%Blend of the hourly trend and the 15-minute pulse; see belowcomputeMomentum(...).value
24h volume14%StonkFun trailing-24h dollar volumelogScore(v, 1,000, 500,000)
Graduation pacebonding-curve tokens only29%Bonding-curve progress ÷ age in hourslogScore(pace, 0.02, 1)
Turnover29%24h volume ÷ market caplogScore(x, 0.05, 15)

Table 1: New Launches component weights

New Launches component weights

Why these weights: holders paid and still trading are weighted heaviest: real distinct wallets, and a live trade within the exclusion window, are what “gaining traction” actually verifies, and neither can be inflated by a mint that's already finished its run. Holding its peak carries the next-most weight: still near its own high is a real anti-dump signal, though a moderate rather than a maximal one on its own. Turnover, graduation pace, and distribution sit in the middle. Last-hour volume and momentum are deliberately light despite sounding like the obvious “traction right now” signals, and market cap and 24h volume are light for the original reason (both reward cumulative size, which a token can only have once it's already grown): re-weighted 2026-09-16 once StonkScout's own continuous learning had 200+ real 24h outcomes per factor to check against, and last-hour volume and momentum were the two factors most correlated with a worse outcome, not a better one — a volume or momentum spike at the moment a token is scored is as often the top of a pump as it is real accumulation. See “Continuous learning” below for the exact correlations behind this and every other screen's weights.

§4

Overlooked, seeing renewed interest

Question: which tokens has the market priced as dead (far below their own peak) that are nonetheless trading live today, and picking up rather than fading?

Candidate pool

The top 300 projects by 24h volume, minus the top 25. The busiest markets on the platform are the opposite of overlooked, whatever their drawdown: a coin at #4 by volume was showing up here before this cut.

Hard exclusions

  • Younger than 7 days: a same-day launch shows a 90% “drawdown” from ordinary first-day volatility.
  • Less than 50% below its peak market cap, or under $500 of 24h volume.
  • After the chart check: no trade in the last 120 minutes.
  • After the chart check: momentum under 50, the last hour slower than the token's own earlier pace. “Picking up, not fading” is the screen's question, so fading is an exclusion, not a low score.

Components (total weight 11)

ComponentWeightShareSignal0 → 100 scaling
Still tradingsecond pass only327%Minutes since the last candle with volume100 × (1 − minutes / 120)
Momentumsecond pass only; also the momentum gate327%Blend of the hourly trend (last hour vs. the previous 11) and the 15-minute pulse; see Shared mathcomputeMomentum(...).value
Below peak218%Drawdown from all-time-peak market cap100 × (drawdown − 0.5) / (1 − 0.5)
Current turnover218%24h volume ÷ market caplogScore(x, 0.05, 10)
Track record19%Lifetime reward payout count (reward ledger)logScore(n, 10, 20,000); 0 if no ledger row

Table 2: Overlooked component weights

Overlooked component weights

Deliberately no market-cap-size component: a $9k comeback with real accelerating volume is exactly what this screen exists to surface and must not be outranked by a $500k one for being larger. A unit test enforces this. Also deliberately no on-chain distribution component, unlike New Launches: these candidates are already established (≥7 days old, traded at real scale), so a live holder scan is both less informative and (for a pool of hundreds of sometimes large-holder-count tokens) meaningfully more expensive to run than on a pool of brand-new, small-holder-count mints.

§5

DEGEN MODE

Question: among launches under an hour old, which ones show no confirmed rug mechanism? This is deliberately not the same question the other three screens ask. For most of this screen's population, none of their proven signals exist yet: New Launches' own volume and market-cap gates ($5,000 / $15,000) are essentially unclearable in the first few minutes, and computeMomentum (see “Shared math”) returns nothing at all under roughly 30 minutes of age — and this screen never leans on momentum even for the older end of its window, close to the one-hour mark, where it could technically be computed. Rather than guess at “potential” from data that mostly doesn't exist yet, this screen is elimination-first: drop anything showing a real, permanent rug mechanism, then score what survives on how clean its remaining on-chain signals are.

Candidate pool

Every StonkFun launch created within the last 60 minutes: the newest-sorted list is fetched fresh every 30 seconds, so a launch enters and leaves this screen entirely based on age, not on how it's performing.

Hard exclusions: only on confirmed-true data

  • Top 10 wallets hold 60% or more of supply, confirmed, but only once at least 15 holders exist. Below that, “top 10 hold X%” is an artifact of being new (top 10 of 8 holders is ~100% by construction), not a real concentration signal, so the gate, and the score component below, simply don't apply yet.
  • Mint or freeze authority confirmed still active: verified on-chain per token, kept as a defensive check, but see the callout below for why this should never actually be what excludes a real StonkFun launch.
A value that couldn't be checked never disqualifies
Every exclusion fires only on a confirmed true. A field the RPC hasn't answered yet (common in the first minute or two of a token's life) leaves that gate un-tripped and its score component at a neutral default: never treated as a pass or a fail it can't actually support.
Why mint/freeze authority isn't scored
Every StonkFun launch renounces both authorities by construction: the platform's own pitch is that liquidity can't be pulled. That makes it a fact about the launchpad, not a signal about any individual token: every legitimate StonkFun launch reads identically, so scoring it can never distinguish one candidate from another, and weighting it would just be wasted capacity. DEGEN MODE still verifies it live per token (a platform's own claim about itself is not the same guarantee as checking the chain directly), but only as a silent gate, never shown as a component or a reason, since a value that never varies isn't information.

The whale signal

StonkScout already maintains a registry of wallets holding real size (10M+ tokens) in the platform's 30 largest tokens: see How holder data works. DEGEN MODE cross-references a brand-new launch's current top holders against that same registry: no new infrastructure, just the holder scan this screen already runs for the concentration gate, checked against data already being fetched for every token page. This is deliberately weighted alongside the safety gates rather than left as a footnote: unlike raw volume or holder count, it's genuinely hard to fake: reproducing it requires an existing wallet with a real, costly prior position in an established token, not a few dollars of self-directed buying.

Components (total weight 65)

Every candidate that remains in Degen Mode receives a Degen Mode Scout Score, including candidates whose newest on-chain checks are still unavailable. Unread signals use the documented neutral value and lower the separate data-confidence label. Top New and Overlooked each calculate their own Scout Score from their own components and gates; scores only compare candidates within the same mode.

ComponentWeightShareSignal0 → 100 scaling
Known whale bought in3554%Is any current top holder a known whale of an established platform token?100 found · 40 checked, none found · 50 couldn't check
Curve pacepre-graduation only812%Bonding-curve progress ÷ age in minutes. Slower measured progress scores higher; zero progress is unverified.100 − logScore(pace, 0.002, 0.15); zero progress is neutral/unknown
Distinct holders1015%How many separate wallets already hold it, read live from chainlogScore(n, 2, 100); 50 if unknown
Holder concentrationonly once 15+ holders exist1218%Top-10 real-wallet share of supply, read live from chainlinear, 20%→100, 60%→0

Table 3: DEGEN MODE component weights

DEGEN MODE component weights
What this screen can't see
The absence of a whale buy is not itself a red flag: most legitimate launches simply won't have one yet. Concentration and distinct-holder checks share a real blind spot: a deployer who pre-splits supply across many fresh wallets before launch can make “well distributed, N holders” read as true while one person still controls all of it, and curve pace is trivially reproducible by a deployer buying their own bonding curve. StonkScout has no per-wallet trade feed to tell organic buying from that pattern: a high score here means the candidate looks clean on a handful of real but gameable checks, not that it's safe to trade.

Feeds the same learning loop as every other screen (see “Continuous learning”, below): once enough sighted coins have closed paper-trade returns, each component's weight adjusts based on what actually happened to tokens that showed it.

§6

The two-pass pipeline

Real-time recency and momentum need one Jupiter chart fetch per token (48 fifteen-minute candles, a 12-hour lookback); on New Launches, the on-chain components need one holder scan per token on top of that (Overlooked is the one screen that reads neither; see its own section for why). Both are cheap individually but not for a pool that can run to thousands, so every tab runs the same shape:

Free signalsscore every candidateShortlisttop 40Chart + holdersfetch in parallelRe-scorefinal top 12
Two-pass ranking · illustrated sequence
  1. Score every candidate on the free signals only (no chart data, no holder scan) and rank.
  2. Take the top 40, fetch their recent-activity candles and (where applicable) their holder scans in parallel.
  3. Re-score those with the real-time and on-chain components added, drop anything that turned out stale, rank again.
  4. Show the top 12.

Only the active tab's pipeline runs per request; the other tabs cost nothing until you open them.

§7

Continuous learning

Alpha records each coin's first score and component values. Its learning target is the average return from that coin's fully closed simulated positions under the strategies' entry and exit rules, net of modelled trading costs. Each closed position has equal weight in that average. Open positions, dip-entry strategies and holder rewards are excluded. The return is joined by coin to its first Alpha sighting; a coin contributes one paired sample, not one sample per strategy. It can contribute before every strategy has closed.

Each screen compares recorded scores and factor values with those returns separately. Only coins taken by the simulation are represented; this is historical calibration, not evidence of out-of-sample predictive power. Current correlations, sample sizes and stored adjustments are on /alpha/learning. Its totals include retired screens, while its factor tables cover live screens.

Since 2026-09-21, the 24h market-cap multiple is context only and never changes a weight. A recovery after a strategy exits is not that strategy's return. Older simulated exits may include stale-price errors fixed on that date. Trend comparisons use only snapshots with the same trade-return target.

  1. 1

    Score

    Record each coin's first Alpha score and factor values.

  2. 2

    Simulate

    Eligible strategies paper-trade calls using their entry and exit rules.

  3. 3

    Measure

    Average closed, non-dip position returns per coin, net of modelled costs.

  4. 4

    Reweight

    Compare factors with returns across the sample, then nudge eligible weights.

Learning cycle · illustrated, not live status

Spearman rank correlation

A few extreme returns can dominate a raw value-based correlation. Spearman correlation instead compares ranks: do higher recorded factor values tend to accompany better average paper-trade returns? It measures association, not the size of the profit or a guarantee that the factor will predict future returns. We write it ρ:

ρ  =  Pearson⁡(rank⁡(component values), rank⁡(average closed paper-trade returns))\rho \;=\; \operatorname{Pearson}\Big(\operatorname{rank}(\text{component values}),\ \operatorname{rank}(\text{average closed paper-trade returns})\Big)
(5)

Three guardrails

Minimum sample size
A component's correlation counts for nothing until it has at least 20 paired coins and both sets of values vary. Below that, its multiplier doesn't move: “not enough evidence yet” is not the same claim as “confirmed neutral,” and must not be treated as one.
Bounded, never dominant
Every multiplier is clamped to 0.7×–1.3×. Real outcomes can sand a factor's influence up or down by at most 30%: never zero it out, never let it eclipse the hand-designed weights in lib/alpha/scoring.ts, which stay authoritative.
Damped, not recalculated
Each cycle moves a multiplier 25% of the way toward what the latest evidence supports, not straight to it: a single noisy batch of outcomes can nudge, but can't swing, a factor's weight.
target=clamp⁡(1+ρ⋅0.3,  0.7,  1.3)adjustment′=clamp⁡(adjustment+0.25 (target−adjustment),  0.7,  1.3)\begin{aligned} \text{target} &= \operatorname{clamp}\Big(1 + \rho \cdot 0.3,\ \ 0.7,\ \ 1.3\Big) \\ \text{adjustment}' &= \operatorname{clamp}\Big(\text{adjustment} + 0.25\,(\text{target} - \text{adjustment}),\ \ 0.7,\ \ 1.3\Big) \end{aligned}
(6)

The Learning page shows how many more paired coins a factor needs, without predicting when trades will close. A constant factor or return also has no measurable correlation. Previously stored multipliers remain in force when evidence is insufficient. A positive correlation can reduce a multiplier already above its target; colours describe the correlation, not the last weight movement. Effective weight is base weight × multiplier.

When this runs

The running server's research maintenance worker checks for learning cycles independently of page traffic, unless that worker is disabled. Portfolio-strength requests can also trigger the same gate. A cycle runs at most once per 3 hours in the running process: it attempts a bounded batch of 24h outcomes for context, then recomputes weights from available closed paper-trade returns. Alpha ranking reads the stored weights; opening an Alpha screen does not wait for retraining. Reports are cached briefly, and current correlations may differ from the evidence behind the last stored adjustment.

§8

Data sources & caveats

  • StonkFun public API: market snapshot (price, market cap, peak, 24h volume, 24h change), the platform reward ledger (payout counts, holders paid), pairs, and launch timestamps. Its only server-side sorts are market cap, volume, and newest, which is why candidate pools are bounded rather than exhaustive.
  • Jupiter chart data (datapi.jup.ag): per-candle volume for recency and momentum. It aggregates every venue a token trades on and runs well above StonkFun's own 24h figure, so last-hour dollar amounts are comparable between launches, not with StonkFun's totals. It is an unofficial endpoint and could change without notice.
  • Holders paid only exists for reward-mode tokens that have completed at least one distribution. Standard launches and pre-first-payout tokens get the neutral default, not zero.
  • On-chain holder scan (Distribution on New Launches; Holder concentration and Known whale bought in on DEGEN MODE): the same live `getProgramAccounts` read used by the token page's Holders panel, not part of the StonkFun snapshot. Not run on Overlooked (see its section for why). Cached 2 minutes per mint, so a token shown on more than one screen, or reloaded within the window, costs nothing extra. A refused or failed scan scores neutral, never a penalty; see “Shared math” above.
  • Mint/freeze authority (DEGEN MODE only): read from the same on-chain mint account StonkScout already fetches for a token's decimals, cached 24 hours since it's immutable once renounced. A defensive gate only, not a scored or displayed signal; see “Why mint/freeze authority isn't scored” above.
  • Scores are recomputed on every request from live data; the same token can move between screens minute to minute as its activity changes. That's the intended behaviour.
Why every scan has a 5-second ceiling
Live-tested against real RPC load, a holder scan occasionally stalled for over 40 seconds on a single mint: long enough to stall an entire page render behind it. Each mint now gets a 5-second ceiling; a scan that hasn't answered by then is treated the same as a failed one, scoring the neutral default rather than blocking the page.

Source: src/lib/alpha/scoring.ts and its unit tests, which pin the regression cases each rule was added for.