Mini-project: Build a cashtag-aware stock watchlist using Bluesky posts and free hosting
mini-projectsocialfinance

Mini-project: Build a cashtag-aware stock watchlist using Bluesky posts and free hosting

ffrees
2026-02-02
10 min read
Advertisement

Hands-on tutorial: scrape Bluesky cashtags, enrich with market APIs, and serve a real-time watchlist using free hosting & edge functions.

Hook: build a low-cost, real-time stock watchlist from Bluesky cashtags — without paying cloud bills

If you're tired of paying for hosted prototypes and spending days wiring APIs together, this mini-project gives you a repeatable, low-cost pattern to go from Bluesky posts to a live, cashtag-aware stock watchlist running on free hosting. You'll scrape cashtags from public Bluesky posts, enrich symbols with market APIs, and serve a tiny real-time web app using free tiers and edge functions available in 2026. The goal: prototype quickly, prove an idea, and keep recurring costs at zero while you evaluate product-market fit.

The why in 2026: why cashtags + Bluesky matter now

Bluesky's 2025–2026 feature rollouts (including cashtags) have pushed more market chatter onto the platform. That creates an opportunity for low-friction, developer-driven tooling: think watchlists and signal monitors built by developers for developers. At the same time, edge compute and serverless offerings matured (edge functions, KV stores, serverless Postgres) and many vendors keep liberal free tiers designed for prototypes. That combination makes this mini-project a practical building block for side projects, research, or proof-of-concept tooling.

What you'll build (overview)

  • Ingest: Poll or scrape public Bluesky posts to detect cashtags (e.g. $AAPL).
  • Enrich: Resolve each cashtag into a ticker and fetch quote data from a public market API (Alpha Vantage, Finnhub, Twelve Data, or a Yahoo-scrape fallback).
  • Persist: Store posts and the latest price/metadata in a lightweight store (SQLite, Supabase free, or Cloudflare KV).
  • Serve: Provide a REST endpoint plus Server-Sent Events (SSE) or WebSocket for real-time UI updates.
  • Deploy: Host the front-end on Cloudflare Pages / Vercel / Netlify and run the ingestion/enrichment on free serverless or edge functions.

For a free, resilient prototype pick components that minimize vendor lock-in and operate well on free tiers:

  • Front-end: Cloudflare Pages or Vercel Hobby — static single-page app + client SSE handler.
  • Edge/backend: Cloudflare Workers or Vercel Serverless Functions for ingestion and webhook endpoints.
  • Storage: Cloudflare KV (cheap, low-latency key-value), Supabase free Postgres for richer queries, or a hosted SQLite file for tiny workloads.
  • Market data: Finnhub / Alpha Vantage / Twelve Data free tiers — pick based on rate limits and symbol coverage.
  • Optional realtime: Supabase Realtime, Cloudflare Workers + Durable Objects, or SSE from a serverless function.

Step 1 — Detect cashtags from Bluesky posts (practical approaches)

Bluesky is decentralized (AT Protocol) and exposes public content. Two pragmatic ingestion strategies work for prototypes:

  1. Polling the public API: register a lightweight client with an AT Protocol server and call the public search/feed endpoints to look for posts containing $-prefixed tokens. This is robust but requires token management.
  2. HTML scraping: fetch public Bluesky post pages (bsky.app/post/...) or profile timelines and extract text. Use careful rate limiting and caching to avoid throttling — browser tooling and extensions can help during development (see tool roundup).

For this tutorial we'll show a simple, low-friction polling/scraping hybrid: fetch recent posts from a public feed endpoint (or scrape a timeline HTML if API access is constrained), then run a cashtag parser on the post text.

Cashtag parsing (JavaScript example)

const CASHTAG_REGEX = /\$([A-Za-z0-9\.\-]{1,8})/g; // captures $AAPL, $BRK.B

function extractCashtags(text) {
  const set = new Set();
  let m;
  while ((m = CASHTAG_REGEX.exec(text)) !== null) {
    set.add(m[1].toUpperCase());
  }
  return Array.from(set);
}

// Example:
console.log(extractCashtags('Thinking about $AAPL and $TSLA after earnings.'));

Important: normalize cashtags (upper-case, strip punctuation when appropriate) and map special cases (like $BRK.B → BRK-B) before querying market APIs.

Step 2 — Enrich cashtags with market APIs

Enrichment has two parts: symbol resolution and live quote retrieval. Free market APIs differ in rate limits and symbol mappings. Design an adapter layer so you can switch providers without changing business logic.

Provider choices and constraints (2026 note)

  • Alpha Vantage — generous free tier but strict rate limits (typical: 5 calls/min). Good for small prototypes and caching strategies.
  • Finnhub — real-time quote support on free tier; requires API key and has fair-use limits.
  • Twelve Data — competitive free tier and clean REST API.
  • Fallback: yfinance scraping library (Python) or unofficial Yahoo endpoints — useful for heavy symbol coverage but less stable and riskier for production.

Enrichment flow (pseudocode)

// 1. receive post with cashtags
// 2. for each cashtag: map to market symbol
// 3. check cache: if stale, call market API
// 4. attach quote data to post record and persist

async function enrichCashtags(cashtags) {
  const results = {};
  for (const tag of cashtags) {
    const ticker = mapCashtagToTicker(tag); // normalize
    const cached = await cache.get(ticker);
    if (cached && !isStale(cached)) {
      results[tag] = cached;
      continue;
    }
    const quote = await fetchQuoteFromProvider(ticker);
    await cache.set(ticker, quote, { ttl: 60 }); // short TTL
    results[tag] = quote;
  }
  return results;
}

Step 3 — Persisting minimal state

For a prototype, keep the schema tiny:

  • posts(id, text, author, posted_at, cashtags[])
  • tickers(symbol, display_name, last_price, last_updated)
  • signals(post_id, symbol, score?) — optional for ranking

Storage suggestions:

  • Cloudflare KV — key-value pairs keyed by post ID or ticker for tiny data sets.
  • Supabase free Postgres — if you want SQL queries, joins, or a realtime subscription out-of-the-box.
  • SQLite — file-based and trivial, good for single-process prototypes deployed on Vercel or a small VM.

Step 4 — Real-time delivery: SSE vs WebSocket

Real-time UX is key for a watchlist. For free hosting, Server-Sent Events (SSE) is often simpler: works over HTTP, easy to proxy through edge functions, and cheap.

  • SSE: one-way stream from server to client. Works with Cloudflare Workers and Vercel serverless functions (with some platform caveats).
  • WebSocket: full duplex, but requires platforms that support persistent connections (not always free or available) — consider micro-edge VPS if you need persistent sockets.

Minimal SSE server (Node/Express example)

app.get('/sse/watchlist', (req, res) => {
  res.set({ 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive' });
  res.flushHeaders();

  const push = (data) => res.write(`data: ${JSON.stringify(data)}\n\n`);
  const interval = setInterval(async () => {
    const updates = await getLatestWatchlistUpdates();
    push(updates);
  }, 2000);

  req.on('close', () => clearInterval(interval));
});

Step 5 — Front-end: lightweight watchlist UI

Keep the UI minimal: a symbol list with last price, delta, and a link back to the original post. Use SSE client-side for updates.

Client SSE handler (vanilla JS)

const es = new EventSource('/sse/watchlist');
es.onmessage = (e) => {
  const payload = JSON.parse(e.data);
  // update UI
};

Step 6 — Deployment pattern on free hosting

Deployment template that works in 2026 and keeps costs low:

  1. Static front-end → Cloudflare Pages or Vercel (free hobby): push main branch, configure environment variables, automatic deployment.
  2. Ingestion & enrichment → Cloudflare Workers or Vercel Serverless Functions: schedule a cron worker to poll Bluesky, enrich, and write to KV or Supabase.
  3. Real-time endpoint → SSE served from a serverless function (watch quotas) or use Supabase Realtime that emits DB changes to clients.

Pro tip: Use short TTL caches for quotes and back-off on API limits. For example, Alpha Vantage only allows a few calls per minute on free plans — so aggregate requests or use a batch endpoint when available.

Operational concerns and rate limiting

Common pitfalls to avoid:

  • Thundering herd on market APIs: deduplicate symbols across posts before calling the market API.
  • Bluesky rate limits / scraping rules: respect robots.txt and the platform's terms; throttle requests and cache aggressively.
  • Data correctness: cashtags are user-generated and may not map to real tickers. Allow for manual mapping and fallback logic.
  • Privacy & compliance: store only public post IDs and minimal metadata; don't harvest PII — follow marketplace safety and moderation guidance in case of abusive content (see safety playbook).

Example end-to-end flow (concise walkthrough)

  1. Schedule a Cloudflare Worker to run every minute and fetch latest posts from a public Bluesky timeline endpoint.
  2. Extract cashtags from post text with the regex shown above.
  3. Batch unique symbols and call your chosen market API; cache responses in Cloudflare KV with a TTL of 60–120 seconds.
  4. Persist posts + enriched quote snapshot to Supabase or KV.
  5. Expose a /api/watchlist endpoint that returns aggregated state ordered by last mention or volatility.
  6. Client subscribes to /sse/watchlist to receive new mentions and price updates in real time.

Code snippets: key pieces (glue code)

Map cashtag to provider-ready ticker

function mapCashtagToTicker(tag) {
  // common transforms: BRK.B → BRK-B, remove leading $
  let s = tag.replace(/\$/g, '').toUpperCase();
  s = s.replace(/\./g, '-');
  return s;
}

Batch quote fetch (pseudo-Node)

async function fetchQuotesBatch(tickers) {
  // provider-specific; many providers accept comma-delimited symbols
  const q = tickers.join(',');
  const res = await fetch(`https://api.provider.example/quotes?symbols=${q}&token=${API_KEY}`);
  return await res.json();
}

Testing & validation

Tests you should write before calling rate-limited APIs:

  • Unit tests for cashtag extraction and mapping rules.
  • Integration test that mocks market API responses to validate your caching and TTL behavior.
  • Load test that simulates many posts containing the same cashtag to validate deduping and batching — use browser and HTTP tooling from the tool roundup during development.

Future improvements & scaling paths (predictions for 2026+)

As your prototype gains traction, consider these next steps:

  • Signal scoring: sentiment analysis and mention velocity to surface hot tickers.
  • Webhook integrations: push alerts to Slack/Discord when a cashtag spikes — implement webhooks via serverless functions and queue jobs to avoid spikes (a common pattern in micro-event architectures).
  • Webhook receiver design: expose a /webhook endpoint that verifies incoming events and enqueues symbol resolution to prevent spikes.
  • Paid scale: move to a small paid VM or managed container when free quotas are insufficient and add background workers for heavy enrichment — consider micro-edge VPS instances for latency-sensitive work.
  • Edge inference: run small ML models at the edge to detect pump-and-dump patterns before storing signals — edge-first patterns make this feasible (see edge-first layouts).

Real-world example & quick case study

In late 2025 and early 2026, platforms that adopted cashtags saw increased trader chatter on alternate networks. A small dev team built a prototype monitor in 48 hours using Cloudflare Pages + Workers, Supabase free Postgres, and Finnhub for quotes. They filtered high-velocity mentions and pushed alerts through a Discord webhook. The result: they validated user interest without a single paid hosting invoice for the first month. That demonstrates the viability of this approach for quick iteration and discovery.

"Prototype quickly on free tiers, instrument limits, then only pay when you have validated demand." — recommended roadmap

Security, ethics, and platform rules

Even for public posts, respect platform policies. Rate limit your ingestion, provide attribution (link back to the original post), and give users a way to opt out if you surface or index their content. Also avoid misrepresenting market data — include a disclaimer: this is for prototyping/research, not investment advice. For governance and cooperative hosting options as you scale, consider community cloud and co-op patterns (community cloud co-ops).

Actionable checklist (get this running in a day)

  1. Choose your free hosting: Cloudflare Pages + Workers or Vercel.
  2. Pick a market API and create an API key (Alpha Vantage or Finnhub).
  3. Implement cashtag extraction and normalization (use provided regex).
  4. Implement batching + caching for API calls.
  5. Persist minimal state in KV or Supabase.
  6. Deploy front-end and wire SSE for live updates.
  7. Run smoke tests and monitor API usage for rate limits.

Closing: why this pattern is valuable

This mini-project gives you a pragmatic pattern: ingest social-signal data, enrich it with public market APIs, and deliver a live UX — all while staying inside free hosting limits. In 2026, with cashtags and edge compute both more common, this is one of the fastest ways to prototype market-aware social tooling without vendor lock-in or surprising bills.

Call to action

Ready to ship your prototype? Start with a single Bluesky timeline, implement cashtag extraction, and connect one free market API. If you want a starter scaffold, clone a minimal template (static front-end + worker ingestion + KV) and iterate from there. Share your fork or questions with the community — and when you hit the limit of free tiers, you'll have the data to justify the first paid upgrade.

Advertisement

Related Topics

#mini-project#social#finance
f

frees

Contributor

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.

Advertisement
2026-02-03T18:54:14.283Z