←Back to the notebook

Python Stock Sentiment Examples: Five Scripts for Bullish and Bearish Reads

Five working Python examples for stock sentiment data: one stock today and over 30 days, whether a mention spike is good or bad news, the most bullish names in a watchlist, market-wide breadth, and sentiment lined up against price. One pip install, one free key, each script runs as written.

SentiSense Team
SentiSense Team
September 24, 2026 · 7 min read

Stock sentiment in Python is pip install sentisense, one free API key, and the five scripts below. Each one runs as written. Together they answer the questions developers put to sentiment data: what the read on one stock is today and over the last month, whether a burst of mentions is good or bad news, which names in a watchlist draw the most bullish coverage, how the whole market leans, and how sentiment lines up with price.

On this page an analysis is one news article or social post our models read as bullish or bearish for a stock. Counts of analyses come from news, Reddit, X, YouTube and other sources together. The stock sentiment API page covers methodology and coverage; this page is code.

Setup

pip install sentisense
export SENTISENSE_API_KEY=ss_live_YOUR_KEY

The scripts need Python 3.10 or later. Every script starts the same way:

import os
from sentisense import SentiSenseClient

client = SentiSenseClient(api_key=os.environ["SENTISENSE_API_KEY"])

1. What is the sentiment on one stock, today and over 30 days?

get_stock_sentiment returns the whole picture in one call: today's read and SentiSense Score, the 30-day average and its change, a trend, mention volume, and the tone of each source. This script prints the direction, the volume and the source split.

s = client.get_stock_sentiment("NVDA").data
print(f"{s['ticker']} as of {s['asOf']}")
print(f"today:   read={s['latestDirection']}  mentions={s['mentions']}")
print(f"30 days: {s['direction']} ({s['scoreLabel']}), trend {s['trend']}")
print(f"mentions/day, 30-day avg: {s['mentionsAvg30d']}")
for src in s["bySource"][:4]:
    print(f"  {src['source']:12} {src['mentionShare']:>4.0f}% of mentions  {src['direction']:8} {src['value']:+.2f}")

Output on 2026-09-24:

NVDA as of 2026-09-24
today:   read=Bullish  mentions=249
30 days: Bullish (Strong Bullish), trend DOWN
mentions/day, 30-day avg: 353
  News           69% of mentions  Bullish  +0.39
  Reddit         13% of mentions  Bullish  +0.10
  X              10% of mentions  Bullish  +0.33
  YouTube         6% of mentions  Bullish  +0.21

The same payload carries sentisenseScore, sentisenseScoreAvg30d and sentisenseScoreDelta30d when you want the numbers. sentisenseScore and latestDirection are None until the day's first analytics run lands, mid-morning Eastern; scoreLabel, direction and the 30-day fields are present at any hour. mentionShare is a whole-number percent per source, rounded independently, so the shares can sum to 99 or 101.

2. Is a mention spike good news or bad news?

A mention count says how loud the conversation is, not which way it leans. Each daily point of the sentisense metric carries bull, bear and directional counts under metricValue["properties"]. This script finds each stock's loudest day of the last 30 and reads the split for that day.

import time, statistics
from datetime import datetime
from zoneinfo import ZoneInfo

NY = ZoneInfo("America/New_York")
start = int(time.time() * 1000) - 30 * 86_400_000

for ticker in ["CRM", "UNH"]:
    mentions = client.get_metrics(ticker, metric_type="mentions", start_time=start)
    score = client.get_metrics(ticker, metric_type="sentisense", start_time=start)
    counts = {p["timestamp"]: p["metricValue"].get("properties", {}) for p in score}

    median = statistics.median(p.get("value", 0) for p in mentions)
    peak = max(mentions, key=lambda p: p.get("value", 0))
    day = datetime.fromtimestamp(peak["timestamp"] / 1000, NY).date()
    c = counts.get(peak["timestamp"], {})
    print(f"{ticker}: peak {day}  mentions={peak['value']:.0f} ({peak['value'] / median:.1f}x the 30-day median)  "
          f"bullish={c.get('bull', 0):.0f}  bearish={c.get('bear', 0):.0f}")

Output on 2026-09-24:

CRM: peak 2026-08-27  mentions=324 (3.9x the 30-day median)  bullish=197  bearish=10
UNH: peak 2026-09-09  mentions=74 (3.4x the 30-day median)  bullish=22  bearish=32

Two spikes of similar size, opposite splits: bullish analyses dominated one, and bearish analyses outnumbered bullish on the other. Points are daily, one per New York calendar day, stamped at midnight Eastern, so the two series join on timestamp. The last point is the current day so far.

3. Which stocks in my watchlist have the most bullish analyses this week?

Sum bull and bear over seven days for each ticker and rank by bullish share.

import time

watchlist = ["AAPL", "MSFT", "NVDA", "AMZN", "META", "GOOGL", "TSLA", "AMD"]
start = int(time.time() * 1000) - 7 * 86_400_000

rows = []
for ticker in watchlist:
    points = client.get_metrics(ticker, metric_type="sentisense", start_time=start)
    bull = sum(p["metricValue"].get("properties", {}).get("bull", 0) for p in points)
    bear = sum(p["metricValue"].get("properties", {}).get("bear", 0) for p in points)
    rows.append((ticker, bull, bear, bull / (bull + bear) if bull + bear else 0.0))

for ticker, bull, bear, share in sorted(rows, key=lambda r: r[3], reverse=True):
    print(f"{ticker:6} bullish={bull:5.0f}  bearish={bear:5.0f}  bullish share={share:.0%}")

Output on 2026-09-24:

AMD    bullish=  613  bearish=   97  bullish share=86%
AMZN   bullish= 1135  bearish=  333  bullish share=77%
AAPL   bullish=  174  bearish=   55  bullish share=76%
NVDA   bullish= 1009  bearish=  341  bullish share=75%
META   bullish= 1186  bearish=  441  bullish share=73%
TSLA   bullish=  543  bearish=  367  bullish share=60%
MSFT   bullish=  467  bearish=  324  bullish share=59%
GOOGL  bullish=  756  bearish=  528  bullish share=59%

A share is only as good as its sample. Print bull + bear beside it, as here, or set a floor before ranking, so a name with a handful of analyses cannot top the list.

4. How many stocks are bullish versus bearish today?

Sentiment breadth is the tone version of an advance/decline line: each covered stock casts one vote by its mean sentiment. It is a plain REST call with the same key.

import requests

resp = requests.get(
    "https://app.sentisense.ai/api/v1/sentiment/breadth",
    headers={"X-SentiSense-API-Key": os.environ["SENTISENSE_API_KEY"]},
    timeout=30,
)
resp.raise_for_status()
breadth = resp.json()

print(f"as of {breadth['asOf']}, stocks with at least {breadth['minMentions']} mentions count")
for day in breadth["series"][-5:]:
    print(f"{day['date']}  covered={day['coveredStocks']:4}  bullish={day['bullish']:4}  "
          f"neutral={day['neutral']:4}  bearish={day['bearish']:4}  net={day['netBreadth']:+6.1f} pts  "
          f"bullish by mentions={day['bullishMentionPct']:.0f}%")

Output on 2026-09-24:

as of 2026-09-24, stocks with at least 5 mentions count
2026-09-20  covered= 186  bullish= 121  neutral=  33  bearish=  32  net= +47.9 pts  bullish by mentions=63%
2026-09-21  covered= 427  bullish= 306  neutral=  54  bearish=  67  net= +56.0 pts  bullish by mentions=77%
2026-09-22  covered= 486  bullish= 354  neutral=  57  bearish=  75  net= +57.4 pts  bullish by mentions=76%
2026-09-23  covered= 547  bullish= 381  neutral=  75  bearish=  91  net= +53.0 pts  bullish by mentions=76%
2026-09-24  covered= 541  bullish= 344  neutral=  82  bearish= 115  net= +42.3 pts  bullish by mentions=68%

coveredStocks is the day's denominator, and it is lower on a quiet day such as a Sunday. When the stock-weighted and mention-weighted shares disagree, a few heavily discussed names are carrying the tone. series holds the daily history back to July 2026 for charting.

5. How does sentiment line up with price?

Pull daily bars and the daily Score for one ticker, join them on the New York date, and compare the two series.

# uses time, statistics, datetime and NY from script 2
ticker = "TSLA"
day = lambda ts: datetime.fromtimestamp(ts / 1000, NY).date()

bars = client.get_stock_chart(ticker, timeframe="3M")          # daily bars, oldest first
start = int(time.time() * 1000) - 90 * 86_400_000
score = {day(p["timestamp"]): p for p in
         client.get_metrics(ticker, metric_type="sentisense", start_time=start) if "value" in p}

rows, prev = [], None
for b in bars:
    d = day(b["timestamp"])
    if prev and d in score:
        ret = (b["close"] / prev - 1) * 100
        props = score[d]["metricValue"].get("properties", {})
        rows.append((d, b["close"], ret, score[d]["value"], props.get("bull", 0), props.get("bear", 0)))
    prev = b["close"]

for d, close, ret, s, bull, bear in rows[-5:]:
    print(f"{d}  close={close:8.2f}  change={ret:+5.1f}%  bullish={bull:4.0f}  bearish={bear:4.0f}")

scores, rets = [r[3] for r in rows], [r[2] for r in rows]
print(f"\n{len(rows)} trading days lined up")
print(f"score vs same-day change:  r = {statistics.correlation(scores, rets):+.2f}")
print(f"score vs next-day change:  r = {statistics.correlation(scores[:-1], rets[1:]):+.2f}")

Output on 2026-09-24:

2026-09-18  close=  364.27  change= -0.5%  bullish=  70  bearish=  66
2026-09-21  close=  375.30  change= +3.0%  bullish= 133  bearish=  90
2026-09-22  close=  378.90  change= +1.0%  bullish= 111  bearish=  68
2026-09-23  close=  380.12  change= +0.3%  bullish=  98  bearish=  41
2026-09-24  close=  377.94  change= -0.6%  bullish=  50  bearish=  44

61 trading days lined up
score vs same-day change:  r = +0.53
score vs next-day change:  r = +0.06

On this run the Score tracked the same day's price change far more closely than the next day's. That is one ticker over one window; rerun it on your own names and dates before drawing anything from it. The 3M timeframe returns daily bars; 1M and shorter return intraday bars, which do not join one-to-one with the daily Score.

What the free key returns

All five scripts run on a free API key. get_stock_sentiment, every get_metrics series used here, sentiment breadth and daily chart bars are served in full on every tier, with no preview trimming. The free key limits volume: 1,000 requests a month and 30 a minute. Script 1 is one request, script 2 is four, script 3 is one per watchlist ticker, script 4 is one and script 5 is two. PRO removes the monthly cap and raises the rate limit to 300 a minute, which matters once script 3 runs over a few hundred tickers on a schedule.

An AI agent gets the same answers without the script: npx -y sentisense sentiment TSLA in the sentisense CLI, a tool on the MCP connector, or the stock-sentiment skill for Claude Code, Codex and OpenClaw. Field-by-field reference: the Metrics API, the Stocks API and the Market Mood API. Same shape, other verticals: Python political trading examples and Python earnings examples.

Ready to run script one? Get a free API key → and paste it into the export line above.