Листая телеграм-каналы с торговыми сигналами, я часто задавался вопросом: а кто из этих экспертов действительно попадает в цель? Одни обещают золотые горы, другие скромно молчат о своих неудачах. Решил разобраться раз и навсегда — создать систему, которая автоматически проверит, кто из гуру трейдинга говорит дело, а кто просто красиво упаковывает воздух.
Архитектура системы
Система состоит из четырех компонентов:
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 вытащить из телеграм-сообщений конкретные торговые прогнозы. Сохраню их в базе данных для последующего анализа.
Полный код проекта будет доступен после публикации серии статей.