Most "stock sentiment" tools hand you one composite score per ticker. That is convenient until the score jumps and someone asks why. A stock sentiment monitoring pipeline is more useful when every alert can be traced back to a specific field: discussion got louder, more participants joined, higher-scored institutions showed up, or the media started naming the company.
This article designs that pipeline on top of Lava (熔岩), an A-share and theme news-flow heat monitoring service available through MCP tools and a REST API. It is commonly used as an attention input in quant research and for institution/KOL heat monitoring. Lava has already done the collection, deduplication, stock and concept tagging, and agency anonymization, so the pipeline only has to decide polling frequency, trigger rules and storage. Thresholds and numbers below are examples.
What "sentiment" means in this pipeline
Lava does not output positive/negative labels. "Sentiment" here means the volume and structure of discussion, split into dimensions you can compute:
| Dimension | Question | Field |
|---|---|---|
| Volume | How often is the stock mentioned? | mentions |
| Breadth | How many distinct participants? | agencies |
| Participant quality | Are high-scored institutions/KOLs involved? | counts under min_score, weighted_heat |
| Timing | When did discussion happen? | hourly_24h, six windows |
| Content | What is being said? | summary (≤120 chars), detail (≤600 chars) |
| Media naming | Which outlets named the stock, and when? | news_id, publish time, media source |
The first five come from opinion-style records (news, broker research, institution/KOL views). Media naming comes from a separate news-hit stream. Keeping them apart lets you say which dimension fired, instead of "the score went up". If you do want polarity, classify summary locally and log the model version.
Architecture: three layers, one cheap loop
watchlist (ts_code) hourly -> /heat/windows hourly series + six windows -> rule check on trigger -> /opinions structured records -> read summaries -> /news/hits media naming in last N hours -> verify timing daily -> /coverage/stocks 7d/30d distinct agencies -> breadth log Only one endpoint runs on every tick, and drill-down runs only when a rule fires. Each successful call costs 0.005 CNY, non-2xx responses are free, and checking balance with get_account is free, so the cost scales with watchlist size, not with how much you read.
Hourly polling is enough. The hourly series is already at one-hour granularity, so polling more often just rereads the same bucket.
Layer 1: the hourly watch
get_stock_heat_windows (/heat/windows) accepts either ts_code or stock_name and returns:
-
windows[]: counts for1h,4h,18h,24h,3d,7d -
hourly_24h[]: per-hour counts for the last 24 hours - a high-score agency count defined by
min_score(default 1050, roughly the top tenth)
Three example rules, each tied to one dimension:
Rule A (volume): last-hour mentions >= mean + 3*stdev of the prior 23 hours, and >= 5 Rule B (breadth): 24h agencies more than double the 7d daily average Rule C (quality): high-score agency count goes from 0 to >= 2 import os, requests, statistics as st BASE = os.environ.get("LAVA_BASE", "<YOUR_LAVA_BASE>/subscribe/v1") H = {"Authorization": "Bearer " + os.environ.get("LAVA_API_KEY", "YOUR_TOKEN")} def get(path, **params): r = requests.get(BASE + path, params=params, headers=H, timeout=30).json() assert r["ok"], r["code"] return r["data"], r["meta"] def rule_a(hourly): # element shape per the OpenAPI spec; assume each item has "mentions", # last item = most recent hour xs = [h["mentions"] for h in hourly] if len(xs) < 2: return False base, last = xs[:-1], xs[-1] mu, sd = st.mean(base), (st.pstdev(base) or 1.0) return last >= max(5, mu + 3 * sd) for code in ["<CODE_1>", "<CODE_2>"]: data, meta = get("/heat/windows", ts_code=code) fired = "A" if rule_a(data.get("hourly_24h", [])) else None print(meta["server_time"], code, fired) The newest hour may still be in progress, so its count can look low. Using mean plus three standard deviations means only clear bursts fire, and a partial hour won't trigger false alerts.
Layer 2: drill-down when a rule fires
A trigger says "discussion changed". It doesn't say what was discussed. Two calls answer that.
/opinions (get_stock_news_heat) returns summary counts plus structured records with topic_time, agency_id, agency_score, stocks, concepts, summary and detail. days goes up to 90 and limit up to 200. If the number of records returned equals limit, shorten the range, because you are probably truncated. Sort by topic_time, read only the records around the trigger, and count by agency_id to see whether many participants are involved or a few are repeating themselves.
/news/hits (get_news_stock_hits) returns news_id, publish time, media source and matched stocks, without headlines or body text. Use it to check whether mainstream outlets named the stock during the trigger window:
hits, meta = get("/news/hits", ts_code="<CODE>", hours=24, limit=200) hits = hits if isinstance(hits, list) else hits.get("list", []) Comparing the two streams is informative on its own. Many opinion records with few media hits suggests the discussion is mainly research and institution/KOL commentary. Many hits with few opinions suggests wire-style news spreading. To follow one event across several stocks, search_news_flow (/opinions/search) takes a keyword of up to 50 characters, at most five space-separated terms that must all match, with since defaulting to 30 days back and reaching up to 180.
Layer 3: the daily coverage log
get_agency_coverage (/coverage/stocks) gives rolling 7-day and 30-day distinct agency counts, cumulative new coverage over the range, and the 30-day share of high-scored agencies. days ranges from 7 to 90 (default 30), and min_score is optional. It returns counts only, never agency ID lists.
Three numbers per stock per day are enough: 7-day distinct agencies, new coverage that day, and 30-day high-score share. Repeated new coverage means new participants are starting to discuss the name. A shifting high-score share means the mix of participants is changing.
Storage: log every tick, not just alerts
If you only store alerts, you can't compute how often each rule fires. A minimal table:
CREATE TABLE sentiment_watch_log ( server_time TIMESTAMPTZ NOT NULL, -- meta.server_time (+08:00) ts_code TEXT NOT NULL, rule TEXT, -- 'A' / 'B' / 'C' / NULL m_1h INTEGER, m_24h INTEGER, a_24h INTEGER, high_score_n INTEGER, news_hits_24h INTEGER, request_id TEXT, -- meta.request_id charged_yuan NUMERIC(8,4), -- meta.billing.charged_yuan PRIMARY KEY (server_time, ts_code) ); request_id and charged_yuan make billing reconcilable. Keep human notes written after reading summaries in a separate table, so subjective judgment never mixes into the count columns. Alert messages should stick to facts: code, rule, window counts, server_time.
FAQ
Does this pipeline produce a positive/negative sentiment score?
No. It tracks volume, breadth, participant quality, timing and content. Polarity, if you need it, is a local classification step on summary.
How do I discover stocks that aren't on my watchlist yet?
Use get_market_hot_stocks (/hot/stocks, refreshed about every minute), which includes heat_chg_1d, heat_chg_3d and heat_z20, or the first-mention stream /hot/first-mentions (hours default 24, max 72) for names nobody mentioned in the prior seven days.
Should I use REST or MCP?
Same data either way. REST suits scheduled jobs and databases; MCP suits AI agents that need to call the same tools conversationally.
Conclusion
A stock sentiment monitoring pipeline doesn't need a black-box score. Poll /heat/windows hourly with rules tied to named dimensions, drill into /opinions and /news/hits only when something fires, log /coverage/stocks daily, and write every tick with its server_time. The result is cheap to run, easy to explain, and reproducible.
中文简介:本文介绍如何用熔岩(Lava)消息面热度搭建股票舆情监控流程:每小时用 /heat/windows 按规则巡检,触发后用 /opinions 与 /news/hits 下钻查看要点和媒体点名,每天用 /coverage/stocks 记录参与主体变化。熔岩不输出正负面标签,舆情在这里指讨论量与结构,所有巡检连同 server_time 落库即可复现。
For a no-code view of how these fields look in a live monitor, see Lava stock heat monitoring.