SQLite 스키마 설계로 거래 기록 영속화하기 | 토스 개발기 #3-1

[토스 자동매매 개발기 시리즈] — 현재 글: 9번째 편 (전체 9편 중)

Phase 0부터 Phase 2까지 기반 시스템, 안정성 강화, 전략 모듈화를 순서대로 끝내고 나니 로드맵상 다음 순서는 데이터 영속성이었다. 그런데 이번 건 코드부터 짜지 않았다. 스코프가 이전 단계들보다 확실히 커서, 짜다가 갈아엎느니 설계부터 문서로 확정하고 넘어가기로 했다.

이번 세션에서 할 일을 먼저 잘라냈다

데이터 영속성 작업은 크게 5단계로 나뉜다. 스키마 설계 → db.py 구현 → auto_trader.py 연동 → toss_portfolio.py 연동 → 테스트 작성. 이번 세션에서는 1단계, 스키마 설계만 확정하기로 했다. 나머지는 다음 세션으로 넘겼다. 한 번에 다 밀어붙이면 중간에 뭐가 잘못됐는지 찾기 어려워지는 걸 이전 Phase들에서 이미 겪었다.

trades / alerts / price_snapshots 스키마

세 개의 테이블로 나눴다.

  • trades — 거래 이력. 종목·수량·가격 외에 is_dry_run(시뮬레이션 여부), strategy_name(어떤 전략이 신호를 냈는지) 컬럼을 넣었다.
  • alerts — 알림 이력. 메시지, 레벨, 관련 종목(nullable)을 기록한다.
  • price_snapshots — 시세 스냅샷. 해외주식 소수점 대응을 위해 가격 컬럼을 REAL 타입으로 잡고, 통화(KRW/USD) 컬럼을 별도로 뒀다. 예전에 해외 보유자산이 합계에서 빠지던 통화 버그를 겪은 이후로, 이 원칙(KRW/USD 분리)을 데이터 계층에도 그대로 가져왔다.

시계열 조회가 잦을 걸 감안해서 CREATE INDEX idx_price_symbol_ts ON price_snapshots(symbol, timestamp) 형태로 종목+시각 복합 인덱스도 미리 계획해뒀다.

가장 오래 고민한 결정 — 시뮬레이션 거래를 기록할 것인가

DRY_RUN 상태로 도는 시뮬레이션 매수를 거래 이력에 남길지 말지가 애매했다. 안 남기면 나중에 전략이 얼마나 자주 신호를 냈는지 알 수가 없고, 남기면 실전 거래와 헷갈릴 위험이 있었다. 결론은 “기록하되 is_dry_run으로 구분”이었다. 3중 안전장치 원칙과도 충돌하지 않는 선에서 정리됐다.

db.py 인터페이스도 이번에 같이 확정

스키마만 정하고 끝낸 게 아니라 db.py 모듈의 함수 시그니처까지 미리 잡아뒀다.

def get_connection():
    ...

def init_db():
    ...

def insert_trade(symbol, quantity, price, is_dry_run, strategy_name):
    ...

def insert_alert(message, level, symbol=None):
    ...

def insert_price_snapshot(symbol, price, currency, timestamp):
    ...

DB 기록이 실패해도 매매 루프 자체가 멈추면 안 되니까, 기존 텔레그램 알림 전송부와 똑같이 “최선 노력(best-effort)” 방식으로 처리하기로 했다. DB insert 실패는 로그만 남기고 넘어간다.

테스트는 실제 파일 대신 인메모리 DB로

테스트할 때마다 실제 sqlite 파일을 만들면 반복 실행할 때 파일이 오염될 수 있어서, sqlite3.connect(':memory:') 방식의 인메모리 DB로 검증하는 전략을 택했다. 다음 세션에서 pytest 테스트를 짤 때 이 방식을 그대로 쓸 예정이다.

다음 단계

이번 글은 설계까지만 다뤘다. 다음 세션에서는 이 설계 문서를 기준으로 db.py를 실제로 구현하고, auto_trader.py·toss_portfolio.py에 연동한 뒤 인메모리 DB로 테스트를 작성할 예정이다. 그 내용은 Phase 3-2로 따로 정리할 계획이다.

“SQLite 스키마 설계로 거래 기록 영속화하기 | 토스 개발기 #3-1”에 대한 5개의 생각

댓글 남기기