📚 토스 자동매매 개발기 시리즈 (전체 27편 중 Phase 7-9)
- Phase 0 — 프로젝트 기반 설계 (#0)
- Phase 0-1 — 통화 버그 수정 (#0-1)
- Phase 1 — 설정 분리 및 재시도 로직 (#3)
- Phase 1-2 — 설정/재시도 후속 작업 (#4)
- Phase 1-3 — pytest 테스트 도입 (#5)
- Phase 1-4 — 로그 로테이션 (#6)
- Phase 2-1 — 전략 클래스 설계 (#7)
- Phase 2-2 — 멀티 종목 모니터링 (#8)
- Phase 3-1 — 데이터 영속화 설계 (#9)
- Phase 3-2 — DB 모듈 구현 (#10)
- Phase 3-3 — DB 통합 (#11)
- Phase 2-3 — 전략 플러그인 인터페이스 (#12)
- Phase 4 — 백테스트 엔진 (#13)
- Phase 4 — 캔들 API 페이지네이션 (#14)
- Phase 5 — 읽기 전용 대시보드 (#15)
- Phase 5 — 텔레그램 제어 로직 (#16)
- Phase 5 — 텔레그램 제어 로직 후속 (#17)
- Phase 6 — 스케줄러 & 워치독 (#18)
- Phase 7 — 주문 한도 가드 (#19)
- Phase 7-2 — 손실 한도 자동정지 (#20)
- Phase 7-3 — 이중 채널 알림 (#21)
- Phase 7-4 — 보안 진단 (#22)
- Phase 7-5 — 익절/손절 로직 (#23)
- Phase 7-6 — 대시보드 알리미 보안 (#24)
- Phase 7-7 — 보유 주식 대시보드 (#25)
- Phase 7-8 — 원클릭 매도 조건 설정 (#26)
- Phase 7-9 — 킬스위치·통화 포맷터·로그 뷰어 (#27) ← 현재 글
⬅️ 이전 글: Phase 7-8 원클릭 매도 조건 설정
💡 이번 Phase 7-9 요약
- 목적: 실전 모의가동 중 드러난 대시보드 사용성·통화 표기 문제를 해결하고, 자산 요약 카드·웹 킬스위치·원클릭 매도 조건·로그 뷰어까지 붙여 콕핏 대시보드를 마무리했다.
- 핵심 포인트: KRW/USD를 분기해서 포맷팅하는 함수와, 요약 카드·보유 목록이 API를 각각 두 번 부르던 걸 한 번으로 합친 단일 호출 파이프라인.
- 검증: 실제 계좌 데이터 연동 확인 + pytest 단위 테스트 130개 전체 통과.
모의로 며칠 돌려보니까 대시보드가 기능적으로는 문제없는데 보기가 불편했다. 국내주식이랑 해외주식 손익이 같은 테이블에 소수점 다르게 막 섞여서 나오고, 뭔가 이상하다 싶으면 텔레그램 켜서 명령어 쳐야 되고… 그래서 이번 Phase는 “돌아가는 봇”에서 “보고 바로 조작할 수 있는 봇”으로 넘어가는 작업이었다.
자산 요약 카드와 웹 킬스위치
dashboard.py 최상단에 KRW 총 평가금액/손익/매수금액, USD 총 평가금액/손익/매수금액, 통합 누적 수익률, 5중 안전 브레이크 상태까지 4개 카드로 정리해서 올렸다. 예전엔 이걸 보려면 로그를 스크롤해서 찾아야 됐는데 이제 새로고침 한 번이면 끝난다.
우측 상단엔 🟢 실시간 감시중 / 🔴 일시정지됨 뱃지를 두고, POST /api/engine/pause랑 POST /api/engine/resume를 붙여서 버튼 하나로 감시를 멈추고 재개할 수 있게 했다. 텔레그램이 응답 안 하거나 폰이 없을 때도 브라우저만 열면 바로 정지시킬 수 있다는 게 생각보다 심리적으로 크더라.
보유 주식 원클릭 매도 조건
보유 종목 각 행에 inline form을 넣어서, 목표가 입력하고 버튼 누르면 바로 strategy_configs 테이블에 매도 조건이 등록되게 했다. 여기에 side 값으로 매수 전용(BUY) / 매도 전용(SELL) / 매수+매도(BOTH)를 독립적으로 고를 수 있게 분리한 게 이번 작업의 핵심이었다. 예전엔 하나의 전략이 매수·매도를 같이 물고 있어서 “이 종목은 지금 갖고 있으니 매도만 감시하고 싶다”는 요구를 처리 못 했는데, 이제는 종목별로 따로 켤 수 있다.
def format_currency_price(amount: float, currency: str) -> str:
if currency == "KRW":
sign = "+" if amount > 0 else ("-" if amount < 0 else "")
return f"{sign}₩{abs(round(amount)):,}"
else: # USD
sign = "+" if amount > 0 else ("-" if amount < 0 else "")
return f"{sign}${abs(amount):,.2f}"
통화별(KRW/USD) 정밀 포맷터
국내주식(예: 삼성전자 005930)은 ₩8,969, -₩33,166처럼 정수 반올림 + 부호로, 해외주식(예: SOXL)은 $144.95, +$5.20처럼 소수점 둘째 자리까지 나오게 format_currency_price, format_profit_amount, format_target_price_condition 세 함수로 분리했다. 목표가 조건도 같은 규칙으로 7,759원 이상 / $152.00 이상처럼 통화에 맞춰 자동으로 붙는다.
종목명도 db.get_display_name을 거쳐서 모든 테이블에 삼성전자 (005930), SOXL (SOXL) 형태로 통일했다. 국내/해외가 섞인 리스트에서 어느 게 뭔지 한눈에 안 들어오던 문제가 이걸로 해결됐다.
API 단일 호출 파이프라인 최적화
원래 요약 카드랑 보유 종목 테이블이 각각 토스 API를 호출하고 있었다. 같은 데이터를 1초 사이에 두 번 부르다 보니 가끔 두 번째 호출에서 데이터가 비어서 오는 경우가 있었고, 그게 “왜 갑자기 보유 종목이 하나 빠졌지” 하는 간헐적 버그로 이어졌다.
_fetch_holdings_and_summary 하나로 합쳐서 API를 한 번만 부르고, 그 결과를 요약 카드와 보유 목록 양쪽에 재사용하도록 리팩터링했다. 결과적으로 로딩 속도가 체감상 2배 정도 빨라졌고, 데이터 누락 버그는 재현이 안 된다.
다크테마 로그 뷰어와 원터치 구동
대시보드 맨 아래에 toss_trader.log의 최신 40줄을 다크테마 콘솔 박스로 실시간 출력하는 Live Terminal Box를 붙였다. VSCode 켜고 터미널 열 필요 없이 브라우저에서 바로 로그를 볼 수 있다.
여기에 start_all_daemons.bat(4개 서비스 일괄 백그라운드 실행)과 stop_all_daemons.bat(정밀 일괄 종료)을 만들어서, Windows에서 더블클릭 한 번으로 봇 전체를 켜고 끌 수 있게 했다.
요약: 이번 Phase에서는 자산 요약 카드, 웹 킬스위치, 원클릭 매도 조건, KRW/USD 통화 포맷터, API 단일 호출 최적화, 다크테마 로그 뷰어를 대시보드에 통합했다.
검증: pytest 130개 통과
통화 포맷터 분기 로직, 킬스위치 API, 매도 조건 등록, API 단일화 파이프라인까지 포함해서 pytest 단위 테스트 130개를 전부 통과시켰다. 실제 계좌로도 며칠 돌려서 요약 카드 숫자랑 실제 잔고가 맞는지 대조했다.
다음 단계
이제 “보는 대시보드”는 얼추 완성됐고, 다음은 여러 전략을 동시에 돌릴 때 우선순위를 어떻게 관리할지 정리할 차례다.
FAQ
Q1. 웹 킬스위치는 텔레그램 명령어랑 뭐가 다른가요?
A. 기능은 같지만(감시 정지/재개) 텔레그램 봇 응답이 늦거나 폰이 없을 때도 브라우저만 있으면 바로 조작할 수 있다는 게 차이다.
Q2. KRW/USD 포맷터를 따로 만든 이유가 뭔가요?
A. 국내주식은 원 단위 반올림이 자연스럽고 해외주식은 센트 단위 소수점이 필요해서, 하나의 함수로 처리하면 어느 한쪽이 어색해지기 때문이다.
Q3. API 호출을 하나로 합치면 어떤 게 좋아지나요?
A. 같은 데이터를 두 번 부르지 않으니 속도가 빨라지고, 두 호출 사이 타이밍 차이로 생기던 데이터 누락 버그가 사라진다.
Q4. 로그 뷰어는 어떤 방식으로 동작하나요?
A. 서버가 toss_trader.log 파일의 최신 40줄을 읽어서 대시보드 페이지에 다크테마 콘솔 박스로 뿌려주는 방식이라 별도 터미널 접속이 필요 없다.