Build a real-time stock news alert bot in Python (WebSocket + Discord)
Step-by-step: stream live stock news over WebSocket and post filtered alerts to Discord in under 60 lines of Python.

In this tutorial you'll build a small Python service that watches the stock news stream for the tickers you care about and posts each new headline to a Discord channel. It takes about 15 minutes and fewer than 60 lines of code.
What you need: Python 3.10+, a MarketNewsAPI key on Pro (WebSocket, included in the 7-day trial), and a Discord webhook URL (Server settings → Integrations → Webhooks).
Why use a WebSocket instead of polling?
Polling a REST endpoint every few seconds wastes requests and still adds latency: on average, half your polling interval. A WebSocket keeps one connection open and the server pushes each article as soon as it's ingested and enriched. You also filter on the server, so your bot only receives the articles it will act on.
1. Install dependencies
pip install websockets httpx2. Connect and subscribe
Connect to the stream with your key, then send a subscribe message. Channels are combined with OR: you get an article if it matches any of them, and you get it only once.
import asyncio
import json
import os
import httpx
import websockets
API_KEY = os.environ["MARKETNEWSAPI_KEY"]
WEBHOOK = os.environ["DISCORD_WEBHOOK_URL"]
URL = f"wss://ws.marketnewsapi.com/v1/stream?api_key={API_KEY}"
CHANNELS = ["ticker:NVDA", "ticker:TSLA", "keyword:guidance"]3. Post each article to Discord
Each article message carries the full article object, including an AI summary, so the alert is useful without opening the link.
async def post(client: httpx.AsyncClient, article: dict, matched: list[str]) -> None:
symbols = article["tickers"]
await client.post(WEBHOOK, json={
"embeds": [{
"title": article["title"][:256],
"url": article["url"],
"description": article.get("summary") or article.get("snippet") or "",
"color": 0x22C55E,
"footer": {"text": f"{article['source']['name']} · {', '.join(symbols)}"},
"timestamp": article["published_at"],
}]
})4. Run forever, reconnect automatically
websockets.connect used as an async iterator reconnects with exponential backoff, so a deploy or network blip doesn't kill the bot.
async def main() -> None:
async with httpx.AsyncClient(timeout=10) as client:
async for ws in websockets.connect(URL):
try:
await ws.send(json.dumps({"action": "subscribe", "channels": CHANNELS}))
async for raw in ws:
msg = json.loads(raw)
if msg["type"] == "article":
await post(client, msg["data"], msg["matched"])
elif msg["type"] == "error":
print("server error:", msg)
except websockets.ConnectionClosed as exc:
print("disconnected:", exc.code, exc.reason)
continue
if __name__ == "__main__":
asyncio.run(main())Try it for 7 days. Signup starts Pro. A card is required. Real-time REST, WebSocket, and MCP, with no request cap. After that it's $15/month, or Free if you stop.
Variation: only alert on press releases
For earnings and company announcements, subscribe to type:press_release and keep articles that mention a holding:
HOLDINGS = {"AAPL", "MSFT", "NVDA"}
CHANNELS = ["type:press_release"]
# inside the loop:
if msg["type"] == "article" and HOLDINGS & set(msg["data"]["tickers"]):
await post(client, msg["data"], msg["matched"])Don't miss articles during reconnects
Store the published_at of the last article you processed. After a reconnect, call GET /v1/news with the same tickers and from=<that timestamp> to backfill anything published while you were offline. Articles have stable ids, so de-duplicating is a set lookup.
Deploying it
Any always-on host works: a $5 VPS, Fly.io, Railway or a container next to your other services. Memory use is tiny. Pro includes 3 concurrent connections, so you can run production and staging at the same time.