vol. 01 · section A // №11 of 11 · friday, may 1, 2026
StalkMarket
A trailing stop-loss notification bot for the Indian stock market (NSE). Multi-broker (Angel One, Upstox, Zerodha, Groww), Telegram + Expo push notifications, an admin web dashboard, and a companion mobile app — all running on a Raspberry Pi 5 in Docker.
- filed
- Apr 2026 - Present
- running
- 4w
- stack
- 20 tech
- read
- ~2 min
stack // typescript node.js 20 fastify better-sqlite3 zod pino vitest react vite expo react native docker github actions ghcr telegram bot api expo push angel one smartapi upstox api kite connect groww api
Disclaimer. StalkMarket is a personal project. It is not financial advice, not a registered advisory service, and not a substitute for your own judgement. Trading involves real risk of capital loss; use at your own discretion.
Overview
Trailing stop-losses are simple in theory: let winners run, ratchet the stop upward, exit when the trend breaks. In practice they need constant attention. You end up refreshing your broker app every few minutes, recomputing stop prices in your head, and talking yourself out of perfectly good rules during the lunchtime chop.
StalkMarket automates the mechanical half of that loop. It polls live LTPs from your broker during NSE market hours, runs a pluggable trailing-stop strategy per position, and pushes a notification (Telegram and mobile push) the moment a stop is set, updated, or hit. Optionally, it can place the SELL MARKET exit for you on a Zerodha account.
The whole stack runs on a Raspberry Pi 5 in a single Docker container. State lives in SQLite (WAL mode), so a power cut won’t cause a duplicate notification on restart.
Highlights
- Pluggable strategy engine. Pure-function
Strategyinterface returning a discriminatedSKIP | STOP_SET | STOP_UPDATED | STOP_HITresult. Trailing-stop today; ATR, Donchian, and breakeven sketched out. - Multi-broker. Angel One (TOTP), Upstox and Zerodha (OAuth), Groww. Per-broker auth, persisted tokens, proactive refresh, and a one-shot 401 retry path, all behind one provider interface.
- Safe order defaults. Auto-execution is off by default, dry-run is on by default, and every attempt is deduplicated via a durable
order_executionsaudit log so a restart never double-fires. - NSE-aware. Market-hours gate (09:15–15:30 IST), holiday calendar fetched and cached from Upstox. No wasted polls on Diwali or Republic Day.
- Three surfaces. Fastify REST API (with
x-api-keyauth), a React/Vite admin dashboard, and an Expo / React Native mobile app with native push. - Crash-resilient. SQLite WAL plus state-based dedupe (
last_notified_stop_price) means a power cut, reboot, and resume cycle produces zero duplicate notifications. - Pi-friendly. Multi-arch
linux/arm64builds viadocker buildx, CI to GHCR, runs in around 80 MB of RAM.
Architecture at a glance
Poller (setInterval, market-hours gated)
└─ Broker manager (auth mutex, token persistence, 401 retry)
└─ getLTP() per user × stock
└─ Trailing-stop strategy (pure function)
└─ Result → DB write + notification dispatch
├─ Telegram (direct fetch)
└─ Expo push (expo-server-sdk)
└─ On STOP_HIT (if enabled) → Order manager → Zerodha SELL MARKET
Tech choices worth a sentence
- Fastify over Express — schema-first, faster, fewer foot-guns.
- better-sqlite3 — synchronous API, prebuilt arm64 binaries, single-file DB, perfect for a low-write workload.
- Zod — single source of truth:
app.jsonvalidation and REST API body validation share schemas. - Pino — structured JSON logs straight to Docker.
- Vitest — fast, ESM-native, broad coverage across
broker/,engine/,db/,api/,notifications/.
Read more
Three deep dives walk through the design decisions: