DEV Community

Jirawat Boonkumnerd
Jirawat Boonkumnerd

Posted on

Zero Market Radar: A Stock-News Dashboard and Voice Alerts for a Friend

Hacktoberfest Weekend Challenge: Build for a Friend Submission 🤝

This is a submission for the Hacktoberfest Weekend Challenge: Build for a Friend.

What I Built

My friend Alex follows a small stock watchlist, but financial news makes even a small watchlist feel like a full-time job. Earnings announcements sit beside speculative listicles. Yesterday's recap looks as urgent as today's material business update. More articles do not necessarily mean more useful information.

I built Zero Market Radar to answer a narrower question: what happened to the companies Alex follows, and which headlines deserve his attention?

It combines a browsable news dashboard with optional Telegram notifications and short spoken alerts. TypeSafe AI's Jev evaluates an article's directional sentiment and materiality; ordinary TypeScript decides how to store, rank, display, and deliver that result. It is a news-triage tool, not an automated trading system or a promise of investment returns.

🌐 Live demo: zeromarketradar.com

💻 MIT-licensed source: ntsd/zero-market-radar

The current default watchlist is AAPL, MSFT, NVDA, GOOGL, AMZN, META, TSLA, AMD, TSM, and BABA. I reduced the original broader list to keep the product focused and removed the China/HK preset; BABA remains an individually selectable ticker.

Demo

A dashboard for browsing, not another notification feed

Open the public dashboard and:

  1. Select individual tickers or the Mega Tech and Semis presets. Choices persist in the browser.
  2. Browse the top four impact-ranked headlines for your selected companies.
  3. Switch between 24H, 3D, 7D, or a custom date range within the recent seven-day window.
  4. Filter the feed by breaking signals, notable catalysts, bullish sentiment, or bearish sentiment; sort by impact, date, or confidence.
  5. Click a ticker badge to open its dedicated quote, price chart, and paginated news view.

The symbol chart offers 24-hour and seven-day views using real Yahoo price candles. Breaking and catalyst articles appear as chart dots; selecting a dot jumps to the corresponding article. The dots show news timing alongside prices—not proof that an article caused a price movement.

Voice where it is useful

For Alex's commute, breaking-critical articles can become ElevenLabs voice dispatches, delivered through Telegram or played from the dashboard. Notable catalysts use text notifications rather than voice. Routine commentary stays available to browse without automatically becoming a spoken alert.

Telegram text and voice alert example

Telegram is optional. Finnhub and TypeSafe API keys are the two required keys for dashboard-only operation; MongoDB, ElevenLabs, Telegram, and Sentry add persistence, speech, notifications, and tracing. The repository includes an API key setup guide.

How I Built It

1. Two typed AI judgments, one request

I did not need a model to write an investment essay. I needed two structured judgments:

  • Sentiment: bullish or bearish, with confidence and choice probabilities.
  • Priority: breaking-critical, notable catalyst, or routine noise.

The Jev service sends the ticker, headline, summary, source, and publication time through a single systemOne request containing both choice questions. It validates the returned answer types before persisting a prediction.

Impact ranking uses this explicit weighting of the priority probabilities:

$$
\text{urgencyScore} = P(\text{breaking critical}) + 0.5\,P(\text{notable catalyst})
$$

This is a ranking heuristic, not a calibrated probability of a future price move. The service retains the source headline and link so Alex can read the underlying report instead of relying on a generated explanation.

Jev is a hosted System 1 decision-model API. Fast inference is useful, but I am not claiming sub-second end-to-end alerts: provider publication delays, round-robin polling, retries, and optional speech generation all contribute to delivery time.

2. Persistence without fake historical predictions

MongoDB Atlas stores article evaluations, per-symbol sync ranges, quotes, price candles, and generated audio. Verified predictions carry evaluatedBy: 'jev'; a redeploy warms the in-memory deduplicator from those verified records for the active watchlist.

The news history setting defaults to seven days:

  • HISTORY_SYNC_DAYS=1..7 bounds historical fetching and downtime catch-up.
  • On a symbol's first turn after startup, stored unverified articles inside that window are evaluated through the same Jev path, silently.
  • HISTORY_SYNC_DAYS=0 skips historical reevaluation and fetches today's live news only.
  • Failed evaluation or storage does not advance the successful sync checkpoint.

Older archive records are preserved, not silently relabeled as genuine model decisions. Historical processing is notification-silent; articles older than 24 hours also avoid Telegram pushes.

This reduces repeated work, but it is not an exactly-once notification guarantee. Coordinating multiple workers and atomically recording delivery are separate problems.

3. Cache per ticker; filter in the browser

The most useful dashboard optimization was changing what the browser asks for.

Instead of a Mongo query for every date change or pagination click, /api/home-news?symbol=AAPL returns a complete bounded news snapshot for one watched symbol, without cached audio data. The server shares snapshots for 30 seconds, coalesces concurrent loads, and serves previous results during refresh. Failed refreshes preserve successful cached data and retry after five seconds.

The browser shares each ticker's snapshot between the spotlight and feed. Date, signal, sort, and page controls operate locally. Periodic refreshes revalidate the snapshots; a newly selected ticker or new UTC day loads the appropriate data.

An isolated browser check confirmed that switching 24H → 7D → next page generated zero API requests. That verifies the filtering behavior, not production network latency.

Equity summaries are aggregated inside MongoDB rather than transferring the historical article collection to Node.js. Audio is fetched separately for playback, and quote reads are scoped to the relevant symbols.

4. Speech is a delivery feature, not a second analyst

The ElevenLabs integration uses eleven_turbo_v2_5 to read a deterministic script containing the ticker, original headline, directional label, and confidence. It does not invent extra market commentary.

Generated audio is cached in MongoDB. Replay reuses the cached clip, and the audio endpoint enforces breaking-critical eligibility even if an older non-breaking article happens to have cached audio.

5. Deployment details that mattered

Render hosts the Node.js service and dashboard behind the custom domain. The Blueprint defines the build, start command, environment, and /health check.

Operational safeguards include:

  • A shared Finnhub request limiter: at most 50 request starts per minute, at least 1.2 seconds apart, with a cooldown after HTTP 429. Quote and news calls use the same limiter.
  • A sequential scheduler that waits two seconds after a symbol's work finishes, rather than overlapping slow backfills.
  • A 10,000-entry, 48-hour TTL deduplication cache.
  • Fail-fast startup when a configured Mongo connection cannot initialize, rather than silently serving an empty in-memory dashboard.
  • Sentry custom spans and exception capture around inference, provider calls, storage, and audio. This implementation does not claim automatic token or cost reporting.
  • Graceful shutdown when Render sends SIGTERM during instance replacement.

A free Render web service can sleep after 15 minutes without inbound traffic. Outbound polling does not prevent that, so continuous unattended alerts need an always-on deployment. In-memory response caches also reset when the process restarts; Mongo history does not.

What I Learned—and What Still Needs Work

The latest local build passes 95 tests, including retry/checkpoint behavior, historical notification suppression, cached audio eligibility, pagination, UTC date boundaries, and browser-side cache filtering.

Two data-model limitations are worth being explicit about:

  • A successful sync range does not prove Finnhub returned every article in that range. Broad provider requests can miss items; per-day historical reconciliation is a future improvement.
  • Predictions currently use a global article ID and one stored symbol. A story appearing under multiple tickers needs better association handling; counts should not be presented as exhaustive company-news coverage.

The lesson was to make a small, honest tool for Alex—not to describe a weekend prototype as an institutional trading terminal. The most valuable improvements were quieter alerts, readable source links, reliable state, and controls that do not keep hitting the database.

Why Open Innovation Matters

The application code is MIT-licensed, with TypeScript, native Node.js HTTP, reproducible configuration, and visible decision rules. Alex can inspect the filters, change the watchlist, self-host the service, or modify the delivery channels instead of depending on an opaque news app.

There is an important boundary: this implementation calls hosted Jev, Finnhub, Yahoo, and ElevenLabs services; it does not run an open-weight AI model locally or work fully offline. Open application code is not the same as open model weights. That distinction matters when evaluating the challenge's open-source-AI requirement, and I do not want to imply a model integration the project does not have.

Community Wisdom

🌐 Idempotency in Data Pipelines: How to Prevent Duplicate Records

Source: 137Foundry
Tags: programming, api, productivity

Stable record identities, upserts, and explicit checkpoints make overlapping retries safer. That supports the storage design here, without turning it into an exactly-once delivery claim.

Read Full Discussion

My Agent Session

The original scaffolding session is embedded below. It records the early build; the implementation has since evolved, including the reduced watchlist and caching changes described above.

Building stock-news-alert with TypeSafe AI Jev, Finnhub, and Render
You

Build a production-ready Node.js backend service called "stock-news-alert" in TypeScript. It polls Finnhub company-news, deduplicates articles, classifies sentiment via TypeSafe AI Jev (System 1 Decision Model) into binary 0/1 with confidence, and sends Telegram alerts. Deploy to Render for the Hacktoberfest Weekend Challenge: Build for a Friend.

Agent

I will critique the setup as a Principal Engineer, address rate-limit distribution across N symbols without drift, implement a dual-eviction LRU cache to prevent memory leaks, solve the cold-start alert storm, and build a turnkey Render Blueprint deployment with native health checks.

Agent

The solution uses a drift-compensated self-scheduling tick at 1200ms (~50 req/min), an LRU cache with 48h TTL, strict HTML escaping for Telegram, TypeSafe Jev Choice primitive inference, and a render.yaml blueprint with /health monitoring. All 5 unit tests pass and live API calls succeeded.

Partner Integrations

  • Render: deployed Node.js backend, web dashboard, custom domain, Blueprint, and health endpoint.
  • ElevenLabs: breaking-news narration, optional Telegram voice delivery, and cached browser playback.
  • MongoDB Atlas: durable evaluations, sync coverage, quotes, candles, and audio storage.
  • Sentry: custom performance spans and exception capture; a trace screenshot would strengthen evidence for the Agent Tracing category.

AI assistance disclosure: AI tools helped develop the project and revise this write-up. The implementation and limitations above are grounded in the current source and local checks.

Top comments (0)