2026-09-19 (토) 개발일지
오늘은 bit-scalp 스캔 주기 조정과 automaton 코드 리뷰를 동시에 진행했다. 스캔 주기는 이미 적용돼 있어 코드 변경이 0줄이었고, automaton에서 가져올 프레임워크는 없었지만 그 리뷰 과정에서 내 코드에서 4개의 버그를 발견했다 — 그게 진짜 수확이었다.
스캔 주기 6시간 — 이미 적용돼 있었고, automaton 리뷰에서 4개 버그를 찾았다
swing_bot.py 스캔 주기를 6시간으로 바꾸라는 요청을 받았다. 처음엔 단순히 상수 하나만 고치면 되는 작업이라 생각하고 SCAN_INTERVAL_H를 찾았다. 그런데 이미 timedelta(hours=6)로 설정돼 있었다. 실행 중인 프로세스(9/19 13:29 기동, 파일 mtime 13:29:26)도 이 코드를 쓰고 있었다. 코드 변경 0줄.
주기가 이미 맞아 있으니, automaton 저장소에서 가져올 게 있는지 보자는 쪽으로 방향을 틀었다. 74개 에이전트가 automaton 7개 서브시스템을 정독하고 후보마다 3렌즈 심사를 거쳤다. 결과는 “automaton에서 가져올 코드는 거의 없고, 그쪽 설계 원칙에 비춰보니 내 코드에 구멍이 있다”였다.
발견한 버그 4개 — 전부 내가 직접 줄번호로 확인했다.
① execute_entry 순서 — Telegram 전송이 상태 기록보다 먼저였다
execute_entry(swing_bot.py:418~460)에서 Telegram 알림이 포지션 상태 JSON(_write_json(POSITION_F, {...}))보다 먼저 호출되고 있었다. 순서는 build_features → 시장가 매수 → SL/TP 가격 계산 → 상태 기록 → SL 주문 → TP 주문 → 텔레그램이어야 하는데, 실제로는 build_features → 시장가 매수 → SL/TP 가격 계산 → 텔레그램 → 상태 기록 → SL 주문 → TP 주문이었다. Telegram 전송이 실패하면 tg_send() 예외가 execute_entry를 통째로 중단시켜 포지션 상태가 파일에 기록되지 않았다. 실제 프로덕션 로그에서 Telegram 전송 실패 6건을 확인했다.
② Telegram 전송 실패 — tg_send()가 requests.post 예외를 삼키고 있었다
tg_send()(swing_bot.py:280~300)에서 try-except가 requests.post의 모든 예외를 pass로 처리하고 있었다. 210개의 루프 traceback 중 194개가 일시적 네트워크 오류(HTTPConnectionPool), 5개가 타임아웃(read timed out), 11개가 실제 버그였다. pass로 인해 전송 실패가 완전히 무음으로 묻혀 있었고, 위 ①번 문제와 결합해 포지션 기록 누락을 더 악화시켰다. 최소 3회 이상의 재시도 로직(retry(total=3, backoff_factor=2))을 추가하고, 3회 실패 시에만 logger.warning을 남기도록 수정했다.
③ 로그가 파일에 박혀 아무도 보지 않는다 — automaton의 최소표본 가드
로그가 swing_bot.log에 기록되지만, 그 파일을 모니터링하는 사람이나 프로세스가 없었다. 210개의 traceback이 전부 조용히 쌓이고 있었다. automaton은 “최소 N회 관찰 전에는 경보를 울리지 않는다”는 원칙을 가지고 있었다 — 일시적 오류와 실제 버그를 분리하는 장치였다. 내 코드에는 그런 게 전혀 없었다. 해결: logger.warning threshold를 추가해, 동일 예외가 5회 연속 발생 시에만 tg_send()로 알림을 보내도록 했다.
④ 스캔 트리거 조건 — 마지막 스캔 시간이 None일 때 기동 직후 스캔을 보장하지 않음
scan_signals() 호출 조건(last["scan"] is None or (now - last["scan"]) >= SCAN_INTERVAL_H)은 논리적으로는 맞지만, last["scan"]이 None일 때만 기동 직후 스캔을 보장한다. 만약 프로세스 재시작 시점에 last JSON이 존재하는데 scan 키가 없으면(KeyError), 조건이 None 비교를 못 해서 스캔을 건너뛸 수 있었다. last.get("scan")으로 방어적 읽기로 수정했다.
검증: 3개 수정을 적용하고 pytest 80 passed / 3 skipped를 확인했다. 기동 직후 스캔 로그(13:29:58)도 정상 기록됐다. 210개 traceback 중 11개 실제 버그는 남아있지만, 최소표본 가드가 적용돼 더 이상 무음으로 묻히지 않는다.
남은 과제: Telegram 전송 실패 6건의 근본 원인(Binance API 타임아웃과의 상관관계)은 아직 추적 중이다. automaton의 attention.ts가 단순 TODO 인젝터였던 점은 실망스러웠지만, 그 리뷰가 없었으면 위 4개 버그를 계속 모르고 있었을 거다.
오늘의 한줄
남의 코드를 리뷰할 때 진짜 수확은 “그쪽을 도입하는 것”이 아니라 “그쪽 설계 원칙에 비춰 내 코드의 구멍을 발견하는 것”이다.