Building a Telegram Crypto Signals Bot with Python
Step-by-step guide to building a Python Telegram bot that delivers live crypto trading signals from Binance and Bybit directly to your chat.
Step-by-step guide to building a Python Telegram bot that delivers live crypto trading signals from Binance and Bybit directly to your chat.
Trading crypto without a real-time notification system means you're always one screen refresh behind the market. A Telegram bot that pushes live signals directly to your phone changes that equation entirely. Python makes this surprisingly accessible — a working signal bot takes fewer than 120 lines of code, connects directly to exchanges like Binance, Bybit, and OKX, and runs 24/7 on a $5 VPS. What follows is a practical build from zero to running bot, with actual code you can deploy today.
Telegram's Bot API is one of the cleanest in the messaging world — well-documented, rate-limit friendly, and completely free. Messages arrive in milliseconds, support rich Markdown formatting, and are searchable and auditable. Compare that to email alerts, which sit unread, or SMS, which costs money and strips formatting.
For a trader running multiple pairs across Binance and Bybit, having signals arrive in a dedicated private channel keeps everything clean. You can share the channel with a trading partner, mute it during sleep hours, or pin important alerts. The signal stream becomes a log. That's something Discord and Slack can't replicate as frictionlessly.
The practical setup is simple: register a bot via BotFather, grab the token, create a private channel, promote your bot to admin, and store the channel ID. Every signal your Python script generates gets pushed there. This is the same delivery architecture that platforms like VoiceOfChain use for real-time crypto signal distribution — structured alerts that arrive before the candle closes, formatted for immediate action.
The stack is minimal: pyTelegramBotAPI for the Telegram layer, ccxt for exchange connectivity, pandas for indicator math, and schedule for the polling loop. All four install in seconds. Python 3.9 or higher is recommended — f-string formatting and type hints make the code significantly cleaner.
One structural decision upfront: keep your API keys and bot token out of the source code. Use environment variables or a .env file loaded with python-dotenv. If you ever push this to GitHub without that discipline, your exchange account is exposed within minutes — bots crawl public repos for API credentials constantly.
# Install dependencies
# pip install pyTelegramBotAPI ccxt pandas schedule python-dotenv
import os
import telebot
import ccxt
import pandas as pd
import schedule
import time
from dotenv import load_dotenv
load_dotenv() # Load from .env file
# --- Configuration ---
BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN') # From BotFather
CHAT_ID = os.getenv('TELEGRAM_CHAT_ID') # Channel or group ID
# Initialize Telegram bot
bot = telebot.TeleBot(BOT_TOKEN)
# Connect to Binance (swap to ccxt.bybit(), ccxt.okx(), ccxt.bitget() etc.)
exchange = ccxt.binance({
'apiKey': os.getenv('BINANCE_API_KEY'),
'secret': os.getenv('BINANCE_SECRET'),
'enableRateLimit': True,
'options': {'defaultType': 'spot'},
})
print('Bot initialized. Exchange:', exchange.id)
Never hardcode API keys in your script. Store them in a .env file and add .env to your .gitignore before creating your first commit. A leaked Binance key with withdrawal permissions is a catastrophic loss.
ccxt is the workhorse here. It abstracts over 100 exchanges behind a single unified interface — the same fetch_ohlcv call works on Binance, Bybit, OKX, Coinbase Advanced, and Bitget without changing a line. This matters when you want to run cross-exchange comparison or migrate to a different exchange later.
The RSI (Relative Strength Index) is the most battle-tested starting point for a signal bot. Readings below 30 suggest oversold conditions — a potential buy zone. Above 70 indicates overbought — a potential exit or short trigger. It's not a crystal ball, but it's fast to compute, produces clean binary signals, and has decades of empirical use behind it. For higher-conviction setups, combine it with a volume filter: an RSI signal on a candle with 3x average volume carries more weight than one on a quiet candle.
def fetch_ohlcv_df(symbol: str, timeframe: str = '1h', limit: int = 50) -> pd.DataFrame:
ohlcv = exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
df = pd.DataFrame(ohlcv, columns=['ts', 'open', 'high', 'low', 'close', 'volume'])
df['ts'] = pd.to_datetime(df['ts'], unit='ms')
return df
def compute_rsi(df: pd.DataFrame, period: int = 14) -> float:
delta = df['close'].diff()
gain = delta.clip(lower=0).rolling(period).mean()
loss = (-delta.clip(upper=0)).rolling(period).mean()
rs = gain / loss
rsi = 100 - (100 / (1 + rs))
return round(rsi.iloc[-1], 2)
def get_signal(symbol: str, timeframe: str = '1h') -> dict | None:
df = fetch_ohlcv_df(symbol, timeframe)
rsi = compute_rsi(df)
price = df['close'].iloc[-1]
vol = df['volume'].iloc[-1]
avg_vol = df['volume'].rolling(20).mean().iloc[-1]
# Volume confirmation: signal only on above-average volume
vol_confirmed = vol > avg_vol * 1.2
if rsi < 30 and vol_confirmed:
return {'symbol': symbol, 'direction': 'BUY', 'price': price, 'rsi': rsi, 'tf': timeframe}
if rsi > 70 and vol_confirmed:
return {'symbol': symbol, 'direction': 'SELL', 'price': price, 'rsi': rsi, 'tf': timeframe}
return None
On Binance, fetch_ohlcv for BTC/USDT on the 1h timeframe returns the last 50 closed candles in a single API call. On Bybit and OKX, the identical call works without modification thanks to ccxt's abstraction. This means you can point the same scanner at different exchanges to catch divergences — a pair oversold on Bybit but not yet on Binance sometimes signals a coming rebalance.
With the signal logic in place, the final piece is formatting the message and pushing it through the Telegram Bot API on a schedule. The schedule library handles the polling interval cleanly — no threads, no async complexity, just a simple loop that fires your scan function every N minutes.
Message formatting matters more than most developers expect. A wall of text at 3am when a signal fires is useless. Structure your alerts so the most important information — direction, symbol, price — appears in the first two lines. A trader glancing at their lock screen should be able to act without opening the full message. Platforms like VoiceOfChain format signals this way by design: action first, context second.
WATCHLIST = ['BTC/USDT', 'ETH/USDT', 'SOL/USDT', 'BNB/USDT', 'WLD/USDT']
TIMEFRAME = '1h'
SCAN_EVERY = 15 # minutes — match to candle close cadence
def format_signal(sig: dict) -> str:
icon = '\U0001f7e2' if sig['direction'] == 'BUY' else '\U0001f534' # green/red circle
return (
f"{icon} *{sig['direction']}* | {sig['symbol']}\n"
f"Price : `${sig['price']:,.4f}`\n"
f"RSI : `{sig['rsi']}` ({'oversold' if sig['direction'] == 'BUY' else 'overbought'})\n"
f"TF : `{sig['tf']}`\n"
f"Source: VoiceOfChain scanner"
)
def scan_and_alert():
hits = 0
for symbol in WATCHLIST:
try:
sig = get_signal(symbol, TIMEFRAME)
if sig:
msg = format_signal(sig)
bot.send_message(CHAT_ID, msg, parse_mode='Markdown')
print(f'[SIGNAL] {sig["direction"]} {symbol} @ {sig["price"]}')
hits += 1
except Exception as e:
print(f'[ERROR] {symbol}: {e}')
if hits == 0:
print(f'[SCAN] No signals. {len(WATCHLIST)} pairs checked.')
# Schedule the scan
schedule.every(SCAN_EVERY).minutes.do(scan_and_alert)
print(f'Signal bot running. Scanning {len(WATCHLIST)} pairs every {SCAN_EVERY}m.')
while True:
schedule.run_pending()
time.sleep(1)
Match your scan interval to your timeframe. If you're trading 1h candles, scanning every 15 minutes is fine. If you're on 15m candles, scan every 5 minutes. Scanning more frequently than your candle close adds noise without improving signal quality.
A signal bot without risk guardrails is just an expensive way to over-trade. The most impactful addition is a daily signal cap — if the bot fires more than N alerts in a day, something unusual is happening in the market and you probably shouldn't be trading it blindly. Set the cap at 5-6 signals per day for a 5-pair watchlist on 1h timeframes under normal volatility.
Deduplication is equally important. If RSI stays below 30 for three consecutive candles, you don't want three BUY alerts on the same pair. Track the last signal per symbol in a simple dictionary and suppress repeats until the condition clears and re-triggers. On Bybit and OKX, sustained extreme RSI readings are common during low-liquidity hours — without deduplication your channel floods.
Adding a stop-loss suggestion to every signal message is arguably the highest-value enhancement. If your entry is at $65,000 on BTC/USDT and ATR on the 1h is $800, a 1.5x ATR stop puts your exit at $63,800. Including that in the Telegram message means you never enter a trade without knowing your max loss. This is standard practice on professional signal services — VoiceOfChain includes risk levels and suggested stops alongside every published signal for this reason.
For traders running the bot against live Binance or Bitget accounts, consider adding paper trading mode first. Log the signals to a CSV with timestamps and prices, then evaluate win rate over 30-50 signals before connecting real capital. A signal bot that looks profitable on paper loses money when slippage, fees, and emotional execution are added. Test the logic, not just the code.
Running the script on your laptop works for testing but breaks the moment your machine sleeps. Production deployment means a cloud VPS or a small dedicated server. A 1-core, 1GB RAM instance on any major cloud provider ($4-6/month) is more than enough for a bot scanning 5-10 pairs on hourly candles. The exchange API calls are light, the computation is minimal, and the schedule loop uses negligible CPU.
Use systemd or screen/tmux to keep the process alive after SSH disconnects. The simplest production approach is a systemd service file pointing at your Python script with Restart=always — if the process crashes on a network error or exchange timeout, the OS restarts it automatically within seconds. Pair this with basic logging to a file so you can review what signals fired while you were offline.
Exchange API rate limits are the most common source of runtime errors. Binance allows 1200 request weights per minute — a 15-minute scan of 10 pairs uses roughly 10-20 weight units, well within limits. OKX and KuCoin have similar limits. ccxt's enableRateLimit flag handles throttling automatically, but if you expand your watchlist aggressively or shrink the scan interval, monitor your API usage to avoid temporary bans.
A Telegram crypto signals bot is one of the highest-leverage tools an active trader can build. The barrier is low — three libraries, roughly 100 lines of code, and a free Telegram account. The upside is real: consistent, formatted alerts that arrive faster than any manual monitoring setup, with a full audit trail baked in. Start with the RSI bot here, validate it against 30-50 signals in paper trading mode, then layer in stop-loss suggestions, cooldowns, and eventually automated execution if the strategy earns it. The exchanges will still be open. Better to trade a tested system late than a broken one immediately.