Transparency

Score Methodology

Every BullRadar Score is produced by the same deterministic pipeline that runs in production. This page documents every input, weight, and edge-case treatment verbatim from the source code — so you can audit any token's score against the formula below.

The number you see

BullRadar Score (0–100)

Five-factor core + optional community social signal

Shown on every token page. Higher = safer and hotter. Clamped 0–100. Computed as Σ(raw × weight). The five core factors are unchanged when RSS is unavailable; a valid normalized BullRadar community score adds one optional 15% social contribution.

FactorRaw signal (0–100)MilestoneWeight
24h VolumevolumeUsd / $50 k × 100, capped 100$50 k
20%
Holder concentration100 − topHolderPct, capped 0–100—
20%
LiquidityliquidityUsd / $100 k × 100, capped 100$100 k
20%
MomentumtrendingScore (0–100, see §3)—
15%
Risk (safety)100 − rugRiskScore—
25%
Community social buzzexact-pair tagged posts + persisted token activity, capped 0–10010 recent posts = 100
15% optional

App-native social signal

The scorer reads only CommunityPost rows whose persisted tokenScannedPairId exactly matches the ScannedPair being written. It keeps posts from the last 24 hours, deduplicates by post id, and ignores stale, future, untagged, or differently-paired rows. Engagement is defined here as tagged-post activity (count plus recency); this checkout has no stored likes, replies, views, or reactions counter.

postScore = min(count ÷ 10 × 100, 100) × (0.5 + 0.5 × averageRecency)
activityScore = weighted(volume, liquidity, |Δ24h|, trendingScore)
socialScore = round(0.70 × postScore + 0.30 × activityScore)

Activity milestones are $50k volume, $100k liquidity, 50% absolute 24-hour change, and the stored 0–100 trending score. Their weights are 40%, 20%, 20%, and 20%; available fields are renormalized. No qualifying post or no positive finite activity returns no signal, leaves the social fields null, and removes the optional 15% leg rather than fabricating a zero.

Social-buzz source, contribution, and freshness

A persisted reddit-rss value comes from the scanner's public Reddit search RSS capture and is saved as a dated token-history point only: its normalized score and fetch time are saved as the observation score and time. It does not feed the live social-buzz score or BullRadar ranking. Those continue to use exact-pair tagged CommunityPost rows, which emit source: 'bullradar-community'. The live CommunityPost signal retains its source URL and fetchedAt metadata; RSS history stores the exact fetch time in observedAt.

RSS and CommunityPost normalization use a fixed 24-hour lookback, discard stale or future results, deduplicate entries, and cap the count at 500. CommunityPost rows must also be tagged to the exact pair; untagged or differently-paired rows are discarded. Ten recent posts maps to 100 before the recency multiplier. Activity uses $50k volume, $100k liquidity, absolute 50% 24-hour price change, and the stored 0–100 trending score; if an activity field is absent, the remaining weights are renormalized.

For valid persisted social data, socialScore = round(0.70 × postScore + 0.30 × activityScore) on a 0–100 scale. The displayed methodology contribution is that social raw score multiplied by the optional 15% weight; the existing core factor weights do not change.

The live CommunityPost signal records the 24-hour lookback and fetch timestamp. The ranking presentation labels that signal stale at 24 hours or older while retaining its attribution, rather than presenting a fabricated zero. No qualifying post, no positive finite activity, invalid persisted metadata, or a provider/read failure yields no social signal (null/omitted optional leg). The core ranking remains usable, and the social contribution is not treated as zero.

BullRadar bands

safe
≥ 70
caution
40 – 69
risky
< 40

Safety leg input

Rug-risk score (1–100)

Additive bumps from a base of 10

Stored in the DB score column. Higher = riskier. The Safety factor fed into the BullRadar Score is 100 − rugRiskScore.

SignalAdditive weight
Base score10
Honeypot detected+85
Top holder > 50% of supply+30
Top holder 20–50% of supply+15
No LP reading (liquidityUsd ≤ 0)+40 base + maturity sub-bump 0–18
Thin liquidity ($0 < liq < $1 k)+25
Low liquidity ($1 k – $10 k)+12
Owner cannot renounce mint key+8

Zero-liquidity maturity sub-bump (0–18 extra points)

When liquidityUsd = 0, the scanner can't confirm a clean LP, so it adds a +40 base bump. Hot pairs (high volume, big price move, or large market cap) receive up to 18 extra points on top, producing distinct risk integers per token instead of a single flat value:

volScore = clamp01((vol − $20 k) / $180 k)// 0 at $20 k vol → 1 at $200 k
moveOrCap = 1 if |Δ24h| ≥ 50% OR mcap ≥ $1 M;
0.5 if |Δ24h| ≥ 20% OR mcap ≥ $250 k;
else 0
subBump = round(volScore × moveOrCap × 18)
total no-LP bump = 40 + subBump

No-security-coverage floor

When no contract-security aggregator (GoPlus / honeypot.is) contributed to the snapshot, the score is clamped to the range 34–66 so the token cannot show riskLevel: 'low' without verified data, but also can't reach riskLevel: 'high' (threshold 67) on absence of evidence alone. Liquidity, holder, and renounce bumps still add on top of the 34 floor, so absent-evidence rows still produce distinct integers per token.

Rug-risk bands

low
< 34
medium
34 – 66
high
≥ 67

Momentum leg input

Trending score (0–100)

Four-component weighted score

Stored as trendingScore on every ScannedPair row. Recomputed on every upsert. trendingScore = round(100 × raw)

ComponentWeightFormula
Vol/liq velocity
45%
clamp01(vol / max(1, liq) / 5)
5+ turnovers = full mark
if liq = 0: clamp01(vol / $500 k)
Liq-weighted price move
35%
clamp01((|Δ24h| / 100) × log₁₀(liq / $10 k))
if liq = 0: clamp01(|Δ24h| / 100)
Age bonus
10%
clamp01(1 − max(0, ageHours − 6) / 24)
1.0 at < 6 h → 0 at > 30 h
Mcap gate
10%
1.0 if mcap ≥ $5 M, else 0.4

Edge-case treatment

Zero or unavailable liquidity

Rug-risk path
riskier

A liquidityUsd = 0 row cannot confirm a clean LP, so the scanner adds a +40 base risk bump instead of the lower thin/low-liq bumps. Hot pairs (high vol, big price move, or large mcap) receive up to +18 extra points on top via the maturity sub-bump formula above, producing distinct risk integers per token instead of a single flat value.

total no-LP bump = 40 + round(volScore × moveOrCap × 18)
Momentum path
falls back

The vol/liq velocity leg falls back to a pure volume ramp (vol / $500 k); the price-move leg uses raw |Δ24h| / 100 (no log weighting). Both legs still move independently, so a zero-liquidity token with real volume and price movement earns non-trivial momentum — it doesn't collapse to zero just because LP data is unavailable.

velocity = clamp01(vol / $500 k)move = clamp01(|Δ24h| / 100)

Quick reference

Key constants at a glance

Volume milestone$50 k / 24 h
Liquidity milestone$100 k
Trending: velocity full5× vol/liq turnover
Trending: vol fallback$500 k vol = full
Age bonus full< 6 h old
Age bonus zero> 30 h old
Mcap gate full≥ $5 M mcap
Mcap gate floor0.4 × weight
No-security floorscore 34 – 66
Community social signal15% when available

Weights and constants are read directly from production source code and updated whenever the scoring pipeline changes. Questions? bullradar-ai@polsia.app