개발/주식 자동매매

주식 자동매매 봇 만들기 6편: 텔레그램 알림 봇 만들기와 한국투자증권 모의투자 연결

bboxnote 2026. 10. 15. 23:44

봇이 무슨 일을 했는지 매일 확인하려면 알림이 있어야 합니다. 이번 편에서는 텔레그램 봇을 만들어 연결하고, 드디어 한국투자증권 모의투자 계좌를 붙였습니다.

한눈에 보기

  • 텔레그램 알림에 필요한 건 봇 토큰과 채팅 ID 두 개뿐입니다.
  • getUpdates가 빈 배열([])이면 봇한테 아직 말을 안 건 겁니다. 아무 메시지나 보내고 새로고침하세요.
  • 토큰과 키는 모두 GitHub Secrets에 넣고 코드에는 남기지 않습니다.
  • 한국투자증권은 모의투자용 키가 따로 있습니다. 첫 주문은 실제 주문 없이 목록만 뽑아서 확인했습니다.

텔레그램 봇 만들기

1. 봇 토큰

  1. 텔레그램에서 @BotFather(파란 체크가 있는 공식 계정)를 찾습니다.
  2. /newbot을 보내고, 봇 이름과 봇 아이디를 정합니다. 아이디는 반드시 bot으로 끝나야 합니다.
  3. 123456789:AAH... 모양의 토큰이 옵니다.

2. 채팅 ID

  1. 만든 봇을 찾아 시작을 누르고 아무 말이나 보냅니다. 이걸 안 하면 봇이 먼저 말을 걸 수 없습니다.
  2. 브라우저에서 https://api.telegram.org/bot<토큰>/getUpdates를 엽니다.
  3. "chat":{"id":숫자에 있는 숫자가 채팅 ID입니다.

처음에는 {"ok":true,"result":[]}만 나왔습니다. 봇한테 메시지를 안 보내서였습니다. "헬로월드"를 보내고 새로고침하니 이렇게 채팅 ID가 보였습니다. (가린 부분이 채팅 ID와 이름입니다.)

토큰과 채팅 ID는 GitHub Secrets에만 넣고, 코드나 캡처에는 남기지 않는 게 좋습니다. 혹시 토큰이 밖에 보였다면 @BotFather에서 /mybots → 봇 선택 → API Token → Revoke current token으로 새로 받으면 됩니다. 채팅 ID는 바뀌지 않습니다.

두 값을 Secrets의 TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID에 넣고 돌리니 메시지가 오기 시작했습니다. 아침 브리핑, 가상매매 결과, 그리고 아래에서 연결할 모의투자 주문 목록입니다.

브리핑에는 그날 시장 분위기와 종목별 주요 뉴스, 악재로 빠진 종목과 제외 기간이 들어갑니다. 이날은 코스피·코스닥이 7%대 급락한 날이라 브리핑부터 꽤 살벌했습니다.

한국투자증권 모의투자 연결

한국투자증권 계좌는 휴대폰 앱으로 비대면 개설했습니다. 그다음 KIS Developers에서 모의투자용 앱키와 앱시크릿을 받았습니다. 모의투자 키와 실전 키는 서로 다르니 헷갈리지 않게 조심해야 합니다.

GitHub Secrets에는 이렇게 넣었습니다.

이름 값
KIS_APP_KEY 앱키
KIS_APP_SECRET 앱시크릿
KIS_ACCOUNT_NO 계좌번호 앞 8자리 (하이픈 없이)
KIS_ACCOUNT_PRODUCT 계좌번호 뒤 2자리 (보통 01)

여기까지 넣으면 저장소 Secrets 목록이 이렇게 됩니다. 이름만 보이고 값은 다시 볼 수 없습니다.

연결 확인은 잔고 조회 → 주문 목록만 뽑기(실제 주문 없음) 순서로 했습니다. 모의투자 잔고 10,000,000원이 제대로 읽혔고, 첫 주문 목록은 "KODEX 단기채권 60주"였습니다. 시장이 하락 추세라 채권부터 사는 겁니다. 위 텔레그램 캡처의 마지막 메시지(dry-run)가 이 결과입니다.

이제 봇은 가상 장부 대신 한국투자증권 모의투자 계좌에 평일 아침 9시 10분마다 실제로 주문을 넣습니다. 실전 계좌 전환은 제가 직접 승인할 때만 되도록 막아 뒀습니다.

코드로 보면

텔레그램 알림 코드는 이게 전부입니다. 토큰이나 채팅 ID가 없으면 화면에만 찍고 넘어가서, 내 PC에서 시험할 때도 그대로 쓸 수 있습니다. 알림이 실패해도 봇이 멈추지 않게 경고만 남깁니다.

def notify(text):
    print(text)
    token = os.getenv("TELEGRAM_BOT_TOKEN", "").strip()
    chat_id = os.getenv("TELEGRAM_CHAT_ID", "").strip()
    if not token or not chat_id:
        return
    try:
        res = requests.post(
            f"https://api.telegram.org/bot{token}/sendMessage",
            json={"chat_id": chat_id, "text": text},
            timeout=10,
        )
        res.raise_for_status()
    except requests.RequestException as e:
        log.warning("텔레그램 알림을 보내지 못했습니다: %s", e)

한국투자증권 API는 모의투자와 실전의 주소와 거래 코드(TR ID)가 다릅니다. 그래서 둘을 표로 묶어 두고, 설정값 KIS_ENV 하나로 고르게 했습니다. 그리고 실전 주문은 별도 설정(QP_ALLOW_REAL_ORDERS=yes)이 없으면 아예 막히게 했습니다. 실수로 실전 키를 넣어도 주문은 나가지 않습니다.

BASE_URLS = {
    "real": "https://openapi.koreainvestment.com:9443",
    "demo": "https://openapivts.koreainvestment.com:29443",
}
ORDER_TR_IDS = {
    ("real", "buy"): "TTTC0012U", ("real", "sell"): "TTTC0011U",
    ("demo", "buy"): "VTTC0012U", ("demo", "sell"): "VTTC0011U",
}


def place_order(self, symbol, side, quantity):
    if not self.settings.is_demo and not self.settings.allow_real_orders:
        raise BrokerError("실전 주문이 막혀 있습니다.")
    data = self.client.request(
        "POST",
        "/uapi/domestic-stock/v1/trading/order-cash",
        ORDER_TR_IDS[(self.settings.env, side)],
        body={
            **self._account_params(),        # 계좌번호는 Secrets 에서
            "PDNO": symbol,
            "ORD_DVSN": "01",                # 01 = 시장가
            "ORD_QTY": str(quantity),
            "ORD_UNPR": "0",
        },
    )
    return data["output"]["ODNO"]            # 주문번호

KIS 접근 토큰은 1분에 한 번만 발급되고, 발급할 때마다 휴대폰으로 알림이 옵니다. 매번 새로 받지 말고 파일에 저장해 두고 만료 전까지 재사용하는 게 좋습니다. 이 토큰 파일은 GitHub에 올라가지 않게 따로 빼 뒀습니다.

다음 편은 조금 다른 이야기입니다. "3년은 너무 길다, 단타로 짧게 먹고 나오면 안 되나?"를 직접 다 돌려 봤습니다.

주식 자동매매 봇 만들기