Scheduled Jobs
The Telegram bot runs three scheduled jobs on NSE trading days. They are configured in skopaq/telegram_bot.py using python-telegram-bot's built-in job queue: each runs Monday to Friday (days=(1, 2, 3, 4, 5); in python-telegram-bot 20+, 0 is Sunday) and returns early on NSE holidays (skopaq/risk/calendar.py, plus SKOPAQ_NSE_HOLIDAYS).
Not the trading daemon
These jobs only send messages. Autonomous trading sessions are started by the
compose scheduler service (Mac mini runbook).
Job Schedule
| Time (IST) | Time (UTC) | Job | Description |
|---|---|---|---|
| 09:00 | 03:30 | Pre-Market Login | Kite Connect login reminder |
| 09:25 | 03:55 | Market Scan | Auto-scan top 10 stocks |
| 15:35 | 10:05 | EOD Summary | End-of-day P&L report |
Pre-Market Login (09:00 IST)
Sends a reminder to connect to Zerodha if the Kite access token is not set.
What it does:
- Checks if
get_access_token()returns a valid token - If connected: sends "Kite connected -- ready for market open"
- If not connected: sends login link
<SKOPAQ_PUBLIC_BASE_URL>/api/kite/login(or says the link is not configured whenSKOPAQ_PUBLIC_BASE_URLis empty)
Why 09:00?
NSE pre-open session starts at 09:00. Logging in early ensures the token is ready before 09:15 when regular trading begins.
# IST 9:00 = UTC 3:30, Monday to Friday
app.job_queue.run_daily(
job_pre_market_login,
time=dt_time(hour=3, minute=30, tzinfo=timezone.utc),
days=(1, 2, 3, 4, 5),
name="pre_market_login",
)
Market Scan (09:25 IST)
Scans NIFTY 50 top stocks and sends the results to all registered chats.
What it does:
- Verifies Kite is connected (sends login prompt if not)
- Fetches live quotes for 10 blue-chip stocks: RELIANCE, HDFCBANK, ICICIBANK, INFY, TCS, SBIN, LT, BHARTIARTL, WIPRO, NTPC
- Sorts by absolute change% (top movers)
- Sends formatted scan results
Example output:
Market Scan (09:25 IST)
RELIANCE: Rs 2,485.50 (+1.23%) Vol: 12,34,567
HDFCBANK: Rs 1,567.80 (+0.89%) Vol: 8,45,230
SBIN: Rs 756.30 (-0.45%) Vol: 15,67,890
TCS: Rs 3,750.00 (+0.32%) Vol: 3,21,456
...
Top pick: RELIANCE (+1.23%)
Reply 'analyze SYMBOL' for deep analysis
Reply 'trade SYMBOL' to execute
Why 09:25?
Prices settle about 10 minutes after market open (09:15). Scanning at 09:25 gives enough data for meaningful volume and direction signals.
Customizing the watchlist
The scanned stocks are hardcoded in the job_market_scan function. To customize, edit the symbols list in skopaq/telegram_bot.py.
EOD Summary (15:35 IST)
Sends end-of-day portfolio summary after market close.
What it does:
- Checks Kite connection (skips if not connected)
- Fetches open positions and funds
- Calculates total day P&L
- Sends formatted summary
Example output:
Market Closed -- EOD Summary
Cash: Rs 9,87,654.00
Open Positions (2):
RELIANCE: 10x @ 2400.00 P&L: Rs +855.00
TCS: 5x @ 3800.00 P&L: Rs -250.00
Total Day P&L: Rs +605.00
Why 15:35?
NSE regular trading ends at 15:30. The 5-minute buffer ensures all orders are settled and final prices are available.
How Jobs Are Registered
All three jobs use run_daily from python-telegram-bot's JobQueue:
from datetime import time as dt_time, timezone
# Monday to Friday (PTB 20+: 0 = Sunday); each job also returns early on NSE holidays.
weekdays = (1, 2, 3, 4, 5)
# IST 9:00 = UTC 3:30
app.job_queue.run_daily(
job_pre_market_login,
time=dt_time(hour=3, minute=30, tzinfo=timezone.utc),
days=weekdays,
name="pre_market_login",
)
# IST 9:25 = UTC 3:55
app.job_queue.run_daily(
job_market_scan,
time=dt_time(hour=3, minute=55, tzinfo=timezone.utc),
days=weekdays,
name="market_scan",
)
# IST 15:35 = UTC 10:05
app.job_queue.run_daily(
job_eod_summary,
time=dt_time(hour=10, minute=5, tzinfo=timezone.utc),
days=weekdays,
name="eod_summary",
)
With SKOPAQ_HEARTBEAT_FILE set (docker compose sets it), a fourth job touches that file
every minute for the container health check.
UTC times
All times are specified in UTC. IST = UTC + 5:30. If you change these, remember to convert correctly.
Notification Routing
Scheduled jobs send messages to all registered chat IDs in alert_chat_ids. A chat ID is registered when a user sends /start to the bot.
If no chat IDs are registered, scheduled jobs run silently (no errors, just no output).
Adding Custom Jobs
To add a new scheduled job, follow this pattern in skopaq/telegram_bot.py:
async def job_custom(context: ContextTypes.DEFAULT_TYPE) -> None:
"""Your custom job description."""
for chat_id in list(alert_chat_ids):
await context.bot.send_message(
chat_id=chat_id,
text="Your scheduled message here",
)
# Register in main():
app.job_queue.run_daily(
job_custom,
time=dt_time(hour=6, minute=0, tzinfo=timezone.utc), # 11:30 IST
days=(1, 2, 3, 4, 5),
name="custom_job",
)
Weekday-Only Execution
days=(1, 2, 3, 4, 5) keeps the jobs off weekends, and each job starts with
if not _is_trading_day_ist(): return, which skips NSE holidays. Add the same check to
custom market-hours jobs. When the current year has no NSE holiday list, the jobs stay
silent until it is added (see the Mac mini runbook, Holidays).