How does PulseBit calculate sentiment? Every article is scored from −1 to +1 with a confidence value. Those scores are aggregated per topic and region into a daily average, from which a 0–100 index, a 24-hour momentum figure, a 7-day trend and a signal-strength score are derived. All four formulas are below, along with what each one currently cannot tell you.
METHODOLOGY
Published in full because a sentiment score you cannot audit is a sentiment score you cannot cite. Nothing here is simplified for presentation — these are the expressions the API evaluates.
1 · Article level
sentiment_score
−1.0 to +1.0A single article’s sentiment. Negative is adverse coverage, positive is favourable, and values near zero are genuinely neutral rather than unknown. Each score carries a confidence between 0 and 1.
Label thresholds. Scores above +0.05 are POSITIVE, below −0.05 NEGATIVE, and the band between is NEUTRAL.
Unscored articles exist. Ingest and enrichment run as separate stages, so an article can be returned within seconds of publication with sentiment_score: null. That is not an error and should not be read as neutral — the scoring worker has simply not reached it yet. Filter on null before averaging.
2 · Topic and region aggregates
Articles are bucketed by topic, region and calendar day. Each bucket yields an average sentiment, an article count, a distinct-source count and the share held by its largest source. Everything below is derived from those buckets.
momentum_24h
≈ −2.0 to +2.0momentum_24h = latest_avg − prev_avgThe change in a topic’s daily average sentiment from the previous day to the most recent one.
Limit. This compares two calendar-day buckets, not a rolling 24-hour window. Shortly after midnight UTC the current bucket is nearly empty, so momentum is at its least reliable then.
trend_7d
≈ −1.0 to +1.0trend_7d = mean(last 7 daily averages) − mean(prior 7)Limit. This returns exactly 0.0 for any topic with fewer than eight days of recorded history — currently the large majority of topics. A zero here means “not enough history yet”, not “flat”. Treat the two differently, and note the consequence for the index below.
sentiment_index
0 to 100sentiment_index = clamp(0, 100,
50 + (latest_avg × 25) + (trend_7d × 25)
)A 0–100 composite where 50 is the neutral midpoint. It exists so a level can be read at a glance without holding the −1…+1 scale in your head.
Consequence of the trend limit. While trend_7d is 0, the index reduces to 50 + (latest_avg × 25) — a linear rescaling of the day’s average sentiment, carrying no independent information. You can invert it exactly: latest_avg = (index − 50) ÷ 25.
volume_ratio
0 upwards, 1.0 = normalvolume_ratio = latest_article_count ÷ mean(prior daily counts)Coverage volume against the topic’s own baseline rather than against other topics. A value of 12 means twelve times its usual number of articles — the clearest available indication that something is happening, independent of direction.
signal_strength
0.0 to 1.0source_component = min(1, unique_source_count ÷ 8)
volume_component = min(1, volume_ratio ÷ 3)
momentum_component = min(1, |momentum_24h| ÷ 0.25)
confidence_component = avg_confidence
concentration_penalty = min(0.5, top_source_share)
signal_strength = max(0,
source_component × 0.25
+ volume_component × 0.20
+ momentum_component × 0.25
+ confidence_component × 0.30
− concentration_penalty × 0.20
)A composite quality score meant to separate a corroborated move from one outlet repeating itself.
Known limits, stated rather than hidden. The momentum term saturates at |momentum_24h| ≥ 0.25, so it contributes a flat +0.25 to essentially every row large enough to be worth looking at. The source term is bounded in practice by unique_source_count rarely exceeding 3 per bucket, capping its contribution near 0.09 of the 0.25 available. And article_count is not a term at all. The practical range is therefore narrow, and a two-article topic can outscore a thirty-article one. Read it alongside article_count, never instead of it.
3 · Reading a row honestly
- Level and change disagree, usefully. A topic at index 50 that fell 0.70 today is not the same as one that has sat at 50 all week. Never read one figure alone.
- Check the article count first. Averages over two articles land on coarse values and collide — two unrelated topics both reporting exactly −0.70 is an artefact of thin data, not a correlation.
- One source is not corroboration. When
top_source_shareis 1.0, a single outlet carries the entire story. - Volume moves before sentiment does.
volume_ratiooften flags an emerging story a day before the average sentiment shifts.
Citing this
These definitions are versioned with the API. If you are publishing research built on PulseBit data, record the date you pulled it — the formulas above are stable, but the history depth that feeds trend_7d is still growing, which changes what the index expresses.
See the numbers live on the alerts board or the live stream.
FROM PROOF TO PRODUCTION
Use signals you can explain.
The methodology is public so you can evaluate the signal. The API is where you can put it to work.