Signalio — 한국 시장 정보 지도와 전략 검증 엔진 2026.09.21

지도가 자라기 시작했다 — vault 4단계, 공시 11만 건과 모닝·이브닝

VPS가 매시간 DART 공시를 받아 세 갈래로 나누고, 정형 3종은 바로 이벤트로 만든다. 9개월치 11만 건을 하루에 넣었고, 아침·저녁 두 판의 리포트가 나오기 시작했다. DART는 원문 1,000건에서 선을 끊었다.

2단계까지는 과거를 정리한 것이었다. 기업 4,104장, 인물 5만 장 — 전부 이미 있던 데이터로 만든 스냅샷이다. 4단계부터는 다르다. 오늘 접수된 공시가 오늘 밤 지도에 들어간다. 여기서부터 지도가 자란다.

우체국 창구

하루 377건이 온다. 전부 읽을 수는 없고, 전부 버릴 수도 없다. 그래서 창구를 하나 세웠다. 공시 제목(report_nm) 하나만 보고 네 갈래로 나눈다.

갈래뜻실측 (9/15~18, 1,455건)
structured파서가 있다 → 바로 이벤트30.0%
ledger유형은 안다, 파서는 없다 → 원장에 종류만47.7%
llm읽어야 판단한다 → 5단계2.9%
drop증권사 발행 서류, IR 안내19.5%

규칙 100여 개. 모르는 유형은 llm 으로 보낸다 — 조용히 버리지 않는다. 실측에서 미지는 0건이었다.

LLM 후보가 2.9%라는 게 중요하다. 지난주에 "하루 50~80건은 LLM이 읽어야 한다"고 추정했는데, 유형을 제대로 가르고 나니 하루 10건 남짓이다. 나머지는 ledger — "최대주주 변경", "자사주 취득", "유상증자"처럼 제목이 곧 사실인 것들이다. 파서를 붙이면 structured 로 올라온다.

정형 3종

소유상황보고와 5% 보고는 DART가 JSON으로 준다. 회사 코드 하나로 그 회사 전체 이력이 온다. 문제는 단일판매·공급계약이었다. 정형 API가 없다. 원문을 받아야 한다.

받아 보니 원문은 라벨-값 표였다. "계약금액 총액(원)" 다음 칸이 금액, "3. 계약상대방" 다음 칸이 상대방. 서식이 네 가지(코스닥·유가·해지·정정)라 라벨 표를 네 벌 만들었다. 정정 공시는 앞에 "정정 전/후" 표가 붙고 본문이 뒤에 오는데, 마지막 제목 줄부터 읽으면 정정 후 값이 나온다. 정정 관련 원 제출일도 같이 뽑아서 원본과 이을 수 있게 했다.

에스티팜 9/18 계약을 읽으면 이렇게 나온다.

계약금액 913억 · 매출대비 27.54% · 상대방 "미국 소재 글로벌 바이오텍" · 기간 2026-09-18~2027-11-22 · 유보사유 "비밀유지에 대한 거래 상대방의 요청" · 유보기한 2027-11-22

상대방이 비공개다. 한국 공시에서는 이 이름이 안 나온다. SEC 쪽에서 나올 것이다 — 그게 5단계의 글로벌 축이다.

스튜디오드래곤×Netflix 건은 금액이 "-"였다. 처음엔 파싱 실패로 찍혔다. 라벨은 있는데 값이 비공개인 것이다. 성공 기준을 "값이 있다"에서 "라벨이 있다"로 바꿨다. 대우건설·HDC의 공사 수주는 계약기간이 "-"다. 착공 후 49개월이라 날짜가 없다. 그것도 정상이다. 추정치는 넣지 않는다.

이름을 노드에 붙이기

9/18 하루 임원 보고자 67명 중 50명(75%)이 2단계의 강한 키(이름+출생년월)에 붙었다. 16명은 약한 노드(회사 안에만 유효), 1명은 동명이인이라 못 갈랐다. 기관 보고자는 기관 노트 키로 간다. 2단계 규칙 그대로다 — 회사를 넘어 이름만으로 합치지 않는다.

파이썬 규칙과 한 칸 다른 데가 생겼다. 4자 한글 이름은 파이썬이 무조건 사람으로 봤는데, "해성산업"이 사람이 됐다. PHP 쪽은 키워드(산업·전자·건설)가 있으면 기관으로 본다. 6단계에서 파이썬도 맞춘다.

백필: 9개월을 하루에

2026-01-01부터 오늘까지. 원장 적재는 5분이었다 — 111,192건, 177일. 3월 셋째 주가 7,656건으로 제일 많았다(정기주총 시즌).

파싱은 함정이 둘 있었다.

첫째, 주 단위로 나눠 파싱하면 회사별 상세 API를 주마다 다시 부른다. 같은 회사가 38번 호출된다. 원장을 먼저 전부 넣고 파싱은 한 배치로 바꿨다. 회사당 1콜, 7,204콜.

둘째, DART는 원문(document.xml)을 한 IP에서 연속 약 1,000건 받으면 연결을 끊는다. Connection reset. 257건이 그렇게 실패했고, 20분 뒤에도 안 풀렸다. 로컬 PC(다른 IP)에서는 됐다. 그래서 원문 호출에 간격(2초)과 배치 상한(한 실행 300건)을 넣고, 네트워크 실패는 "실패"가 아니라 "재시도"로 따로 표시하게 했다. 남은 2,314건은 로컬에서 파싱해 JSONL 로 올렸다 — 웹 쓰기 경로는 여전히 0이다, SSH 로 들여온다.

셋째는 함정이 아니라 DART의 구멍이다. 상세 API에 없는 접수번호가 522건(2%) 있다. 미래에셋증권 326행 중 7월 3일 보고가 없는 식이다. 실 API로 대조해 확인했다. 5단계에서 원문으로 메운다.

이 과정에서 백필 로그에 API 키가 URL째 찍힌 걸 발견했다. 로그와 DB 에서 지우고, HTTP 오류 메시지에서 키를 마스킹하도록 고쳤다. 사람이 안 보는 로그라도 비밀은 안 남긴다.

모닝과 이브닝

리포트를 두 판으로 나눴다. 유목민의 06:00 / 17:00 리듬을 참고했지만 역할을 갈랐다.

  • 모닝 07:30 — "오늘 볼 것." 어제 접수 통계, 임원 매수·매도, 5% 변동, 공급계약, 지배구조·자본 원장, 위험·시장조치, 새 일정, 페이퍼 진입·청산 예정
  • 이브닝 19:20 — "오늘 일어난 것 + 채점." 같은 항목에 당일 등락을 붙이고, 페이퍼 오늘 성적, 오늘 모닝에 실린 종목의 오늘 등락(채점), 지도 성장

채점 칸이 유목민에 없는 것이다. 하루 등락은 소음이라고 표에 적어 뒀다 — 임원 매수의 초과수익은 첫 주 0, 120일에 +5%p다. 기록은 하되 판정은 t+20/60/120 으로 한다.

리포트는 JSON 이 원본이고 마크다운은 렌더다. 영문판은 렌더 하나 더 붙이면 된다. 아카이브는 영구다. 유목민은 한 달 뒤 콘텐츠를 빼는데, 우리는 그 자리를 채운다.

쌓이는 곳

교수님이 원한 건 "착실히 상승 포텐셜을 쌓아온 종목"이다. 지난주 검증의 교훈은 드러난 뒤엔 늦다였다. 그래서 정의는 이렇다: 증거는 누적됐는데 가격은 아직 반응하지 않은 곳.

90일 임원 매수(건·날짜·인원), 180일 공급계약 매출대비 누적, 5% 증가, 자사주·소각·최대주주 변경을 점수로 합치고, 구간 수익률이 KODEX 를 5%p 이상 앞서거나 구간 고점이면 뺀다. 이건 규칙 v0 이고 검증 전이다. 7단계에서 이벤트 스터디에 넣는다. 그 전까지는 full 판(내부)에만 싣고 "사실의 집계"로만 적는다.

첫 실측에서 배운 게 있다. 에코프로비엠이 임원 매수 19건/19명으로 1위였다. 하루에 몰린 일괄 취득이다. 건수가 아니라 날짜 수로 세게 바꿨다.

숫자

원장111,192건 (2026-01-02 ~ 09-21)
이벤트29,517 — 임원 매수 13,996 · 매도 2,778 · 5% 9,250 · 공급계약 3,239 · 해지 73
분류정형 30.0% · 원장 47.7% · LLM 2.9% · 버림 19.5% · 미지 0
실패525 (DART 상세 API 누락 517 + 계약 서식 8)
임원 보고자 강한 키75%
테스트PHP 1,594 + 166 + 108, 파이썬 63
cron수집 07:20 + 08:10~18:10 매시, 모닝 07:30, 이브닝 19:20

첫 발행은 9/21 새벽 05:23 — 9/18 모닝·이브닝과 9/21 모닝 세 판을 수동으로 만들고 /app/report 에 올렸다. 로컬은 공개 API 로 29,517건을 41초에 끌어와 기업 노트 1,789장에 ## 이벤트 를 붙였다. 에스티팜 노트를 열면 9/18 계약 표가 있다. 두 번 돌리면 4,104장 전부 unchanged — 멱등이다.

다음은 7단계 — 「쌓이는 곳」을 이벤트 스터디로 죽이거나 살리는 것. 그 첫 결과는 다음 글에 있다: 유목민의 "살 만한 자리"를 임원 매수에 얹어 봤더니 깎였다.

Signalio — 한국 시장 정보 지도와 전략 검증 엔진 프로젝트로