Catalyst Data Collection Plan

바이오 트레이딩에서 가장 중요한 두 이벤트 — PDUFA datePhase readout — 를 공식/준공식 소스에서 전수 수집해 biotech.duckdb 내 통합 catalyst 테이블로 저장한다.

현재 보유 데이터는 clinical_trials.primary_completion_date 정도가 전부로, 실제 공시 기반 catalyst 이벤트는 없음. 이를 4개 소스 통합으로 보강.


Scope & Target

  • 기간: 2017-01-01 ~ 현재 (+ forward-looking 미래 PDUFA 포함)
  • 대상 종목: universe 테이블에 한 번이라도 포함된 모든 바이오 ticker (+ excluded_symbols 도 커버해서 survivorship bias 방지)
  • 이벤트 타입:
    • pdufa — FDA 승인 결정일
    • phase_readout — Phase 1/2/3 topline 발표
    • adcom — FDA AdCom 회의
    • chmp — EMA CHMP opinion (옵션, 후순위)
    • interim — interim readout (옵션)
  • 커버리지 목표:
    • PDUFA: 95%+
    • Phase 2/3 readout: 85%+
    • Phase 1 readout: 60%+ (중요도 낮음)

Target Schema

DuckDB biotech.duckdb 에 신규 테이블 catalysts 와 감사용 catalyst_sources 2개 추가.

catalysts (primary — merge된 결과)

CREATE TABLE catalysts (
  id BIGINT PRIMARY KEY,
  symbol VARCHAR NOT NULL,
  catalyst_type VARCHAR NOT NULL,     -- 'pdufa' | 'phase_readout' | 'adcom' | 'chmp' | 'interim'
  phase VARCHAR,                       -- 'Phase 1' | 'Phase 2' | 'Phase 3' | NULL
  drug_name VARCHAR,
  indication VARCHAR,
  nct_id VARCHAR,
  event_date DATE NOT NULL,            -- 실제 이벤트 발생일
  announced_date DATE NOT NULL,        -- 예정일이 최초 공시된 날 (look-ahead 방지 필수)
  original_event_date DATE,            -- PDUFA 연기 등 change tracking
  outcome VARCHAR,                     -- 'positive' | 'negative' | 'mixed' | 'pending'
  source VARCHAR NOT NULL,             -- 최종 채택 소스
  source_confidence INTEGER NOT NULL,  -- 1(최고) ~ 4(최저)
  source_url VARCHAR,
  notes TEXT,
  collected_at TIMESTAMP NOT NULL,
  updated_at TIMESTAMP NOT NULL
);
 
CREATE UNIQUE INDEX catalysts_natural_key ON catalysts(symbol, catalyst_type, event_date, COALESCE(nct_id, ''));
CREATE INDEX catalysts_symbol_date ON catalysts(symbol, event_date);
CREATE INDEX catalysts_type ON catalysts(catalyst_type);

catalyst_sources (audit — 소스별 raw)

CREATE TABLE catalyst_sources (
  id BIGINT PRIMARY KEY,
  catalyst_id BIGINT REFERENCES catalysts(id),
  source VARCHAR NOT NULL,             -- 'edgar_8k' | 'biopharmacatalyst' | 'prnewswire' | 'clinicaltrials_gov' | 'manual'
  event_date DATE NOT NULL,
  announced_date DATE,
  outcome VARCHAR,
  raw_payload JSON,                    -- 원본 응답 (디버깅용)
  source_url VARCHAR,
  collected_at TIMESTAMP NOT NULL
);

핵심 원칙: announced_date 필수

  • 모든 row는 “이 정보가 언제 대중에게 공개됐는가” 를 알아야 함
  • 없는 row는 백테스트에서 제외 (conservative)
  • PDUFA의 경우: NDA/BLA acceptance PR 공시일
  • Phase readout 의 경우: 회사 가이던스 발표일 (보통 2~4개월 전)

4개 소스 전략

#Source비용커버리지품질(1~4)역할
1EDGAR 8-K (Item 7.01 / 8.01)무료~100% (미 상장사)1 (최고)Primary
2BioPharma Catalyst (historical)$99~249/월과거 5년+ 큐레이션2최종 검증/보강
3PR Newswire / BusinessWire무료 (스크래핑)부분 (PR 낸 것만)3EDGAR 보조
4ClinicalTrials.gov API무료광범위 but proxy4 (최저)baseline fallback

Source 1 — EDGAR 8-K (Primary) 🥇

왜 1순위: 미 상장 바이오는 주요 임상 결과 / FDA 결정을 법적으로 8-K 공시 의무. 신뢰도 최고.

대상 공시

  • Item 7.01 (Regulation FD Disclosure) — Phase readout / 임상 업데이트
  • Item 8.01 (Other Events) — PDUFA 일정 확정, FDA acceptance, AdCom 확정

수집 방법

  1. profile 테이블에서 symbol → CIK 매핑 확보
  2. EDGAR full-text search API: https://efts.sec.gov/LATEST/search-index?q=...&forms=8-K&dateRange=...
  3. 쿼리 키워드 세트:
    • PDUFA: "PDUFA", "Prescription Drug User Fee Act", "FDA acceptance"
    • Phase readout: "topline", "primary endpoint", "Phase 2 results", "Phase 3 results", "met the primary endpoint", "did not meet", "missed"
  4. 각 8-K 페이지에서 exhibit 99.1 (press release) URL 추출
  5. 본문 HTML 다운로드 (rate limit 10 req/sec 준수)

파싱 로직

  • event_date = 공시일
  • announced_date = 본문의 previously announced, as previously disclosed, in [month] [year] 문구에서 역추출
  • outcome = 첫 3문단 키워드 매칭
  • drug_name / indication = 본문 NER (regex + LLM 하이브리드)

활용 자산: 기존 sec_filings 테이블에 8-K 메타는 이미 있을 것 → 본문 수집 및 파싱만 추가.

예상 작업량: 23일 (CIK 매핑 0.5d + fetcher 1d + parser 11.5d)

Source 2 — BioPharma Catalyst (최종 검증) 💰

왜 쓰나: 전문 editors 가 큐레이션한 과거 catalyst 캘린더. 커버리지와 정확도가 동시에 높음. 단 유료.

접근

  • https://www.biopharmcatalyst.com/calendars/historical-catalyst-calendar — 구독 필요
  • CSV export 기능 있음
  • API 없음 → HTML 파싱 또는 CSV import

전략

  • Phase A~C (EDGAR + PR + CT.gov) 로 baseline 만든 뒤
  • 1~2개월 $99 플랜 결제 → 과거 데이터 일괄 export
  • 기존 데이터와 cross-check → 누락/불일치 측정
  • 보강 후 구독 해지

예상 작업량: 구독 후 importer 개발 1일

Source 3 — PR Newswire / BusinessWire 🗞

왜 쓰나: 회사가 직접 발표하는 보도자료. EDGAR 8-K 보다 빠를 때 있음. 무료.

대상

  • PR Newswire 헬스 섹션: https://www.prnewswire.com/news-releases/health-latest-news/health-latest-news-list/
  • BusinessWire: https://www.businesswire.com/portal/site/home/news/industries/health/
  • 각 사이트 RSS 피드 + historical archive

수집 방법

  1. RSS 피드 매일 cron으로 긁음
  2. historical backfill: archive 페이지 순회 (2017-01-01 부터)
  3. 필터 키워드:
    • "topline data", "primary endpoint", "Phase 1/2/3 results", "PDUFA", "FDA acceptance", "BLA", "NDA", "AdCom"
  4. 본문에서 symbol 추출: 상단의 (Nasdaq: XYZ) / (NYSE: XYZ) 형식 regex

파싱 로직

  • event_date = 보도자료 발행일
  • announced_date = 본문에서 previously announced on [date] 추출 (없으면 event_date 사용, source_confidence 낮춤)
  • outcome = 첫 두 문단 키워드

운영 주의

  • User-Agent 로테이션 (bot 차단 대비)
  • Rate limit: 사이트당 1 req/sec 이하
  • Proxy 고려 (특히 historical backfill)

예상 작업량: 2~3일

Source 4 — ClinicalTrials.gov (baseline fallback)

역할: 다른 소스에서 놓친 trial readout 을 보완하는 최후 수단.

수집 방법

  1. 이미 clinical_trials 테이블에 데이터 있음 → 새 API 호출 불필요
  2. 정규화 규칙:
    • primary_completion_dateevent_date (추정)
    • announced_date = primary_completion_date - 90 days (conservative 추정)
    • outcome = NULL (이 소스로는 결과 모름)
    • source_confidence = 4

주의: 이 데이터는 “언제쯤 발표될 가능성이 있다” 수준. 실제 readout 날짜와 수개월 어긋남. backtest 에 단독 사용 시 결과 신뢰 낮음.


Deduplication & Merge

같은 이벤트가 여러 소스에 동시에 존재. 예: Phase 3 readout 이 EDGAR 8-K + PR Newswire + BioPharma Catalyst 모두에.

Merge 키

(symbol, catalyst_type, event_date ± 3 days, nct_id) — 날짜 3일 오차 허용, nct_id 가 있으면 추가 구분자.

규칙

  • 최종 row 는 source_confidence 최저(=최고 품질) 소스 기준
  • 나머지 소스는 catalyst_sources 감사 테이블에 raw 저장
  • announced_date가장 이른 날짜 채택 (먼저 공시된 시점이 진실)
  • outcome 충돌 시 confidence 1 우선

아키텍처

biotech/data/ingest/catalyst/
├── common/
│   ├── schema.py          # DDL + migration
│   ├── symbol_mapper.py   # 회사명 → symbol
│   └── cik_mapper.py      # symbol → CIK
├── sources/
│   ├── edgar_8k.py        # Source 1
│   ├── biopharma.py       # Source 2
│   ├── prnewswire.py      # Source 3
│   └── clinicaltrials.py  # Source 4
├── normalizer.py          # 공통 normalizer
├── merger.py              # dedup + priority resolution
├── quality.py             # coverage / conflict dashboard
└── cli.py                 # 실행 엔트리포인트

운영

  • 초기 backfill: python -m catalyst.cli backfill --source all --start 2017-01-01
  • 증분 갱신:
    • PR Newswire: 매일 cron (1시간 간격 권장 — 빠른 pickup)
    • EDGAR: 주 1회 cron
    • CT.gov: 월 1회
  • DuckDB write: INSERT ... ON CONFLICT DO UPDATE 스타일 upsert

작업 분해

Phase A — Schema + baseline (1일)

  • catalysts, catalyst_sources DDL + 마이그레이션
  • ClinicalTrials.gov 기존 데이터 → catalysts 파생 (source=clinicaltrials_gov, conf=4)
  • profile.cik 존재 여부 확인, 없으면 EDGAR 에서 CIK 매핑

Phase B — EDGAR 8-K Primary (2~3일)

  • EDGAR full-text search 쿼리 개발
  • 8-K 본문 fetcher (rate limit, disk caching)
  • Item 7.01 / 8.01 parser (regex + LLM 하이브리드)
  • 샘플 50건 수동 검증 → 정확도 95% 이상 확인

Phase C — PR Newswire / BusinessWire (2~3일)

  • RSS 피드 리스너
  • Historical archive backfill scraper
  • User-Agent rotation + rate limit
  • 회사명 → symbol 매핑 (profile 테이블 활용)
  • Cron 구성

Phase D — Merger + Backfill (1~2일)

  • Dedup 로직 구현
  • 전체 backfill 1회 (2017~현재)
  • Quality 대시보드: 소스별 커버리지, 충돌 건수, outcome 분포 집계

Phase E — BioPharma Catalyst (옵션, 1일)

  • Phase D 결과 gap 확인
  • 부족하면 1개월 구독 → CSV import → cross-check → 보강
  • 구독 해지

Phase F — Ongoing 운영 (지속)

  • Cron 모니터링
  • 실패 알림 (slack/discord)
  • 월간 quality report

품질 검증

Sample spot-check (필수)

각 소스별로 30~50건 샘플링 → 회사 IR 페이지 / 원문 PR 과 수동 비교. 정확도 95% 미만이면 파서 재점검.

Coverage metric

  • universe 종목 중 catalyst 보유 비율
  • 목표: PDUFA 95%+, Phase 2/3 readout 85%+

Look-ahead check

  • 전수: announced_date < event_date 검증
  • announced_date NULL row 는 백테스트에서 제외

Change tracking

  • PDUFA 연기는 흔함 → original_event_date 보존 + 로그
  • 변경 이력은 catalyst_sources 감사 테이블로 추적

리스크 & 주의

  1. EDGAR 파싱 정확도: Item 7.01 본문은 자연어. regex만으로는 부족 가능 → LLM 보조 파싱 검토 (비용 약간 발생).
  2. PR Newswire bot 차단: 대량 요청 시 IP 차단. rate limit + proxy + UA rotation 필수.
  3. Survivorship bias: 상장폐지 회사의 과거 이벤트 누락 주의. excluded_symbols 도 커버.
  4. Ambiguous events: Phase 2b, Phase 2/3 조합, interim 등 → 별도 tag 또는 분류 가이드 필요.
  5. PDUFA 변경: 한 번 공시된 PDUFA date 연기 빈번 → original_event_date + 변경 이력 저장 필수.
  6. EDGAR API rate limit: SEC 정책상 10 req/sec. 대량 backfill 시 며칠 걸림.
  7. 회사명 → symbol 매핑 실패: 작은 바이오는 profile 에 없을 수 있음 → manual override 테이블 필요.

일정

  • Week 1: Phase A + Phase B (EDGAR)
  • Week 2: Phase C (PR Newswire) + Phase D (merger + quality)
  • Week 3: 운영 cron 정착 + sampling 검증
  • Week 4 (선택): Phase E (BioPharma Catalyst) + 최종 보강

백테스트 활용 (간단 요약)

이 데이터는 주로 v13.1 (event-window pre-exit) 과 v13.2 (post-catalyst IV crush exit) 에 쓰임. 자세한 backtest 설계는 docs/plans/v13-plan.md 참조.

핵심 활용 포인트:

  1. 백테스트 엔트리 시점 T 에서 해당 symbol 의 향후 가장 가까운 catalyst 까지의 일수 를 조회
  2. announced_date <= T 만 유효 (look-ahead bias 방지)
  3. v13.1: catalyst D-N 일 도달 시 청산
  4. v13.2: catalyst +1 거래일 종가 청산
  5. outcome 정보는 entry 시점에는 모름 → entry 의사결정에 사용 불가. 단, 사후 분석 에서 “positive readout 전에 많이 빠진 전략” vs “negative readout 피한 전략” 구분용으로 활용.

이 외에 clinical_trials + catalysts 조인으로 “Phase 2 → Phase 3 transition 이벤트” 같은 새로운 엔트리/exit 피쳐 엔지니어링도 가능.


다음 액션

승인 나면 bio-v2-data 채널에서:

  1. Phase A Schema DDL 확정
  2. profile.cik 컬럼 존재 확인, 없으면 CIK 매핑 선작업
  3. ClinicalTrials.gov 파생 백필 (하루짜리 작업)
  4. EDGAR fetcher 개발 착수