Wed 08-12 · 오늘 5건 · 통산 1,432호
falette
스크랩 쪽지 로그인
업비트 자동매매 봇 만들기
 

⑦ 업비트 자동매매 봇 만들기 — 명령 파일은 넉 달째 빈 채로 있다

사진: Sebastian Schuster (Unsplash)

이 봇에는 웹에서 누르는 버튼이 일곱 개 있다. 일시정지, 재개, 종료, 수동 매수, 수동 매도, 와치독 시작, 와치독 정지. 전부 실제로 동작한다 — 버그를 두 번 고쳐가며 지금 모습으로 다듬었다.

그런데 로그를 열어 넉 달치를 다 뒤져봐도, 이 일곱 개 중 어느 것도 눌린 기록이 없었다.

구조 — 웹은 파일에 쓰고, 봇은 파일을 읽는다

이 봇을 웹에서 제어하는 방식에는 큐도, 소켓도, 웹훅도 없다. 있는 건 파일 하나다. 관리자 화면에서 "일시정지"를 누르면 PHP가 {"action":"pause",...} 같은 JSON을 파일 하나에 그대로 써넣는다. 봇은 자기 루프를 돌 때마다 그 파일을 열어 읽고, 처리한 뒤 다시 빈 값으로 덮어써서 지운다. 지금 이 순간 그 파일의 실제 내용은 {} — 비어 있다.

봇의 루프 주기는 설정값으로 조절되는데, 지금은 30초다. 즉 버튼을 누른 시점과 실제로 반영되는 시점 사이에는 최대 30초, 평균으로 보면 15초 정도의 지연이 구조적으로 있다. 화면에는 "명령 전송 완료"가 즉시 뜨지만, 그건 파일 쓰기가 끝났다는 뜻이지 봇이 그 명령을 처리했다는 뜻이 아니다.

다만 모든 제어가 이 파일을 거치는 건 아니다. 전략 파라미터(RSI 기간, 손절폭 같은 것)를 바꾸는 "설정 저장"은 이 명령 파일을 쓰지 않는다. 설정 파일 자체를 통째로 덮어쓰고, 봇은 매 루프 그 설정 파일을 처음부터 다시 읽는다. 결과적으로 두 기능은 같은 화면 안에 있지만 완전히 다른 경로를 탄다 — 하나는 이벤트(명령 파일), 하나는 상태(설정 파일 통째 재로딩)다.

명령 파일을 거치는 일곱 개 동작

  1. 일시정지 / 재개 — 매매 루프는 계속 돌지만 신규 진입만 멈추거나 다시 연다.
  2. 종료 — 프로세스를 정상 종료한다. 와치독이 살아 있으면 다시 띄운다.
  3. 수동 매수 / 매도 — 지정한 종목을 즉시 시장가·지정가로 주문한다.
  4. 와치독 시작 / 정지 — 감시 스크립트 자체를 웹에서 켜고 끈다.

두 번 고친 이유

이 파일 기반 구조가 처음부터 지금 모습이었던 건 아니다. 기록에 남은 것만 두 번, 실제로 사고가 날 뻔한 뒤에 고쳤다.

재시작하면 오래된 명령이 그대로 실행됐다

가장 위험했던 버전은 이랬다. 봇이 꺼져 있는 동안 웹에서 수동 매수를 눌러도 화면에는 "명령 전송 완료"가 뜬다. 명령은 파일에 그대로 쌓인 채 기다린다. 그리고 나중에 봇을 재시작하면, 그 파일에 남아 있던 오래된 매수 명령을 시각 확인 없이 그대로 실행해버렸다. 며칠 전에 누른 버튼이 재시작 순간에 뒤늦게 발사되는 구조였다.

고친 방식은 명령에 타임스탬프를 실어 보내고, 봇이 그 명령을 집어들 때 60초가 넘게 지났으면 실행하지 않고 경고 로그만 남긴 뒤 버리는 것이었다. 동시에 봇이 꺼져 있는 상태에서는 애초에 "명령 전송 완료"라고 답하지 않도록 화면 쪽도 같이 고쳤다. 이중으로 막아야 안심할 수 있는 실패였다.

파일 권한을 낮췄다가 명령이 무한 반복됐다

다른 한 번은 방향이 반대였다. 서버 전체 파일 권한을 정리하면서 이 명령 파일 권한도 습관적으로 낮췄더니, 봇이 처리를 끝내고 파일을 비우는 동작 자체가 권한 부족으로 실패했다. 파일에 명령이 그대로 남아 있으니 다음 루프에서 또 읽고, 또 처리하고, 또 못 지운다. 매수 명령 하나가 루프가 도는 대로 계속 반복 실행될 수 있는 상태였다.

이 파일은 웹 서버 쪽 계정이 쓰고 봇 프로세스 쪽 계정이 지운다 — 소유자가 서로 다른 두 프로세스가 같은 파일을 주고받는 구조라, 권한을 한쪽 기준으로만 좁히면 반드시 다른 쪽이 막힌다. 그래서 이 파일 하나만 권한을 낮추지 않기로 하고, 나머지 설정 파일들만 따로 좁혔다. 전체를 한 번에 손대는 대신 파일마다 이유를 남겨두는 쪽을 택한 셈이다.

그래서 실제로 몇 번 썼나

구조도 두 번 고쳤고, 버그도 실사고로 이어지기 전에 잡았다. 그러니 이 기능은 잘 만들어졌다고 해도 될 것 같았다. 그런데 정작 "그래서 몇 번 썼는지"는 한 번도 확인해본 적이 없었다. 이번에 넉 달치 로그와 거래 기록을 전부 열어서 세어봤다.

집계 기간 2026-04-08 ~ 2026-08-12(약 126일), 출처: coin_bot 일별 로그 전량 · coin_trades.db · coin_web_control.log. 로그에 남는 문자열을 그대로 검색한 결과이며 추정치 없음.
동작실사용 횟수
일시정지 / 재개0건
종료0건
수동 매수 / 매도0건
와치독 시작 / 정지0건
(대조군) 전략 설정 변경사용 흔적 있음

정확히 하자면, 거래 기록에 사유가 "수동"이라고 적힌 행이 딱 하나 있었다. 그런데 그 사유 문구가 코드가 실제로 남기는 문자열과 달랐다 — 자세히 보니 예전에 데이터를 옮기면서 수기로 채워 넣은 백필 기록이었고, 실제로 이 명령 파일을 거쳐 나간 주문이 아니었다. 와치독 시작/정지는 그보다 더 확실하다. 이 두 동작만 별도로 기록되는 로그 파일이 있는데, 그 파일 자체가 서버에 존재하지 않는다. 한 번도 만들어진 적이 없다는 뜻이다.

반대로 대조군으로 넣은 "전략 설정 변경"은 다른 경로(명령 파일이 아니라 설정 파일 통째 교체)를 쓰는데, 이건 백업 스냅샷들이 여러 시점에 남아 있어 실제로 쓰였다는 걸 알 수 있었다. 즉 이 화면에서 진짜로 쓰인 건 파라미터를 조정하는 기능 하나뿐이었고, 봇을 직접 세우고 켜고 사람 손으로 주문을 넣는 일곱 개 동작은 만들어놓고 한 번도 손대지 않은 채 넉 달을 보냈다.

왜 이런 걸 만들어놓고 안 썼을까

돌이켜보면 이유는 단순하다. 이 봇이 "생각대로 안 움직인다"고 느낀 순간에는 대개 파라미터가 문제였다. 그럴 때 손이 먼저 가는 건 일시정지 버튼이 아니라 설정 화면이었다 — 조건을 좁히거나, 손절폭을 당기거나. 봇 자체를 멈추거나 사람이 직접 주문을 넣어야 할 만큼 급한 상황은 넉 달 동안 한 번도 오지 않았다. 종료·수동매매·와치독 제어는 비상 상황을 가정하고 만든 기능인데, 비상 상황이 없었으니 안 쓰인 것도 당연하다면 당연하다.

문제는 그 "당연함"을 이번에 로그를 세어보기 전까지는 나 스스로도 몰랐다는 것이다. ⑤편에서 평문 API 키를 넉 달 동안 못 보고 지나친 것과 같은 자리에서 나온 문제다. 만들어놓고 확인을 안 했다. 이번엔 보안이 아니라 "이 코드가 정말 쓰이고 있는가"라는, 훨씬 기본적인 질문이었을 뿐이다.

이번 편에서 배운 것

⑥편 결산에서 지금까지 다룬 다섯 편의 문제를 표로 정리했을 때, 이 명령 파일 이야기는 그 표에 없었다. 매수 관문도, 손절 구조도, 국면 분류도, API 키도 전부 실제 거래에 흔적을 남겼지만, 이 기능은 실행 기록이 없어서 애초에 결산할 데이터 자체가 없었다. 결산에서 빠졌다는 사실 자체가 이번 편의 발견이었던 셈이다.

비상 스위치는 안 눌리는 게 정상이다. 화재경보기도 안 울리는 게 좋은 상태다. 다만 그 스위치가 정말 작동하는지는 안 눌러보면 알 수 없다는 게 다르다. 이 봇의 경우는 코드를 두 번 고치면서 실제로 눌러본 셈이니 그나마 나은 경우다. 그래도 "만들어놓고 4개월 동안 존재를 잊고 있었다"는 사실은 남는다.

정리

이 편을 준비하면서 확인한 건 셋이다. 명령은 파일 하나로 오가고 최대 30초 지연이 있다는 것, 그 파일을 둘러싼 버그를 두 번 고쳤다는 것, 그리고 그렇게 다듬어놓은 기능을 넉 달 동안 실제로는 한 번도 쓰지 않았다는 것. 세 번째를 확인하는 데 걸린 시간이 앞의 두 개를 고치는 데 걸린 시간보다 짧았다. 로그는 이미 다 있었고, 세어보기만 하면 됐다.

댓글 0
첫 번째 댓글을 남겨보세요
댓글 작성
LOGIN