Skip to content

trading_agent.channels.telegram_channel

The Telegram channel: talk to the desks and hear from them on your phone.

It runs two loops beside the desks:

  1. The message loop asks Telegram for new messages. A chat that is not paired gets a pairing code and nothing else; every such message is logged as a channel event. A paired chat's code-only commands (/status, /positions, /pnl, /pause, /resume, /kill, /desk, /help) are answered at once; anything else is sent to the chat's desk as a user message.
  2. The event loop listens to the event bus. A desk's answer to a message from Telegram goes back to the chat it came from. The notifications chosen in [telegram] notifications, such as fills and halts, go to every paired chat.

A message sent while its desk is working steers the running turn, as on the web page; the chat is told so, and the running turn's answer comes to it too.

A photo or file from a paired chat is downloaded into the desk's uploads, as an upload from the web page is, and goes to the desk with the caption as the message. Files from chats that are not paired are never downloaded.

Typical usage example:

channel = TelegramChannel(api, pairing, commands, formatter, desks, parts, event_bus, 'main') await channel.run()

TelegramChannel

TelegramChannel(api, pairing, commands, formatter, desks, parts, event_bus, default_desk, attachments=None, requests=None)

The bot, its paired chats and its notifications.

Attributes:

Name Type Description
state

"connecting", "connected" or "failed".

error

Why the last attempt failed, or an empty string.

username

The bot's username once connected.

Creates the channel without contacting Telegram.

Parameters:

Name Type Description Default
api TelegramApi

The connection to Telegram.

required
pairing PairingStore

The paired chats and codes.

required
commands TelegramCommands

The code-only commands.

required
formatter NotificationFormatter

Decides what is pushed.

required
desks Mapping[str, DeskRuntime]

The running desks.

required
parts Mapping[str, DeskParts]

Each desk's session store, for the daily summaries.

required
event_bus EventBus

Where the desks' events arrive.

required
default_desk str

The desk a newly paired chat talks to.

required
attachments AttachmentStore | None

Where photos and files sent to the bot are kept, or None to refuse them.

None
requests TelegramRequests | None

Shows the desks' questions and plans with buttons, or None to leave them to the web page.

None
Source code in src/trading_agent/channels/telegram_channel.py
def __init__(
    self,
    api: telegram_api_module.TelegramApi,
    pairing: pairing_store_module.PairingStore,
    commands: telegram_commands_module.TelegramCommands,
    formatter: notification_formatter_module.NotificationFormatter,
    desks: Mapping[str, desk_runtime.DeskRuntime],
    parts: Mapping[str, gateway_parts.DeskParts],
    event_bus: event_bus_module.EventBus,
    default_desk: str,
    attachments: attachment_store_module.AttachmentStore | None = None,
    requests: telegram_requests_module.TelegramRequests | None = None,
):
    """Creates the channel without contacting Telegram.

    Args:
        api (telegram_api_module.TelegramApi): The connection to Telegram.
        pairing (pairing_store_module.PairingStore): The paired chats and codes.
        commands (telegram_commands_module.TelegramCommands): The code-only commands.
        formatter (notification_formatter_module.NotificationFormatter): Decides what is pushed.
        desks (Mapping[str, desk_runtime.DeskRuntime]): The running desks.
        parts (Mapping[str, gateway_parts.DeskParts]): Each desk's session store, for the daily summaries.
        event_bus (event_bus_module.EventBus): Where the desks' events arrive.
        default_desk (str): The desk a newly paired chat talks to.
        attachments (attachment_store_module.AttachmentStore | None): Where photos and files sent to the bot are kept, or None to refuse them.
        requests (telegram_requests_module.TelegramRequests | None): Shows the desks' questions and plans with buttons, or None to leave them to the web page.
    """
    self._api = api
    self._pairing = pairing
    self._commands = commands
    self._formatter = formatter
    self._desks = desks
    self._parts = parts
    self._event_bus = event_bus
    self._default_desk = default_desk
    self._attachments = attachments
    self._requests = requests
    self._subscription = event_bus.subscribe(maximum_queued=_QUEUE_SIZE)
    self._reply_chats: dict[str, set[int]] = {}
    self._trigger_sources: dict[str, tuple[str, str]] = {}
    self._running_triggers: dict[str, str] = {}
    self.state = _STATE_OFF
    self.error = ''
    self.username = ''

run async

run()

Connects, then runs the message loop and the event loop until cancelled.

Source code in src/trading_agent/channels/telegram_channel.py
async def run(self) -> None:
    """Connects, then runs the message loop and the event loop until cancelled."""
    try:
        await self._connect()
        async with asyncio.TaskGroup() as group:
            group.create_task(self._message_loop())
            group.create_task(self._event_loop())
    finally:
        await self.close()

close async

close()

Closes the connection to Telegram.

Source code in src/trading_agent/channels/telegram_channel.py
async def close(self) -> None:
    """Closes the connection to Telegram."""
    self._event_bus.unsubscribe(self._subscription)
    await self._api.close()

approve async

approve(code)

Pairs the chat that was given a code, and tells the chat.

Parameters:

Name Type Description Default
code str

The pairing code.

required

Returns:

Type Description
PairedChat

The paired chat.

Raises:

Type Description
PairingError

No valid code matches.

Source code in src/trading_agent/channels/telegram_channel.py
async def approve(self, code: str) -> pairing_store_module.PairedChat:
    """Pairs the chat that was given a code, and tells the chat.

    Args:
        code (str): The pairing code.

    Returns:
        pairing_store_module.PairedChat: The paired chat.

    Raises:
        pairing_store_module.PairingError: No valid code matches.
    """
    chat = self._pairing.approve(code, self._default_desk)
    self._publish_channel('paired', chat.chat_id, chat.name, '')
    await self._send(
        chat.chat_id,
        f'Paired. You are talking to the {chat.desk} desk. Send /help to see what you can do.',
    )
    return chat