Листая телеграм-каналы с торговыми сигналами, я часто задавался вопросом: а кто из этих экспертов действительно попадает в цель? Одни обещают золотые горы, другие скромно молчат о своих неудачах. Решил разобраться раз и навсегда — создать систему, которая автоматически проверит, кто из гуру трейдинга говорит дело, а кто просто красиво упаковывает воздух.
Архитектура системы
Система состоит из четырех компонентов:
-
TG-Reader — собирает сообщения из телеграм-каналов через MTProto API
-
Trade-Radar — извлекает торговые прогнозы из текста с помощью AI
-
Analyzer — сравнивает прогнозы с реальными движениями цен
-
Visualizer — показывает статистику точности каналов
В этой статье разберем первую часть — автоматизацию сбора данных из Telegram.
Bot API vs MTProto API
Первая мысль — использовать Bot API. Создал бота, добавил в каналы и готово! Но есть проблема: боты видят только сообщения, адресованные им напрямую. В каналах они практически слепы.
Для полноценного анализа нужна вся история сообщений. Поэтому используем MTProto API — тот же протокол, что и в официальном клиенте Telegram. С ним мы эмулируем обычного пользователя и получаем доступ к полной истории каналов.
Настройка доступа
Для работы с MTProto нужны API ID и API Hash:
-
Идем на my.telegram.org
-
Выбираем "API development tools" → "Create Application"
-
Заполняем форму (платформа — Desktop)
-
Получаем ключи
Реализация на Go
Выбрал Go за производительность и отличную поддержку конкурентности. Для MTProto использую библиотеку gotd/td.
Структура проекта
tg-reader/
├── cmd/main.go # Точка входа
├── internal/
│ ├── config/ # Конфигурация
│ ├── storage/ # PostgreSQL
│ │ ├── models.go # Модели Channel, Message
│ │ └── postgres.go # Реализация хранилища
│ └── telegram/ # MTProto API
├── configs/config.yaml # Настройки
└── go.mod
Модели данных
type Channel struct {
ID int64
Username string
Title sql.NullString
Link sql.NullString
CreatedAt time.Time
}
type Message struct {
ID int64
TelegramID int64
ChannelID int64
Text sql.NullString
SentAt time.Time
SenderUsername sql.NullString
IsForward sql.NullBool
MessageType sql.NullString
RawData []byte // JSONB для полных данных
CreatedAt time.Time
}
Интерфейс Telegram Reader
type Reader interface {
GetLastMessages(ctx context.Context, channel string, limit int) ([]storage.Message, error)
GetMessagesFromDate(ctx context.Context, channel string, startDate time.Time) error
GetMessagesInDateRange(ctx context.Context, channel string, startDate, endDate time.Time) error
SubscribeToChannel(ctx context.Context, channel string) (<-chan storage.Message, error)
Close() error
}
-
GetLastMessages— для отладки, получает N последних сообщений -
GetMessagesFromDate— собирает историю с указанной даты -
GetMessagesInDateRange— работает в диапазоне дат -
SubscribeToChannel— подписка на новые сообщения в реальном времени
Конфигурация
telegram:
api_id: "ВАШ_API_ID"
api_hash: "ВАШ_API_HASH"
start_date: "2024-01-01"
channels:
- "trading_signals"
- "crypto_experts"
- "btc_analytics"
С какими проблемами столкнулся
Доступ только к публичным каналам
Первая неприятность — приватные каналы остались недоступными. MTProto API позволяет читать только те каналы, к которым у аккаунта есть доступ как у обычного пользователя. Это значит, что для закрытых VIP-каналов нужно либо получать приглашения, либо ограничиться публичными источниками.
Кроме того, для программного доступа к каналу нужно знать его точное имя (username). Ссылки вида t.me/channel_name работают, а вот по ID каналов получить данные сложнее.
FLOOD_WAIT — главный враг автоматизации
Самая серьезная проблема — лимиты Telegram на частоту запросов. При активном чтении сообщений API начинает возвращать ошибку FLOOD_WAIT_X, где X — количество секунд, которые нужно подождать перед следующим запросом.
Эти ограничения зависят от многих факторов:
-
Возраст аккаунта (новые аккаунты ограничены сильнее)
-
Предыдущая активность аккаунта
-
Текущая нагрузка на серверы Telegram
-
Количество уже отправленных запросов
Без правильной обработки система просто встает колом после первых нескольких сотен сообщений.
Решение проблемы FLOOD_WAIT
Пришлось добавить собственный парсер ошибок FLOOD_WAIT, так как стандартные методы библиотеки не всегда корректно их обрабатывали:
// isFloodWaitError проверяет, является ли ошибка FloodWaitError, и извлекает время ожидания
func isFloodWaitError(err error) (time.Duration, bool) {
if err == nil {
return 0, false
}
s := err.Error()
if strings.Contains(s, "rpc error code 420: FLOOD_WAIT") {
// Пример: "rpc error code 420: FLOOD_WAIT (19)"
parts := strings.Split(s, "(")
if len(parts) > 1 {
waitStr := strings.TrimSuffix(strings.TrimSpace(parts[1]), ")")
if seconds, parseErr := strconv.Atoi(waitStr); parseErr == nil {
return time.Duration(seconds) * time.Second, true
}
}
// Если не удалось распарсить время, по умолчанию ждем 5 секунд
return 5 * time.Second, true
}
return 0, false
}
И применяем это в основном цикле чтения сообщений:
func (c *Client) GetMessagesFromDate(ctx context.Context, channel string, startDate time.Time) error {
// ... инициализация и подготовка запроса
for {
historyResult, err := c.client.API().MessagesGetHistory(ctx, request)
if err != nil {
// Проверяем на FloodWaitError с нашей собственной реализацией
if wait, ok := isFloodWaitError(err); ok {
log.Printf("Получена FLOOD_WAIT ошибка. Ожидание %v перед повторной попыткой...", wait)
select {
case <-time.After(wait):
case <-ctx.Done():
return ctx.Err()
}
continue // Повторяем запрос
}
log.Printf("Ошибка при получении истории сообщений для %s: %v", channel, err)
return fmt.Errorf("failed to get history for %s: %w", channel, err)
}
// ... обработка полученных сообщений
// Добавляем паузу между успешными запросами
select {
case <-time.After(1 * time.Second):
case <-ctx.Done():
return ctx.Err()
}
}
}
Ключевые моменты решения:
-
Парсим строку ошибки для извлечения времени ожидания
-
Перехватываем ошибки и ждем указанное Telegram время
-
Добавляем задержку в 1 секунду между всеми запросами для профилактики
-
Используем
contextдля возможности прерывания долгих операций
Запуск и использование
Два режима работы:
Отладочный — быстрая проверка последних сообщений:
./tg-reader -c config.yaml -d -l 10
Полная обработка — сбор истории в базу данных:
./tg-reader -c config.yaml
При первом запуске система попросит номер телефона и код подтверждения из Telegram.
Пример работы с базой данных
// Сохранение канала
channel := &storage.Channel{
Username: "trading_signals",
Title: sql.NullString{String: "Trading Signals Pro", Valid: true},
}
err := storage.SaveChannel(ctx, channel)
// Сохранение сообщения
message := &storage.Message{
TelegramID: 12345,
ChannelID: channel.ID,
Text: sql.NullString{String: "BUY BTCUSDT at $50000", Valid: true},
SentAt: time.Now(),
MessageType: sql.NullString{String: "text", Valid: true},
}
err = storage.SaveMessage(ctx, message)
Результат
На выходе получаем структурированную базу данных со всеми сообщениями из торговых каналов. Каждое сообщение содержит текст, время публикации, метаданные и сырые данные в JSONB для гибкости анализа.
Система обрабатывает тысячи сообщений за несколько секунд и готова к реальному времени отслеживанию новых постов через SubscribeToChannel.
Что дальше
В следующей статье покажу самое интересное — как с помощью AI вытащить из телеграм-сообщений конкретные торговые прогнозы. Сохраню их в базе данных для последующего анализа.
Полный код проекта будет доступен после публикации серии статей.
Автор: rkazmin
