Developing a Telegram Bot for Crypto Price Tracking
A typical task: a user wants to receive a notification when BTC drops below $60,000 or ETH rises above $4,000. Options: continuous price polling or alert subscriptions. Key question — update frequency. For long-term alerts (sufficient to update once a minute) the architecture is simple. For HFT monitoring with second-level latency — you need WebSocket to exchange stream.
Stack and Structure
Telegram Bot API ← → Bot server (Node.js/Python)
↓
Price data source (CoinGecko / Binance WS)
↓
PostgreSQL (users, alerts)
↓
Job scheduler (cron / BullMQ)
Implementation on Node.js + Grammy
grammy — modern TypeScript library for Telegram Bot API, best choice for new projects:
npm install grammy @grammyjs/conversations ioredis
import { Bot, Context, session } from 'grammy'
import { conversations, createConversation } from '@grammyjs/conversations'
const bot = new Bot(process.env.BOT_TOKEN!)
// Command to add alert
bot.command('alert', async (ctx) => {
await ctx.reply(
'Enter alert in format:\n`BTC > 70000` or `ETH < 3000`',
{ parse_mode: 'Markdown' }
)
})
// Parse alert
bot.on('message:text', async (ctx) => {
const match = ctx.message.text.match(/^(\w+)\s*([<>])\s*(\d+(?:\.\d+)?)$/)
if (!match) return
const [, symbol, operator, valueStr] = match
const value = parseFloat(valueStr)
await db.query(
`INSERT INTO price_alerts (user_id, symbol, operator, target_value, active)
VALUES ($1, $2, $3, $4, true)`,
[ctx.from!.id, symbol.toUpperCase(), operator, value]
)
await ctx.reply(
`Alert set: when ${symbol.toUpperCase()} ${operator} $${value.toLocaleString()}, ` +
`I'll send notification.`
)
})
Data Source: CoinGecko vs Binance
CoinGecko Free API — aggregated prices, no registration, 30 requests/min limit:
async function getPrices(symbols: string[]): Promise<Record<string, number>> {
// CoinGecko uses its own IDs, not tickers
// bitcoin, ethereum, solana, etc.
const ids = symbols.map(s => COINGECKO_IDS[s]).join(',')
const res = await fetch(
`https://api.coingecko.com/api/v3/simple/price?ids=${ids}&vs_currencies=usd`
)
const data = await res.json()
return Object.fromEntries(
symbols.map(s => [s, data[COINGECKO_IDS[s]]?.usd ?? 0])
)
}
Binance WebSocket — for real-time, no connection limits:
import WebSocket from 'ws'
// Subscribe to multiple pairs at once
const streams = ['btcusdt', 'ethusdt', 'solusdt']
.map(s => `${s}@miniTicker`)
.join('/')
const ws = new WebSocket(`wss://stream.binance.com:9443/stream?streams=${streams}`)
ws.on('message', (data) => {
const msg = JSON.parse(data.toString())
const ticker = msg.data
// ticker.s = 'BTCUSDT', ticker.c = current price (close)
priceCache.set(ticker.s.replace('USDT', ''), parseFloat(ticker.c))
})
ws.on('close', () => {
// Reconnect in 5 seconds
setTimeout(connectBinanceStream, 5000)
})
Binance WebSocket provides real-time updates (every second), making it preferable for alerts with high accuracy.
Alert Checking
// Runs via cron every 30 seconds (or after each price update)
async function checkAlerts(): Promise<void> {
const alerts = await db.query<Alert>(
`SELECT * FROM price_alerts WHERE active = true`
)
for (const alert of alerts.rows) {
const currentPrice = priceCache.get(alert.symbol)
if (!currentPrice) continue
const triggered =
(alert.operator === '>' && currentPrice > alert.target_value) ||
(alert.operator === '<' && currentPrice < alert.target_value)
if (triggered) {
await bot.api.sendMessage(
alert.user_id,
`🔔 Alert triggered!\n${alert.symbol} now $${currentPrice.toLocaleString()} ` +
`(target: ${alert.operator} $${alert.target_value.toLocaleString()})`
)
// Deactivate after trigger (or keep active — depends on requirements)
await db.query(
`UPDATE price_alerts SET active = false, triggered_at = NOW() WHERE id = $1`,
[alert.id]
)
}
}
}
Database
CREATE TABLE price_alerts (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL, -- Telegram user ID
symbol VARCHAR(10) NOT NULL, -- 'BTC', 'ETH', etc.
operator CHAR(1) NOT NULL, -- '>' or '<'
target_value NUMERIC(20, 8) NOT NULL,
active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
triggered_at TIMESTAMPTZ
);
CREATE INDEX ON price_alerts(active, symbol) WHERE active = true;
Bot Commands
| Command | Description |
|---|---|
/price BTC |
Current price |
/alert BTC > 70000 |
Set alert |
/alerts |
List active alerts |
/remove 5 |
Remove alert by ID |
/subscribe BTC 1h |
Periodic updates every hour |
Deploy: VPS with 512 MB RAM is enough for a bot with thousands of users. For scaling — Redis for price cache and horizontal scaling of workers via BullMQ.







