telegram

package module
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 16, 2026 License: MIT Imports: 25 Imported by: 0

README

kite-mcp-telegram

Go Reference

Telegram bot integration for the algo2go ecosystem. Provides mobile-friendly trading via inline-keyboard commands (/buy /sell /quick /setalert) with confirmation flow, morning briefings (9 AM IST: alerts + token status), daily P&L summary (3:35 PM IST: holdings + positions, weekend skip + dedup, HTML formatted), disclaimer/consent flow, and pluggable command extension via plugin_commands.

Used by Sundeepg98/kite-mcp-server as the optional Telegram bot endpoint wired in app/app.go, app/http.go, app/adapters.go.

Why a separate module?

Telegram is a foundational mobile-first endpoint primitive applicable to any algo2go consumer running a trading bot — not just kite-mcp-server. Hosting as its own module:

  • Centralizes the Bot + Handler + Commands + Briefings contracts
  • Lets command syntax + keyboard layouts version independently
  • Decouples Telegram-specific UI flow from any one runtime

Stability promise

v0.x — unstable. Pin v0.1.0 deliberately.

Install

go get github.com/algo2go/kite-mcp-telegram@v0.1.0

Public API

  • Bot — Telegram client + dispatch
  • Handler — message handler routing (auth, portfolio, trading, plugin commands)
  • Trading commands — /buy, /sell, /quick (1-tap presets), /setalert
  • Briefings — morning (9 AM IST) + daily P&L (3:35 PM IST) scheduler with weekend skip + dedup
  • Disclaimer — consent flow before first trading command
  • PluginCommands — extension hook for custom commands
  • TradingFuzzTest — fuzz test harness for command parsing

Dependencies (8 algo2go modules)

  • github.com/algo2go/kite-mcp-alerts v0.1.0
  • github.com/algo2go/kite-mcp-broker v0.1.0 (incl. /ticker + /zerodha subpkgs)
  • github.com/algo2go/kite-mcp-domain v0.1.0
  • github.com/algo2go/kite-mcp-instruments v0.1.0
  • github.com/algo2go/kite-mcp-papertrading v0.1.0
  • github.com/algo2go/kite-mcp-riskguard v0.1.0
  • github.com/algo2go/kite-mcp-ticker v0.1.0
  • github.com/algo2go/kite-mcp-watchlist v0.1.0
  • github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
  • github.com/stretchr/testify v1.10.0

All algo2go deps published; no upstream replace directives needed.

Reference consumer

Sundeepg98/kite-mcp-server — consumed across 3 .go files: app/app.go, app/http.go, app/adapters.go (Telegram bot wiring + handler registration).

License

MIT — see LICENSE.

Authors

Original design: Sundeepg98 (Zerodha Tech). Multi-module promotion (2026-05-10): algo2go contributors.

Documentation

Overview

Package telegram implements a Telegram bot webhook handler that provides read-only commands (/price, /portfolio, /positions, /orders, /pnl, /alerts, /prices, /mywatchlist, /status, /help) and trading commands (/buy, /sell, /quick, /setalert) with inline-keyboard confirmation for registered users.

Index

Constants

View Source
const DisclaimerFullText = `` /* 699-byte string literal not displayed */

DisclaimerFullText is returned by the /disclaimer command. Mirrors the classification-statement excerpt from TERMS.md §3 so users always see the same wording whether they read the website or the bot.

View Source
const DisclaimerPrefix = "\u26A0\uFE0F <i>Not investment advice. Kite MCP is a tool, not an advisor.</i>\n\n"

DisclaimerPrefix is the short banner prepended to every financial outbound Telegram message. Keep it short (one visible line) so it doesn't dominate the chat surface, but unambiguous.

Variables

This section is empty.

Functions

This section is empty.

Types

type AlertLookup

type AlertLookup interface {
	Add(email, tradingsymbol, exchange string, instrumentToken uint32, targetPrice float64, direction alerts.Direction) (string, error)
	AddWithReferencePrice(email, tradingsymbol, exchange string, instrumentToken uint32, targetPrice float64, direction alerts.Direction, referencePrice float64) (string, error)
	List(email string) []*alerts.Alert
	ActiveCount(email string) int
}

AlertLookup is the narrow port over the alert store's CRUD surface used by /alerts, /setalert, /status. Locally-declared (rather than imported from kc) so the package stays free of an upward dependency on the parent kc package; *alerts.Store satisfies it structurally, as does kc.AlertStoreInterface.

type BotHandler

type BotHandler struct {
	// contains filtered or unexported fields
}

BotHandler handles incoming Telegram webhook updates and routes them to the appropriate command handler. It enforces that only private chats from registered users are served, with per-chat rate limiting.

func NewBotHandler

func NewBotHandler(bot alerts.BotAPI, webhookSecret string, manager KiteManager, logger *slog.Logger, factory KiteClientFactory) *BotHandler

NewBotHandler creates a new BotHandler and starts a background goroutine that periodically prunes stale rate-limit and pending-order entries. A nil factory disables trading commands (getClient returns an error).

tradingEnabled defaults to *true* for back-compat with local/single-user builds and the existing test suite. The hosted multi-user server constructs this and then calls SetTradingEnabled(false) from app.Config.EnableTrading so /buy, /sell, /quick are gated consistently with the MCP tool gating.

func (*BotHandler) CleanupNow

func (h *BotHandler) CleanupNow()

CleanupNow triggers an immediate cleanup of stale rate-limit and pending-order entries. Exposed for testing the cleanup logic without waiting for the background ticker.

func (*BotHandler) RegisterCommand

func (h *BotHandler) RegisterCommand(name string, fn PluginCommandHandler) error

RegisterCommand installs a plugin handler for the given /command. Returns an error when:

  • name is empty or doesn't start with "/";
  • name contains whitespace (commands are single tokens);
  • name collides with a built-in (reservedCommands).

Registering the same name twice is allowed — last-wins semantics — which matches a plugin's likely lifecycle (re-registration on a scheduler restart or a config-reload).

Rationale for "built-in wins" precedence:

  • Built-in commands carry rate-limiting, tier gating, and confirmation flows that plugins can't replicate correctly. Letting a plugin shadow /buy would bypass riskguard. Hard no.
  • Plugins that want to CO-OPERATE with a built-in (e.g. "run a metric after /buy") use OnAfterToolExecution-equivalent hooks at the MCP layer, not the Telegram bot layer.

func (*BotHandler) ServeHTTP

func (h *BotHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler. It validates the request, parses the Telegram update, and dispatches to the appropriate command handler.

func (*BotHandler) SetTradingEnabled

func (h *BotHandler) SetTradingEnabled(enabled bool)

SetTradingEnabled toggles the /buy, /sell, /quick Telegram commands on or off. Called once at wire-up from app.Config.EnableTrading so the Telegram surface matches the MCP tool gating (Path 2 compliance — NSE/INVG/69255 Annexure I Para 2.8 Algo Provider classification).

func (*BotHandler) Shutdown

func (h *BotHandler) Shutdown()

Shutdown stops the background cleanup goroutine. It is safe to call multiple times.

func (*BotHandler) TradingEnabled

func (h *BotHandler) TradingEnabled() bool

TradingEnabled reports the current trading-gate state. Exported so tests and admin tools can read it without reaching into unexported fields.

type InstrumentLookup

type InstrumentLookup interface {
	GetByID(id string) (instruments.Instrument, error)
}

InstrumentLookup is the narrow port over the instruments manager used by /setalert (resolves "NSE:SYMBOL" → instrument token). Returns the upstream concrete instruments.Instrument value type.

type KiteClientFactory

type KiteClientFactory = zerodha.KiteClientFactory

KiteClientFactory creates Kite API clients. Type alias to the canonical declaration in broker/zerodha — kc/telegram cannot import kc directly (would cycle through kc → kc/telegram), but broker/zerodha is a leaf package safely importable from anywhere. Returns the hexagonal zerodha.KiteSDK port rather than the concrete SDK client so trading commands can be exercised off-HTTP with zerodha.MockKiteSDK.

F4 consolidation (Phase B/D close-out): was a duplicate 2-method interface declaration; aliased to broker/zerodha.KiteClientFactory to eliminate drift risk while preserving the package-local name + existing callsites unchanged. See broker/zerodha/sdk_adapter.go for the canonical declaration.

type KiteManager

type KiteManager interface {
	TelegramStore() TelegramLookup
	AlertStore() AlertLookup
	WatchlistStore() WatchlistLookup
	GetAPIKeyForEmail(email string) string
	GetAccessTokenForEmail(email string) string
	TelegramNotifier() *alerts.TelegramNotifier
	InstrumentsManager() InstrumentLookup
	IsTokenValid(email string) bool

	// Trading support — riskguard, paper trading, ticker.
	RiskGuard() *riskguard.Guard
	PaperEngine() PaperEngineLookup
	TickerService() TickerLookup
}

KiteManager abstracts the kc.Manager methods needed by the bot handler. Phase 3a kc/-side migration: store/service returns are now port-typed (AlertLookup, WatchlistLookup, InstrumentLookup, PaperEngineLookup, TickerLookup) instead of concrete pointer types — the prior `*alerts.Store`, `*watchlist.Store`, `*instruments.Manager`, `*papertrading.PaperEngine`, `*ticker.Service` returns leaked Concrete-pattern abstraction breaks across the package boundary. TelegramNotifier and RiskGuard remain on concrete types per their leaf-domain ownership (alerts.TelegramNotifier is the alert subsystem's exposed type; riskguard.Guard is the canonical Guard whose full method surface trading consumers depend on).

type PaperEngineLookup

type PaperEngineLookup interface {
	IsEnabled(email string) bool
	PlaceOrder(email string, params map[string]any) (map[string]any, error)
}

PaperEngineLookup is the narrow port over the paper engine used by /buy, /sell, /quick to detect paper-trading mode and route confirmed orders through the paper-trading simulator. *papertrading.PaperEngine satisfies it structurally.

type PluginCommandContext

type PluginCommandContext struct {
	// ChatID is the Telegram chat the command came from. Private chats
	// only; group/channel messages are filtered upstream by ServeHTTP.
	ChatID int64
	// Email is the registered user's email, resolved via TelegramLookup.
	// Guaranteed non-empty by the time the plugin handler runs.
	Email string
	// Args is the raw argument text after the command token, stripped
	// of surrounding whitespace. Empty when the user types just the
	// command (e.g. "/echo" with no args).
	Args string
}

PluginCommandContext is what a plugin-registered command handler receives when a user sends its command. It is intentionally narrow: chat ID, caller email, and the raw argument string (everything after the command token). Plugins that need to send rich responses can use the BotHandler reference on the caller side — PluginCommandContext only carries inputs.

type PluginCommandHandler

type PluginCommandHandler func(ctx PluginCommandContext) string

PluginCommandHandler is the function signature a plugin implements to handle a /command. It returns the reply text (HTML-formatted; the bot will send it via sendHTML). An empty return string means "nothing to say" — useful when the plugin sends its own response out-of-band.

Safety contract: the bot's ServeHTTP recovers panics in plugin handlers and converts them into an error message to the user. Plugins SHOULD return errors via a formatted string rather than panicking, but the recovery net keeps one buggy plugin from crashing the bot for every other user.

type TelegramLookup

type TelegramLookup interface {
	GetEmailByChatID(chatID int64) (string, bool)
}

TelegramLookup abstracts the Telegram chat-ID-to-email mapping needed by the bot. Separated from alerts per Single Responsibility Principle.

type TickerLookup

type TickerLookup interface {
	IsRunning(email string) bool
	Subscribe(email string, tokens []uint32, mode brokerticker.Mode) error
}

TickerLookup is the narrow port over the ticker service used by /setalert to auto-subscribe instruments for real-time monitoring.

type WatchlistLookup

type WatchlistLookup interface {
	ListWatchlists(email string) []*watchlist.Watchlist
	GetItems(watchlistID string) []*watchlist.WatchlistItem
}

WatchlistLookup is the narrow port over the watchlist store's read surface used by /mywatchlist. Returns are exactly the upstream concrete pointer slices so the call site needs no projection layer; *watchlist.Store satisfies it structurally.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL