상장폐지 예고나 지갑 점검에 따른 입출금 중단은 국내 가격이 해외보다 크게 벌어지는 이벤트를 자주 동반합니다. 판단에 필요한 것은 공지 한 건이 아니라 유형별 패턴(공지에서 효력까지 걸리는 시간, 중단 유지 기간, 그 사이 갭의 움직임)입니다. 거래소 공지는 형식이 제각각이고 재개 소식이 원래 공지를 고치는 방식으로 나오는 경우가 많아, 공지 아카이브를 모아 규칙으로 분류하고 수정까지 추적해 시각 축(공지 → 효력 → 해제)을 복원한 뒤 가격 데이터와 붙여 통계로 만듭니다.

Overview
Features
- 공지 수집과 수정 감지: 업비트 아카이브 백필과 주기 폴링, 빗썸 공식 API 신규 공지 수집, 끝나지 않은 중단 공지는 본문 해시로 수정 이력(revision)을 남기고 재파싱
- 규칙 기반 분류: 상장폐지, 입출금 중단, 투자유의 지정·해제, 해킹 사고, 토큰 스왑·메인넷 전환으로 분류하고 코인·효력·해제 시각과 중단 사유·범위를 추출, 확신할 수 없으면 unclassified로 검토 대기열
- 유형별 이력 통계: 공지→효력 리드타임과 중단 유지 기간의 분포(중앙값, 사분위, P90, 표본 수, 제외 사유)
- 이벤트 기간의 현선 갭: 진행 중인 이벤트의 코인만 국내 현물·원화-USDT·해외 선물 mark를 공개 API로 수집하고, 과거 이벤트는 캔들로 소급해 유형·사유별 플레이북 분포로 표시
- 조회 서버와 웹 화면: GET 전용 API와 활성 이벤트 보드·코인 타임라인·유형별 통계·분류 검토 대기 화면
Architecture

다이어그램 원문 (Mermaid)
flowchart TB
subgraph SRC["공개 무인증 endpoint"]
UN["업비트 공지 API"]
BN["빗썸 공식 공지 API"]
MK["시세 API<br/>업비트·빗썸 현물 · KRW-USDT<br/>Binance·Bybit 선물 mark"]
end
SNAP["사용자가 저장한<br/>빗썸 공지 스냅샷"]
subgraph COL["공지 수집 (폴러)"]
HTTP["HTTP 클라이언트<br/>요청 간 지연 · ETag · backoff"]
PARSE["거래소별 파서"]
IMP["오프라인 importer<br/>(네트워크 없음)"]
end
subgraph ANA["분석"]
CLS["규칙 분류기<br/>유형 · 코인 · 시각<br/>사유 · 중단 범위"]
MAN["수동 분류 CLI<br/>(append-only 이력)"]
end
NDB[("notice DB (SQLite)<br/>events · revisions<br/>source snapshots · analysis")]
subgraph MKT["가격 수집"]
FWD["전방 수집기<br/>활성 이벤트 코인만 구독"]
BF["과거 백필<br/>승인된 요청 manifest만"]
GAP["갭 계산 · 품질 판정<br/>dry-run → apply → publication"]
end
MDB[("market DB (SQLite)<br/>분 bar · gap point<br/>metric · playbook")]
subgraph SRV["조회 서버 (127.0.0.1, GET 전용)"]
API["FastAPI<br/>/api/v1 · /api/v2/gaps"]
UI["Jinja2 화면<br/>+ 갭 패널 JS (SVG)"]
end
UN --> HTTP
BN --> HTTP
HTTP --> PARSE --> NDB
SNAP --> IMP --> NDB
NDB --> CLS --> NDB
MAN --> NDB
NDB -- "이벤트 창" --> FWD
NDB -- "닫힌 이벤트 창" --> BF
MK --> FWD --> MDB
MK --> BF --> MDB
MDB --> GAP --> MDB
NDB -. "읽기 전용" .-> API
MDB -. "읽기 전용" .-> API
API --> UI
다이어그램 원문 (Mermaid)
flowchart LR
A["공지 게시<br/>announced_at"] --> B["효력<br/>effective_at<br/>(중단 시작·상폐일)"]
B --> C["해제<br/>lifted_at<br/>(재개)"]
R["원 공지 수정 감지<br/>→ revision 추가"] -. "재파싱" .-> C
C --> G["갭 창<br/>core = 효력~해제<br/>buffer = 종료 전후"]
G --> Q{"품질 판정<br/>core 커버리지<br/>종료 anchor 완전성"}
Q -- "통과" --> P["플레이북 통계"]
Q -- "미달" --> X["low_coverage로<br/>사유 보존"]Key decisions
모르면 모른다고 남긴다
규칙으로 확신할 수 없는 공지는 unclassified로 남기고, 파싱 실패는 기본값으로 뭉개지 않고 수집 기록의 카운터로 화면에 드러냅니다. 유형을 늘릴 때도 실측 표본을 세어 보고 독립 유형으로 승격하거나 보류했습니다.
차단을 우회하지 않고 범위를 조정했다
빗썸 공지 사이트는 브라우저가 아닌 클라이언트를 TLS 지문 수준에서 차단했습니다. 지문 위장은 수집 예의에 어긋난다고 보고 채택하지 않았고, 공식 API로 신규분만 받되 과거분은 사용자가 브라우저에서 저장한 응답을 네트워크 기능이 없는 importer로 적재하도록 나눴습니다.
갭 숫자는 정의와 품질 기준을 먼저 고정한 뒤 만든다
원화 현물은 업비트 KRW-USDT로 환산하고, 선물은 한 거래소의 mark를 기준으로 둡니다. 세 가격이 모두 같은 UTC 분 구간의 닫힌 캔들로 있을 때만 확정 포인트로 쓰고, 빈 구간은 채우지 않습니다. 커버리지 기준에 미달한 이벤트는 사유와 함께 따로 셉니다. 백필과 갭 반영은 dry-run 결과를 먼저 승인받은 뒤 실행하고 결과를 해시와 건수로 대조했습니다.
Security
- 완전 읽기 전용: 주문·자금 이동·인증이 필요한 거래소 API를 쓰지 않고, 저장소에 거래소 API 키가 없음
- 유일한 비밀은 선택 기능인 텔레그램 알림 토큰이며 .env로만 주입, 알림 기본값은 꺼짐
- 조회 서버는 GET 엔드포인트만(테스트로 OpenAPI 스키마 전체 검사), SQLite를 읽기 전용 URI로 열고 루프백에만 바인딩
- 원문과 분석의 분리: 수집 원문은 append-only 스냅샷으로 보존하고 재분류는 파생 필드만 갱신, 분석 근거는 감사 테이블에
- 수집 예의: 요청 간 지연, 조건부 요청(ETag), 지수 backoff, 429·Retry-After 준수, 거래소 한도보다 보수적인 자체 상한
Status
운영 중
공지 수집·분류·통계·조회 서버와 이벤트 기간 현선 갭 결합까지 마치고 개인 로컬에서 상시 가동 중입니다. 텔레그램 알림은 구현했지만 운영에서는 꺼 두었습니다.
Screens
Screens
