8-K 긴급 공시
실시간 모니터링
07_secrag(SEC 공시 RAG)의 심화편이다. 이 실습이 끝나면 로컬 PC를 꺼도 24시간 도는 모니터링 시스템이 생긴다 — HuggingFace Space가 10분마다 SEC RSS를 폴링해 새 8-K를 감지하고, Supabase에 영구 저장하고, LLM으로 자동 분석한 뒤 텔레그램으로 알림까지 보낸다.
로컬 스크립트를 온라인 서비스로 바꾼다
지금까지의 실습은 내 PC에서 실행하는 순간에만 동작했다. 이번에는 클라우드에 상주하며 스스로 도는 시스템을 만든다.
전체 구조: SEC RSS → HF Space → Supabase → 대시보드
파이프라인의 몸통은 HuggingFace Space다. Space 안에서 스케줄러가 10분마다 SEC EDGAR RSS를 확인하고, 새 8-K를 감지하면 파싱해서 Supabase에 저장한다. Gradio 대시보드는 그 데이터를 실시간으로 보여준다.
8-K란 무엇인가
8-K는 미국 상장사가 중요한 사건이 발생했을 때 4영업일 내에 제출해야 하는 긴급 공시다. 분기마다 나오는 정기 보고서(10-Q, 10-K)와 달리, 시장을 움직일 만한 일이 생기면 즉시 나온다.
NVIDIA 기준으로 실적 발표, 임원 변경, 대규모 계약, 주식 환매 같은 사건이 8-K로 공시된다. 그래서 8-K를 실시간으로 감지하는 것은 투자 정보 시스템의 기본기다 — 07_secrag에서 만든 RAG 도구가 과거 공시를 "검색"했다면, 이번 시스템은 새 공시를 "감시"한다.
계정 두 개, 패키지 설치, 테이블 하나
준비물은 세 가지다. HuggingFace 계정(Space 배포용), Supabase 계정(DB용, 무료 플랜으로 충분), 그리고 로컬 개발용 패키지.
pip install gradio apscheduler supabase requests beautifulsoup4 -q
Supabase 테이블 생성
Supabase 프로젝트를 만든 뒤 SQL Editor에서 아래를 실행해 filings 테이블을 만든다.
create table filings ( id bigserial primary key, ticker text default 'NVDA', filing_type text, filed_at date, title text, content text, sec_url text unique, analysis text, created_at timestamptz default now() );
sec_url에 걸린 unique 제약이 이 시스템의 중복 방지 장치다. 같은 공시를 다시 저장하려 하면 DB가 거부하므로, 폴링을 아무리 반복해도 데이터가 두 번 쌓이지 않는다.
확인할 값
- Project URL — 설정 → API에서 확인
- anon key — 설정 → API에서 확인
이 두 값은 Step 1의 코드와 Step 3의 Space Secrets에서 사용한다.
8-K 감지 & Supabase 저장
RSS를 파싱해 새 공시를 찾고 DB에 넣는 핵심 모듈을 만든다. 이 함수 하나가 시스템 전체의 심장이 된다.
check_and_store() — 감지에서 저장까지 한 함수로
SEC EDGAR는 회사별 공시를 RSS(atom) 피드로 공개한다. 새 공시가 나오면 피드에 항목이 추가되므로, 주기적으로 확인해서 새 항목만 Supabase에 저장하면 된다. 중복은 sec_url unique 제약이 자동으로 막아 준다.
NVIDIA 8-K 전용 RSS 주소는 다음과 같다 (CIK 0001045810이 NVIDIA다).
https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany&CIK=0001045810&type=8-K&dateb=&owner=include&count=10&output=atom
SEC EDGAR RSS 피드로 NVIDIA 8-K 공시를 감지해 Supabase에 저장하는 모듈을 만들어줘. - RSS URL: (위 atom URL) - requests + xml.etree.ElementTree로 RSS 파싱 (beautifulsoup4 금지) - 각 항목에서 추출: title, filed_at, sec_url, 본문 텍스트(링크 따라가서 파싱) - Supabase 저장: SUPABASE_URL/KEY, 테이블 filings, sec_url 중복 시 skip (upsert) - 함수 check_and_store() 하나로 캡슐화 - 실행 시 "신규 X건 저장, Y건 중복 스킵" 출력 파일명: monitor_core.py
확인 포인트
- monitor_core.py를 실행하면 "신규 X건 저장, Y건 중복 스킵"이 출력된다
- Supabase Table Editor → filings 테이블에 행이 쌓여 있다
- 한 번 더 실행하면 전부 "중복 스킵"으로 처리된다 (unique 제약 동작 확인)
filings에 행이 쌓이는 것을 확인했다면 감지·저장 로직은 완성이다. 그런데 이 스크립트는 내가 실행하는 순간에만 동작한다. 새 공시가 새벽에 나오면? 수동 실행은 의미가 없다. 다음 단계에서 자동 주기 실행으로 넘어가는 이유다.
백그라운드 스케줄러 + Gradio 대시보드
APScheduler가 10분마다 check_and_store()를 자동 호출하고, Gradio 화면에서 감지 현황을 실시간으로 본다.
앱이 떠 있는 동안, 10분마다 스스로 돈다
APScheduler의 BackgroundScheduler는 Gradio 앱이 실행 중인 동안 별도 스레드에서 지정한 주기로 함수를 호출한다. 앱을 띄워 두기만 하면 check_and_store()가 10분마다 자동으로 실행되는 구조다.
monitor_core.py를 import해서 Gradio 모니터링 대시보드를 만들어줘. - APScheduler BackgroundScheduler로 10분마다 check_and_store() 실행 - UI: 상단 "마지막 체크: [시각]" + [지금 체크] 버튼 / 중간 filings 최근 20건 gr.Dataframe(날짜, 제목, 공시유형, URL) / 하단 행 선택 시 본문 gr.Textbox - [지금 체크] 클릭 시 즉시 실행 후 테이블 갱신, 앱 시작 시 스케줄러 자동 시작 - SUPABASE_URL, SUPABASE_KEY는 환경변수(os.environ)에서 로드 파일명: app.py
확인 포인트
- 앱 시작과 동시에 스케줄러가 자동으로 시작된다
- [지금 체크] 버튼을 누르면 즉시 실행되고 테이블이 갱신된다
- 최근 20건이 Dataframe에 표시되고, 행을 선택하면 본문이 보인다
Supabase URL과 Key를 코드에 직접 쓰지 않고 환경변수에서 읽게 한 이유가 있다. 다음 단계에서 이 코드를 HuggingFace에 그대로 업로드하는데, 코드에 키가 박혀 있으면 공개 저장소에 비밀값이 노출된다. 환경변수 방식이면 Space Secrets에 등록하는 것만으로 안전하게 배포된다.
HuggingFace Space 배포
로컬에서만 돌던 앱을 클라우드에 올린다. 이 단계가 끝나면 내 PC를 꺼도 시스템이 돈다.
무료로 24시간 호스팅되는 Gradio 앱
HuggingFace Spaces는 Gradio 앱을 무료로 24시간 호스팅한다. 로컬 PC를 꺼도 Space 위의 앱과 스케줄러는 계속 실행된다. Supabase URL/Key는 코드가 아니라 Space Secrets에 등록해 노출을 막는다.
업로드할 파일 구성
my-8k-monitor/ ├── app.py ├── monitor_core.py └── requirements.txt (gradio, apscheduler, supabase, requests, beautifulsoup4)
배포 절차
- HuggingFace에서 New Space 생성 (SDK: Gradio)
- Settings → Secrets에 SUPABASE_URL, SUPABASE_KEY 등록
- Files 탭에 위 3개 파일 업로드
- 자동 빌드가 끝나면 Space URL이 생성된다
무료 플랜의 Space는 일정 시간 사용(방문)이 없으면 슬립 모드로 들어가 스케줄러도 함께 멈춘다. 정기적으로 방문하거나, 끊김 없는 상시 실행이 필요하면 유료 플랜을 쓴다.
확인 포인트
- Space URL로 접속하면 대시보드가 뜬다
- 로컬 PC를 꺼도 Supabase에 데이터가 계속 쌓이면 성공
신규 8-K 즉시 RAG 분석 연결
새 공시를 저장만 하지 않고, 감지 즉시 LLM이 분석해서 결과를 함께 저장한다. 대시보드에서 바로 확인한다.
감지 → 자동 LLM 분석 → analysis 컬럼 저장
새 8-K가 감지되면 HF Inference API로 즉시 분석을 돌리고, 결과를 filings 테이블의 analysis 컬럼에 저장한다. 대시보드는 분석 완료 여부를 표시하고, 행을 선택하면 원문과 AI 분석을 나란히 보여준다.
07_secrag에서 만든 SEC RAG 도구의 벡터 DB는 히스토리 검색용이고, 신규 8-K 분석은 여기서 별도 처리한다는 점을 구분해 두자.
요청 1 — monitor_core.py 수정
check_and_store()에 자동 RAG 분석 기능을 추가해줘.
- 새 8-K 저장 직후 HF Inference API(Qwen/Qwen3-8B, HF_TOKEN 환경변수) 분석
- 분석 프롬프트: "다음 NVIDIA 8-K 공시를 분석하시오. 1. 공시 핵심 내용 (2~3줄) 2. 투자자 관점에서 중요한 이유 3. 긍정/부정/중립 중 시장 영향 예상. 한국어로 답변. [공시 내용]: {content}"
- 결과를 analysis 컬럼에 업데이트, 실패 시 "분석 실패" 저장 후 계속 진행
요청 2 — app.py 수정
대시보드에 분석 결과 표시 기능을 추가해줘. - Dataframe에 analysis 컬럼 (있으면 ✅, 없으면 ⏳) - 행 선택 시 왼쪽 8-K 원문 / 오른쪽 AI 분석(gr.Markdown) - 분석 없는 항목 선택 시 [지금 분석] 버튼 → 즉시 분석
확인 포인트
- 새 공시가 저장되면 analysis 컬럼에 분석 결과가 함께 채워진다
- 대시보드 Dataframe에 분석 완료 ✅ / 대기 ⏳ 표시가 보인다
- 행 선택 시 왼쪽 원문, 오른쪽 AI 분석이 나란히 표시된다
- 분석이 없는 항목은 [지금 분석] 버튼으로 즉시 분석된다
분석 실패 시 예외를 던지지 않고 "분석 실패"를 저장한 뒤 계속 진행하게 한 이유를 생각해 보자. 이 시스템의 1순위 임무는 감지·저장이고, 분석은 부가 기능이다. LLM API가 잠시 죽었다고 모니터링 전체가 멈추면 안 된다 — 자동화 시스템 설계에서 "부가 기능의 실패가 핵심 기능을 막지 않게" 하는 것이 기본 원칙이다.
텔레그램 알림 연결
대시보드를 열어 보지 않아도 새 8-K가 나오면 폰으로 즉시 알림이 온다. 그룹 채팅에 연결하면 팀 공유도 된다.
새 8-K 감지 → 텔레그램 즉시 발송
텔레그램 봇 API는 토큰과 chat_id만 있으면 HTTP POST 한 번으로 메시지를 보낼 수 있다. check_and_store()가 신규 공시를 저장한 직후 알림을 발송하게 연결한다.
발송할 메시지 구성: "📡 NVIDIA 새 8-K 공시 / 제목 / 날짜 / 분석(AI 요약 2~3줄) / 원문 URL"
봇 만들기 (1회)
@BotFather에서 /newbot → 봇 이름·계정명(…bot) 입력 → 토큰 발급 → 봇에게 /start를 보낸 뒤 https://api.telegram.org/bot[토큰]/getUpdates에서 message.chat.id를 확인한다. 그룹에 연결하려면 봇을 그룹에 초대한 뒤 같은 방법으로 확인하며, 그룹의 chat.id는 음수다. (막히면 아래 해설의 상세 가이드를 펼쳐서 따라 한다.)
monitor_core.py에 텔레그램 알림 기능을 추가해줘.
- 환경변수 TELEGRAM_TOKEN, TELEGRAM_CHAT_ID
- 함수 send_telegram(title, filed_at, analysis, sec_url)
- API: POST https://api.telegram.org/bot{token}/sendMessage (parse_mode: Markdown)
- 메시지: "📡 *NVIDIA 새 8-K 공시* / *제목:* / *날짜:* / *분석:* [앞 300자] / *원문:*"
- check_and_store()에서 신규 저장 후 호출, 발송 실패 시 print만 하고 계속, TOKEN 없으면 알림 없이 정상 동작
완성되면 Space의 Settings → Secrets에 TELEGRAM_TOKEN, TELEGRAM_CHAT_ID를 추가한다.
확인 포인트
- 로컬 실행 → 봇 채팅방에 메시지가 수신된다
- 신규 공시가 없어 테스트가 안 되면: Supabase에서 행 하나를 지우고 재실행 (신규로 재감지된다)
- Space Secrets에 TELEGRAM_TOKEN, TELEGRAM_CHAT_ID가 등록되어 있다
1단계 — BotFather 찾기. 텔레그램에서 BotFather를 검색한다. 파란 체크 뱃지가 붙은 공식 계정인지 확인할 것 — 사칭 계정이 있으니 주의한다.
2단계 — 봇 생성과 토큰 발급. BotFather에게 /newbot을 보내고, 봇 이름(예: NVIDIA 공시 알림)과 계정명(예: nvda_filing_alert_bot)을 입력하면 토큰이 발급된다. 토큰은 비밀번호처럼 보관한다 — 코드에 적지 말고 Secrets/.env에만 둔다.
3단계 — 봇에게 /start. 방금 만든 봇과의 채팅방에서 /start를 보낸다. 이것이 chat 기록을 생성해 다음 단계에서 chat.id를 읽을 수 있게 한다.
4단계 — chat.id 확인. 브라우저에서 https://api.telegram.org/bot[토큰]/getUpdates를 열어 message.chat.id 값을 확인한다. result가 []로 비어 있다면 3단계의 /start를 보내지 않은 것이다.
5단계 — 연결 테스트. sendMessage URL로 테스트 메시지를 보내 봇 채팅방에 도착하는지 확인한다.
6단계 — Space Secrets 등록. TELEGRAM_TOKEN과 TELEGRAM_CHAT_ID를 Space Secrets에 등록하고 재배포한다.
그룹 채팅에 연결하려면. 그룹 생성 → 봇 초대 → 그룹에 메시지 전송 → getUpdates에서 음수 chat.id 확인 → 그 값을 그대로 TELEGRAM_CHAT_ID로 등록하면 된다.
완성 시스템 정리
로컬 PC와 무관하게 24시간 도는 공시 모니터링 시스템이 완성됐다. 각 구성 요소의 역할과 비용, 다음 확장 방향을 정리한다.
세 구성 요소의 역할
| 구성 요소 | 역할 |
|---|---|
| HuggingFace Space | 대시보드 24시간 호스팅 · 10분마다 SEC RSS 폴링 · 신규 8-K 자동 분석 |
| Supabase | filings 테이블 영구 저장 · sec_url unique 중복 방지 · 대시보드 데이터 소스 |
| SEC EDGAR | 공개 RSS 피드 제공 · 별도 인증 불필요 |
비용
| 항목 | 비용 | 비고 |
|---|---|---|
| HF Spaces | 무료 (CPU) | 슬립 모드 있음 · 유료 $9/월이면 슬립 없음 |
| Supabase | 무료 | 500MB DB · 50만 API 호출 — 이 용도에 충분 |
| HF Inference API | 무료 할당량 | 초과 시 skip 처리로 시스템은 계속 동작 |
확장 아이디어
- 다른 종목 모니터링 — ticker와 CIK만 바꾸면 어떤 미국 상장사든 같은 구조로 감시할 수 있다.
- Slack·이메일 알림 — 텔레그램 자리에 webhook을 추가하면 채널을 늘릴 수 있다.
- 감성 분석 차트 — 축적된 analysis의 긍정/부정/중립을 시계열 차트로 시각화한다.
- 벡터 DB 추가 인덱싱 — 신규 8-K를 07_secrag의 기존 벡터 DB에 추가해 히스토리 검색까지 통합한다.
하나. 로컬 스크립트와 온라인 서비스의 차이는 코드가 아니라 실행 환경이다. 같은 check_and_store()가 HF Space에 올라가는 순간 24시간 서비스가 된다.
둘. 자동화 시스템의 데이터 무결성은 코드가 아니라 DB 제약으로 지킨다. sec_url unique 하나가 모든 중복 문제를 해결했다.
셋. 비밀값(URL, Key, 토큰)은 코드가 아니라 환경변수·Secrets에 둔다. 부가 기능(분석·알림)의 실패는 핵심 기능(감지·저장)을 멈추지 않게 설계한다.
출처 원본 노션 실습 자료: 8-K 긴급 공시 실시간 모니터링 (HuggingFace + Supabase)
이 자료는 단일 HTML 파일로 배포되는 실습 가이드다. "AI에게 이렇게 요청" 블록의 프롬프트는 복사 버튼으로 그대로 복사해 사용한다.