온체인 봇을 위한 텔레그램 알림 시스템 설계

🌐 English

자동매매 봇은 대개 화면 없이, 최소화된 콘솔에서 조용히 돕니다. 문제는 무슨 일이 일어나는지 실시간으로 알 수 없다는 것. 진입했는지, 손절 맞았는지, 주문이 실패했는지를 매번 로그 파일을 열어 확인할 수는 없습니다. 텔레그램 알림은 이 문제를 가장 적은 비용으로 해결합니다. 봇이 폰으로 직접 말을 걸게 만드는 거죠.

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) — 알림이 매매 루프를 막으면 안 된다
  • 진입·청산·오류를 타입별로 만들고, 특히 청산/손절 실패는 최우선으로 알린다

좋은 알림 시스템은 "모든 걸 알리는" 게 아니라 "지금 봐야 할 것만" 알립니다. 그 경계를 잘 잡으면, 봇을 신뢰하고 손을 뗄 수 있게 됩니다.

이 글은 교육·정보 목적이며 투자 권유가 아닙니다. 자동매매에는 원금 손실 위험이 있고, 결과에 대한 책임은 본인에게 있습니다.

댓글

이 블로그의 인기 게시물

한국투자증권 KIS API로 실시간 시세 받기 (WebSocket 실전)

파이썬으로 업비트 API 연동하기 — 시세 조회부터 주문까지 기초

Go로 자동매매 신호봇 프레임워크 설계하기