주문 코드가 없습니다
이 저장소에는 매수·매도 주문 호출이 없습니다. DRY_RUN 은 메시지 머리표를 고르는 데만 쓰이고, README 도 지금은 알림 봇만 만들었다고 적습니다.
업비트에서 1시간 봉 30개와 현재가를 읽고, 최근 5개·20개 종가 평균과 RSI14 를 계산해 BUY·SELL·HOLD 를 정합니다. BUY 나 SELL 일 때만 텔레그램으로 마켓·가격·금액·이유를 보내고, HOLD 는 조용히 넘어갑니다. 주문을 내는 코드는 없습니다. DRY_RUN 은 기본 true 이고, 메시지 머리에 [DRY_RUN] 과 [LIVE] 중 무엇을 붙일지 정하는 데만 쓰입니다.
화면이 없는 제품이라, 이 페이지의 이미지는 전부 저장소의 실제 코드와 터미널 출력입니다. 거래소와 텔레그램에는 연결하지 않았고, 테스트의 가짜 시세로만 돌렸습니다.
![저장소 복사본에서 실제로 돌린 터미널 출력 — wc -l 로 센 464줄, strategy.py 전문, unittest 8개 통과, 가짜 시세로 엔진을 돌려 찍은 [DRY_RUN] KRW-BTC BUY·SELL 메시지와 HOLD](/assets/cases/upbit-bot/screen-tall.jpg)
run_bot.py 는 이 사이클을 기본 60초마다 되풀이합니다. 판단과 알림은 테스트가 덮고, 시세를 읽는 부분은 테스트 밖에 있습니다.
pyupbit 로 KRW-BTC 1시간 봉 30개와 현재가를 받아 최근 5개·20개 종가 평균과 RSI14 를 셉니다. 키가 있으면 원화와 코인 잔고도 읽습니다.
단기 평균이 장기 평균 위이고 RSI 가 30 이하이며 원화가 5,000원 이상이면 BUY 입니다. 하락 추세에 RSI 70 이상이고 코인이 있으면 SELL. 그 밖은 신뢰도 0.3 의 HOLD 입니다.
판단이 BUY 나 SELL 이면 [DRY_RUN] 머리표와 마켓·가격·금액·이유를 보냅니다. 금액은 TRADE_KRW 기본 10,000원을 적을 뿐, 주문은 어디에서도 나가지 않습니다.
전략 3개, 엔진 2개, 알림 1개, chat_id 2개입니다. 시세와 전송을 가짜 객체로 바꿔 네트워크 없이 돌고, 2026-09-25 에 돌려 8개 모두 통과했습니다.


![가짜 시세로 실제 엔진을 돌린 출력 — [DRY_RUN] KRW-BTC BUY 와 SELL 메시지 본문, HOLD 는 메시지 없음](/assets/cases/upbit-bot/step-3.jpg)

화면이 없으니 코드가 곧 표면입니다. 아래는 저장소 파일을 줄 번호 그대로 옮긴 것입니다.
기본값DRY_RUN=true, KRW-BTC, 60초, 10,000원. 키와 토큰은 환경변수로만 받습니다.
HOLD 는 조용히BUY·SELL 일 때만 알림 함수를 부릅니다. 대부분의 사이클은 아무 메시지도 보내지 않습니다.
chat_id 찾기봇에게 먼저 말을 건 뒤, getUpdates 응답에서 가장 최근 대화의 id 를 꺼냅니다. 없으면 NO_CHAT_ID 를 출력합니다.
빈 설정으로는 시작하지 않음텔레그램 토큰·chat_id·업비트 키 중 하나라도 비면 루프에 들어가기 전에 멈춥니다.
조건 하나라도 빠지면 HOLD 이고, HOLD 는 메시지를 만들지 않습니다.
이 저장소에는 매수·매도 주문 호출이 없습니다. DRY_RUN 은 메시지 머리표를 고르는 데만 쓰이고, README 도 지금은 알림 봇만 만들었다고 적습니다.
업비트 키와 봇 토큰은 .env 에서만 읽습니다. README 는 설치 안내보다 먼저, 토큰과 키를 README 나 채팅에 적지 말라고 경고합니다.
전략은 행동만이 아니라 신뢰도와 한국어 이유를 함께 돌려줍니다. 텔레그램 메시지의 마지막 줄이 그 이유입니다.
엔진은 시세 클라이언트와 알림을, 알림은 전송 함수를 주입받습니다. 그래서 테스트가 거래소나 텔레그램에 닿지 않습니다.
출력이 텔레그램 메시지 한 통이기 때문입니다. 사람이 보는 곳은 휴대폰 알림이고, 그 알림을 언제 보낼지가 설계의 대부분입니다. 그래서 HOLD 는 보내지 않고, BUY·SELL 만 네 줄로 보냅니다.
아닙니다. 이 저장소에는 주문을 내는 코드가 없습니다. DRY_RUN=false 로 바꾸면 메시지 머리표가 [LIVE] 로 바뀔 뿐입니다. 주문과 리스크 한도는 이후 StockAgent 에서 따로 만들었습니다.
지금은 루프가 멈춥니다. 알림 함수도, 엔진도, run_bot.py 도 전송 예외를 잡지 않기 때문입니다. 나중에 만든 StockAgent 의 알림 모듈은 이 점을 바꿔, 전송 실패가 매매 흐름을 막지 않도록 예외를 삼킵니다.
봇은 사람이 먼저 말을 걸기 전에는 그 사람에게 메시지를 보낼 수 없고, chat_id 도 그때 생깁니다. get_chat_id.py 는 getUpdates 응답에서 가장 최근 대화의 id 를 꺼내 출력하고, 없으면 NO_CHAT_ID 라고 적습니다.
전부 저장소의 실제 코드와, 2026-09-25 에 복사본에서 돌린 터미널 출력입니다. 거래소와 텔레그램에는 연결하지 않았습니다. 메시지 예시는 테스트 파일에 있는 가짜 시세 — BUY 는 1억 5천만원·RSI 28, SELL 은 가격 100·RSI 75 — 를 실제 엔진에 넣고, 전송 함수 대신 화면에 찍은 결과입니다.