A production-ready, async, event-driven alert bot for Indian indices
(NIFTY50, BANKNIFTY) using Angel One SmartAPI live ticks and
Telegram for delivery.
This is NOT an auto-trading bot. It only sends alerts.
- 🔌 Angel One SmartAPI (REST + WebSocket V2) with TOTP login
- 📈 Live 5-min candle aggregation from raw ticks
- 🧠 Pluggable setup system (Strategy Pattern + auto-registry)
inside_candlebreakout/breakdown
- 🔍 Pluggable pattern engine (future-ready, off by default)
- Head & Shoulders / Inverse H&S
- Custom M / W (double top / bottom)
- Parallel channels (asc / desc / horizontal)
- Fibonacci retracement / extensions
- 🤖 Telegram bot with rich commands and multi-chat broadcast
- 💾 SQLite-backed dedup, state, and user preferences
- 🪵 Loguru rotating logs
- ♻️ Hot-reloadable YAML config (
/reload_config) - 🚦 Daily quotas + idempotent dedup (no duplicate alerts)
- 🔁 WebSocket auto-reconnect, stale-tick recovery, healthcheck
Stock-Scanner-v2/
├── main.py # Orchestrator (asyncio)
├── run.py # Entrypoint
├── config/
│ ├── settings.py # .env (pydantic-settings) + YAML loader
│ └── config.yaml # All runtime knobs
├── broker/
│ ├── angelone_client.py # REST (login / TOTP / history)
│ └── websocket_client.py # SmartWebSocketV2 with auto-reconnect
├── telegram_bot/ # Renamed from `telegram` to avoid PTB conflict
│ ├── bot.py
│ └── handlers.py
├── setups/
│ ├── base_setup.py # Abstract base + registry
│ └── inside_candle.py # 9:15 ref + 9:20/9:25/9:30 inside
├── patterns/
│ ├── swing_detector.py
│ ├── trendline_engine.py
│ ├── head_shoulders.py
│ ├── inverse_head_shoulders.py
│ ├── m_pattern.py
│ ├── w_pattern.py
│ ├── parallel_channel.py
│ └── fibonacci.py
├── engines/
│ ├── candle_engine.py # Ticks → OHLCV → observers
│ ├── setup_engine.py # Routes candles to setups
│ ├── pattern_engine.py # Routes candles to pattern detectors
│ ├── alert_engine.py # Render + dedup + broadcast
│ └── state_engine.py # Persistent setup state
├── database/
│ └── sqlite.py # aiosqlite repository
├── models/
│ ├── candle.py
│ ├── signal.py
│ └── pattern.py
├── services/
│ └── market_data_service.py # Wires broker → engines
├── utils/
│ ├── logger.py
│ ├── helpers.py
│ └── time_utils.py
├── logs/
├── data/
├── requirements.txt
├── .env.example
└── README.md
Note — the project folder is
telegram_bot/, nottelegram/, to avoid shadowing thepython-telegram-botlibrary's top-leveltelegrampackage.
git clone <your-fork-url> Stock-Scanner-v2
cd Stock-Scanner-v2
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtPython 3.14+ is required.
- Create a SmartAPI app at https://smartapi.angelbroking.com/.
- Note down:
API KeyClient ID(your Angel One client code)PIN/ password (Angel One uses MPIN for login)
- Enable TOTP on your Angel One account and copy the TOTP secret (the base32 string under the QR code).
- Make sure your subscription includes the live data feed for NSE indices (NIFTY 50 and Nifty Bank).
- Open Telegram and message
@BotFather. /newbot→ choose a name → copy the token.- Start a chat with your new bot once so it can message you.
- To get your chat ID:
- DM the bot, then visit
https://api.telegram.org/bot<TOKEN>/getUpdatesin a browser. - Find
"chat":{"id":<number>}in the response.
- DM the bot, then visit
- For a group, add the bot to the group, send any message,
call
getUpdates, and pick up the negative group chat ID (looks like-100123456789).
cp .env.example .envEdit .env:
ANGEL_API_KEY=your_api_key
ANGEL_CLIENT_ID=A1234
ANGEL_PASSWORD=1234 # MPIN
ANGEL_TOTP_SECRET=BASE32SECRET
TELEGRAM_BOT_TOKEN=123456:ABC-...
TELEGRAM_CHAT_IDS=123456789,-100987654321
LOG_LEVEL=INFO
TZ=Asia/Kolkata
DATABASE_PATH=data/scanner.db
CONFIG_PATH=config/config.yamlTELEGRAM_CHAT_IDS is a comma-separated list — every alert is fanned
out to every chat.
Open config/config.yaml to customize:
- Market hours / holidays
- Active indices (
enabled: true/false) - Active setups + their parameters
- Pattern engine flags
- Telegram parse mode and rate limits
- Logging level / rotation / retention
You can reload the file at runtime with /reload_config — no restart needed.
python run.pyLogs are written to console and logs/scanner.log (rotated).
On startup the bot:
- Reads
.envandconfig.yaml. - Opens SQLite (
data/scanner.db) and prunes stale state rows. - Loads every setup module under
setups/(auto-registry). - Loads every pattern module under
patterns/(auto-registry). - Sends a startup ping to all
TELEGRAM_CHAT_IDS. - Logs into Angel One (TOTP) and seeds today's candles via REST.
- Subscribes to live ticks via WebSocket V2.
| Command | Description |
|---|---|
/start |
Welcome message |
/help |
Full command list |
/status |
WebSocket health, time since last tick, active items |
/indices |
Show all configured indices and their state |
/active_indices |
Only enabled indices |
/active_setups |
Only enabled setups |
/enable_setup <name> |
Enable a setup (e.g. /enable_setup inside_candle) |
/disable_setup <name> |
Disable a setup |
/reload_config |
Reload config.yaml without restarting |
Inside-candle breakout (BUY):
🟢 INSIDE CANDLE BREAKOUT BUY
Index: NIFTY50
Time: 09:35
Breakout Price: 22150.30
Reference High: 22148.10
Reference Low: 22095.55
Inside-candle breakdown (SELL):
🔴 INSIDE CANDLE BREAKDOWN SELL
Index: BANKNIFTY
Time: 09:40
Breakdown Price: 47020.15
Reference High: 47180.50
Reference Low: 47038.25
- Timeframe: 5 minutes
- Reference candle: 09:15
- Inside candles: 09:20, 09:25, 09:30 — each must satisfy
high < ref.highANDlow > ref.low - After all 3 inside candles confirm, ANY subsequent 5-min candle:
- that closes above
ref.high→ 🟢 BUY alert - that closes below
ref.low→ 🔴 SELL alert
- that closes above
- At most one BUY and one SELL per index per day.
- Resets automatically at start-of-day.
- Create
setups/my_setup.py:
from setups.base_setup import BaseSetup
from models.candle import Candle
from models.signal import Signal, SignalDirection
class MySetup(BaseSetup):
name = "my_setup"
async def on_candle(self, symbol, candle: Candle):
if not candle.is_closed:
return None
if candle.close > candle.open * 1.005:
return Signal(
setup=self.name,
index=symbol,
direction=SignalDirection.BUY,
price=candle.close,
timeframe=candle.timeframe,
timestamp=candle.start,
)
return None- Add it to
config/config.yaml:
setups:
my_setup:
enabled: true
timeframe: "5min"
indices: ["NIFTY50", "BANKNIFTY"]
alert_cooldown_seconds: 60
max_buy_alerts_per_day: 1
max_sell_alerts_per_day: 1- Either restart, or send
/reload_configin Telegram.
That's it — no engine code changes required.
- Create
patterns/my_pattern.py:
from engines.pattern_engine import register_detector
from models.candle import CandleSeries
from models.pattern import Pattern, PatternKind, PatternStatus
@register_detector("my_pattern")
def detect(series: CandleSeries, config: dict) -> list[Pattern]:
if not config.get("enabled", False):
return []
...- Add a key under
patterns:inconfig/config.yaml:
patterns:
enabled: true
my_pattern:
enabled: true- Logs rotate at
10 MBand retain14 days(configurable). - All alerts are de-duplicated using
<date>|<setup>|<index>|<direction>as the idempotency key. - WebSocket auto-reconnects with exponential backoff (cap 30s).
- Healthcheck restarts the WS if no tick arrives for 60s during market hours.
- The bot ignores market holidays and weekends — configure in
config.yaml > market.holidays(ISO dates).
| Symptom | Fix |
|---|---|
ModuleNotFoundError: SmartApi |
pip install smartapi-python |
Login failed |
Re-verify API key, MPIN, and TOTP secret (it must be the secret, not a current code) |
Telegram send failed: chat not found |
Start a chat with the bot, then re-run /getUpdates to fetch the right chat ID |
| No alerts fire | Check /status — WebSocket connected? Last tick recent? Setup enabled? |
| Stale state across days | The bot prunes state older than today on every boot — no action needed |
MIT — see LICENSE.