온체인 봇을 위한 텔레그램 알림 시스템 설계
자동매매 봇은 대개 화면 없이, 최소화된 콘솔에서 조용히 돕니다. 문제는 무슨 일이 일어나는지 실시간으로 알 수 없다는 것. 진입했는지, 손절 맞았는지, 주문이 실패했는지를 매번 로그 파일을 열어 확인할 수는 없습니다. 텔레그램 알림은 이 문제를 가장 적은 비용으로 해결합니다. 봇이 폰으로 직접 말을 걸게 만드는 거죠.
SDK는 필요 없다 — 그냥 HTTP
텔레그램 봇 알림에 무거운 라이브러리는 필요 없습니다. 봇 토큰으로 sendMessage에 폼 POST 한 번이면 끝입니다. 클라이언트는 상태가 없어 동시 호출도 안전합니다.
type Telegram struct {
Token string
ChatID string
client *http.Client
}
func (t *Telegram) Enabled() bool {
return t.Token != "" && t.ChatID != ""
}
func (t *Telegram) Send(text string) error {
// 미설정이면 조용히 로그로 대체 — 알림 없이도 봇은 돈다
if !t.Enabled() {
log.Printf("[텔레그램] 미설정 — 로그 대체: %s", text)
return nil
}
apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", t.Token)
params := url.Values{
"chat_id": {t.ChatID},
"text": {text},
"parse_mode": {"HTML"}, // <b>, <code> 서식 사용
}
resp, err := t.client.PostForm(apiURL, params)
if err != nil {
return fmt.Errorf("텔레그램 전송 실패: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("텔레그램 HTTP %d", resp.StatusCode)
}
return nil
}
핵심 설계 하나. 토큰이 없으면 조용히 로그로 폴백합니다. 알림 설정을 안 해도 봇은 멀쩡히 돌아야 하니까요. 알림은 편의 기능이지, 봇의 생존 조건이 아닙니다.
알림이 봇을 막으면 안 된다 — 비동기 전송
여기서 초보가 자주 실수합니다. 매매 루프 안에서 알림을 동기로 보내면, 텔레그램 서버가 느릴 때 봇 전체가 그만큼 멈춥니다. 시장은 기다려 주지 않죠. 그래서 알림은 항상 fire-and-forget, 별도 goroutine으로 던지고 잊습니다.
func (t *Telegram) sendAsync(text string) {
go func() {
if err := t.Send(text); err != nil {
log.Printf("[텔레그램] 전송 오류: %v", err)
}
}()
}
전송이 실패해도 매매 로직은 영향받지 않습니다. 실패는 로그에만 남기고, 봇은 하던 일을 계속합니다.
이벤트별 알림 — 무엇을 언제 보낼까
모든 걸 알리면 알림 피로가 옵니다. 결정적 순간만 골라 타입별로 만듭니다. 진입, 청산, 오류, 그리고 정보성 알림.
// 진입 알림 — 방향/레버리지/진입가/TP/SL 한눈에
func (t *Telegram) SendEntry(symbol, direction string,
entry, sl, tp float64, leverage int) {
emoji := "📈"
if direction == "SHORT" {
emoji = "📉"
}
msg := fmt.Sprintf(
"%s <b>[진입]</b> %s %s (x%d)\n"+
"진입가: <code>%.6g</code>\n"+
"TP: <code>%.6g</code> / SL: <code>%.6g</code>",
emoji, symbol, direction, leverage, entry, tp, sl)
t.sendAsync(msg)
}
// 청산 알림 — 손익 부호에 따라 이모지/기호를 바꿔 한눈에
func (t *Telegram) SendExit(symbol, direction string,
pnlUSD, pnlRatio float64, hold time.Duration, reason string) {
emoji, sign := "✅", "+"
if pnlUSD < 0 {
emoji, sign = "❌", ""
}
msg := fmt.Sprintf(
"%s <b>[청산]</b> %s %s\n"+
"손익: <code>%s%.4f USDT (%s%.2f%%)</code>\n"+
"사유: <code>%s</code>",
emoji, symbol, direction, sign, pnlUSD, sign, pnlRatio*100, reason)
t.sendAsync(msg)
}
func (t *Telegram) SendError(errMsg string) {
t.sendAsync(fmt.Sprintf("⚠️ <b>[에러]</b>\n<code>%s</code>", errMsg))
}
HTML 파싱 모드를 쓰면 <b>로 강조하고 <code>로 숫자를 고정폭으로 보여줄 수 있어, 폰에서 훨씬 읽기 좋습니다.
특히 중요한 알림 — "실패한 손절"
온체인/거래소 봇에서 가장 위험한 상황은 강제 청산이나 손절 주문이 실패했는데 조용한 것입니다. 포지션은 열려 있는데 안전장치가 안 걸린 상태죠. 이런 실패는 반드시, 즉시, 눈에 띄게 알려야 합니다.
if err := strat.Close(symbol, "손절"); err != nil {
// 청산 실패는 조용히 넘기면 안 된다 — 최우선 경보
tg.SendError(fmt.Sprintf("청산 실패! %s 수동 확인 필요: %v", symbol, err))
}
정리
- 텔레그램 알림은 SDK 없이 sendMessage POST 하나로 충분하다
- 토큰 미설정 시 로그로 폴백 — 알림은 봇의 생존 조건이 아니다
- 전송은 항상 비동기(goroutine) — 알림이 매매 루프를 막으면 안 된다
- 진입·청산·오류를 타입별로 만들고, 특히 청산/손절 실패는 최우선으로 알린다
좋은 알림 시스템은 "모든 걸 알리는" 게 아니라 "지금 봐야 할 것만" 알립니다. 그 경계를 잘 잡으면, 봇을 신뢰하고 손을 뗄 수 있게 됩니다.
이 글은 교육·정보 목적이며 투자 권유가 아닙니다. 자동매매에는 원금 손실 위험이 있고, 결과에 대한 책임은 본인에게 있습니다.
댓글
댓글 쓰기