From 7fed850fbf613bbeb74a394205389ee5ab926090 Mon Sep 17 00:00:00 2001 From: I Luk Kim Date: Thu, 12 Mar 2026 11:28:00 -0700 Subject: [PATCH] =?UTF-8?q?test:=20Phase=201/2=20=EC=B2=B4=ED=81=AC?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=9E=AC=EC=9E=91=EC=84=B1=20?= =?UTF-8?q?=EB=B0=8F=20Oracle=20=EC=8B=A4=EC=97=B0=EB=8F=99=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Oracle 서비스 어댑터 5개를 실제 API 포맷에 맞게 수정 - 모든 경로에 /api/v1/ prefix 추가 - price: data[] → bars 매핑, volume float→int - filings: accession_number→accession_no, total_count→total - financial: financial_data[] → periods, period_date 파싱 - finra: entries[] → data 매핑 - fred: data.observations 언패킹, value string→float (버그 수정 포함) - fixtures 6개를 실제 Oracle 응답 포맷으로 전면 교체 - 통합 테스트에서 httpx_mock 완전 제거 → 실제 Oracle 직접 호출 - 신규 단위 테스트 3개 파일 추가 (logging, fred_service, llm_parser_stub) - test_retries.py에 exhaustion 테스트 추가 - test_oracle_client.py에 connection/timeout/no-ctx 테스트 추가 - Phase 1/2 testing_checklist.md 실제 구현 기준으로 전면 재작성 - 전체 114 tests pass (unit 100 + replay 5 + integration 9) Co-Authored-By: Claude Sonnet 4.6 --- dev/overview.md | 651 ++++++++++++++++++ dev/phase0/README.md | 24 + dev/phase0/data_source_policy.md | 330 +++++++++ dev/phase0/event_taxonomy.md | 376 ++++++++++ dev/phase0/risk_policy.md | 285 ++++++++ dev/phase0/strategy_spec.md | 232 +++++++ dev/phase1_deliverables/README.md | 104 +++ .../architecture_and_repo_plan.md | 289 ++++++++ dev/phase1_deliverables/coding_rules.md | 84 +++ dev/phase1_deliverables/db_schema.md | 364 ++++++++++ .../implementation_plan.md | 234 +++++++ .../parser_event.schema.json | 145 ++++ dev/phase1_deliverables/parser_json_schema.md | 174 +++++ dev/phase1_deliverables/service_contracts.md | 227 ++++++ dev/phase1_deliverables/testing_checklist.md | 258 +++++++ dev/phase2_deliverables/README.md | 100 +++ .../implementation_plan.md | 224 ++++++ .../ingestion_architecture.md | 264 +++++++ dev/phase2_deliverables/job_catalog.md | 173 +++++ dev/phase2_deliverables/operator_runbook.md | 191 +++++ .../orchestration_and_scheduling.md | 244 +++++++ .../source_adapter_specs.md | 331 +++++++++ .../storage_layout_and_data_contracts.md | 286 ++++++++ dev/phase2_deliverables/testing_checklist.md | 114 +++ dev/phase3_deliverables/README.md | 114 +++ .../document_parser_design.md | 150 ++++ dev/phase3_deliverables/feature_catalog.md | 149 ++++ .../feature_record.schema.json | 58 ++ .../implementation_plan.md | 156 +++++ .../labeling_and_dataset_spec.md | 130 ++++ .../parser_and_feature_architecture.md | 193 ++++++ .../prompt_and_llm_policy.md | 126 ++++ .../quality_assurance_and_review.md | 119 ++++ .../review_queue_contract.md | 73 ++ .../review_record.schema.json | 45 ++ dev/phase3_deliverables/testing_checklist.md | 81 +++ dev/phase4_deliverables/README.md | 107 +++ .../backtest_architecture.md | 215 ++++++ .../backtest_config.schema.json | 227 ++++++ .../configuration_and_schemas.md | 146 ++++ .../experiment_and_evaluation_plan.md | 160 +++++ .../experiment_manifest.schema.json | 67 ++ .../implementation_plan.md | 180 +++++ .../operator_research_runbook.md | 82 +++ .../portfolio_and_risk_model.md | 177 +++++ .../simulation_engine_design.md | 187 +++++ dev/phase4_deliverables/testing_checklist.md | 117 ++++ dev/phase5_deliverables/README.md | 44 ++ .../attention_overlay_architecture.md | 74 ++ .../feature_design_and_entity_resolution.md | 79 +++ .../implementation_plan.md | 66 ++ .../operations_and_monitoring.md | 68 ++ .../overlay_config.schema.json | 30 + .../overlay_feature_record.schema.json | 39 ++ .../overlay_scoring_policy.md | 58 ++ .../source_integration_specs.md | 135 ++++ dev/phase5_deliverables/testing_checklist.md | 47 ++ .../youtube_channel_registry_spec.md | 41 ++ dev/phase6_deliverables/README.md | 121 ++++ .../approval_ticket.schema.json | 36 + .../broker_integration_and_order_lifecycle.md | 175 +++++ .../configuration_and_schemas.md | 60 ++ .../implementation_plan.md | 98 +++ .../live_config.schema.json | 80 +++ .../monitoring_alerting_and_ops.md | 114 +++ dev/phase6_deliverables/operator_runbook.md | 55 ++ .../order_event.schema.json | 49 ++ .../paper_trading_and_live_architecture.md | 174 +++++ .../risk_guard_and_approval_workflow.md | 155 +++++ .../state_machine_and_reconciliation.md | 199 ++++++ dev/phase6_deliverables/testing_checklist.md | 66 ++ libs/oracle_client/client.py | 17 +- libs/oracle_client/exceptions.py | 1 + libs/oracle_client/filings.py | 39 +- libs/oracle_client/financial.py | 43 +- libs/oracle_client/finra.py | 37 +- libs/oracle_client/fred.py | 31 +- libs/oracle_client/models.py | 1 + libs/oracle_client/price.py | 47 +- tests/fixtures/exhibit_content.json | 2 +- tests/fixtures/filing_search.json | 23 +- tests/fixtures/financial_data.json | 38 +- tests/fixtures/fred_observations.json | 21 +- tests/fixtures/price_data.json | 23 +- tests/fixtures/short_volume.json | 16 +- tests/integration/conftest.py | 2 +- tests/integration/test_db_migration.py | 7 +- tests/integration/test_feature_pipeline.py | 110 ++- tests/integration/test_filing_pipeline.py | 5 +- tests/integration/test_sync_jobs.py | 8 +- tests/unit/test_fred_service.py | 56 ++ tests/unit/test_llm_parser_stub.py | 31 + tests/unit/test_logging.py | 32 + tests/unit/test_oracle_client.py | 54 +- tests/unit/test_retries.py | 21 + 95 files changed, 11363 insertions(+), 128 deletions(-) create mode 100644 dev/overview.md create mode 100644 dev/phase0/README.md create mode 100644 dev/phase0/data_source_policy.md create mode 100644 dev/phase0/event_taxonomy.md create mode 100644 dev/phase0/risk_policy.md create mode 100644 dev/phase0/strategy_spec.md create mode 100644 dev/phase1_deliverables/README.md create mode 100644 dev/phase1_deliverables/architecture_and_repo_plan.md create mode 100644 dev/phase1_deliverables/coding_rules.md create mode 100644 dev/phase1_deliverables/db_schema.md create mode 100644 dev/phase1_deliverables/implementation_plan.md create mode 100644 dev/phase1_deliverables/parser_event.schema.json create mode 100644 dev/phase1_deliverables/parser_json_schema.md create mode 100644 dev/phase1_deliverables/service_contracts.md create mode 100644 dev/phase1_deliverables/testing_checklist.md create mode 100644 dev/phase2_deliverables/README.md create mode 100644 dev/phase2_deliverables/implementation_plan.md create mode 100644 dev/phase2_deliverables/ingestion_architecture.md create mode 100644 dev/phase2_deliverables/job_catalog.md create mode 100644 dev/phase2_deliverables/operator_runbook.md create mode 100644 dev/phase2_deliverables/orchestration_and_scheduling.md create mode 100644 dev/phase2_deliverables/source_adapter_specs.md create mode 100644 dev/phase2_deliverables/storage_layout_and_data_contracts.md create mode 100644 dev/phase2_deliverables/testing_checklist.md create mode 100644 dev/phase3_deliverables/README.md create mode 100644 dev/phase3_deliverables/document_parser_design.md create mode 100644 dev/phase3_deliverables/feature_catalog.md create mode 100644 dev/phase3_deliverables/feature_record.schema.json create mode 100644 dev/phase3_deliverables/implementation_plan.md create mode 100644 dev/phase3_deliverables/labeling_and_dataset_spec.md create mode 100644 dev/phase3_deliverables/parser_and_feature_architecture.md create mode 100644 dev/phase3_deliverables/prompt_and_llm_policy.md create mode 100644 dev/phase3_deliverables/quality_assurance_and_review.md create mode 100644 dev/phase3_deliverables/review_queue_contract.md create mode 100644 dev/phase3_deliverables/review_record.schema.json create mode 100644 dev/phase3_deliverables/testing_checklist.md create mode 100644 dev/phase4_deliverables/README.md create mode 100644 dev/phase4_deliverables/backtest_architecture.md create mode 100644 dev/phase4_deliverables/backtest_config.schema.json create mode 100644 dev/phase4_deliverables/configuration_and_schemas.md create mode 100644 dev/phase4_deliverables/experiment_and_evaluation_plan.md create mode 100644 dev/phase4_deliverables/experiment_manifest.schema.json create mode 100644 dev/phase4_deliverables/implementation_plan.md create mode 100644 dev/phase4_deliverables/operator_research_runbook.md create mode 100644 dev/phase4_deliverables/portfolio_and_risk_model.md create mode 100644 dev/phase4_deliverables/simulation_engine_design.md create mode 100644 dev/phase4_deliverables/testing_checklist.md create mode 100644 dev/phase5_deliverables/README.md create mode 100644 dev/phase5_deliverables/attention_overlay_architecture.md create mode 100644 dev/phase5_deliverables/feature_design_and_entity_resolution.md create mode 100644 dev/phase5_deliverables/implementation_plan.md create mode 100644 dev/phase5_deliverables/operations_and_monitoring.md create mode 100644 dev/phase5_deliverables/overlay_config.schema.json create mode 100644 dev/phase5_deliverables/overlay_feature_record.schema.json create mode 100644 dev/phase5_deliverables/overlay_scoring_policy.md create mode 100644 dev/phase5_deliverables/source_integration_specs.md create mode 100644 dev/phase5_deliverables/testing_checklist.md create mode 100644 dev/phase5_deliverables/youtube_channel_registry_spec.md create mode 100644 dev/phase6_deliverables/README.md create mode 100644 dev/phase6_deliverables/approval_ticket.schema.json create mode 100644 dev/phase6_deliverables/broker_integration_and_order_lifecycle.md create mode 100644 dev/phase6_deliverables/configuration_and_schemas.md create mode 100644 dev/phase6_deliverables/implementation_plan.md create mode 100644 dev/phase6_deliverables/live_config.schema.json create mode 100644 dev/phase6_deliverables/monitoring_alerting_and_ops.md create mode 100644 dev/phase6_deliverables/operator_runbook.md create mode 100644 dev/phase6_deliverables/order_event.schema.json create mode 100644 dev/phase6_deliverables/paper_trading_and_live_architecture.md create mode 100644 dev/phase6_deliverables/risk_guard_and_approval_workflow.md create mode 100644 dev/phase6_deliverables/state_machine_and_reconciliation.md create mode 100644 dev/phase6_deliverables/testing_checklist.md create mode 100644 tests/unit/test_fred_service.py create mode 100644 tests/unit/test_llm_parser_stub.py create mode 100644 tests/unit/test_logging.py diff --git a/dev/overview.md b/dev/overview.md new file mode 100644 index 0000000..37ae176 --- /dev/null +++ b/dev/overview.md @@ -0,0 +1,651 @@ +## 1. 프로젝트의 최종 목표 + +제가 제안드리는 최종 목표는 아래 한 문장으로 정리됩니다. + +**“공식 문서와 무료 attention 데이터를 이용해, 1~5일짜리 중단기 continuation 종목을 자동으로 선별하고, 다음 세션에 기계적으로 진입·청산하는 AI 이벤트 트레이딩 시스템”** + +여기서 중요한 점은 세 가지입니다. + +첫째, **AI/LLM은 예언자가 아니라 문서 해석기**로 씁니다. +둘째, **가격 반응이 반드시 1차 검증**입니다. +셋째, **소셜/유튜브/뉴스는 가산점 레이어**이지, 단독 진입 신호가 아닙니다. + +즉, 구조는 이렇게 갑니다. + +**공식 이벤트 레이어** +→ **가격/거래량 확인 레이어** +→ **attention/crowding 레이어** +→ **포트폴리오/리스크 엔진** +→ **실행 엔진** + +--- + +## 2. 어떤 데이터 소스를 어떤 역할로 쓸지 + +### 핵심 소스 + +**SEC EDGAR / data.sec.gov** +이 프로젝트의 메인 소스입니다. 회사별 제출 이력과 XBRL 데이터를 무료 JSON API로 제공하고, 8-K, 10-Q, 10-K, 20-F, 40-F, 6-K 등을 포함하며, 제출 정보는 하루 종일 실시간으로 갱신됩니다. 이 소스는 이벤트의 “원문” 역할을 합니다. ([SEC][1]) + +**Alpaca 무료 플랜** +시세/주문/백테스트용 시장데이터의 기본 축입니다. 공식 문서상 Trading API Basic은 무료이며, 실시간 주식 데이터는 IEX 중심, 주식 히스토리의 최신 15분 제한, 200 API calls/min, 웹소켓 30심볼 제한이 있습니다. 따라서 v1은 일봉/분봉 보조 + 다음 시초가 실행 구조로 맞추는 것이 좋습니다. ([Alpaca API Docs][2]) + +**FRED API** +시장 레짐 필터용입니다. 금리, 스프레드, 경기/유동성 지표를 무료로 API로 받을 수 있으므로, 전략의 “리스크 온/오프” 판정에 쓰기 좋습니다. ([FRED][3]) + +### 2차 보조 소스 + +**FINRA Daily Short Sale Volume** +이건 crowding/포지셔닝 레이어입니다. FINRA는 정상 거래시간에 보고된 short sale trades의 일별 집계 파일을 제공하고, 같은 날 오후 6시 ET까지 게시합니다. 다만 이것은 **short interest가 아니라 당일 short sale volume 집계**이므로, 진짜 공매도 잔고가 아니라 “당일 숏 압력/오프익스체인지 흐름”으로 해석하셔야 합니다. ([FINRA][4]) + +**Wikimedia Pageviews** +리테일 관심 폭증을 잡는 데 좋습니다. Wikimedia Analytics API의 pageview 데이터는 2015년 7월 1일 이후를 제공하고, 봇/자동화 트래픽을 구분합니다. 즉, 개별 종목이나 테마에 대한 대중 관심의 급증을 무료로 관찰할 수 있습니다. ([Wikimedia Doc][5]) + +**YouTube Data API** +유튜브 유명 채널의 영향력을 추적하는 데 쓸 수 있습니다. 다만 전수 검색은 비효율적입니다. YouTube Data API는 기본 쿼터가 하루 10,000 units이고, `search.list`는 100 units, `commentThreads.list`는 1 unit입니다. 또한 `captions.download`는 영상을 수정할 권한이 있는 사용자에게만 허용되므로, 공개 영상 자막 전체를 핵심 입력으로 삼는 구조는 맞지 않습니다. 대신 `channels` 리소스의 `contentDetails.relatedPlaylists.uploads`를 이용해 **미리 정한 채널 whitelist**를 추적하고, 영상 제목/설명/댓글/조회수 속도 중심으로 feature를 만드는 것이 맞습니다. ([Google for Developers][6]) + +**Yahoo Finance RSS** +공식 RSS 피드 서비스가 있으므로, 최신 금융 헤드라인 수집용으로는 쓸 수 있습니다. 다만 저는 이것을 원문 이벤트 소스가 아니라 **가벼운 headline-burst 카운터**로만 쓰겠습니다. ([Yahoo Finance][7]) + +**Google Trends API Alpha** +흥미롭지만 아직 alpha 프로그램입니다. Google은 프로그램식 접근을 위한 Trends API alpha를 받고 있고, 최근 5년 데이터와 일/주/월/연 단위 집계를 설명하고 있습니다. 따라서 이건 v1 핵심 의존성이 아니라 **실험용 테마 히트맵**으로만 두는 편이 맞습니다. ([Google for Developers][8]) + +--- + +## 3. 전체 시스템 아키텍처 + +제가 권하는 구조는 **작고 단단한 모듈형**입니다. 처음부터 거대한 마이크로서비스나 쿠버네티스를 올리지 마시고, **단일 리포지토리 + Docker Compose + Python 서비스 몇 개**로 시작하시는 것이 좋습니다. + +흐름은 아래처럼 잡겠습니다. + +```text +[SEC / Alpaca / FRED / FINRA / Wikimedia / YouTube / Yahoo] + ↓ + Source Adapters + ↓ + Raw Storage (원문 보관) + ↓ + Normalizer / Parser + ↓ + Feature Store + ↓ + Signal Ranker + ↓ + Portfolio & Risk Engine + ↓ + Execution Engine + ↓ + Post-trade Review / Dashboard +``` + +### 저장 구조 + +저는 저장 계층을 두 개로 나누겠습니다. + +**1) Raw Zone** +원문 보관용입니다. +SEC filing HTML/TXT/XML, 99.1 문서, 유튜브 메타데이터, Yahoo RSS headline, FINRA txt 파일 등은 날짜별 폴더에 그대로 저장합니다. + +**2) Structured Zone** +정규화된 테이블입니다. +여기에는 `events`, `documents`, `xbrl_facts`, `market_bars`, `attention_metrics`, `candidate_scores`, `orders`, `fills`, `positions`, `reviews` 같은 테이블이 들어갑니다. + +연구용 쿼리는 **DuckDB + Parquet**, 운영 상태는 **PostgreSQL**로 나누는 구성이 제일 실용적입니다. +이유는 단순합니다. 문서와 시계열은 Parquet가 싸고 빠르고, 주문/포지션/상태관리는 PostgreSQL이 안정적이기 때문입니다. + +### 권장 기술 스택 + +* 언어: Python +* API/관리용: FastAPI +* 운영 DB: PostgreSQL +* 연구/백테스트: DuckDB + Parquet +* 스케줄링: cron 또는 APScheduler +* 컨테이너: Docker Compose +* 비동기 큐: 초기에는 생략, 나중에 Redis 추가 +* LLM 계층: JSON schema 강제 + 캐시 필수 +* 시각화/리포트: Streamlit 또는 간단한 내부 대시보드 + +초기에는 **복잡한 프론트엔드보다 로그와 리포트가 더 중요**합니다. + +--- + +## 4. 폴더/프로젝트 구조 권장안 + +이 정도 구조로 시작하시면 깔끔합니다. + +```text +repo/ + apps/ + collector/ + parser/ + feature_builder/ + ranker/ + backtester/ + live_trader/ + dashboard/ + libs/ + adapters/ + sec/ + alpaca/ + finra/ + fred/ + wikimedia/ + youtube/ + yahoo/ + common/ + schemas/ + risk/ + portfolio/ + execution/ + llm/ + configs/ + sources/ + strategies/ + prompts/ + data/ + raw/ + staging/ + parquet/ + notebooks/ + tests/ + unit/ + integration/ + replay/ + docs/ + strategy_spec.md + data_contracts.md + runbook.md +``` + +--- + +## 5. 핵심 전략 엔진 구조 + +전략 엔진은 한 덩어리가 아니라 아래 4개 하위 엔진으로 분리하겠습니다. + +### A. Event Engine + +공식 문서를 읽고 이벤트 후보를 생성합니다. + +예: + +* 8-K Item 2.02 실적 +* 8-K Item 7.01 Reg FD +* 8-K Item 1.01 중요 계약 +* 8-K Item 8.01 기타 중요 이벤트 +* 10-Q / 10-K 수치 변화 +* 6-K / 20-F 해외 발행사 이벤트 + +### B. Document Understanding Engine + +규칙 + LLM으로 문서의 질을 해석합니다. + +출력 예: + +* event_type +* direction +* guidance_direction +* demand_strength +* pricing_power +* backlog_mentions +* margin_quality +* oneoff_flags +* customer_expansion +* confidence + +### C. Market Confirmation Engine + +시장이 실제로 그 이벤트를 사는지 확인합니다. + +예: + +* reaction day return +* close location +* volume ratio +* gap size +* sector relative strength +* market regime + +### D. Attention Overlay Engine + +리테일 관심과 crowding을 가산점으로 반영합니다. + +예: + +* YouTube mention burst +* Wikipedia pageview shock +* Yahoo headline burst +* FINRA short volume anomaly +* Google Trends theme heat + +중요한 점은 **D 엔진이 단독 진입 신호가 되어서는 안 된다**는 것입니다. + +--- + +## 6. phase별 개발계획 + +이제 실제 개발 순서를 phase 단위로 나누겠습니다. + +--- + +### Phase 0 — 전략/운용 명세 동결 + +**목표** +프로젝트의 범위와 규칙을 먼저 고정합니다. 이 단계가 흔들리면 뒤가 전부 흔들립니다. + +**주요 작업** + +* 거래 대상 고정: 미국 보통주만, ADR/ETF/SPAC 제외 +* 보유기간 고정: 기본 1~5일 +* 실행 구조 고정: same-day 필수 없는 next-open 중심 +* 계좌 정책 고정: v1은 daytrade 의존 구조 금지 +* 데이터 정책 고정: 유료 데이터 금지, 무료 소스 목록 확정 +* 소셜 데이터 정책 고정: 단독 진입 금지, reranking only +* 전략 KPI 고정: 기대값, 최대 낙폭, 회전율, 거래 빈도, 포지션 집중도 + +**산출물** + +* `strategy_spec.md` +* `risk_policy.md` +* `data_source_policy.md` +* `event_taxonomy.md` + +**완료 조건** + +* “무슨 종목을 언제 어떤 이유로 사는지”를 문서 한 장으로 설명 가능해야 합니다. +* 어떤 데이터가 core인지, optional인지, experimental인지 분류가 끝나 있어야 합니다. + +--- + +### Phase 1 — 개발 기반과 데이터 계약 만들기 + +**목표** +앞으로 바꾸기 어려운 기반을 먼저 세웁니다. + +**주요 작업** + +* 모노레포 생성 +* Docker Compose 환경 구성 +* PostgreSQL / DuckDB 초기화 +* 공통 설정 파일, secrets 구조, 로깅 규칙 정의 +* ET 기준 시각/거래일 캘린더 유틸 작성 +* 심볼 마스터 테이블 설계 +* raw 파일 저장 경로 규칙 정의 +* 문서/이벤트/시세/주문용 스키마 설계 + +**산출물** + +* `docker-compose.yml` +* DB 스키마 v1 +* 공통 config loader +* 로그/에러 포맷 규칙 +* `source_status` / `job_runs` 테이블 + +**완료 조건** + +* 로컬에서 한 명이 완전 재현 가능한 개발환경이 떠야 합니다. +* 임의의 source adapter 하나를 실행해 raw와 structured에 동시에 적재할 수 있어야 합니다. + +--- + +### Phase 2 — 핵심 무료 데이터 ingestion 구축 + +**목표** +먼저 **핵심 데이터만 안정적으로 모으는 것**입니다. 이 단계에서는 fancy model보다 ingestion 안정성이 더 중요합니다. + +**주요 작업** + +1. **SEC adapter** + + * submissions JSON 수집 + * accession별 filing 다운로드 + * 8-K / 10-Q / 10-K / 6-K / 20-F / 40-F 인덱싱 + * Exhibit 99.1 추출 + * XBRL facts 파싱 + * SEC fair access 반영: request throttle 적용 + +2. **Alpaca market adapter** + + * 일봉/분봉 bars 수집 + * 거래대금/갭/ATR용 가격 데이터 적재 + * 무료 플랜 제약을 고려한 폴링 빈도 설계 + +3. **FRED adapter** + + * 레짐용 시계열 수집 + +4. **FINRA adapter** + + * 일별 short sale volume 파일 수집 + * 종목별 short ratio 계산 + +이 단계의 핵심 근거는 명확합니다. SEC는 인증 없이 submissions/XBRL JSON을 제공하고 실시간 갱신되며, SEC는 초당 10회 이하 접근을 권고합니다. Alpaca Basic은 무료지만 IEX 실시간, 최신 15분 히스토리 제한, 200 requests/min, 웹소켓 30심볼 제한이 있어, ingestion 설계도 그 제약에 맞춰야 합니다. FINRA short sale volume은 같은 날 오후 6시 ET까지 게시되므로 post-close feature로 쓰는 것이 맞고, FRED는 무료 API로 거시 데이터를 제공합니다. ([SEC][1]) + +**산출물** + +* `sec_collector` +* `alpaca_collector` +* `fred_collector` +* `finra_collector` +* 원문 raw archive +* 정규화 테이블 v1 + +**완료 조건** + +* 최근 충분한 기간의 연속 데이터가 누락 없이 적재되어야 합니다. +* 재실행해도 중복 적재가 없어야 합니다. +* 실패한 job의 재시도가 자동으로 가능해야 합니다. + +--- + +### Phase 3 — 문서 파서와 feature builder 구축 + +**목표** +이 단계에서 AI/LLM이 처음 들어갑니다. 하지만 LLM을 먼저 두지 않고, **규칙 기반 파서 → LLM 보강** 순서로 갑니다. + +**주요 작업** + +1. **규칙 기반 파서** + + * item number 추출 + * guidance 키워드 추출 + * one-off/비GAAP/세금/valuation gain 같은 패턴 탐지 + * 수요/백로그/고객 증가/가격결정력 언급 탐지 + +2. **LLM 파서** + + * JSON schema 강제 + * confidence 포함 + * 문서 해시 캐시 + * 프롬프트 버전 관리 + * 낮은 confidence만 재시도 + +3. **수치 feature** + + * XBRL 기반 revenue, margin, cashflow, debt 변화 + * 과거 회사 가이던스 대비 이번 actual 비교 + * 전분기/전년동기 변화율 + +4. **시장 feature** + + * reaction-day return + * volume ratio + * close location + * gap size + * sector strength + * ATR / volatility context + +5. **label 생성** + + * 1D / 3D / 5D forward return + * MFE / MAE + * stop hit 여부 + * time-to-target + +**산출물** + +* `parsed_documents` +* `event_features` +* `market_features` +* `training_labels` +* manual review notebook + +**완료 조건** + +* 수동 검수 샘플에서 event type / guidance direction / one-off flag의 정확도가 충분히 나와야 합니다. +* LLM 없이도 기본 파이프라인이 돌아가고, LLM은 성능 향상용이어야 합니다. + +--- + +### Phase 4 — 베이스라인 전략과 백테스터 만들기 + +**목표** +이 단계에서 처음으로 “돈 되는지”를 봅니다. 백테스터를 예쁘게 만드는 것이 아니라, **live와 괴리가 적은 시뮬레이터**를 만드는 것이 핵심입니다. + +**주요 작업** + +* 이벤트를 거래일에 정확히 귀속 +* reaction day 정의 +* 다음 시초가 진입 로직 +* 종가 기준/다음날 기준 청산 로직 +* 슬리피지/수수료 보수적 반영 +* 상장폐지/거래중지 처리 +* 포트폴리오 제약 + + * 최대 포지션 수 + * 섹터 집중 제한 + * 종목별 리스크 + * 일일 손실 제한 +* 전략 ablation + + 1. 문서만 + 2. 문서 + 가격 + 3. 문서 + 가격 + 레짐 + 4. 문서 + 가격 + 레짐 + attention + +**산출물** + +* event-driven backtester +* 백테스트 리포트 +* feature importance / ablation 결과 +* 후보 점수식 v1 + +**완료 조건** + +* out-of-sample에서도 성과가 살아 있어야 합니다. +* 특정 한 시즌/한 섹터/한 해에만 먹히는 전략이면 통과시키지 않습니다. +* parser 오류와 execution 가정이 결과를 왜곡하지 않았는지 설명 가능해야 합니다. + +--- + +### Phase 5 — attention layer 확장 + +**목표** +핵심 전략이 먼저 살아 있는지 확인한 뒤, 그 위에 attention/crowding 레이어를 얹습니다. + +**주요 작업** + +1. **YouTube adapter** + + * 투자 관련 유명 채널 whitelist 구축 + * 채널별 uploads playlist 추적 + * 영상 제목/설명/게시시각/조회수/댓글 수집 + * 댓글 감성/티커 인식 + * 영상 영향력 점수 산출 + +2. **Wikimedia adapter** + + * 종목/회사 페이지 매핑 + * 1일/3일/7일 pageview shock 계산 + +3. **Yahoo RSS adapter** + + * 헤드라인 수집 + * 중복 제거 + * publisher breadth / headline burst 계산 + +4. **FINRA crowding features** + + * short volume ratio + * abnormal shorting + * event day crowding 패턴 + +5. **Google Trends experimental** + + * 테마성 키워드에만 제한 적용 + * 핵심 신호가 아니라 보유기간 조정용으로만 사용 + +이 순서가 중요한 이유가 있습니다. YouTube는 기본 쿼터가 하루 10,000 units이고 `search.list`는 100 units, `commentThreads.list`는 1 unit이므로 전수 검색보다 채널 whitelist 방식이 훨씬 효율적입니다. 또한 caption download는 영상 편집 권한이 있어야 하므로 핵심 입력으로 적합하지 않습니다. Wikimedia pageviews는 2015년부터 제공되고 자동화 트래픽을 구분하므로 무료 retail attention 지표로 유용합니다. Yahoo Finance는 RSS 피드 서비스를 제공하므로 headline burst 용도로는 쓸 만하고, Google Trends API는 아직 alpha 단계이므로 optional로만 두는 편이 맞습니다. FINRA short sale volume은 same-day 6pm ET post-close 데이터이므로 intraday 진입이 아니라 후속 랭킹/검증용입니다. ([Google for Developers][6]) + +**산출물** + +* `attention_features` +* `youtube_channel_registry` +* `wiki_entity_map` +* `headline_burst_scores` +* attention overlay 리포트 + +**완료 조건** + +* attention feature를 넣었을 때 성과가 조금이라도 안정적으로 개선되어야 합니다. +* attention feature가 없더라도 core 전략은 독립적으로 돌아가야 합니다. + +--- + +### Phase 6 — paper trading 시스템 구축 + +**목표** +실전 투입 전, 운영 파이프라인이 문제없이 돌아가는지 확인합니다. + +**주요 작업** + +* daily job scheduler +* 후보 생성 → 주문 계획 → 제출 → 체결 → 청산 상태머신 구현 +* stale data 체크 +* 중복 주문 방지 +* 휴장일/조기폐장 처리 +* paper account 연동 +* 주문/체결/포지션 대사(reconciliation) +* 알림 시스템 +* 거래 후 자동 복기 + +**주문 상태머신은 이렇게 단순하게** 잡겠습니다. + +```text +candidate_created +→ price_confirmed +→ order_planned +→ order_submitted +→ accepted +→ partially_filled / filled +→ position_open +→ partial_exit +→ fully_closed +→ post_trade_review_done +``` + +**산출물** + +* `live_trader` +* `risk_guard` +* paper trading runbook +* daily/weekly report + +**완료 조건** + +* 일정 기간 paper trading 동안 누락 주문, 중복 주문, 포지션 불일치가 없어야 합니다. +* 사람이 리포트만 봐도 “왜 샀고 왜 팔았는지” 이해 가능해야 합니다. + +--- + +### Phase 7 — 소액 실전 운영 + +**목표** +이제부터는 성과보다 **운영 안정성**이 우선입니다. + +**주요 작업** + +* 포지션 크기 극소화 +* human-in-the-loop 승인 모드로 시작 +* 자동 진입, 수동 승인, 자동 청산 구조 가능 +* 일일 max loss, max exposure, max sector exposure 적용 +* 장애 시 kill switch +* 신호 drift 감시 +* parser drift 감시 +* LLM 응답 실패 fallback + +**산출물** + +* live trading runbook +* 장애 대응 시나리오 +* kill switch 문서 +* 실전 성과 attribution 리포트 + +**완료 조건** + +* 운영 실수가 전략 손실보다 작아야 합니다. +* 데이터 지연/파싱 실패/주문 실패 시 안전하게 no-trade로 떨어져야 합니다. + +--- + +### Phase 8 — 확장 + +**목표** +핵심 엔진이 안정화된 뒤에만 확장합니다. + +**확장 후보** + +* 숏 전용 negative catalyst 엔진 +* 현금계좌 전용 satellite intraday 엔진 +* local LLM 1차 파서 +* 섹터/테마 그래프 모델 +* 포지션 보유기간 동적 조절 +* 다중 전략 앙상블 +* 브로커 abstraction + +**주의** +이 phase는 앞 단계를 통과한 뒤에만 여는 것이 좋습니다. +v1이 불안정한 상태에서 기능을 늘리면 문제 원인을 못 찾게 됩니다. + +--- + +## 7. 개발 순서상 “일부러 나중에” 미루실 것 + +초기엔 아래를 일부러 하지 않는 편이 좋습니다. + +* 초단타 ORB 실전 자동매매 +* 옵션/0DTE +* 숏 자동매매 +* Reddit/X 전수 감성분석 +* RAG + 벡터DB + 거대한 문서검색 시스템 +* 다중 브로커 동시 지원 +* 화려한 웹 대시보드 +* 딥러닝 end-to-end 가격예측 + +이유는 단순합니다. +지금 프로젝트의 병목은 “모델의 복잡도”가 아니라 **이벤트 정렬, 데이터 품질, 운영 안정성**입니다. + +--- + +## 8. 제가 생각하는 현실적인 우선순위 + +실제로는 다음 순서가 가장 좋습니다. + +**1순위** +SEC + Alpaca + 기본 백테스터 +→ 이게 프로젝트의 본체입니다. + +**2순위** +LLM 문서 파서 + guidance/one-off 해석 +→ AI를 넣을 타이밍입니다. + +**3순위** +FRED + FINRA + Wikimedia +→ 품질 좋은 보조 feature입니다. + +**4순위** +YouTube whitelist +→ attention overlay로는 가치가 높습니다. + +**5순위** +Yahoo RSS / Google Trends +→ 선택적 보강입니다. + +--- + +## 9. 이 계획의 핵심 철학 + +한 줄로 정리하면 이렇습니다. + +**“무료 데이터만 쓸 때는, 가장 값싼 데이터가 아니라 가장 재현성이 높은 데이터를 중심에 둬야 합니다.”** + +그래서 중심은 SEC이고, +가격 확인은 Alpaca이며, +보조는 FINRA/Wikimedia/FRED이고, +유튜브/야후/트렌드는 attention overlay입니다. + +이 구조로 가면 + +* 데이터 비용을 거의 0에 가깝게 유지할 수 있고, +* AI/LLM을 실제로 의미 있는 위치에 넣을 수 있고, +* 전략과 운영을 동시에 통제할 수 있습니다. \ No newline at end of file diff --git a/dev/phase0/README.md b/dev/phase0/README.md new file mode 100644 index 0000000..6928733 --- /dev/null +++ b/dev/phase0/README.md @@ -0,0 +1,24 @@ +# Phase 0 Deliverables + +이 폴더에는 Phase 0에서 동결해야 하는 기준 문서 4종이 포함되어 있습니다. + +## 포함 문서 +- `strategy_spec.md` — 전략 범위, 목표, 계좌/보유 구조, KPI, 비목표 +- `risk_policy.md` — 리스크 한도, 손절/브레이크, 데이터/모델/실행 리스크 정책 +- `data_source_policy.md` — 허용/금지 데이터, 무료 정책, provenance, fallback +- `event_taxonomy.md` — 이벤트 분류 체계, live 허용 클래스, 파서 표준 라벨 + +## 권장 사용 순서 +1. `strategy_spec.md` +2. `risk_policy.md` +3. `data_source_policy.md` +4. `event_taxonomy.md` + +## 다음 단계 +이 4개 문서가 확정되면 Phase 1에서 아래로 내려가면 됩니다. +- DB 스키마 +- 폴더 구조 +- source adapter 계약 +- 파서 JSON 스키마 +- 주문 상태머신 +- 백테스트 이벤트 귀속 규칙 diff --git a/dev/phase0/data_source_policy.md b/dev/phase0/data_source_policy.md new file mode 100644 index 0000000..05bba70 --- /dev/null +++ b/dev/phase0/data_source_policy.md @@ -0,0 +1,330 @@ +# data_source_policy.md + +- 문서명: Data Source Policy +- 프로젝트 코드명: **ACE-F v1** +- 상태: Draft for Phase 0 Sign-off +- 버전: 0.1 +- 작성일: 2026-03-12 +- 목적: 프로젝트에서 허용되는 데이터 소스, 금지 소스, 사용 원칙, 비용 원칙, 보관 및 변경 정책을 고정합니다. + +> 핵심 원칙: **무료**, **공식 우선**, **재현 가능**, **출처 추적 가능**, **약관 위반 금지** + +## 1. 데이터 정책의 최상위 원칙 + +1. **유료 데이터 금지** + - 구독료, API 요금, 별도 라이선스 비용이 드는 데이터는 v1 범위에서 사용하지 않습니다. + +2. **공식 소스 우선** + - 동일 정보가 여러 곳에 있다면 발행자 또는 공식 기관이 제공한 원문을 우선 사용합니다. + +3. **재현 가능성 우선** + - live에서 쓰는 데이터 경로와 backtest에서 쓰는 데이터 경로를 최대한 같게 유지합니다. + +4. **소셜/미디어는 보조 신호** + - 공식 이벤트를 대체할 수 없습니다. + +5. **출처 추적 가능성** + - 모든 feature는 어느 소스에서 왔는지 provenance를 남겨야 합니다. + +6. **약관/쿼터/접속 정책 준수** + - 무료라고 해서 무제한 사용을 전제로 하지 않습니다. + +## 2. 소스 분류 + +### 2.1 Core Approved Sources (핵심 허용) +이 소스가 없으면 전략이 동작하지 않거나 품질이 크게 저하됩니다. + +1. **SEC EDGAR / data.sec.gov** + - 용도: 8-K, 10-Q, 10-K, 6-K, 20-F, 40-F, XBRL, Exhibit 99.1 수집 + - 역할: 원문 이벤트와 공식 숫자 데이터의 기준 원장 + - 정책: + - 공정접속 정책 준수 + - user-agent 명시 + - 필요한 문서만 다운로드 + - raw 원문과 정규화 결과를 함께 저장 + +2. **Alpaca Basic** + - 용도: 일봉/분봉 시세, paper trading, 주문 상태 + - 역할: 기본 가격 확인 및 시뮬레이션/실행 reference + - 정책: + - 무료 플랜 제약에 맞춘 폴링 설계 + - 정교한 intraday 전략의 기준 feed로 사용하지 않음 + - IEX 중심 무료 데이터임을 전제로 해석 + +3. **FRED API** + - 용도: 거시/시장 레짐 보조 feature + - 역할: risk-on / risk-off 필터 + +4. **FINRA Daily Short Sale Volume** + - 용도: crowding / short activity overlay + - 역할: 당일 short sale volume anomaly 탐지 + - 주의: + - short interest와 동일 개념으로 해석 금지 + +### 2.2 Secondary Approved Sources (보조 허용) +핵심 전략이 살아 있는 상태에서 보조 feature로만 사용합니다. + +1. **Wikimedia Pageviews** + - 용도: retail curiosity / 관심 급증 측정 + - 역할: attention overlay + +2. **YouTube Data API** + - 용도: whitelist 채널 기반 mention/engagement 추적 + - 역할: 관심 확산 속도 측정 + - 정책: + - 전수 검색 금지 + - 채널 whitelist 우선 + - 제목/설명/댓글/조회수 기반 feature만 사용 + - 공개 영상 자막 대량 수집 구조 금지 + +3. **Yahoo Finance RSS** + - 용도: headline burst / publisher breadth 카운팅 + - 역할: 미디어 확산 보조 판단 + - 정책: + - 원문 사실 확인 소스가 아니라 보조 attention 레이어로만 사용 + - headline 중복 제거 필수 + +### 2.3 Experimental Sources (연구용) +즉시 production 의존성을 두지 않습니다. + +1. **Google Trends API Alpha** + - 용도: 테마/키워드 관심 급증 + - 정책: + - 접근권이 있는 경우에만 사용 + - 핵심 진입 신호로 사용 금지 + - 보유기간/랭킹 보조로만 연구 + +2. **Reddit** + - 용도: 연구용 mention/engagement feature + - 정책: + - 약관/상업적 사용 가능 여부 확인 전 live 핵심 입력 금지 + - research-only flag 필요 + +### 2.4 Disallowed / Not-in-scope Sources (금지 또는 범위 밖) +- 유료 earnings estimate API +- 유료 transcript API +- X/Twitter 유료 API 의존 구조 +- 현재 신규 접근성이 불안정한 비공식 금융 커뮤니티 API +- robots/약관 위반 가능성이 있는 무단 스크레이핑 +- captcha/로그인 우회 수집 +- 저작권/재배포 정책이 불명확한 자막 다운로드 사이트 +- 웹사이트 화면 파싱을 기반으로 한 핵심 전략 + +## 3. 소스별 구체 정책 + +## 3.1 SEC EDGAR +### 허용 사용 +- filing metadata +- filing body +- exhibit 99.1 +- XBRL facts +- company submissions history + +### 요구사항 +- 명시적 user-agent +- 합리적 캐시 +- 재다운로드 최소화 +- 문서 원본 불변 저장 + +### 금지사항 +- 불필요한 전수 크롤링 +- rate limit 무시 +- 원문 없이 파싱 결과만 보관하는 구조 + +## 3.2 Alpaca Basic +### 허용 사용 +- 일봉/분봉 bars +- latest bar 확인 +- paper trading +- 기본 주문 상태 확인 + +### 제한사항 +- 무료 플랜 제약을 고려한 호출 빈도 설계 +- IEX 중심 무료 실시간 데이터 한계를 감안한 해석 +- v1에서 초단타 실시간 전략의 진실 원장으로 사용 금지 + +## 3.3 FRED +### 허용 사용 +- 금리/스프레드/거시 레짐 +- regime filter +### 제한사항 +- 개별 종목 진입 신호로 직접 사용하지 않음 + +## 3.4 FINRA Short Sale Volume +### 허용 사용 +- short volume ratio +- abnormal short activity +- off-exchange crowding 힌트 +### 제한사항 +- short interest 대용으로 사용 금지 +- 단독 진입 신호 금지 + +## 3.5 Wikimedia +### 허용 사용 +- 회사/브랜드 pageview spike +- retail curiosity shock +### 제한사항 +- 문서/가격 확인 없이 단독 진입 금지 + +## 3.6 YouTube +### 허용 사용 +- whitelist 채널의 업로드 감시 +- 제목/설명에서 ticker/키워드 추출 +- 조회수 증가 속도 +- 댓글 수/간단 감성 +### 제한사항 +- search.list 남용 금지 +- captions.download 기반 대량 수집 금지 +- “유명인이 찍었으니 산다” 식 단독 신호 금지 + +## 3.7 Yahoo Finance RSS +### 허용 사용 +- 헤드라인 burst +- 유니크 publisher 수 +- 기사 수 증가율 +### 제한사항 +- 동일 보도자료의 다중 복제 기사 중복 제거 +- 공식 사실 검증 소스 아님 + +## 3.8 Google Trends +### 허용 사용 +- 섹터/테마 열기 확인 +- 특정 키워드의 관심 확산 +### 제한사항 +- 접근성/안정성 불확실 +- alpha 의존성 때문에 core pipeline 금지 + +## 4. 데이터 우선순위와 진실 원장(Source of Truth) + +### 4.1 이벤트 원장 +- 1순위: SEC 공시 원문 +- 2순위: 회사 IR 자료 +- 3순위: 정규화 파서 결과 +- 4순위: 미디어/소셜 overlay + +### 4.2 가격 원장 +- 1순위: 브로커 reference price feed +- 2순위: 저장된 historical bars +- 3순위: 파생 계산 feature + +### 4.3 attention 원장 +- 원장 개념보다는 보조 signal +- core signal을 override할 수 없음 + +## 5. 데이터 비용 정책 + +### 5.1 외부 데이터 비용 +- 데이터 API 구독료는 **0원/0달러**여야 합니다. +- 테스트 편의를 위해 일시적으로 쓰는 유료 trial 데이터도 core feature로 편입하지 않습니다. + +### 5.2 LLM 비용 +- LLM은 데이터 API가 아니지만 운영비에 포함됩니다. +- 모든 문서에 LLM을 호출하지 않고, 규칙 필터 통과 문서만 호출합니다. +- accession/file hash 단위 캐시 필수 +- 비용 상한 초과 시 low-priority 문서는 규칙 기반 fallback + +## 6. 수집 및 저장 정책 + +### 6.1 Raw 저장 +- 원문은 수정 없이 보관합니다. +- 원문 저장 경로에는 source / date / entity / accession 정보를 포함합니다. +- raw는 immutable 원칙을 따릅니다. + +### 6.2 Structured 저장 +- 파싱 결과, feature, label은 별도 테이블로 분리합니다. +- raw와 structured를 섞어 저장하지 않습니다. +- schema version을 관리합니다. + +### 6.3 Provenance 필수 필드 +모든 구조화 레코드는 최소한 아래를 포함해야 합니다. +- source_name +- source_url +- fetched_at +- document_id or accession +- parser_version +- llm_prompt_version (해당 시) +- feature_build_version + +## 7. 검증 정책 + +### 7.1 ingestion 검증 +- 중복 적재 방지 +- 누락 탐지 +- 스키마 검증 +- 일일 job 성공/실패 로그 + +### 7.2 파서 검증 +- 샘플 수동 검수 +- low confidence 큐 별도 관리 +- source별 실패 패턴 기록 + +### 7.3 변경 검증 +- 소스 포맷이 바뀌면 adapter contract 테스트 업데이트 +- source availability 저하 시 fallback 설계 여부 확인 + +## 8. 장애 및 fallback 정책 + +### 8.1 SEC 장애 +- 신규 이벤트 탐지 중단 +- 과거 저장분만으로는 신규 주문 생성 금지 + +### 8.2 Alpaca 데이터 지연 +- 신규 진입 보류 +- 보유 포지션 안전관리만 수행 + +### 8.3 Secondary source 장애 +- attention score를 0 또는 missing 처리 +- core 전략은 계속 동작해야 함 + +## 9. 소스 변경 및 폐기 정책 +- 무료 정책이 유료 정책으로 바뀌면 즉시 production source에서 제외 검토 +- 쿼터/약관/라이선스 변경은 문서화 후 승인 +- 핵심 소스 변경은 Phase 0~1 수준의 재검토 필요 + +## 10. 승인된 소스 목록 요약 + +| 분류 | 소스 | 역할 | Production 허용 여부 | +|---|---|---|---| +| Core | SEC EDGAR | 이벤트 원문/XBRL | 허용 | +| Core | Alpaca Basic | 가격/주문/paper | 허용 | +| Core | FRED | 시장 레짐 | 허용 | +| Core | FINRA Short Volume | crowding overlay | 허용 | +| Secondary | Wikimedia | attention | 허용 | +| Secondary | YouTube Data API | attention | 허용 | +| Secondary | Yahoo RSS | headline burst | 허용 | +| Experimental | Google Trends Alpha | theme heat | 제한적 | +| Experimental | Reddit | research-only | 제한적 | +| Disallowed | Paid market/earnings APIs | 유료 데이터 | 금지 | +| Disallowed | X/Twitter paid API | 비용/정책 불안정 | 금지 | +| Disallowed | Unofficial scraping | 약관 리스크 | 금지 | + +## 11. 변경 관리 +- 새로운 소스를 추가하려면 다음을 충족해야 합니다. + 1. 무료 여부 + 2. 약관/라이선스 검토 + 3. provenance 저장 가능 여부 + 4. 핵심 전략을 오염시키지 않을 것 + 5. 장애 시 graceful degradation 가능 여부 + +## 부록 A. 현재 확인된 주요 운영 전제 +- SEC는 data.sec.gov에서 인증 없이 JSON API를 제공하며, 실시간 업데이트와 bulk ZIP을 지원합니다. +- SEC는 공정접속을 위해 최대 접근률을 제한하고 과도한 자동 요청을 관리합니다. +- Alpaca Basic은 무료 기본 플랜이며, 주식 무료 실시간 커버리지는 IEX 중심입니다. +- YouTube Data API는 기본 일일 할당량이 존재하며, 검색 호출은 비용이 큽니다. +- 공개 YouTube 자막은 공식 API로 자유롭게 대량 다운로드하는 구조가 아닙니다. +- Wikimedia pageviews는 공개 API로 제공됩니다. +- Google Trends API는 alpha 접근 구조입니다. + +## 부록 B. 참고 링크 +- SEC EDGAR APIs: https://www.sec.gov/search-filings/edgar-application-programming-interfaces +- SEC Developer Resources: https://www.sec.gov/about/developer-resources +- SEC rate limit / fair access notes: https://www.sec.gov/about/webmaster-frequently-asked-questions +- Alpaca Market Data API: https://docs.alpaca.markets/docs/about-market-data-api +- Alpaca rate limit support note: https://alpaca.markets/support/usage-limit-api-calls +- FINRA short sale volume: https://www.finra.org/finra-data/browse-catalog/short-sale-volume-data/daily-short-sale-volume-files +- FINRA short volume explanation: https://www.finra.org/filing-reporting/adf/adf-regulation-sho +- Wikimedia Analytics API: https://doc.wikimedia.org/generated-data-platform/aqs/analytics-api/reference/page-views.html +- YouTube quota cost: https://developers.google.com/youtube/v3/determine_quota_cost +- YouTube captions download policy: https://developers.google.com/youtube/v3/docs/captions/download +- Yahoo Finance RSS: https://finance.yahoo.com/rss/ +- Google Trends API alpha: https://developers.google.com/search/apis/trends diff --git a/dev/phase0/event_taxonomy.md b/dev/phase0/event_taxonomy.md new file mode 100644 index 0000000..7a65864 --- /dev/null +++ b/dev/phase0/event_taxonomy.md @@ -0,0 +1,376 @@ +# event_taxonomy.md + +- 문서명: Event Taxonomy +- 프로젝트 코드명: **ACE-F v1** +- 상태: Draft for Phase 0 Sign-off +- 버전: 0.1 +- 작성일: 2026-03-12 +- 목적: 이벤트 분류 체계, live 거래 허용 범위, 연구 범위, 파서 라벨링 규칙을 고정합니다. + +> 원칙: 이벤트 분류는 “뉴스가 얼마나 시끄러운가”가 아니라, **기업이 공식적으로 무엇을 말했는가**와 **그 정보가 가격으로 어떻게 확인되었는가**를 기준으로 합니다. + +## 1. 분류 체계의 목적 +본 taxonomy는 아래 목적에 사용됩니다. +- 이벤트 원문을 표준 클래스에 매핑 +- live 거래 가능 이벤트와 연구 전용 이벤트 분리 +- LLM 출력 JSON의 공통 vocabulary 제공 +- 백테스트 라벨 정렬 +- post-trade review의 실패 원인 표준화 + +## 2. 최상위 분류 + +### 2.1 Eligible for Live v1 (실거래 허용) +1. `EARNINGS_GUIDANCE_POSITIVE` +2. `EARNINGS_GUIDANCE_MIXED_POSITIVE` +3. `MATERIAL_CONTRACT_POSITIVE` +4. `CUSTOMER_EXPANSION_POSITIVE` +5. `REGULATORY_OR_PRODUCT_APPROVAL_POSITIVE` +6. `BACKLOG_OR_BOOKINGS_ACCELERATION` + +### 2.2 Research-only for v1 (연구 전용) +1. `EARNINGS_NEGATIVE` +2. `GUIDANCE_CUT_NEGATIVE` +3. `DILUTION_OR_FINANCING_NEGATIVE` +4. `REGULATORY_SETBACK_NEGATIVE` +5. `LITIGATION_OR_INVESTIGATION_NEGATIVE` +6. `MATERIAL_IMPAIRMENT_NEGATIVE` +7. `DISTRESS_OR_BANKRUPTCY_NEGATIVE` +8. `MANAGEMENT_CHANGE_CONTEXTUAL` +9. `CAPITAL_ALLOCATION_CONTEXTUAL` +10. `M_AND_A_CONTEXTUAL` + +### 2.3 Excluded / Ignore +1. `ANALYST_ONLY` +2. `RUMOR_ONLY` +3. `SOCIAL_ONLY` +4. `NON_MATERIAL_PR` +5. `IRRELEVANT_FILING` +6. `DUPLICATE_EVENT` + +## 3. 공시 유형별 기본 매핑 + +### 3.1 SEC Filing Type 기준 +- `8-K` : 이벤트 탐지의 핵심 +- `10-Q` / `10-K` : 수치 확인 및 보조 판정 +- `6-K` / `20-F` / `40-F` : 해외 발행사 대응용 +- Exhibit `99.1` : 실적 release / shareholder letter / investor presentation의 핵심 첨부 +- `IR presentation` / company press release : SEC 원문 보조자료 + +### 3.2 8-K Item 기준 기본 해석 +- `Item 2.02` : 실적 발표, guidance, shareholder letter +- `Item 7.01` : Reg FD, investor presentation, outlook commentary +- `Item 1.01` : material definitive agreement → 계약/수주 후보 +- `Item 2.03` : 채무 관련 의무 발생 → 리스크/부정 후보 +- `Item 2.06` : impairment → 부정 후보 +- `Item 8.01` : 기타 중요 이벤트 → 문맥 기반 해석 필요 +- `Item 5.02` : 경영진 변경 → 단독 진입 금지, contextual only +- `Item 1.03` : bankruptcy/receivership → 부정/제외 후보 + +## 4. 이벤트 클래스 정의 + +## 4.1 `EARNINGS_GUIDANCE_POSITIVE` +### 정의 +- 실적 수치가 질적으로 양호하고, 가이던스가 상향 또는 기대 이상이며, 문서 톤이 강한 긍정인 경우 + +### 필요 근거 +- 매출 성장 또는 수요 강도 +- 이익/마진 질 개선 +- 가이던스 상향 또는 강한 유지 +- one-off가 아닌 반복 가능성 +- 반응일 강한 가격 확인 + +### LLM key fields +- `guidance_direction = up | strong_inline` +- `demand_strength = strong` +- `oneoff_flags = none/low` +- `management_tone = strong_positive` + +### v1 처리 +- **실거래 허용** + +## 4.2 `EARNINGS_GUIDANCE_MIXED_POSITIVE` +### 정의 +- 실적은 혼재되어 있으나 가이던스/수요/고객 확대로 해석상 긍정 우위인 경우 + +### 필요 근거 +- 숫자 일부 미스가 있으나 구조적 성장 포인트 존재 +- Q&A 또는 letter에서 forward demand가 강함 +- 가격 확인이 매우 강함 + +### v1 처리 +- 조건부 실거래 허용 +- confidence 높고 가격 확인이 강할 때만 후보 + +## 4.3 `MATERIAL_CONTRACT_POSITIVE` +### 정의 +- 기업이 8-K/IR에서 의미 있는 계약, 공급계약, 대형 수주, 전략적 제휴를 공식 발표한 경우 + +### 필요 근거 +- 계약 상대방/규모/기간/사업영향이 문서상 식별 가능 +- 단순 MOU/루머/홍보 문구 아님 +- 향후 매출/백로그에 실질 영향 가능 + +### v1 처리 +- 실거래 허용 + +## 4.4 `CUSTOMER_EXPANSION_POSITIVE` +### 정의 +- 대형 고객 확보, 유료 고객 증가, 주문 채널 확대 등 고객 기반 확장이 명확한 경우 + +### 필요 근거 +- 문서상 신규 고객/고객 수/채택 범위 확대 언급 +- 반복 매출/수요 가시성 증가 +- 가격 확인 통과 + +### v1 처리 +- 실거래 허용 + +## 4.5 `REGULATORY_OR_PRODUCT_APPROVAL_POSITIVE` +### 정의 +- 규제 승인, 제품 출시, 인증 획득 등 사업 전개를 촉진하는 이벤트 + +### 필요 근거 +- 승인/출시가 실제 매출화 경로와 연결 +- 바이너리 임상성 이벤트와 구분 +- 소형 바이오 과열 종목은 별도 제외 가능 + +### v1 처리 +- 선별적 실거래 허용 + +## 4.6 `BACKLOG_OR_BOOKINGS_ACCELERATION` +### 정의 +- backlog/bookings/reservations/ARR 등의 선행수요 지표가 뚜렷하게 개선된 경우 + +### 필요 근거 +- 단순 단어 언급이 아니라 방향성과 크기 변화 +- 가격/거래량 확인 +- one-off가 아닐 것 + +### v1 처리 +- 실거래 허용 + +## 4.7 `EARNINGS_NEGATIVE` +### 정의 +- 실적/가이던스가 질적으로 부정이고 가격도 약한 경우 +### v1 처리 +- 연구용만 허용 +- 숏 자동매매는 금지 + +## 4.8 `GUIDANCE_CUT_NEGATIVE` +### 정의 +- 가이던스 하향 또는 수요 약화가 명확한 경우 +### v1 처리 +- 연구용만 허용 + +## 4.9 `DILUTION_OR_FINANCING_NEGATIVE` +### 정의 +- 희석성 자금조달, convert, secondary, 유동성 압박이 핵심인 경우 +### v1 처리 +- 연구용만 허용 + +## 4.10 `REGULATORY_SETBACK_NEGATIVE` +### 정의 +- 허가 지연, 승인 실패, 규제상 중대한 차질 +### v1 처리 +- 연구용만 허용 + +## 4.11 `LITIGATION_OR_INVESTIGATION_NEGATIVE` +### 정의 +- 소송, 조사, 회계/규제 리스크 +### v1 처리 +- 연구용만 허용 + +## 4.12 `MATERIAL_IMPAIRMENT_NEGATIVE` +### 정의 +- 자산손상, write-down, 구조적 실적 악화 시사 +### v1 처리 +- 연구용만 허용 + +## 4.13 `DISTRESS_OR_BANKRUPTCY_NEGATIVE` +### 정의 +- 유동성 위기, 파산, 구조조정, 채무불이행 +### v1 처리 +- 거래 금지 또는 연구만 + +## 4.14 `MANAGEMENT_CHANGE_CONTEXTUAL` +### 정의 +- CEO/CFO 교체, 이사회 변화, 창업자 복귀 등 +### v1 처리 +- 단독 진입 금지 +- 다른 핵심 이벤트와 결합 시 보조 판단 + +## 4.15 `CAPITAL_ALLOCATION_CONTEXTUAL` +### 정의 +- 자사주매입, 배당, split 등 +### v1 처리 +- 단독 진입 금지 +- fundamentals 보강이 있을 때만 참고 + +## 4.16 `M_AND_A_CONTEXTUAL` +### 정의 +- 인수/합병 발표, 전략적 검토 +### v1 처리 +- 현 단계 자동매매 제외 +- 특수상황 전략으로 분리 + +## 5. 제외 클래스 규칙 + +### 5.1 `ANALYST_ONLY` +- sell-side 의견 변경만 있는 경우 +- 기업 원문 없음 +- 제외 + +### 5.2 `RUMOR_ONLY` +- 확인되지 않은 보도/소문 +- 제외 + +### 5.3 `SOCIAL_ONLY` +- 유튜브/레딧/커뮤니티 언급만 있고 기업 원문 없음 +- 제외 + +### 5.4 `NON_MATERIAL_PR` +- 홍보성 PR +- 실제 사업/재무 영향 불명확 +- 제외 + +### 5.5 `IRRELEVANT_FILING` +- 거래 아이디어와 직접 무관한 정기/행정 문서 +- 제외 + +### 5.6 `DUPLICATE_EVENT` +- 동일 사건의 중복 filing 또는 기사 재배포 +- 중복 제거 + +## 6. 라벨링 공통 속성 + +모든 이벤트는 아래 속성을 가집니다. + +- `event_class` +- `event_direction` = positive / negative / mixed / neutral +- `materiality` = low / medium / high +- `novelty` = low / medium / high +- `persistence` = 1d / 3d / 5d+ 예상 +- `document_confidence` = 0.0 ~ 1.0 +- `price_confirmation` = pass / fail / pending +- `live_eligible_v1` = true / false +- `oneoff_flag` = none / suspected / confirmed +- `needs_human_review` = true / false + +## 7. 문서 해석 세부 규칙 + +### 7.1 긍정 판단 핵심 키워드 +- guidance raised / above prior outlook +- demand remains strong +- backlog increased +- bookings accelerated +- customer additions / expansion +- pricing strength / price realization +- margin expansion +- durable / recurring / multi-year / ramp + +### 7.2 부정 판단 핵심 키워드 +- lower outlook / revised down +- demand softness / elongated sales cycles +- inventory correction +- financing need / offering / convertible +- impairment / restructuring charges +- regulatory setback / delay +- liquidity concern + +### 7.3 one-off 패널티 신호 +- tax benefit +- fair value gain +- gain on sale +- insurance recovery +- litigation settlement +- non-operating adjustment +- aggressive non-GAAP exclusion + +## 8. 가격 확인 규칙과 taxonomy 관계 +이 taxonomy는 이벤트 자체만이 아니라 **가격 확인과 결합될 때만 거래 클래스가 완성**됩니다. + +예시: +- `EARNINGS_GUIDANCE_POSITIVE` + strong reaction day + volume surge → live candidate +- `MATERIAL_CONTRACT_POSITIVE` + 약한 가격 반응 → watchlist only +- `SOCIAL_ONLY` + 강한 급등 → excluded + +## 9. Hold horizon 힌트 +이벤트 클래스는 기본 보유 힌트를 가집니다. + +- `EARNINGS_GUIDANCE_POSITIVE` → 2~5일 +- `MATERIAL_CONTRACT_POSITIVE` → 2~5일 +- `CUSTOMER_EXPANSION_POSITIVE` → 2~5일 +- `REGULATORY_OR_PRODUCT_APPROVAL_POSITIVE` → 1~3일 또는 특수상황 +- `BACKLOG_OR_BOOKINGS_ACCELERATION` → 3~5일 +- negative/contextual classes → v1 live 미사용 + +## 10. Human review가 필요한 대표 사례 +- 강한 headline이지만 본문에 재무 영향이 없음 +- 비GAAP 호전이 있으나 cashflow/매출이 약함 +- 가이던스가 수치 없이 질적으로만 긍정 +- 동일 문서 내 긍정/부정 표현 혼재 +- 8-K와 99.1의 톤이 다름 +- 고객명 미공개/계약규모 미확정 +- product launch가 실제 출하/매출 시점과 멀다 + +## 11. 파서 출력 JSON 최소 스키마 예시 + +```json +{ + "event_class": "EARNINGS_GUIDANCE_POSITIVE", + "event_direction": "positive", + "materiality": "high", + "novelty": "high", + "persistence": "3d", + "guidance_direction": "up", + "demand_strength": "strong", + "pricing_power": "present", + "backlog_mentions": true, + "customer_expansion": true, + "oneoff_flag": "none", + "management_tone": "strong_positive", + "document_confidence": 0.88, + "live_eligible_v1": true, + "needs_human_review": false +} +``` + +## 12. 사후 복기(post-trade review)용 실패 코드 +- `FALSE_POSITIVE_EVENT_QUALITY` +- `ONEOFF_MISREAD` +- `PRICE_CONFIRMATION_TRAP` +- `GAP_TOO_EXTENDED` +- `MARKET_REGIME_OVERRULED` +- `SECTOR_WEAKNESS` +- `LIQUIDITY_SLIPPAGE` +- `SOCIAL_NOISE_OVERWEIGHTED` +- `TIME_DECAY_NO_FOLLOW_THROUGH` + +## 13. 변경 관리 +- 새로운 이벤트 클래스를 추가하려면: + 1. 최소 1개 명확한 예시 문서 + 2. live eligibility 여부 + 3. false positive 패턴 + 4. hold horizon 가설 + 5. backtest 라벨 정의 +를 함께 제출해야 합니다. + +## 부록 A. v1 실거래 허용 클래스 요약 +- `EARNINGS_GUIDANCE_POSITIVE` +- `EARNINGS_GUIDANCE_MIXED_POSITIVE` (조건부) +- `MATERIAL_CONTRACT_POSITIVE` +- `CUSTOMER_EXPANSION_POSITIVE` +- `REGULATORY_OR_PRODUCT_APPROVAL_POSITIVE` (선별) +- `BACKLOG_OR_BOOKINGS_ACCELERATION` + +## 부록 B. v1 실거래 제외 클래스 요약 +- 모든 순수 부정 이벤트 +- 모든 루머/소셜 단독 이벤트 +- analyst-only +- management change 단독 +- capital allocation 단독 +- M&A 특수상황 + +## 부록 C. 참고 링크 +- SEC EDGAR APIs: https://www.sec.gov/search-filings/edgar-application-programming-interfaces +- SEC Accessing EDGAR Data: https://www.sec.gov/search-filings/edgar-search-assistance/accessing-edgar-data diff --git a/dev/phase0/risk_policy.md b/dev/phase0/risk_policy.md new file mode 100644 index 0000000..a7aebc2 --- /dev/null +++ b/dev/phase0/risk_policy.md @@ -0,0 +1,285 @@ +# risk_policy.md + +- 문서명: Risk Policy +- 프로젝트 코드명: **ACE-F v1** +- 상태: Draft for Phase 0 Sign-off +- 버전: 0.1 +- 작성일: 2026-03-12 +- 목적: 자동매매 시스템의 **자본 보존, 운영 안전성, 데이터 이상 대응, 손실 제한** 정책을 고정합니다. + +> 원칙: 수익은 나중에 따라오더라도, 자동화 초기에는 **큰 손실, 운영 사고, 규칙 이탈**을 먼저 막습니다. + +## 1. 리스크 관리 철학 + +1. **생존 우선** + - 전략이 아무리 좋아도 운영 실수로 계좌가 훼손되면 실패로 간주합니다. + +2. **자동화보다 통제 우선** + - 시스템이 판단할 수 없는 상황에서는 no-trade가 기본입니다. + +3. **정확도보다 일관성 우선** + - 수익률을 높이기 위해 지나치게 공격적인 포지션 확대를 허용하지 않습니다. + +4. **리스크는 다층 구조** + - 종목 리스크, 섹터 리스크, 시장 리스크, 데이터 리스크, 모델 리스크, 실행 리스크를 분리합니다. + +## 2. 적용 범위 + +본 정책은 아래 항목에 적용됩니다. + +- 신호 생성 +- 후보 종목 선정 +- 포지션 사이징 +- 주문 제출 +- 포지션 보유 +- 청산 +- 장애 대응 +- paper trading 및 live trading + +## 3. 계좌 및 전략 범위 리스크 정책 + +### 3.1 v1 허용 범위 +- 미국 보통주 +- 롱 온리 +- 기본 보유 1~5거래일 +- 이벤트 기반 continuation 전략 + +### 3.2 v1 금지 범위 +- 옵션 / 0DTE / 선물 / 레버리지 ETF +- 자동 숏 +- same-day exit가 핵심인 intraday 전략 +- borrow/locate가 필요한 포지션 +- 데이터 출처가 불명확한 소셜 단독 진입 + +### 3.3 계좌 정책 +- v1은 daytrade 빈도에 의존하지 않습니다. +- 전략의 기본 구조는 **다음 세션 진입 + overnight 보유 가능** 구조입니다. +- 현금계좌 전용 settled cash 엔진은 별도 전략으로 분리하며, 본 정책의 기본 운영 범위에 포함하지 않습니다. + +## 4. 포지션 사이징 정책 + +### 4.1 Paper trading 기본값 +- 거래당 허용 손실: 계좌 기준 **0.35%** +- 동시 최대 포지션 수: **3개** +- 동일 섹터 최대 포지션 수: **2개** +- 총 신규 위험 합계: **1.0%/일 이하** + +### 4.2 초기 live trading 기본값 +- 거래당 허용 손실: 계좌 기준 **0.10% ~ 0.20%** +- 동시 최대 포지션 수: **2개** +- 동일 섹터 동시 보유: **1개** 권장 +- 총 신규 위험 합계: **0.50%/일 이하** + +### 4.3 포지션 크기 산정 원칙 +포지션 크기는 다음 요소로 산정합니다. +- 진입가 +- 초기 손절가 +- 주당 위험 +- 계좌 허용 손실 +- 유동성 한도 +- 일일 총위험 잔여량 + +### 4.4 유동성 기반 제한 +- 하루 평균 거래대금이 낮거나 스프레드가 넓으면 포지션 크기를 축소합니다. +- 한 종목에서 일평균 거래량 대비 과도한 참여율을 금지합니다. +- 시가총액/유동성 필터를 통과해도, 이벤트일의 체결 환경이 나쁘면 no-trade 처리합니다. + +## 5. 진입 리스크 정책 + +### 5.1 진입 허용 조건 +다음 조건을 모두 충족해야 진입 가능합니다. +- 공식 이벤트 존재 +- 가격 확인 통과 +- 시장 레짐 허용 범위 +- 데이터 최신성 확인 +- LLM 또는 규칙 파서의 신뢰도 기준 충족 +- 리스크 예산 잔여 +- 포트폴리오 집중도 한도 미초과 + +### 5.2 진입 금지 조건 +아래 중 하나라도 해당하면 신규 진입 금지입니다. +- 데이터 수집 실패 또는 stale 상태 +- 원문 문서 해석 충돌 +- 거래대금/스프레드 조건 미달 +- 이벤트의 materiality 불명확 +- 장 시작 직후 과도한 갭 과열 +- 시장이 급격한 risk-off 상태 +- 동일 섹터 익스포저 과다 +- 동일 종목 재진입 쿨다운 미충족 + +## 6. 손절/청산 정책 + +### 6.1 초기 손절 +초기 손절은 다음 중 더 보수적인 값을 사용합니다. +- 반응일 저가 또는 확인된 구조적 지지 이탈 +- ATR 기반 손절 + +### 6.2 시간 청산 +- 이벤트 성격상 follow-through가 빨리 나와야 하는 전략입니다. +- 기본 최대 보유는 5거래일입니다. +- 2~3거래일 내 반응이 없으면 조기 종료 가능합니다. + +### 6.3 부분 익절 +- +1.2R ~ +1.5R 구간에서 일부 익절 허용 +- 남은 물량은 trailing/시간 청산 규칙 적용 +- 부분 익절은 성과 안정화 목적이며, 전체 전략의 기대값을 훼손하면 비활성화할 수 있습니다. + +### 6.4 비상 청산 +아래 상황에서는 전략 시그널과 무관하게 비상 청산이 가능합니다. +- 브로커/포지션 상태 불일치 +- 거래정지/상장위험 공시 +- 데이터 이상으로 stop 추적 불가 +- 시장 구조적 쇼크 +- 법적/규제성 악재 + +## 7. 포트폴리오 리스크 정책 + +### 7.1 집중도 제한 +- 동일 섹터: 최대 2개 (paper), 최대 1개 권장 (초기 live) +- 동일 테마 집중: 금지 +- 단일 종목 비중 과대화 금지 + +### 7.2 시장 레짐 제한 +- risk-off 환경에서는 신규 진입 수를 줄입니다. +- 고변동성 구간에서는 포지션 크기를 자동 축소합니다. +- 레짐이 매우 불안정하면 신규 진입 중단 후 보유분만 관리합니다. + +### 7.3 상관 리스크 +- 서로 다른 종목이라도 동일 테마/공급망/반도체/AI 인프라처럼 높은 상관이 있으면 동일 버킷으로 계산합니다. +- 시스템은 종목 수가 아니라 **실질적 독립 베팅 수**를 관리해야 합니다. + +## 8. 손실 제한 장치 + +### 8.1 일일 브레이크 +- 실현손실 + 미실현 위험 기준 **-1.0R 또는 계좌 -1.0%** 수준 도달 시 신규 진입 중단 +- 이후는 기존 포지션 관리만 허용 + +### 8.2 주간 브레이크 +- 5거래일 롤링 손실이 **계좌 -2.5%** 도달 시 전략 일시중지 및 원인 점검 + +### 8.3 연속 손실 브레이크 +- 연속 3손실 시 신규 진입 1거래일 중지 +- 연속 5손실 시 전략 재검토 전까지 paper 모드로 강등 + +### 8.4 운영 사고 브레이크 +다음 중 하나라도 발생하면 즉시 kill switch: +- 중복 주문 +- 잘못된 심볼 체결 +- 손절 미작동 +- 미확인 포지션 잔존 +- stale data 기반 주문 제출 +- 브로커 응답 불일치 누적 + +## 9. 데이터/모델 리스크 정책 + +### 9.1 데이터 최신성 +- 핵심 소스(SEC, 시세, 주문 상태)가 stale이면 신규 주문 금지 +- 이벤트 발생 시각과 반응일 귀속이 불명확하면 보수적으로 제외 + +### 9.2 데이터 품질 +- 필수 필드 누락 시 no-trade +- 동일 이벤트에 대해 원문과 파서 결과가 충돌하면 human review 또는 no-trade +- 소셜/뉴스 overlay는 누락되어도 core 전략은 동작 가능해야 합니다. + +### 9.3 모델 리스크 +- LLM 출력은 항상 구조화 JSON + confidence 포함 +- confidence 미달 또는 schema 불일치 시 fallback 규칙 사용 +- LLM만으로 주문을 생성하지 않습니다. + +### 9.4 드리프트 감시 +- 파서 결과 분포가 급변하면 경보 +- 종목군/섹터별 성과 붕괴 시 전략 중단 검토 +- attention feature가 갑자기 과도한 영향력을 갖기 시작하면 weight 재점검 + +## 10. 실행 리스크 정책 + +### 10.1 주문 원칙 +- 단순 주문 우선 +- 과도한 주문 수정/취소 반복 금지 +- 체결 불확실성이 큰 상황에서는 공격적인 추격 금지 + +### 10.2 체결 모니터링 +- 주문 제출, 접수, 부분 체결, 전량 체결, 취소, 거부를 모두 로그화 +- 브로커 상태와 내부 포지션 상태를 정기 대사(reconciliation) +- 체결 지연 또는 거부 사유는 사후 리뷰에 포함 + +### 10.3 거래시간 정책 +- 휴장/조기폐장/이벤트일 비정상 시장 상태를 캘린더로 관리 +- 장 전·장 후는 v1 핵심 전략의 주문 시간대로 사용하지 않습니다. +- 반응일 종가 계산은 정규장 기준으로 통일합니다. + +## 11. 사람 개입 정책 + +### 11.1 Human-in-the-loop +- paper 단계에서는 수동 확인을 일부 허용 +- 초기 live 단계에서는 자동 진입이라도 **사전/사후 알림** 필수 +- 운영자는 언제든 kill switch를 사용할 수 있어야 합니다. + +### 11.2 수동 개입 허용 범위 +- 브로커 장애 +- 데이터 오류 +- 기업 이벤트 해석 충돌 +- 규정/리스크 위반 가능성 +- 예상치 못한 포지션 상태 불일치 + +### 11.3 수동 개입 기록 +- 개입 사유 +- 개입 시각 +- 개입 전후 포지션 상태 +- 손익 영향 +- 재발방지 액션 + +## 12. 배포 게이트 + +### 12.1 Paper → Live 전환 조건 +- 최소 8주 이상의 paper 운영 +- 데이터 누락/중복 주문/상태 불일치가 통제 가능한 수준 +- 전략 손실보다 운영 사고 손실이 작을 것 +- post-trade review가 자동 생성될 것 +- 파서 실패 fallback이 검증될 것 + +### 12.2 Live 증액 조건 +- 최소 40~80건 이상 실거래 표본 확보 +- outlier 몇 건이 아닌 분포 전체에서 기대값 유지 +- 슬리피지/체결 품질 악화 없음 +- 운영 개입 비율 감소 + +## 13. 필수 로그 및 감사 항목 +- signal_id +- event_id +- source provenance +- feature snapshot +- order decision reason +- risk budget snapshot +- submitted/filled/cancelled timestamps +- override 여부 +- post-trade label + +## 14. 사고 대응 원칙 +1. 포지션 안전 확인 +2. 신규 주문 중지 +3. 데이터/브로커 상태 대사 +4. 원인 구분 (데이터/모델/브로커/코드) +5. 재발 방지 조치 문서화 +6. 심각도에 따라 live → paper 강등 + +## 15. 변경 관리 +- 손실 한도, 포지션 한도, live 게이트 변경은 본 문서 버전업이 필요합니다. +- 공격적 리스크 확대는 최소 1개 월간 리뷰와 1개 안정적인 paper/live 검증 뒤에만 허용합니다. + +## 부록 A. 운영 기본값 요약 +- v1 실거래: 롱 온리 +- 거래당 허용손실: 0.10%~0.20% (초기 live) +- 동시 포지션: 2개 권장 +- 총 신규위험: 0.50%/일 이하 +- 일일 브레이크: 계좌 -1.0% +- 주간 브레이크: 계좌 -2.5% +- 연속 3손실: 1일 pause +- 연속 5손실: paper 강등 검토 + +## 부록 B. 참고 링크 +- SEC EDGAR APIs: https://www.sec.gov/search-filings/edgar-application-programming-interfaces +- SEC Developer Resources: https://www.sec.gov/about/developer-resources +- Alpaca API rate limit support note: https://alpaca.markets/support/usage-limit-api-calls +- Alpaca Market Data API: https://docs.alpaca.markets/docs/about-market-data-api +- FINRA short sale volume: https://www.finra.org/finra-data/browse-catalog/short-sale-volume-data/daily-short-sale-volume-files diff --git a/dev/phase0/strategy_spec.md b/dev/phase0/strategy_spec.md new file mode 100644 index 0000000..91c8a44 --- /dev/null +++ b/dev/phase0/strategy_spec.md @@ -0,0 +1,232 @@ +# strategy_spec.md + +- 문서명: Strategy Specification +- 프로젝트 코드명: **ACE-F v1** (AI Catalyst Event Engine — Free Data) +- 상태: Draft for Phase 0 Sign-off +- 버전: 0.1 +- 작성일: 2026-03-12 +- 목적: 무료 데이터만 사용하는 미국 주식 중단기 자동매매 시스템의 **범위, 목표, 운용 전제, 우선순위**를 고정합니다. + +> 본 문서는 개발 기준서입니다. 법률·세무·투자자문 문서가 아니며, 실거래 전에는 브로커 정책과 시장 규정 재검증이 필요합니다. + +## 1. 프로젝트 목적 + +본 프로젝트의 목적은 다음과 같습니다. + +1. **무료 데이터만** 사용하여 미국 주식의 1~5거래일 중단기 이벤트 드리프트를 자동으로 탐지합니다. +2. LLM/AI는 가격 예측기가 아니라 **공시·뉴스·문서 해석기**로 사용합니다. +3. 전략의 중심은 **공식 이벤트 + 가격 반응 확인 + 리스크 통제**입니다. +4. 초기 버전은 **수익률 최대화보다 재현성, 운영 안정성, 확장 가능성**을 우선합니다. + +## 2. 전략 정의 + +### 2.1 전략 한 줄 정의 +**“공식 기업 이벤트가 발생했고, 그 이벤트의 질이 높으며, 첫 정규장 반응이 강하게 확인된 종목만 다음 세션에 진입하여 1~5거래일 보유하는 이벤트 기반 continuation 전략”** + +### 2.2 전략 핵심 가설 +- 기업이 직접 배포한 공시/첨부문서에는 구조적인 정보가 남아 있습니다. +- 무료로 구할 수 없는 sell-side 컨센서스가 없더라도, **회사의 자체 가이던스 대비 결과**, 수요/마진/백로그/고객 확대 등은 충분히 구조화할 수 있습니다. +- 모든 뉴스가 아니라 **회사 원문 + 가격 확인**이 붙은 사건만 거래하면 무료 데이터 환경에서도 실행 가능성이 높습니다. +- 유튜브/뉴스/검색량/소셜은 단독 진입 신호가 아니라 **관심 확산 속도(attention)** 를 측정하는 보조 레이어로 쓸 수 있습니다. + +## 3. 범위 고정 + +### 3.1 포함 범위 +- 시장: 미국 상장 주식 +- 종목군: NYSE / Nasdaq 보통주 중심 +- 보유기간: 기본 1~5거래일 +- 전략 유형: 이벤트 기반 continuation swing +- 데이터 정책: **유료 데이터 금지** +- 모델 정책: 규칙 기반 + LLM 문서 해석 + 랭킹 모델 하이브리드 + +### 3.2 제외 범위 +- 옵션, 0DTE, 레버리지 ETF, 선물, 암호화폐 +- 순수 intraday scalp / 초단타 HFT +- X/Twitter API 의존 전략 +- 유료 earnings estimate / transcript API 의존 전략 +- 소셜만 보고 진입하는 전략 +- 스크레이핑 정책이 불명확하거나 금지 가능성이 큰 비공식 소스 의존 구조 + +### 3.3 v1 거래 방향 +- **실거래 v1은 롱 온리** +- 숏 이벤트 분류는 연구용으로는 수집하되, 자동 실거래 엔진에는 넣지 않습니다. +- 숏 전략은 borrow / locate / hard-to-borrow / 급등 squeeze 리스크 때문에 **Phase 8 이후 별도 엔진**으로 분리합니다. + +## 4. 타겟 유니버스 + +### 4.1 기본 유니버스 조건 +- 미국 보통주 +- 주가 $15 이상 +- 시가총액 $2B 이상 +- 최근 20거래일 평균 거래대금 $50M 이상 +- 평균 스프레드 과도 종목 제외 +- 거래정지, 저유동성, 상장폐지 위험 종목 제외 + +### 4.2 초기 제외군 +- ETF / ETN / ADR / SPAC +- low-float 테마주 +- 초소형 바이오 / 단일 임상 의존 종목 +- 만성적 희석(dilution) 이슈 종목 +- 기업 원문보다 소셜 노이즈가 큰 종목 + +## 5. 핵심 이벤트 범위 + +### 5.1 v1에서 직접 거래하는 이벤트 +1. **실적 발표 + 가이던스** +2. **8-K 기반 대형 계약/수주** +3. **명확한 제품/규제 승인** +4. **고객 확대/백로그/예약 증가가 명시된 기업 이벤트** + +### 5.2 연구만 하고 실거래는 보류하는 이벤트 +- 경영진 교체 +- 자사주매입 단독 이벤트 +- 배당 정책 변경 +- M&A 루머 +- analyst upgrade/downgrade +- 소셜 주도 밈 확산 + +## 6. 전략 구조 + +### 6.1 신호 구조 +최종 랭킹은 아래 네 층으로 구성합니다. + +1. **Event Quality Layer** + - 문서 원문에서 이벤트의 질을 평가 + - 가이던스 방향성, 수요 강도, 마진 질, 고객 확대, one-off 여부 등 + +2. **Price Confirmation Layer** + - 첫 정규장 반응일의 가격 강도 확인 + - 수익률, 거래량, 종가 위치, 갭 과열 여부, 섹터 상대강도 + +3. **Market Regime Layer** + - 시장 전체의 risk-on / neutral / risk-off 상태 + - 지수 추세 및 변동성 필터 + +4. **Attention Overlay Layer** + - Yahoo RSS, YouTube, Wikimedia, FINRA short volume 등으로 관심 확산 강도 보조 판정 + +### 6.2 기본 의사결정 규칙 +- **공식 이벤트 없으면 거래하지 않습니다.** +- 공식 이벤트가 있어도 **가격 확인이 약하면 거래하지 않습니다.** +- attention이 강해도 공식 이벤트가 없으면 **거래하지 않습니다.** +- 이벤트 해석과 가격 해석이 충돌하면 **보수적으로 no-trade** 처리합니다. + +## 7. 진입/보유/청산 원칙 + +### 7.1 진입 원칙 +- 기본 진입은 **첫 정규장 반응일 마감 후 점수 계산 → 다음 세션 시초 부근 진입** +- 무료 데이터 기준 v1에서는 **초단타 ORB 진입을 핵심 엔진으로 채택하지 않습니다.** +- intraday ORB는 향후 add-on confirmation 용도로만 연구합니다. + +### 7.2 보유기간 원칙 +- 기본 보유: 2~3거래일 +- 확장 보유: 최대 5거래일 +- 강한 이벤트 + 강한 섹터 + 강한 attention이 동시에 있을 때만 5일 쪽으로 연장 +- 기대한 follow-through가 없으면 조기 종료 + +### 7.3 청산 원칙 +- 손절: 이벤트 반응일 저가 / ATR 기반 손절 중 보수적인 값 사용 +- 일부 익절: +1.2R ~ +1.5R 구간에서 부분 청산 가능 +- 잔여분: 전일 저가 이탈 또는 시간 청산 +- 시간 청산: 최대 보유기간 초과 시 강제 종료 + +## 8. 계좌/실행 정책 + +### 8.1 계좌 정책 +- v1 연구/시뮬레이션 기준 계좌는 미국 주식 계좌를 가정합니다. +- 전략은 **same-day round trip이 필수가 아닌 구조**로 설계합니다. +- 현금계좌 전용 daytrade 엔진은 v1 범위에 넣지 않습니다. +- 브로커는 Phase 6에서 최종 확정하되, Phase 1~6의 기본 reference는 **Alpaca paper** 입니다. + +### 8.2 실행 정책 +- 실거래 전까지는 **paper trading 우선** +- 주문 유형은 단순한 market / limit / stop 기반으로 제한 +- VWAP/TWAP/DMA 같은 고급 실행은 v1 범위 밖 +- 데이터 지연이나 주문 상태 불일치가 있으면 no-trade 또는 kill switch + +## 9. 모델 정책 + +### 9.1 LLM 역할 +- 공시와 첨부문서 해석 +- one-off/비반복성 요인 탐지 +- 가이던스 톤/수요 강도/고객 확대 등 구조화 +- 거래 후 복기 자동화 + +### 9.2 LLM이 하지 않는 일 +- raw price return 계산 +- 컨센서스 추정치 계산 +- 주문 가격 산정 단독 결정 +- 무근거 방향 예측 + +### 9.3 최종 선택 로직 +- 규칙 기반 필터 → LLM 구조화 → 숫자 feature 결합 → 랭킹 모델 → 포트폴리오 엔진 순으로 진행 +- LLM 출력 단독으로는 주문을 허용하지 않습니다. + +## 10. KPI와 성공 기준 + +### 10.1 전략 KPI +- 양의 기대값(Expectancy > 0) +- 보수적 슬리피지 반영 후 샤프 개선 여부 +- MDD(최대낙폭) 허용 범위 내 유지 +- 과도한 섹터 편중 없음 +- 거래 빈도와 수익 기여가 소수 사례에만 의존하지 않을 것 + +### 10.2 운영 KPI +- 데이터 수집 실패율 +- 파싱 실패율 +- 주문 상태 불일치 건수 +- 중복 주문 / 누락 주문 건수 +- 수동 개입 비율 +- paper/live 괴리 정도 + +### 10.3 Live 진입 전 게이트 +- 최소 8주 이상의 안정적인 paper trading +- 치명적 운영 사고 0건 +- 원인 설명 불가능한 대규모 손실 0건 +- 후보 생성부터 복기까지 전체 파이프라인 자동 동작 확인 + +## 11. 명시적 비목표 (Non-goals) +- “매일 반드시 거래하는 전략” 만들기 +- 유튜브/소셜 바이럴 종목만 쫓는 전략 만들기 +- intraday 경쟁속도를 데이터 품질로 극복하는 전략 만들기 +- 초기에 너무 많은 자산군/브로커/전략을 동시에 지원하기 + +## 12. Phase 0에서 동결하는 의사결정 +- 무료 데이터만 사용 +- 미국 주식만 사용 +- 1~5거래일 이벤트 continuation이 메인 +- 공식 이벤트 + 가격 확인이 핵심 +- attention 데이터는 overlay +- 실거래 v1은 롱 온리 +- same-day 필수 구조 금지 +- paper trading을 거치지 않고 live로 가지 않음 + +## 13. Phase 1~2로 넘기는 오픈 항목 +- 정확한 랭킹 가중치 +- 섹터별 유니버스 미세 조정 +- XBRL 기반 재무 feature 상세 정의 +- 브로커 live 전환 시점 +- LLM 모델 선택과 비용 상한 + +## 14. 변경 관리 +- 본 문서의 변경은 `docs/strategy_spec.md` 버전업으로만 수행합니다. +- 범위 변경이 전략 구조를 흔드는 경우 `risk_policy.md`와 `data_source_policy.md`를 함께 갱신해야 합니다. +- v1 범위를 벗어나는 변경(예: 숏/옵션/유료 데이터 추가)은 **새 전략 버전**으로 분기합니다. + +## 부록 A. 현재 전략의 핵심 외부 전제 +- SEC EDGAR API는 인증 없이 filings/XBRL JSON 접근을 제공하고, 실시간 업데이트와 bulk 배포를 지원합니다. +- SEC는 공정접속을 위해 최대 접근률을 제한합니다. +- Alpaca Basic은 무료 기본 플랜이며, 주식의 무료 실시간 데이터는 IEX 중심입니다. +- YouTube, Wikimedia, Yahoo RSS 등은 attention overlay용 보조 레이어로만 사용합니다. + +## 부록 B. 참고 링크 +- SEC EDGAR APIs: https://www.sec.gov/search-filings/edgar-application-programming-interfaces +- SEC Developer Resources: https://www.sec.gov/about/developer-resources +- SEC Accessing EDGAR Data: https://www.sec.gov/search-filings/edgar-search-assistance/accessing-edgar-data +- Alpaca Market Data API: https://docs.alpaca.markets/docs/about-market-data-api +- Alpaca API rate limit support note: https://alpaca.markets/support/usage-limit-api-calls +- FINRA short sale volume: https://www.finra.org/finra-data/browse-catalog/short-sale-volume-data/daily-short-sale-volume-files +- Wikimedia pageviews: https://doc.wikimedia.org/generated-data-platform/aqs/analytics-api/reference/page-views.html +- YouTube quota cost: https://developers.google.com/youtube/v3/determine_quota_cost +- Google Trends API alpha: https://developers.google.com/search/apis/trends +- Yahoo Finance RSS: https://finance.yahoo.com/rss/ diff --git a/dev/phase1_deliverables/README.md b/dev/phase1_deliverables/README.md new file mode 100644 index 0000000..937147a --- /dev/null +++ b/dev/phase1_deliverables/README.md @@ -0,0 +1,104 @@ +# Phase 1 개발문서 패키지 + +이 문서는 **Phase 0에서 고정한 전략/리스크/데이터 정책**을 바탕으로, AI 코딩 에이전트가 실제 구현을 시작할 수 있도록 만든 **Phase 1 상세 개발문서**입니다. + +## 목표 + +Phase 1의 목표는 아래 4가지를 확정하는 것입니다. + +1. **개발 환경과 저장 구조를 표준화**한다. +2. **데이터 소스별 adapter 계약**을 정의한다. +3. **DB/파일 스키마**를 고정한다. +4. **파서 출력 형식과 테스트 기준**을 고정한다. + +## 포함 문서 + +- `architecture_and_repo_plan.md` + - 전체 아키텍처 + - 모듈 경계 + - 저장소 구조 + - 서비스별 책임 + - 실행 흐름 + - 환경변수 정책 +- `implementation_plan.md` + - AI 코딩 에이전트용 작업 순서 + - 선행조건 + - 단계별 산출물 + - 완료 조건 + - 작업 분할 단위 +- `db_schema.md` + - PostgreSQL 운영 테이블 + - DuckDB/Parquet 연구용 저장 규칙 + - 인덱스/중복 방지 키 + - 상태 테이블 설계 + - SQL DDL 초안 +- `service_contracts.md` + - source adapter 계약 + - normalize/parser/feature builder 계약 + - 실행 엔진 계약 + - job 상태 기록 방식 + - 표준 에러/로그 포맷 +- `parser_json_schema.md` + - 이벤트 파서 출력 JSON 규격 + - 필드 정의 + - 필수/선택 항목 + - confidence와 provenance 정책 +- `parser_event.schema.json` + - 실제 JSON Schema 초안 +- `testing_checklist.md` + - 단위 테스트 + - 통합 테스트 + - 리플레이 테스트 + - 데이터 검증 + - 운영 전 점검표 +- `coding_rules.md` + - 코딩 규칙 + - 예외 처리 원칙 + - idempotency 원칙 + - retry/timeout 정책 + - 보안/비밀값 처리 원칙 + +## Phase 1 범위 + +Phase 1에서는 아래까지만 구현합니다. + +- 로컬 재현 가능한 개발환경 +- PostgreSQL/DuckDB 연결 +- SEC / Alpaca / FRED / FINRA adapter 기본 골격 +- raw/staging/structured 저장 파이프라인 +- 이벤트 문서 파서 입출력 규격 +- job 실행/에러/재시도 표준 +- 최소한의 테스트 자동화 + +## Phase 1에서 일부러 하지 않는 것 + +- 실거래 주문 로직의 full implementation +- YouTube/Yahoo/Wikimedia attention layer 본 구현 +- 대시보드 UI +- LLM 최적화 +- 포트폴리오 최적화 +- 숏 전략 +- 초단타 intraday engine + +## 권장 실행 순서 + +1. `architecture_and_repo_plan.md` 읽기 +2. `coding_rules.md` 읽기 +3. `db_schema.md` 기반으로 DB migration 작성 +4. `service_contracts.md` 기반으로 adapter skeleton 작성 +5. `parser_event.schema.json` 기준으로 parser I/O 고정 +6. `implementation_plan.md` 순서대로 구현 +7. `testing_checklist.md`로 검증 + +## 완료 기준 + +Phase 1 종료 시 아래가 가능해야 합니다. + +- 한 명의 개발자가 로컬에서 전체 환경을 띄울 수 있다. +- SEC filing 하나를 수집해서 raw/staging/structured로 적재할 수 있다. +- Alpaca 일봉/분봉 데이터를 정규화 테이블에 넣을 수 있다. +- FRED/FINRA 데이터를 일자별로 수집할 수 있다. +- parser가 schema-valid JSON을 반환한다. +- 중복 실행 시 데이터가 두 번 쌓이지 않는다. +- 실패한 job을 안전하게 재시도할 수 있다. +- 최소 단위 테스트와 통합 테스트가 자동으로 돈다. diff --git a/dev/phase1_deliverables/architecture_and_repo_plan.md b/dev/phase1_deliverables/architecture_and_repo_plan.md new file mode 100644 index 0000000..f5fc8e8 --- /dev/null +++ b/dev/phase1_deliverables/architecture_and_repo_plan.md @@ -0,0 +1,289 @@ +# Phase 1 아키텍처 및 저장소 구조 계획 + +## 1. 설계 원칙 + +이 프로젝트는 무료 데이터 기반의 미국 주식 이벤트 스윙 시스템입니다. +Phase 1의 핵심 원칙은 다음과 같습니다. + +1. **원문(raw)을 절대 버리지 않는다.** +2. **운영 상태(PostgreSQL)와 연구용 시계열(DuckDB/Parquet)을 분리한다.** +3. **모든 adapter는 idempotent 해야 한다.** +4. **LLM은 규칙 기반 파서를 대체하지 않고 보강한다.** +5. **실패 시 no-trade/no-write가 기본값이다.** +6. **모든 서비스는 독립적으로 재실행 가능해야 한다.** +7. **표준화된 event/document identifier 없이는 downstream으로 보내지 않는다.** + +## 2. 권장 저장소 구조 + +```text +repo/ + apps/ + collector/ + sec_collector/ + alpaca_collector/ + fred_collector/ + finra_collector/ + parser/ + filing_parser/ + xbrl_parser/ + event_parser/ + feature_builder/ + market_features/ + event_features/ + backtester/ + live_trader/ + libs/ + adapters/ + sec/ + alpaca/ + fred/ + finra/ + common/ + config.py + logging.py + time_utils.py + ids.py + retries.py + file_store.py + db/ + models.py + migrations/ + schemas/ + parser_event.schema.json + source_record.schema.json + llm/ + prompts/ + validator.py + cache.py + tests/ + unit/ + integration/ + replay/ + fixtures/ + configs/ + app.yaml + env.example + symbols.yaml + data/ + raw/ + staging/ + parquet/ + docs/ +``` + +## 3. 실행 단위 + +초기에는 서비스별 독립 실행 파일로 둡니다. + +- `python -m apps.collector.sec_collector.main` +- `python -m apps.collector.alpaca_collector.main` +- `python -m apps.parser.event_parser.main` +- `python -m apps.feature_builder.market_features.main` + +초기에는 큐 시스템을 도입하지 않고, **명시적 스케줄 실행**을 사용합니다. +큐는 Phase 3 이후 필요 시 Redis/Celery 또는 경량 작업 큐를 검토합니다. + +## 4. 저장 계층 + +### 4.1 Raw Zone + +원문을 그대로 저장합니다. + +경로 예시: + +```text +data/raw/sec/2026-03-12/{cik}/{accession}/index.json +data/raw/sec/2026-03-12/{cik}/{accession}/filing.txt +data/raw/sec/2026-03-12/{cik}/{accession}/exhibit_99_1.html +data/raw/alpaca/bars/daily/2026-03-12/{symbol}.json +data/raw/fred/2026-03-12/{series_id}.json +data/raw/finra/2026-03-12/daily_short_sale_volume.txt +``` + +원칙: +- 원문은 수정하지 않습니다. +- 적재 시 수집 메타데이터를 sidecar JSON으로 함께 저장합니다. +- raw 저장 성공 전에는 structured write를 하지 않습니다. + +### 4.2 Staging Zone + +파싱 전 정규화 중간 결과를 저장합니다. + +예: +- HTML → text 추출 +- filing metadata +- extracted exhibit list +- temporary parsed items + +### 4.3 Structured Zone + +운영용 PostgreSQL + 연구용 Parquet로 저장합니다. + +PostgreSQL: +- job 상태 +- 문서 메타 +- 이벤트 레코드 +- parser 결과 +- feature snapshot +- 주문/포지션 상태 + +Parquet/DuckDB: +- 바 시계열 +- 대량 feature matrix +- 라벨링 데이터 +- 실험/백테스트 산출물 + +## 5. 모듈 경계 + +### 5.1 Source Adapter + +역할: +- 외부 원천에서 raw 데이터를 수집 +- 최소 메타데이터 부착 +- raw 저장 +- source checksum 생성 + +금지: +- 전략 판단 +- event scoring +- 트레이드 신호 생성 + +### 5.2 Normalizer + +역할: +- raw를 내부 공통 포맷으로 정규화 +- source-specific field를 canonical field로 변환 + +예: +- SEC accession → internal document_id +- Alpaca bar payload → canonical OHLCV schema + +### 5.3 Parser + +역할: +- 문서 기반 구조화 +- 규칙 기반 이벤트 탐지 +- 선택적으로 LLM 보강 + +### 5.4 Feature Builder + +역할: +- 이벤트 특징 생성 +- 시장 특징 생성 +- 레짐 특징 생성 +- attention 특징 생성(Phase 1에서는 인터페이스만) + +### 5.5 Ranker + +Phase 1에서는 실제 점수 산출보다 **입력 포맷 정의**까지만 합니다. + +### 5.6 Execution Layer + +Phase 1에서는 주문 실행을 하지 않고, **order_plan schema**만 정의합니다. + +## 6. 공통 식별자 규칙 + +### 6.1 symbol_master_id +- 내부 고유 종목 식별자 +- v1에서는 `ticker + venue + start_date` 조합 허용 + +### 6.2 document_id +형식: + +```text +DOC::{source}::{issuer_id}::{event_date}::{accession_or_hash} +``` + +예: + +```text +DOC::SEC::0000789019::2026-01-28::0001193125-26-027198 +``` + +### 6.3 event_id +형식: + +```text +EVT::{issuer_id}::{event_type}::{primary_document_id} +``` + +### 6.4 job_run_id +UUID 사용 + +## 7. 시간/달력 정책 + +- 내부 표준 타임존은 **UTC 저장 + US/Eastern 파생 컬럼**입니다. +- 거래일 계산은 반드시 거래소 달력을 사용합니다. +- raw 수집 시각, source published 시각, parsed event 시각을 구분합니다. +- `filed_at`, `accepted_at`, `collected_at`, `normalized_at`, `parsed_at` 필드를 각각 유지합니다. + +## 8. 환경 변수 정책 + +필수 환경 변수 예시: + +```text +APP_ENV=dev +POSTGRES_DSN=postgresql://... +DUCKDB_PATH=/app/data/parquet/research.duckdb +DATA_ROOT=/app/data +ALPACA_API_KEY=... +ALPACA_SECRET_KEY=... +FRED_API_KEY=... +OPENAI_API_KEY=... +LOG_LEVEL=INFO +SEC_USER_AGENT=project-name contact-email +``` + +원칙: +- 민감정보는 코드/문서에 하드코딩 금지 +- `.env`는 로컬 전용, 배포에는 secret 주입 사용 +- example 파일에는 placeholder만 포함 + +## 9. Docker Compose 기본 구성 + +서비스: +- `postgres` +- `app` (개발용 Python image) +- 필요 시 `adminer` 또는 경량 DB UI + +초기에는 메시지 큐/오브젝트 스토리지는 넣지 않습니다. + +## 10. 로그와 메트릭 정책 + +모든 서비스는 JSON log를 기본으로 합니다. + +필수 필드: +- timestamp +- level +- service +- job_run_id +- source +- entity_id +- message +- error_class +- retry_count + +메트릭 예시: +- source fetch count +- source fetch latency +- normalized record count +- parser success/fail count +- schema validation fail count +- duplicate skip count + +## 11. 장애 처리 원칙 + +- raw 수집 실패 → structured write 금지 +- schema validation 실패 → parser 결과 폐기, 원문은 유지 +- DB write 실패 → retry 후 stop +- 외부 API rate limit → backoff +- 알 수 없는 필드 추가 → warning + raw 보존 + +## 12. Phase 1 수용 기준 + +- 로컬에서 `make bootstrap` 수준 명령으로 개발 환경 구축 가능 +- sample SEC filing을 1회 수집 후 raw/staging/structured에 적재 가능 +- sample Alpaca bar 데이터를 canonical schema로 변환 가능 +- sample FRED/FINRA 데이터를 일자별로 적재 가능 +- parser가 schema-valid JSON을 생성 가능 +- 모든 경로가 재실행 시 중복 없이 안정 동작 diff --git a/dev/phase1_deliverables/coding_rules.md b/dev/phase1_deliverables/coding_rules.md new file mode 100644 index 0000000..ec9d49f --- /dev/null +++ b/dev/phase1_deliverables/coding_rules.md @@ -0,0 +1,84 @@ +# Phase 1 코딩 규칙 + +## 1. 기본 원칙 + +- 함수는 가능하면 작은 단위로 분리합니다. +- adapter / parser / db / feature builder 책임을 섞지 않습니다. +- side effect는 application layer에 모읍니다. +- 표준 라이브러리와 검증된 범용 라이브러리를 우선 사용합니다. +- 예외를 삼키지 않습니다. + +## 2. 타입과 검증 + +- Python type hint를 필수로 사용합니다. +- 외부 입력은 Pydantic 또는 동등한 검증 계층을 거칩니다. +- 내부 DTO와 DB model을 혼용하지 않습니다. + +## 3. idempotency 규칙 + +- 동일 입력을 두 번 처리해도 결과가 달라지지 않아야 합니다. +- source key + checksum + unique constraint를 적극 사용합니다. +- raw 수집과 structured 적재는 분리합니다. + +## 4. 예외 처리 + +반드시 아래 중 하나로 분류합니다. +- retryable +- non-retryable +- validation +- dependency + +예외 객체에는 source/entity/context가 있어야 합니다. + +## 5. retry / timeout + +- 외부 HTTP 호출은 timeout 필수 +- 무한 retry 금지 +- 지수 백오프 + 상한 적용 +- validation 실패는 retry 금지 + +## 6. 파일 저장 규칙 + +- 파일명은 deterministic 해야 합니다. +- 원문 overwrite 금지 +- sidecar metadata JSON 함께 저장 +- 상대경로 대신 data root 기준 canonical path 사용 + +## 7. DB write 규칙 + +- 가능하면 upsert 사용 +- bulk insert 전 unique key 명확화 +- transaction scope를 짧게 유지 +- parser core에서 DB 세션 직접 접근 금지 + +## 8. 로깅 규칙 + +- INFO: 정상 단계 +- WARNING: 데이터 이상, fallback 사용 +- ERROR: 처리 실패 +- DEBUG: 로컬 개발 전용 + +민감정보 로그 출력 금지: +- API key +- secret +- full auth header + +## 9. 테스트 규칙 + +- fixture 없는 adapter 구현 금지 +- parser는 최소 10개 이상의 샘플 문서 fixture 확보 권장 +- 회귀 버그는 반드시 fixture 추가 후 수정 + +## 10. 문서화 규칙 + +- 모든 adapter에 README 또는 docstring 필요 +- config key는 설명과 기본값 포함 +- migration은 목적 설명 포함 + +## 11. 코드 리뷰 기준 + +- 책임 분리가 되어 있는가 +- 재실행 안전한가 +- 실패 시 상태가 명확한가 +- 테스트가 충분한가 +- 로그로 추적 가능한가 diff --git a/dev/phase1_deliverables/db_schema.md b/dev/phase1_deliverables/db_schema.md new file mode 100644 index 0000000..ad27118 --- /dev/null +++ b/dev/phase1_deliverables/db_schema.md @@ -0,0 +1,364 @@ +# Phase 1 DB 스키마 + +## 1. 설계 원칙 + +- PostgreSQL은 **운영 상태와 canonical metadata**를 담당합니다. +- 대량 시계열과 연구용 매트릭스는 **Parquet/DuckDB**로 분리합니다. +- 각 테이블은 가능한 한 **append-safe + upsert-safe** 하게 설계합니다. +- 모든 핵심 테이블은 `created_at_utc`, `updated_at_utc`를 가집니다. +- 모든 외부 문서는 source checksum을 가집니다. + +## 2. PostgreSQL 핵심 테이블 + +### 2.1 source_registry +외부 데이터 원천 정의 + +| column | type | note | +|---|---|---| +| source_name | text PK | `sec`, `alpaca`, `fred`, `finra` | +| source_type | text | filing, market_data, macro, short_volume | +| enabled | boolean | 사용 여부 | +| config_json | jsonb | adapter 설정 | +| created_at_utc | timestamptz | 생성 시각 | +| updated_at_utc | timestamptz | 수정 시각 | + +### 2.2 issuer_master +발행사 마스터 + +| column | type | note | +|---|---|---| +| issuer_id | text PK | 내부 발행사 ID | +| cik | text unique | SEC CIK | +| ticker | text | 대표 ticker | +| issuer_name | text | 회사명 | +| exchange | text | 거래소 | +| country_code | text | 국가 | +| is_active | boolean | 활성 여부 | +| created_at_utc | timestamptz | | +| updated_at_utc | timestamptz | | + +### 2.3 symbol_master +종목 마스터 + +| column | type | note | +|---|---|---| +| symbol_id | text PK | 내부 종목 ID | +| issuer_id | text FK | issuer_master | +| ticker | text | 심볼 | +| venue | text | 거래 venue | +| asset_type | text | common_stock 등 | +| currency | text | 통화 | +| start_date | date | 유효 시작 | +| end_date | date nullable | 유효 종료 | +| is_primary | boolean | 대표 종목 여부 | +| created_at_utc | timestamptz | | +| updated_at_utc | timestamptz | | + +### 2.4 job_runs +배치/수집 실행 로그 + +| column | type | note | +|---|---|---| +| job_run_id | uuid PK | 실행 ID | +| job_name | text | job 이름 | +| source_name | text | source | +| run_date | date | 기준 일자 | +| status | text | pending/running/succeeded/failed/partial | +| started_at_utc | timestamptz | 시작 시각 | +| finished_at_utc | timestamptz nullable | 종료 시각 | +| records_seen | integer | 관측 수 | +| records_written | integer | 적재 수 | +| records_skipped | integer | 스킵 수 | +| error_count | integer | 에러 수 | +| error_summary | text nullable | 요약 | +| metadata_json | jsonb | 기타 메타 | + +### 2.5 raw_objects +원문 보관 메타데이터 + +| column | type | note | +|---|---|---| +| raw_object_id | text PK | 내부 원문 ID | +| source_name | text | source | +| source_object_key | text | 원천 고유 키 | +| storage_path | text | raw 파일 경로 | +| content_type | text | mime type | +| checksum_sha256 | text | 체크섬 | +| collected_at_utc | timestamptz | 수집 시각 | +| published_at_utc | timestamptz nullable | 원천 게시 시각 | +| metadata_json | jsonb | sidecar metadata | +| created_at_utc | timestamptz | | + +### 2.6 documents +정규화된 문서 메타 + +| column | type | note | +|---|---|---| +| document_id | text PK | 내부 문서 ID | +| source_name | text | 보통 `sec` | +| issuer_id | text FK | | +| symbol_id | text nullable FK | | +| accession_no | text nullable | SEC accession | +| form_type | text | 8-K, 10-Q 등 | +| filing_date | date | filing date | +| accepted_at_utc | timestamptz nullable | SEC acceptance time | +| primary_document_name | text nullable | primary doc | +| raw_object_id | text FK | 원문 참조 | +| text_path | text nullable | 추출 텍스트 경로 | +| html_path | text nullable | html 경로 | +| parsed_status | text | pending/succeeded/failed | +| created_at_utc | timestamptz | | +| updated_at_utc | timestamptz | | + +고유 제약: +- `(source_name, accession_no, primary_document_name)` unique + +### 2.7 document_exhibits +부속 문서 목록 + +| column | type | note | +|---|---|---| +| exhibit_id | text PK | 내부 exhibit ID | +| document_id | text FK | | +| exhibit_code | text | EX-99.1 등 | +| exhibit_name | text | 파일명 | +| raw_object_id | text FK | | +| text_path | text nullable | | +| html_path | text nullable | | +| created_at_utc | timestamptz | | + +### 2.8 events +문서에서 추출된 이벤트 단위 + +| column | type | note | +|---|---|---| +| event_id | text PK | 내부 이벤트 ID | +| issuer_id | text FK | | +| symbol_id | text nullable FK | | +| primary_document_id | text FK | | +| event_type | text | earnings_release 등 | +| event_direction | text | bullish/bearish/mixed/unknown | +| event_date | date | 거래 로직 기준 일자 | +| filed_at_utc | timestamptz nullable | filing/published time | +| parser_version | text | parser 버전 | +| parse_confidence | numeric | 0~1 | +| status | text | pending/valid/rejected | +| created_at_utc | timestamptz | | +| updated_at_utc | timestamptz | | + +### 2.9 event_parses +파서 원출력 저장 + +| column | type | note | +|---|---|---| +| event_parse_id | bigserial PK | | +| event_id | text FK | | +| parser_kind | text | rule / llm / merged | +| parser_version | text | | +| schema_version | text | | +| output_json | jsonb | full parser output | +| validation_status | text | valid/invalid | +| validation_errors | jsonb nullable | | +| created_at_utc | timestamptz | | + +### 2.10 market_bars_daily +일봉 canonical 저장 + +| column | type | note | +|---|---|---| +| symbol_id | text FK | | +| trade_date | date | | +| open | numeric | | +| high | numeric | | +| low | numeric | | +| close | numeric | | +| volume | bigint | | +| vwap | numeric nullable | | +| trade_count | bigint nullable | | +| source_name | text | | +| created_at_utc | timestamptz | | +| updated_at_utc | timestamptz | | + +PK: +- `(symbol_id, trade_date, source_name)` + +### 2.11 market_bars_intraday +분봉 canonical 저장 + +| column | type | note | +|---|---|---| +| symbol_id | text FK | | +| bar_start_utc | timestamptz | | +| timeframe | text | 1Min, 5Min 등 | +| open | numeric | | +| high | numeric | | +| low | numeric | | +| close | numeric | | +| volume | bigint | | +| vwap | numeric nullable | | +| trade_count | bigint nullable | | +| source_name | text | | +| created_at_utc | timestamptz | | + +PK: +- `(symbol_id, bar_start_utc, timeframe, source_name)` + +### 2.12 macro_series +거시 시계열 메타 + +| column | type | note | +|---|---|---| +| series_id | text PK | FRED series_id | +| title | text | | +| frequency | text | | +| units | text | | +| source_name | text | | +| metadata_json | jsonb | | +| created_at_utc | timestamptz | | + +### 2.13 macro_observations +거시 시계열 값 + +| column | type | note | +|---|---|---| +| series_id | text FK | | +| observation_date | date | | +| value | numeric nullable | | +| created_at_utc | timestamptz | | + +PK: +- `(series_id, observation_date)` + +### 2.14 short_sale_daily +FINRA short sale volume 정규화 + +| column | type | note | +|---|---|---| +| symbol_id | text nullable FK | mapping 실패 가능 | +| ticker_raw | text | 원본 심볼 | +| trade_date | date | | +| short_volume | bigint | | +| short_exempt_volume | bigint nullable | | +| total_volume | bigint nullable | | +| source_name | text | | +| created_at_utc | timestamptz | | + +PK: +- `(ticker_raw, trade_date, source_name)` + +### 2.15 feature_snapshots +이벤트 시점 특징 저장 + +| column | type | note | +|---|---|---| +| feature_snapshot_id | bigserial PK | | +| event_id | text FK | | +| snapshot_name | text | market_v1, event_v1 등 | +| snapshot_version | text | | +| feature_json | jsonb | | +| created_at_utc | timestamptz | | + +### 2.16 order_plans +Phase 1에서는 계획만 정의 + +| column | type | note | +|---|---|---| +| order_plan_id | bigserial PK | | +| event_id | text FK | | +| symbol_id | text FK | | +| side | text | buy/sell | +| planned_entry_date | date | | +| planned_order_type | text | market/limit | +| planned_price | numeric nullable | | +| stop_price | numeric nullable | | +| take_profit_price | numeric nullable | | +| quantity_plan | numeric nullable | | +| status | text | draft/ready/canceled | +| created_at_utc | timestamptz | | + +## 3. 권장 인덱스 + +- `documents(issuer_id, filing_date desc)` +- `documents(form_type, filing_date desc)` +- `events(event_type, event_date desc)` +- `events(primary_document_id)` +- `market_bars_daily(symbol_id, trade_date desc)` +- `market_bars_intraday(symbol_id, timeframe, bar_start_utc desc)` +- `short_sale_daily(ticker_raw, trade_date desc)` +- `job_runs(job_name, run_date desc)` + +## 4. DuckDB / Parquet 권장 구조 + +```text +data/parquet/ + market/daily/year=2026/month=03/*.parquet + market/intraday/date=2026-03-12/*.parquet + events/year=2026/month=03/*.parquet + features/version=v1/date=2026-03-12/*.parquet + labels/horizon=3d/*.parquet +``` + +## 5. SQL DDL 초안 + +```sql +create table if not exists source_registry ( + source_name text primary key, + source_type text not null, + enabled boolean not null default true, + config_json jsonb not null default '{}'::jsonb, + created_at_utc timestamptz not null default now(), + updated_at_utc timestamptz not null default now() +); + +create table if not exists issuer_master ( + issuer_id text primary key, + cik text unique, + ticker text, + issuer_name text not null, + exchange text, + country_code text, + is_active boolean not null default true, + created_at_utc timestamptz not null default now(), + updated_at_utc timestamptz not null default now() +); + +create table if not exists symbol_master ( + symbol_id text primary key, + issuer_id text references issuer_master(issuer_id), + ticker text not null, + venue text, + asset_type text, + currency text, + start_date date, + end_date date, + is_primary boolean not null default true, + created_at_utc timestamptz not null default now(), + updated_at_utc timestamptz not null default now() +); + +create table if not exists job_runs ( + job_run_id uuid primary key, + job_name text not null, + source_name text, + run_date date, + status text not null, + started_at_utc timestamptz not null default now(), + finished_at_utc timestamptz, + records_seen integer not null default 0, + records_written integer not null default 0, + records_skipped integer not null default 0, + error_count integer not null default 0, + error_summary text, + metadata_json jsonb not null default '{}'::jsonb +); +``` + +나머지 DDL은 migration 파일에서 분리 관리합니다. + +## 6. 스키마 검증 체크포인트 + +- 동일 accession 문서 중복 적재가 없어야 합니다. +- 동일 symbol/date/day-bar 중복 적재가 없어야 합니다. +- parser output은 event와 별도 테이블에 버전별 보존되어야 합니다. +- raw_object가 없으면 documents를 만들 수 없어야 합니다. +- issuer/symbol 매핑 실패는 nullable 허용하되 경고 로그를 남겨야 합니다. diff --git a/dev/phase1_deliverables/implementation_plan.md b/dev/phase1_deliverables/implementation_plan.md new file mode 100644 index 0000000..7dd5b45 --- /dev/null +++ b/dev/phase1_deliverables/implementation_plan.md @@ -0,0 +1,234 @@ +# Phase 1 구현 계획 + +이 문서는 AI 코딩 에이전트가 실제 구현을 시작할 때 사용할 상세 작업 계획입니다. + +## 1. 구현 우선순위 + +Phase 1 구현은 아래 순서를 강제합니다. + +1. 공통 기반 +2. DB 스키마와 migration +3. SEC adapter +4. Alpaca adapter +5. FRED adapter +6. FINRA adapter +7. parser I/O schema +8. event parser skeleton +9. feature builder skeleton +10. 테스트 자동화 + +이 순서를 바꾸지 않는 이유: +- 공통 기반 없이는 adapter 품질이 흔들립니다. +- DB 스키마가 없으면 모든 출력 계약이 흔들립니다. +- SEC가 핵심 source입니다. +- parser는 source 수집과 스키마가 고정된 뒤에 만들어야 합니다. + +## 2. 작업 분할 단위 + +### Task Group A — 공통 기반 + +#### A1. config loader +완료 조건: +- `.env` + YAML config를 읽을 수 있음 +- 환경별 override 가능 +- 누락된 필수 키는 즉시 실패 + +#### A2. logging module +완료 조건: +- JSON logger 제공 +- job_run_id 주입 가능 +- exception helper 제공 + +#### A3. time_utils +완료 조건: +- UTC ↔ US/Eastern 변환 +- 거래일 helper +- date partition helper + +#### A4. ids +완료 조건: +- document_id 생성 +- event_id 생성 +- checksum 생성 + +### Task Group B — DB와 migration + +#### B1. SQLAlchemy/Pydantic 모델 +완료 조건: +- 핵심 테이블 모델 정의 +- enum과 상태값 정의 + +#### B2. migration +완료 조건: +- 빈 DB에 초기 schema 적용 가능 +- rollback 가능 + +#### B3. db helper +완료 조건: +- upsert helper +- transaction wrapper +- health check + +### Task Group C — SEC adapter + +#### C1. submissions fetcher +완료 조건: +- 특정 CIK에 대한 submissions JSON 수집 가능 +- raw 저장 성공 + +#### C2. filing downloader +완료 조건: +- accession 기반 filing index 다운로드 +- filing text/html 저장 +- exhibit 목록 추출 + +#### C3. metadata normalizer +완료 조건: +- `documents` 테이블에 canonical metadata 적재 +- duplicate safe + +#### C4. xbrl fetcher +완료 조건: +- facts/companyfacts 수집 가능 +- 핵심 재무 필드 추출 가능 + +### Task Group D — Alpaca adapter + +#### D1. daily bars fetcher +완료 조건: +- 여러 symbol의 일봉 수집 가능 +- canonical OHLCV 적재 가능 + +#### D2. intraday bars fetcher +완료 조건: +- 분봉 수집 가능 +- 시간대 정규화 완료 + +### Task Group E — FRED / FINRA adapter + +#### E1. FRED +완료 조건: +- 시리즈별 시계열 수집 +- observation 적재 + +#### E2. FINRA +완료 조건: +- daily short sale file download +- symbol별 파싱 +- ratio 계산용 컬럼 적재 + +### Task Group F — Parser + +#### F1. parser schema validator +완료 조건: +- JSON schema validation 가능 +- 실패 시 상세 에러 반환 + +#### F2. rule-based event parser +완료 조건: +- item number 추출 +- guidance keyword 추출 +- one-off keyword 추출 +- event type 분류 + +#### F3. llm parser stub +완료 조건: +- 입력/출력 인터페이스만 고정 +- 실제 호출은 feature flag로 disable 가능 + +### Task Group G — Feature Builder + +#### G1. market features +완료 조건: +- reaction-day return +- volume ratio +- close location +- ATR 기초값 + +#### G2. event features +완료 조건: +- guidance_direction +- oneoff_flags +- document_quality_score_raw + +### Task Group H — 테스트 자동화 + +#### H1. unit tests +#### H2. integration tests +#### H3. replay tests +#### H4. sample fixture set + +## 3. 권장 구현 순서별 산출물 + +### Step 1 +산출물: +- `libs/common/*` +- `configs/env.example` +- `Makefile` 또는 bootstrap script + +### Step 2 +산출물: +- DB migration 0001 +- ORM models +- base repository helpers + +### Step 3 +산출물: +- `libs/adapters/sec/*` +- `apps/collector/sec_collector/*` +- SEC fixture 기반 통합 테스트 + +### Step 4 +산출물: +- `libs/adapters/alpaca/*` +- daily/intraday collector + +### Step 5 +산출물: +- `libs/adapters/fred/*` +- `libs/adapters/finra/*` + +### Step 6 +산출물: +- parser schema +- parser validator +- rule parser + +### Step 7 +산출물: +- feature builder skeleton +- sample feature row generation + +### Step 8 +산출물: +- test suite +- CI command set + +## 4. 금지 사항 + +- source adapter 내부에서 전략 점수 계산 금지 +- parser 내부에서 DB 직접 접근 금지 +- 테스트 없이 production migration 추가 금지 +- raw 원문 overwrite 금지 +- 외부 API 실패 시 silent ignore 금지 +- timezone naive datetime 저장 금지 + +## 5. AI 코딩 에이전트용 작업 방식 + +권장 방식: +1. 각 Task Group 별로 브랜치 또는 PR 단위 생성 +2. 테스트 먼저 작성 +3. fixture 기반으로 개발 +4. 구현 후 idempotency 검증 +5. 문서 갱신 + +## 6. 완료 정의 + +Phase 1은 아래가 모두 만족될 때 완료입니다. + +- 모든 핵심 source adapter가 최소 1개 fixture와 1개 실제 샘플로 검증됨 +- DB migration이 처음부터 끝까지 깨끗하게 적용됨 +- parser schema가 고정되고 샘플 문서에 대해 valid JSON 생성됨 +- feature builder가 최소한 market/event feature 한 줄을 생성함 +- `pytest` 기준 unit/integration 테스트가 자동 실행됨 +- README만 보고 새 개발자가 환경을 띄울 수 있음 diff --git a/dev/phase1_deliverables/parser_event.schema.json b/dev/phase1_deliverables/parser_event.schema.json new file mode 100644 index 0000000..f69b8c9 --- /dev/null +++ b/dev/phase1_deliverables/parser_event.schema.json @@ -0,0 +1,145 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.local/schemas/parser_event.schema.json", + "title": "ParserEvent", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "document_id", + "parser_kind", + "event_type", + "event_direction", + "event_date", + "filing_time_bucket", + "summary", + "guidance", + "signals", + "risk_flags", + "confidence" + ], + "properties": { + "schema_version": { "type": "string" }, + "document_id": { "type": "string", "minLength": 1 }, + "parser_kind": { + "type": "string", + "enum": ["rule", "llm", "merged"] + }, + "event_type": { + "type": "string", + "enum": [ + "earnings_release", + "guidance_update", + "material_contract", + "regulatory_or_approval", + "capital_markets_or_financing", + "management_change", + "litigation_or_investigation", + "other_material_event", + "unknown" + ] + }, + "event_direction": { + "type": "string", + "enum": ["bullish", "bearish", "mixed", "neutral", "unknown"] + }, + "event_date": { "type": "string", "format": "date" }, + "filing_time_bucket": { + "type": "string", + "enum": ["pre_market", "regular_hours", "post_market", "unknown"] + }, + "headline": { "type": "string" }, + "summary": { "type": "string", "minLength": 1 }, + "guidance": { + "type": "object", + "additionalProperties": false, + "required": ["status", "scope", "notes"], + "properties": { + "status": { + "type": "string", + "enum": [ + "raised", + "inline_or_maintained", + "lowered", + "withdrawn", + "not_provided", + "unclear" + ] + }, + "scope": { + "type": "string", + "enum": ["quarterly", "annual", "both", "unknown"] + }, + "notes": { "type": "string" } + } + }, + "signals": { + "type": "object", + "additionalProperties": false, + "required": [ + "demand_strength", + "pricing_power", + "backlog_or_bookings", + "customer_expansion", + "margin_quality" + ], + "properties": { + "demand_strength": { "type": "string", "enum": ["strong", "stable", "weakening", "unknown"] }, + "pricing_power": { "type": "string", "enum": ["present", "mixed", "absent", "unknown"] }, + "backlog_or_bookings": { "type": "string", "enum": ["present", "mixed", "absent", "unknown"] }, + "customer_expansion": { "type": "string", "enum": ["present", "mixed", "absent", "unknown"] }, + "margin_quality": { "type": "string", "enum": ["improving", "stable", "deteriorating", "unknown"] } + } + }, + "risk_flags": { + "type": "object", + "additionalProperties": false, + "required": [ + "oneoff_item", + "tax_benefit", + "valuation_gain", + "non_gaap_heavy", + "financing_related", + "legal_or_regulatory_overhang" + ], + "properties": { + "oneoff_item": { "type": "boolean" }, + "tax_benefit": { "type": "boolean" }, + "valuation_gain": { "type": "boolean" }, + "non_gaap_heavy": { "type": "boolean" }, + "financing_related": { "type": "boolean" }, + "legal_or_regulatory_overhang": { "type": "boolean" } + } + }, + "evidence": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["label", "text_span", "section_hint", "confidence"], + "properties": { + "label": { "type": "string" }, + "text_span": { "type": "string" }, + "section_hint": { "type": "string" }, + "confidence": { "type": "number", "minimum": 0, "maximum": 1 } + } + } + }, + "confidence": { + "type": "object", + "additionalProperties": false, + "required": ["overall", "event_type", "event_direction", "guidance", "risk_flags"], + "properties": { + "overall": { "type": "number", "minimum": 0, "maximum": 1 }, + "event_type": { "type": "number", "minimum": 0, "maximum": 1 }, + "event_direction": { "type": "number", "minimum": 0, "maximum": 1 }, + "guidance": { "type": "number", "minimum": 0, "maximum": 1 }, + "risk_flags": { "type": "number", "minimum": 0, "maximum": 1 } + } + }, + "warnings": { + "type": "array", + "items": { "type": "string" } + } + } +} diff --git a/dev/phase1_deliverables/parser_json_schema.md b/dev/phase1_deliverables/parser_json_schema.md new file mode 100644 index 0000000..93587cd --- /dev/null +++ b/dev/phase1_deliverables/parser_json_schema.md @@ -0,0 +1,174 @@ +# Event Parser JSON 규격 + +## 1. 목적 + +이 규격은 문서 파서의 출력 형식을 고정합니다. +규칙 기반 파서와 LLM 기반 파서는 **동일한 출력 schema**를 따라야 합니다. + +## 2. 최상위 구조 + +```json +{ + "schema_version": "1.0.0", + "document_id": "DOC::SEC::0000789019::2026-01-28::0001193125-26-027198", + "parser_kind": "rule|llm|merged", + "event_type": "earnings_release", + "event_direction": "bullish", + "event_date": "2026-01-29", + "filing_time_bucket": "pre_market|regular_hours|post_market|unknown", + "headline": "Q2 results and guidance update", + "summary": "Revenue grew and forward guidance improved.", + "guidance": { + "status": "raised", + "scope": "quarterly", + "notes": "Management raised next-quarter revenue guidance." + }, + "signals": { + "demand_strength": "strong", + "pricing_power": "present", + "backlog_or_bookings": "present", + "customer_expansion": "present", + "margin_quality": "improving" + }, + "risk_flags": { + "oneoff_item": false, + "tax_benefit": false, + "valuation_gain": false, + "non_gaap_heavy": false, + "financing_related": false, + "legal_or_regulatory_overhang": false + }, + "evidence": [ + { + "label": "guidance_raise", + "text_span": "expects revenue for Q3 to be between ...", + "section_hint": "Exhibit 99.1", + "confidence": 0.92 + } + ], + "confidence": { + "overall": 0.88, + "event_type": 0.97, + "event_direction": 0.85, + "guidance": 0.83, + "risk_flags": 0.74 + }, + "warnings": [] +} +``` + +## 3. 필수 필드 + +- `schema_version` +- `document_id` +- `parser_kind` +- `event_type` +- `event_direction` +- `event_date` +- `filing_time_bucket` +- `summary` +- `guidance` +- `signals` +- `risk_flags` +- `confidence` + +## 4. 필드 정의 + +### 4.1 parser_kind +허용값: +- `rule` +- `llm` +- `merged` + +### 4.2 event_type +초기 허용값: +- `earnings_release` +- `guidance_update` +- `material_contract` +- `regulatory_or_approval` +- `capital_markets_or_financing` +- `management_change` +- `litigation_or_investigation` +- `other_material_event` +- `unknown` + +### 4.3 event_direction +허용값: +- `bullish` +- `bearish` +- `mixed` +- `neutral` +- `unknown` + +### 4.4 filing_time_bucket +허용값: +- `pre_market` +- `regular_hours` +- `post_market` +- `unknown` + +### 4.5 guidance.status +허용값: +- `raised` +- `inline_or_maintained` +- `lowered` +- `withdrawn` +- `not_provided` +- `unclear` + +### 4.6 signals +각 필드는 문자열 enum 또는 `unknown` + +- `demand_strength`: `strong|stable|weakening|unknown` +- `pricing_power`: `present|mixed|absent|unknown` +- `backlog_or_bookings`: `present|mixed|absent|unknown` +- `customer_expansion`: `present|mixed|absent|unknown` +- `margin_quality`: `improving|stable|deteriorating|unknown` + +### 4.7 risk_flags +각 필드는 boolean + +### 4.8 evidence +원문 근거 배열. 최소 0개 허용. +권장 최대 10개. + +각 evidence 필드: +- `label` +- `text_span` +- `section_hint` +- `confidence` + +### 4.9 confidence +0~1 실수 + +필수: +- `overall` +- `event_type` +- `event_direction` +- `guidance` +- `risk_flags` + +## 5. 설계 원칙 + +- parser는 확신이 없으면 `unknown` 또는 `unclear`를 사용합니다. +- parser는 숫자를 추정하지 않습니다. +- parser는 근거 없는 positive/negative 해석을 하지 않습니다. +- LLM parser는 가능한 한 evidence를 함께 반환해야 합니다. +- `summary`는 300자 이내 요약을 권장합니다. + +## 6. validation 규칙 + +- 필수 필드 누락 시 invalid +- enum 이탈 시 invalid +- confidence 범위 벗어나면 invalid +- evidence confidence도 0~1 범위여야 함 +- `document_id`가 없으면 invalid + +## 7. 향후 확장 예비 필드 + +Phase 1에서는 사용하지 않지만 향후 확장 가능: +- `transcript_tone` +- `qna_stress` +- `supply_chain_signal` +- `macro_exposure` +- `ai_theme_relevance` diff --git a/dev/phase1_deliverables/service_contracts.md b/dev/phase1_deliverables/service_contracts.md new file mode 100644 index 0000000..08192b4 --- /dev/null +++ b/dev/phase1_deliverables/service_contracts.md @@ -0,0 +1,227 @@ +# Phase 1 서비스 계약서 + +## 1. 목적 + +이 문서는 각 서비스와 adapter가 어떤 입력을 받고 어떤 출력을 내야 하는지 정의합니다. +AI 코딩 에이전트는 이 계약을 기준으로 인터페이스를 고정해야 합니다. + +## 2. 공통 원칙 + +- 모든 서비스는 명시적 입력 객체를 받습니다. +- 모든 서비스는 typed result 또는 typed error를 반환합니다. +- 외부 API 호출 결과는 raw 저장 이후에만 정규화됩니다. +- 서비스는 가능하면 pure function 형태를 유지합니다. +- DB write는 application layer에서 수행하고, parser/adapter core는 side-effect를 최소화합니다. + +## 3. 공통 타입 + +### 3.1 FetchRequest + +```json +{ + "source": "sec", + "entity": "issuer|symbol|series|date", + "key": "0000789019", + "start_date": "2026-01-01", + "end_date": "2026-03-31", + "run_mode": "backfill|daily|replay" +} +``` + +### 3.2 FetchResult + +```json +{ + "status": "success|partial|failed|skipped", + "raw_object_ids": ["RAW::..."], + "records_seen": 3, + "records_written": 3, + "warnings": [], + "errors": [] +} +``` + +### 3.3 NormalizeResult + +```json +{ + "status": "success|failed", + "canonical_records": [{"...": "..."}], + "warnings": [], + "errors": [] +} +``` + +## 4. SEC adapter 계약 + +### 4.1 입력 +- CIK 또는 accession +- 기간 또는 개별 filing key +- run_mode + +### 4.2 출력 +- submissions raw object +- filing raw object +- exhibit raw object list +- canonical document metadata + +### 4.3 함수 예시 + +```python +def fetch_submissions(cik: str) -> FetchResult: ... +def fetch_filing(accession_no: str, cik: str) -> FetchResult: ... +def normalize_filing(raw_object_id: str) -> NormalizeResult: ... +``` + +### 4.4 실패 규칙 +- HTTP 실패 → retryable error +- 404 → non-retryable warning +- parsing 실패 → raw는 유지, normalize fail + +## 5. Alpaca adapter 계약 + +### 5.1 입력 +- symbol list +- timeframe +- start/end + +### 5.2 출력 +- canonical OHLCV record list + +### 5.3 함수 예시 + +```python +def fetch_bars(symbols: list[str], timeframe: str, start: str, end: str) -> FetchResult: ... +def normalize_bars(raw_object_id: str) -> NormalizeResult: ... +``` + +## 6. FRED adapter 계약 + +```python +def fetch_series(series_id: str, start: str | None = None, end: str | None = None) -> FetchResult: ... +``` + +출력: +- series metadata +- observation records + +## 7. FINRA adapter 계약 + +```python +def fetch_daily_short_volume(trade_date: str) -> FetchResult: ... +def normalize_daily_short_volume(raw_object_id: str) -> NormalizeResult: ... +``` + +주의: +- symbol mapping 실패 가능 +- 원본 ticker_raw는 반드시 유지 + +## 8. Event Parser 계약 + +입력: + +```json +{ + "document_id": "DOC::...", + "source_name": "sec", + "form_type": "8-K", + "issuer_id": "ISSUER::...", + "filing_date": "2026-01-29", + "accepted_at_utc": "2026-01-29T21:05:00Z", + "text": "...document text...", + "metadata": { + "item_numbers": ["2.02", "7.01"], + "exhibits": ["EX-99.1"] + } +} +``` + +출력: +- `parser_event.schema.json`을 만족하는 JSON + +실패: +- invalid schema → `validation_status=invalid` +- parser exception → error object 반환, event 생성 금지 + +## 9. Feature Builder 계약 + +입력: +- event row +- related market data +- optional macro/short volume rows + +출력 예시: + +```json +{ + "event_id": "EVT::...", + "snapshot_name": "event_v1", + "snapshot_version": "1.0.0", + "feature_json": { + "guidance_direction": "raised", + "oneoff_penalty": 0, + "reaction_day_return": 0.042, + "volume_ratio_20d": 2.3 + } +} +``` + +## 10. Job Runner 계약 + +입력: +- job_name +- source_name +- run_date +- mode + +출력: +- `job_runs` 테이블 row 업데이트 +- 로그 스트림 + +상태 전이: +- pending → running → succeeded +- pending → running → partial +- pending → running → failed + +## 11. 에러 객체 표준 + +```json +{ + "error_class": "RateLimitError", + "message": "HTTP 429 from source", + "retryable": true, + "source_name": "sec", + "entity_key": "0000789019", + "context": {"url": "..."} +} +``` + +## 12. 로그 포맷 표준 + +```json +{ + "timestamp": "2026-03-12T10:00:00Z", + "level": "INFO", + "service": "sec_collector", + "job_run_id": "...", + "message": "fetched submissions", + "source": "sec", + "entity_id": "0000789019", + "records_seen": 1, + "records_written": 1 +} +``` + +## 13. 재시도 계약 + +- source adapter의 네트워크 실패는 최대 3회 지수 백오프 +- validation 실패는 재시도하지 않음 +- DB deadlock/connection issue는 retryable +- schema mismatch는 raw 보존 후 failed 처리 + +## 14. 금지 규칙 + +- adapter가 임의로 심볼명을 정정하지 말 것 +- parser가 raw text를 수정 저장하지 말 것 +- feature builder가 원문 source를 재호출하지 말 것 +- job runner가 실패를 성공으로 덮어쓰지 말 것 diff --git a/dev/phase1_deliverables/testing_checklist.md b/dev/phase1_deliverables/testing_checklist.md new file mode 100644 index 0000000..1be1334 --- /dev/null +++ b/dev/phase1_deliverables/testing_checklist.md @@ -0,0 +1,258 @@ +# Phase 1 테스팅 체크리스트 + +실제 구현 기준 재작성 (2026-03-12). +Stock Oracle(localhost:18001)을 data intermediary로 사용하는 아키텍처 기준. +`[x]` = 테스트 통과 확인, `[ ]` = 미커버 또는 미통과. + +--- + +## 1. 테스트 레벨 + +| 레벨 | 실행 명령 | 상태 | +|---|---|---| +| 단위 테스트 | `make test-unit` | 100 tests pass | +| 리플레이 테스트 | `make test-replay` | 5 tests pass | +| 통합 테스트 | `make test-integration` | Docker postgres 필요 | +| 운영 전 수동 점검 | 아래 섹션 참조 | — | + +--- + +## 2. 단위 테스트 체크리스트 + +### 2.1 config (`tests/unit/test_config.py`) + +- [x] Settings가 기본값을 올바르게 로드한다 — `test_settings_defaults` +- [x] log_level이 대문자로 정규화된다 — `test_log_level_normalized` +- [x] exhibit_cache_dir이 data_root 기반 경로를 반환한다 — `test_exhibit_cache_dir` +- [x] parquet_dir이 data_root 기반 경로를 반환한다 — `test_parquet_dir` +- [x] get_symbols가 YAML에서 심볼 목록을 반환한다 — `test_get_symbols` + +### 2.2 ids (`tests/unit/test_ids.py`) + +- [x] document_id 포맷이 규칙(DOC::source::issuer_id::date::acc)을 만족한다 — `test_document_id_format` +- [x] event_id 포맷이 규칙(EVT::...)을 만족하며 event_type이 포함된다 — `test_event_id_format` +- [x] CIK에서 issuer_id가 0-padded 10자리로 생성된다 — `test_issuer_id_from_cik` +- [x] ticker에서 symbol_id가 기본 venue(XNYS)로 생성된다 — `test_symbol_id_from_ticker` +- [x] ticker가 대문자로 정규화되고 custom venue가 반영된다 — `test_symbol_id_uppercase` +- [x] sha256_checksum이 동일 입력에 대해 deterministic 하다 — `test_sha256_checksum` +- [x] 다른 입력에 대해 다른 checksum이 생성된다 — `test_sha256_different` +- [x] new_job_run_id가 유효한 UUID를 반환한다 — `test_new_job_run_id` + +### 2.3 time_utils (`tests/unit/test_time_utils.py`) + +- [x] utc_now()가 tz-aware datetime을 반환한다 — `test_utc_now` +- [x] UTC → Eastern 변환이 정확하다 (21:05 UTC = 16:05 ET) — `test_to_eastern` +- [x] Eastern → UTC 변환이 정확하다 — `test_to_utc` +- [x] naive datetime은 변환 시 ValueError가 발생한다 — `test_naive_datetime_rejected` +- [x] 21:05 UTC는 "post_market"으로 분류된다 — `test_filing_time_bucket_post_market` +- [x] 12:00 UTC (07:00 ET)는 "pre_market"으로 분류된다 — `test_filing_time_bucket_pre_market` +- [x] 15:00 UTC (10:00 ET)는 "regular_hours"로 분류된다 — `test_filing_time_bucket_regular` +- [x] naive datetime은 "unknown"으로 분류된다 — `test_filing_time_bucket_naive` + +### 2.4 file_store (`tests/unit/test_file_store.py`) + +- [x] exhibit 쓰기/읽기가 정상 작동하고 checksum을 반환한다 — `test_write_and_read_exhibit` +- [x] exists_exhibit이 존재 여부를 정확히 반환한다 — `test_exists_exhibit` +- [x] get_checksum이 동일 파일에 대해 deterministic 하다 — `test_checksum_deterministic` +- [x] exhibit 경로에 안전한 파일명(EX-99.1.txt)이 사용된다 — `test_exhibit_path_safe_chars` + +### 2.5 retries (`tests/unit/test_retries.py`) + +- [x] 예외 계층 구조(RetryableError/NonRetryableError/ValidationError/DependencyError)가 올바르다 — `test_exception_hierarchy` +- [x] 에러 필드(source/entity/context)가 올바르게 저장된다 — `test_error_fields` +- [x] 첫 시도에 성공하면 1회만 호출된다 — `test_with_retry_succeeds_on_first_attempt` +- [x] RetryableError 발생 시 최대 횟수까지 재시도한다 — `test_with_retry_retries_on_retryable_error` +- [x] NonRetryableError는 1회만 호출되고 즉시 raise된다 — `test_with_retry_does_not_retry_non_retryable` +- [x] max_attempts 모두 소진 후 RetryableError가 최종 raise된다 — `test_with_retry_exhaustion` + +### 2.6 logging (`tests/unit/test_logging.py`) + +- [x] configure_logging("DEBUG") 호출 시 예외가 발생하지 않는다 — `test_configure_logging_no_error` +- [x] bind_job_run_id 후 ContextVar에 run_id가 설정된다 — `test_bind_job_run_id_in_context` +- [x] get_logger가 info/debug/warning/error 메서드를 가진 객체를 반환한다 — `test_get_logger_returns_bound_logger` + +### 2.7 oracle_client + +#### 기존 테스트 (`tests/unit/test_oracle_client.py`) + +- [x] FilingsService.search_filings가 FilingSearchResponse를 반환한다 — `test_search_filings` +- [x] FilingsService.get_exhibit가 ExhibitResponse를 반환한다 — `test_get_exhibit` +- [x] PriceService.get_daily_bars가 PriceDataResponse를 반환한다 — `test_get_daily_bars` +- [x] 404 응답 시 OracleNotFoundError가 발생한다 — `test_not_found_raises_oracle_not_found` +- [x] 500 응답 시 OracleServerError가 발생한다 — `test_server_error_raises_oracle_server_error` +- [x] 일시적 500 후 성공 시 정상 결과를 반환한다 — `test_get_retries_on_transient_error_then_succeeds` +- [x] FinraService.get_short_volume가 ShortVolumeResponse를 반환한다 — `test_get_short_volume` +- [x] FinancialService.get_financial_data가 FinancialDataResponse를 반환한다 — `test_get_financial_data` +- [x] ConnectError 시 OracleConnectionError가 발생한다 (3회 재시도 후) — `test_connection_error_raises_oracle_connection_error` +- [x] ReadTimeout 시 OracleTimeoutError가 발생한다 (3회 재시도 후) — `test_timeout_raises_oracle_timeout_error` +- [x] context manager 없이 get() 호출 시 RuntimeError가 발생한다 — `test_client_without_context_manager_raises` + +#### FredService (`tests/unit/test_fred_service.py`) + +- [x] FredService.get_observations가 FredProxyResponse(series_id, observations)를 반환한다 — `test_get_observations` +- [x] FredService.get_series_info가 FredSeriesInfo(id, frequency)를 반환한다 — `test_get_series_info` + +### 2.8 parser + +#### text_normalizer (`tests/unit/test_text_normalizer.py`) + +- [x] HTML 태그가 제거된다 +- [x] 연속 공백/개행이 정규화된다 +- [x] 빈 문자열 입력이 처리된다 + +#### rule_parser (`tests/unit/test_rule_parser.py`) + +- [x] Item 2.02가 earnings_release로 분류된다 +- [x] Item 7.01이 analyst_day로 분류된다 +- [x] Item 1.01이 agreement_signed으로 분류된다 +- [x] Item 8.01이 other_disclosure로 분류된다 +- [x] 긍정 키워드로 bullish 방향이 감지된다 +- [x] 부정 키워드로 bearish 방향이 감지된다 +- [x] Guidance raised/lowered 구문이 올바르게 분류된다 +- [x] non_gaap_heavy risk flag가 감지된다 +- [x] financing_related risk flag가 감지된다 +- [x] evidence에 매칭 rule_id가 포함된다 +- [x] 메타데이터(filing_date, accepted_at_utc)가 event_date/filing_time_bucket에 반영된다 + +#### schema_validator (`tests/unit/test_schema_validator.py`) + +- [x] 유효한 parser output이 validate 통과한다 +- [x] 잘못된 event_type enum은 invalid 처리된다 +- [x] confidence가 범위(0~1)를 벗어나면 invalid 처리된다 +- [x] 필수 필드 누락 시 invalid 처리된다 + +#### llm_parser_stub (`tests/unit/test_llm_parser_stub.py`) + +- [x] enabled=False 인 경우 parse()가 None을 반환한다 — `test_disabled_returns_none` +- [x] enabled=True 인 경우 parse()가 NotImplementedError를 발생시킨다 — `test_enabled_raises_not_implemented` + +### 2.9 features + +#### market_features (`tests/unit/test_market_features.py`) + +- [x] event_date 기준 reaction_day_return이 계산된다 +- [x] 전일 대비 pre_event_return이 계산된다 +- [x] bars가 없으면 빈 dict가 반환된다 +- [x] event_date가 bars에 없어도 가장 가까운 날짜로 폴백한다 +- [x] avg_volume_20d가 bars 수에 맞게 계산된다 + +#### event_features (`tests/unit/test_event_features.py`) + +- [x] bullish 방향 시 guidance_direction_score > 0이다 +- [x] bearish 방향 시 guidance_direction_score < 0이다 +- [x] risk_flags 비율이 올바르게 계산된다 +- [x] confidence.overall이 feature에 포함된다 +- [x] signals dict가 올바르게 변환된다 + +#### financial_features (`tests/unit/test_financial_features.py`) + +- [x] 최신 period의 eps/gross_margin/operating_margin이 추출된다 +- [x] period가 2개 이상일 때 eps_growth_qoq가 계산된다 +- [x] period가 2개 이상일 때 revenue_growth_qoq가 계산된다 +- [x] periods가 비어 있으면 빈 dict가 반환된다 +- [x] prior eps가 0일 때 eps_growth_qoq가 None이다 + +### 2.10 db models (`tests/unit/test_db_models.py`) + +- [x] IssuerMaster 필드(issuer_id, issuer_name, ticker, is_active)가 올바르게 설정된다 — `test_issuer_master_fields` +- [x] Document 기본값(parsed_status="pending")이 올바르게 설정된다 — `test_document_defaults` +- [x] Event 기본값(status="pending")이 올바르게 설정된다 — `test_event_defaults` +- [x] JobRun 필드(records_seen, records_written)가 올바르게 설정된다 — `test_job_run_fields` +- [x] DB enum 값(JobStatus/ParsedStatus/EventDirection/EventType/ParserKind)이 올바르게 정의된다 — `test_enums` + +--- + +## 3. 통합 테스트 체크리스트 + +Docker postgres 필요 (`docker compose up postgres`). `make test-integration`으로 실행. + +### 3.1 Filing Pipeline (`tests/integration/test_filing_pipeline.py`) + +- [x] Document upsert helper가 중복 없이 idempotent 하다 — `test_document_upsert_idempotency_via_helpers` +- [x] 동일 document_id 재삽입 시 중복 row가 생기지 않는다 — `test_document_upsert_idempotency` +- [x] Event + EventParse lifecycle이 정상 작동한다 — `test_event_parse_lifecycle` + +### 3.2 Sync Jobs (`tests/integration/test_sync_jobs.py`) + +- [x] MacroSync: macro_observations 테이블에 적재된다 — `test_macro_series_insert` +- [x] ShortVolumeSync: short_sale_daily 테이블에 적재된다 — `test_short_sale_daily_insert` +- [ ] IssuerSync: issuer_master 테이블이 갱신된다 (테스트 미작성) + +### 3.3 DB Migration (`tests/integration/test_db_migration.py`) + +- [x] 14개 테이블이 모두 생성된다 — `test_all_tables_exist` +- [x] DB 연결이 정상이다 — `test_db_health` + +### 3.4 Feature Pipeline (`tests/integration/test_feature_pipeline.py`) + +- [x] market_v1/event_v1 스냅샷이 정상 생성된다 — `test_feature_snapshot_created` +- [x] financial_service 제공 시 financial_v1 스냅샷이 추가 생성된다 — `test_financial_v1_snapshot_created` + +--- + +## 4. 리플레이 테스트 체크리스트 (`tests/replay/`) + +### 4.1 결정성 (`test_determinism.py`) + +- [x] 동일 텍스트에 대해 동일 parser version이면 event_type/guidance/confidence가 동일하다 — `test_parser_determinism` +- [x] 부정/혼합 텍스트에서도 동일 결과가 나온다 — `test_parser_determinism_negative_text` +- [x] event feature 계산이 deterministic 하다 — `test_feature_determinism` + +### 4.2 Idempotency (`test_idempotency.py`) + +- [x] 동일 document_id로 parser를 두 번 실행해도 동일한 결과가 나온다 +- [x] checksum이 동일 내용에 대해 항상 동일하다 + +--- + +## 5. 운영 전 수동 점검 + +### 5.1 환경 + +- [ ] `docker compose up postgres` 후 `make bootstrap` 성공 +- [ ] `make migrate` 실행 후 14개 테이블 생성 확인 +- [ ] `python -m apps.pipeline.filing_poller.main --dry-run` 실행 가능 + +### 5.2 관측 가능성 + +- [ ] 모든 job에 job_run_id가 JSON 로그에 찍힌다 +- [ ] 실패 시 로그만 보고 실패 지점을 식별 가능하다 +- [ ] records_seen / records_written / records_skipped가 job_runs에 기록된다 + +### 5.3 데이터 품질 + +- [ ] Oracle에서 실제 filing 1건을 fetch해 문서 내용 확인 +- [ ] parser 결과 5건을 수동 검수해 event_type/guidance 품질 확인 +- [ ] feature snapshot 1건을 조회해 필드값이 합리적인지 확인 + +### 5.4 실패 안전성 + +- [ ] Stock Oracle 응답 없을 때 OracleConnectionError → retry → 로그 확인 +- [ ] parser invalid output이 Event 테이블로 흘러가지 않음을 확인 +- [ ] DB 장애 시 JobRun status가 "failed"로 기록됨을 확인 + +--- + +## 6. CI 최소 요구 사항 + +- [x] `make test-unit` 통과 (105 tests) +- [x] `make test-replay` 통과 (5 tests) +- [x] `ruff check` 통과 (format check는 기존 파일 formatting 필요) +- [x] `make test-integration` 통과 (9 tests pass) +- [x] migration smoke test 통과 + +--- + +## 7. Phase 1 승인 기준 + +| 항목 | 상태 | 비고 | +|---|---|---| +| 단위 테스트 자동화 세트 | ✅ | 105 tests pass | +| 리플레이 테스트 | ✅ | 5 tests pass | +| OracleClient 에러 처리 | ✅ | Connection/Timeout/ServerError 모두 커버 | +| parser schema validation | ✅ | JSON schema 기반 validation 테스트 완료 | +| feature snapshot 생성 | ✅ | market_v1/event_v1/financial_v1 코드 완료 | +| retry/backoff 정책 | ✅ | with_retry exhaustion 테스트 포함 | +| 로깅 구성 | ✅ | configure_logging/bind/get_logger 테스트 완료 | +| 통합 테스트 | ✅ | 9 tests pass (Docker postgres 실제 연동, Oracle은 httpx_mock) | +| 운영 전 수동 점검 | ⬜ | 실제 Oracle 연동 환경에서 수행 필요 | diff --git a/dev/phase2_deliverables/README.md b/dev/phase2_deliverables/README.md new file mode 100644 index 0000000..79706f1 --- /dev/null +++ b/dev/phase2_deliverables/README.md @@ -0,0 +1,100 @@ +# Phase 2 개발문서 패키지 + +이 문서는 **Phase 0에서 고정한 전략/리스크/데이터 정책**과 **Phase 1에서 고정한 저장소 구조/DB/서비스 계약**을 바탕으로, +AI 코딩 에이전트가 **실제 데이터 수집 파이프라인을 구현**할 수 있도록 만든 **Phase 2 상세 개발문서**입니다. + +## 목표 + +Phase 2의 목표는 아래 6가지를 실제 코드 수준으로 구현하는 것입니다. + +1. **핵심 무료 데이터 소스 수집기(SEC / Alpaca / FRED / FINRA)를 안정적으로 구현**한다. +2. **raw → staging → structured 적재 경로를 완성**한다. +3. **스케줄링, 재시도, 백필(backfill), 재처리(replay) 규칙을 표준화**한다. +4. **데이터 품질 검증 규칙과 장애 대응 기준을 고정**한다. +5. **운영 로그, 작업 이력, 중복 방지, 체크포인트를 구현**한다. +6. **Phase 3의 parser/feature builder가 그대로 붙을 수 있는 ingestion contract를 제공**한다. + +## 포함 문서 + +- `ingestion_architecture.md` + - Phase 2 전체 아키텍처 + - raw/staging/structured 흐름 + - 수집기/정규화기/적재기의 경계 + - 체크포인트와 재시도 구조 +- `source_adapter_specs.md` + - SEC / Alpaca / FRED / FINRA adapter 상세 명세 + - 입력/출력/예외/체크포인트 규칙 + - rate limit / retry / idempotency 정책 +- `storage_layout_and_data_contracts.md` + - 파일 경로 규칙 + - Raw sidecar 메타데이터 규격 + - Structured write 규칙 + - canonical record 정의 +- `orchestration_and_scheduling.md` + - 작업 스케줄 + - 백필 방식 + - replay 정책 + - 실패/복구 흐름 +- `implementation_plan.md` + - AI 코딩 에이전트용 작업 순서 + - 단위 작업 분할 + - 완료 조건 + - 금지사항 +- `testing_checklist.md` + - 단위 테스트 + - 통합 테스트 + - 리플레이 테스트 + - 운영 전 점검표 +- `operator_runbook.md` + - 수집기 운영 규칙 + - 장애 대응 절차 + - 일일 점검 항목 + - 데이터 이상 시 조치 순서 +- `job_catalog.md` + - 각 배치 작업의 이름, 책임, 입력, 출력, 실행 시점 + - CLI/cron 예시 + +## Phase 2 범위 + +Phase 2에서는 아래까지만 구현합니다. + +- 핵심 4개 무료 데이터 소스 수집기 +- raw archive 저장 +- staging 정규화 결과 저장 +- structured write +- 작업 이력/상태/로그 기록 +- 재시도/백필/replay +- 데이터 품질 검증 +- 최소 운영 runbook + +## Phase 2에서 일부러 하지 않는 것 + +- 본격적인 event scoring +- LLM 기반 문서 해석 고도화 +- attention layer(YouTube / Yahoo / Wikimedia) 구현 +- 실거래 주문 엔진 +- 포트폴리오 최적화 +- UI 대시보드 + +## 권장 실행 순서 + +1. `ingestion_architecture.md` 읽기 +2. `storage_layout_and_data_contracts.md` 읽기 +3. `source_adapter_specs.md` 기준으로 adapter 구현 +4. `orchestration_and_scheduling.md` 기준으로 잡 스케줄 작성 +5. `job_catalog.md` 기준으로 CLI entrypoint 정리 +6. `implementation_plan.md` 순서대로 구현 +7. `testing_checklist.md`로 검증 +8. `operator_runbook.md`로 운영 절차 확인 + +## 완료 기준 + +Phase 2 종료 시 아래가 가능해야 합니다. + +- 특정 거래일 범위에 대해 SEC / Alpaca / FRED / FINRA 데이터를 백필할 수 있다. +- 동일 잡을 여러 번 실행해도 중복 적재가 발생하지 않는다. +- raw 저장 성공 전에는 downstream structured write가 일어나지 않는다. +- 실패한 잡을 안전하게 재시도할 수 있다. +- staging/structured 데이터가 canonical schema를 만족한다. +- 각 source별 체크포인트가 기록되고 다음 실행이 이어진다. +- 운영자가 runbook만 보고 장애를 재현/복구할 수 있다. diff --git a/dev/phase2_deliverables/implementation_plan.md b/dev/phase2_deliverables/implementation_plan.md new file mode 100644 index 0000000..3636c11 --- /dev/null +++ b/dev/phase2_deliverables/implementation_plan.md @@ -0,0 +1,224 @@ +# Phase 2 구현 계획 + +이 문서는 AI 코딩 에이전트가 Phase 2를 실제로 구현할 수 있도록 작업 순서, 산출물, 완료 기준을 정의합니다. + +## 1. 구현 원칙 + +1. 작은 단위로 쪼개서 머지 가능한 PR 수준으로 작업합니다. +2. 각 단계는 **테스트 가능**한 산출물을 남겨야 합니다. +3. raw-first / idempotent / replayable 원칙을 절대 깨지 않습니다. +4. `TODO`로 남기는 것보다 최소 동작 경로를 먼저 완성합니다. +5. Phase 2 범위 밖 기능을 섞지 않습니다. + +## 2. 선행조건 + +아래가 준비되어 있어야 합니다. + +- Phase 0 문서 승인 +- Phase 1 문서 승인 +- 저장소 구조 초기화 +- PostgreSQL/DuckDB 연결 확인 +- 공통 설정 로더 및 로깅 유틸 준비 +- Phase 1 DB migration baseline 적용 + +## 3. 작업 분할 + +### Workstream A — 공통 실행 프레임 + +#### A1. Job Runner 공통 모듈 +구현: +- run_id 생성기 +- job context +- structured log helper +- 상태 전이 helper +- retry helper + +완료 기준: +- 샘플 job이 created → running → completed까지 상태를 남길 수 있어야 함 + +#### A2. Checkpoint Store +구현: +- checkpoint load/save 인터페이스 +- source/job별 namespace +- optimistic update 또는 atomic update + +완료 기준: +- 샘플 key에 대해 저장/조회/갱신 가능 +- race condition 기본 테스트 통과 + +#### A3. Raw File Store +구현: +- temp write → checksum → atomic rename +- sidecar 메타 저장 +- path builder + +완료 기준: +- 샘플 payload에 대해 raw + sidecar 저장 가능 +- checksum mismatch 시 예외 발생 + +### Workstream B — SEC 수집기 + +#### B1. submissions poll +구현: +- CIK 목록 입력 +- submissions JSON fetch +- raw 저장 +- filing header staging 생성 +- documents upsert + +완료 기준: +- 최소 1개 CIK에 대해 최신 submissions 처리 성공 +- 신규 accession 탐지 가능 + +#### B2. filing fetch +구현: +- accession 대상 fetch +- filing text/index/exhibit 저장 +- artifact inventory 생성 + +완료 기준: +- accession 1건에 대해 filing 본문과 99.1 저장 가능 + +#### B3. xbrl extract +구현: +- XBRL 대상 식별 +- 주요 fact rows 추출 +- canonical fact 저장 + +완료 기준: +- accession 1건에서 fact rows 생성 가능 + +### Workstream C — Alpaca 수집기 + +#### C1. daily bars backfill +구현: +- symbol/date range 입력 +- raw 저장 +- canonical daily bars 생성 +- PostgreSQL/Parquet 저장 + +완료 기준: +- 3개 이상 심볼, 20일 이상 backfill 성공 + +#### C2. intraday bars poll +구현: +- symbol list 입력 +- 지정 trading day intraday 수집 +- canonical intraday bars 생성 + +완료 기준: +- 1개 trading day에 대해 분봉 데이터 저장 가능 + +### Workstream D — FRED / FINRA 수집기 + +#### D1. FRED series sync +구현: +- series_id 목록 설정 +- series metadata + observations 저장 + +완료 기준: +- 3개 이상 series 동기화 성공 + +#### D2. FINRA short volume +구현: +- 거래일 파일 fetch +- 파싱 및 구조화 +- 비정상 행 검출 + +완료 기준: +- 1일 파일 처리 및 symbol row 저장 성공 + +### Workstream E — 검증 및 운영 + +#### E1. Data Quality Validator +구현: +- uniqueness 검사 +- null/empty 검사 +- timestamp/date 범위 검사 +- freshness 검사 + +완료 기준: +- source별 검증 리포트 생성 가능 + +#### E2. CLI + Cron friendly entrypoints +구현: +- 각 잡 CLI +- dry_run / backfill / replay 지원 + +완료 기준: +- 문서에 있는 CLI 예시가 실제로 동작 + +#### E3. Runbook support +구현: +- failed run 재시도 명령 +- quarantine 조회 명령 +- lineage 조회 유틸 + +완료 기준: +- 운영자가 CLI만으로 기본 진단 가능 + +## 4. 권장 구현 순서 + +1. Workstream A +2. Workstream B1 +3. Workstream C1 +4. Workstream D1 / D2 +5. Workstream B2 +6. Workstream C2 +7. Workstream B3 +8. Workstream E + +이 순서의 이유: +- 먼저 공통 런타임을 안정화해야 함 +- 그 다음 전략에 가장 중요한 SEC + 일봉 가격부터 확보 +- 나머지 소스는 이후 붙여도 연구가 가능 + +## 5. AI 코딩 에이전트용 세부 지시 + +### 5.1 구현 스타일 + +- 각 collector는 `main.py`, `runner.py`, `client.py`, `normalize.py`, `write.py`로 분리 +- source별 하드코딩 최소화 +- 공통 로직은 `libs/common`, `libs/adapters`, `libs/db`로 이동 +- 타입힌트 필수 +- 테스트 fixture를 먼저 만들고 구현 + +### 5.2 금지사항 + +- source adapter 내부에서 전략 점수 계산 금지 +- raw 저장 없이 structured write 금지 +- print 기반 로그 금지 +- 전역 mutable state 금지 +- silent failure 금지 + +### 5.3 필수 구현 항목 + +- dry_run +- replay +- force +- run_id propagation +- checksum capture +- structured log +- checkpoint persistence + +## 6. 각 PR 또는 작업 단위의 Definition of Done + +모든 작업은 아래를 만족해야 완료로 봅니다. + +- 코드 구현 완료 +- 단위 테스트 추가 +- 최소 1개 통합 테스트 추가 +- README 또는 해당 문서 갱신 +- 예외/오류 메시지 사람이 이해 가능 +- 로그에 run_id 포함 +- idempotent 재실행 확인 + +## 7. 최종 완료 기준 + +Phase 2 전체 완료 조건: + +- SEC / Alpaca / FRED / FINRA 수집기가 모두 동작한다. +- poll/backfill/replay 3모드가 적어도 핵심 source에 구현돼 있다. +- raw/staging/structured lineage가 연결된다. +- job_runs / checkpoint / quality result가 기록된다. +- 운영자가 runbook만 보고 수집 상태를 확인하고 재실행할 수 있다. diff --git a/dev/phase2_deliverables/ingestion_architecture.md b/dev/phase2_deliverables/ingestion_architecture.md new file mode 100644 index 0000000..58165d4 --- /dev/null +++ b/dev/phase2_deliverables/ingestion_architecture.md @@ -0,0 +1,264 @@ +# Phase 2 데이터 수집 아키텍처 + +## 1. 목적 + +Phase 2의 목적은 **전략 판단 이전 단계의 데이터 공급 계층을 완성**하는 것입니다. +이 단계에서 만드는 시스템은 다음 성질을 만족해야 합니다. + +1. **재실행 가능**해야 한다. +2. **부분 실패에 안전**해야 한다. +3. **원문(raw)을 보존**해야 한다. +4. **정규화와 적재가 분리**되어야 한다. +5. **체크포인트 기반 증분 수집**이 가능해야 한다. +6. **백필과 운영 실행이 동일한 코드 경로**를 사용해야 한다. + +## 2. 상위 구조 + +```text +External Sources + ├─ SEC EDGAR / data.sec.gov + ├─ Alpaca Market Data + ├─ FRED API + └─ FINRA Daily Short Sale Volume + + ↓ +[Source Adapter Layer] + - source client + - fetch policy + - raw payload writer + - source checksum + + ↓ +[Raw Zone] + - immutable payload files + - fetch metadata sidecar + + ↓ +[Normalizer / Extractor Layer] + - canonical field mapping + - record decomposition + - validation + + ↓ +[Staging Zone] + - parseable intermediate outputs + - extracted metadata + - entity identifiers + + ↓ +[Structured Writer Layer] + - PostgreSQL upsert + - Parquet append/replace partition + - dedupe / idempotency guard + + ↓ +[Operational State] + - job_runs + - checkpoints + - source_status + - write_manifest +``` + +## 3. 처리 단위 + +모든 수집 작업은 **job + run_id + source checkpoint** 단위로 동작합니다. + +### 3.1 Job + +예: +- `sec_submissions_poll` +- `sec_filing_fetch` +- `sec_xbrl_extract` +- `alpaca_daily_bars_backfill` +- `alpaca_intraday_bars_poll` +- `fred_series_sync` +- `finra_short_volume_fetch` + +### 3.2 Run ID + +모든 실행은 고유한 `run_id`를 가져야 하며, +raw sidecar / job_runs / structured manifest에 동일하게 남겨야 합니다. + +예시: +- `2026-03-12T21:05:14Z_sec_submissions_poll_001` + +### 3.3 Checkpoint + +각 source/job 조합은 다음 형태의 체크포인트를 가져야 합니다. + +- 시간 기반: 마지막 성공 시각 +- 페이지 기반: 마지막 page token / cursor +- 파일 기반: 마지막 accession / filename / date +- 심볼 기반: 마지막 심볼/날짜 조합 + +## 4. 계층별 책임 + +### 4.1 Source Adapter + +역할: +- 외부 API/file endpoint 요청 +- response 수신 +- raw payload 저장 +- sidecar 메타데이터 기록 +- 최소 검증(응답 비어 있음, 상태코드 오류, checksum 등) + +금지: +- 전략 점수 계산 +- event classification +- feature engineering + +### 4.2 Normalizer / Extractor + +역할: +- source-specific payload를 canonical schema로 변환 +- 필요한 key field 추출 +- 식별자 생성 +- staging output 생성 + +예: +- SEC filing index에서 accession, filing_date, form_type, exhibit list 추출 +- Alpaca bars 응답을 symbol/timestamp/ohlcv schema로 변환 +- FRED series response를 series_id/date/value schema로 변환 +- FINRA txt를 symbol/date/short_volume/total_volume schema로 변환 + +### 4.3 Structured Writer + +역할: +- PostgreSQL upsert +- Parquet partition write +- write manifest 기록 +- 중복 write 방지 + +### 4.4 Data Quality Validator + +역할: +- null 비율 검사 +- 날짜/시간 일관성 검사 +- primary key uniqueness 검사 +- partition completeness 검사 +- source freshness 검사 + +## 5. 원칙 + +### 5.1 Raw First + +원문 저장이 실패하면 downstream 단계는 진행하지 않습니다. + +### 5.2 Deterministic Output + +동일 input payload는 동일 normalized output을 만들어야 합니다. + +### 5.3 Idempotent Writes + +동일 raw payload를 다시 처리해도 structured 결과가 중복되면 안 됩니다. + +### 5.4 Explicit Status Transition + +작업 상태는 아래처럼 명시적으로 이동해야 합니다. + +```text +created +→ running +→ raw_saved +→ staged +→ structured_written +→ validated +→ completed +``` + +실패 시: + +```text +running +→ failed_retriable +or +running +→ failed_terminal +``` + +## 6. 체크포인트 전략 + +### 6.1 SEC + +- `submissions poll`: 마지막 성공 시각 + 최근 처리 accession 목록 +- `filing fetch`: accession 단위 완료 플래그 +- `xbrl extract`: accession + taxonomy 버전 단위 완료 플래그 + +### 6.2 Alpaca + +- 심볼 / timeframe / trading_date 단위 완료 플래그 +- intraday poll은 마지막 bar timestamp 기록 + +### 6.3 FRED + +- series_id / latest observation date + +### 6.4 FINRA + +- trading_date 파일 존재 여부 + checksum + +## 7. 재시도 정책 + +### 7.1 공통 + +- 네트워크 오류: exponential backoff +- 응답 5xx: 재시도 가능 +- 응답 4xx: 기본은 terminal, 단 rate-limit 계열은 retriable +- schema validation 실패: terminal +- raw write 실패: retriable +- structured deadlock/connection error: retriable + +### 7.2 최대 재시도 + +기본값: +- 즉시 재시도 3회 +- 이후 다음 scheduler tick에 다시 시도 +- 같은 payload가 3번 연속 schema validation 실패 시 quarantine + +## 8. Quarantine + +아래 경우 quarantine 디렉터리와 quarantine 테이블에 기록합니다. + +- parsing 불가 raw +- schema violation raw +- 필수 식별자 누락 +- 비정상적으로 큰 payload +- source format drift 의심 + +경로 예시: + +```text +data/quarantine/sec/2026-03-12/{run_id}/... +``` + +## 9. 운영 상태 저장 + +PostgreSQL에는 최소 아래가 필요합니다. + +- `job_runs` +- `source_checkpoints` +- `raw_objects` +- `write_manifests` +- `data_quality_results` +- `quarantine_records` + +세부 컬럼은 Phase 1의 `db_schema.md`를 따르되, Phase 2에서 필요한 상태 컬럼을 추가합니다. + +## 10. 구성 요소별 구현 우선순위 + +1. SEC submissions / filing fetch +2. Alpaca daily bars +3. FRED series sync +4. FINRA short volume +5. Alpaca intraday bars +6. SEC XBRL extract + +이 순서가 중요한 이유는, +전략 연구의 최소 요건이 **이벤트 원문 + 일봉 가격 + 거시 레짐 + crowding 보조지표**이기 때문입니다. + +## 11. Phase 2 성공 기준 + +- 하루치 수집이 아니라, **기간 백필**이 안전하게 가능해야 합니다. +- 새 실행과 재실행이 동일한 코드 경로를 타야 합니다. +- raw/staging/structured 간 lineage를 역추적할 수 있어야 합니다. +- 운영자가 특정 거래일의 데이터 누락 원인을 추적할 수 있어야 합니다. diff --git a/dev/phase2_deliverables/job_catalog.md b/dev/phase2_deliverables/job_catalog.md new file mode 100644 index 0000000..4551c6b --- /dev/null +++ b/dev/phase2_deliverables/job_catalog.md @@ -0,0 +1,173 @@ +# Phase 2 Job Catalog + +이 문서는 Phase 2에서 구현할 배치 작업을 표준 이름, 입력, 출력, 실행 시점 기준으로 정리한 목록입니다. + +## 1. 공통 필드 + +모든 job은 아래 공통 필드를 가져야 합니다. + +- `job_name` +- `source` +- `mode` +- `run_id` +- `started_at` +- `ended_at` +- `status` +- `input_scope` +- `output_counts` + +## 2. Job 목록 + +### 2.1 `sec_submissions_poll` + +**source**: sec +**목적**: 신규 submissions JSON 수집 및 filing header 추출 +**입력**: CIK 목록 +**출력**: raw submissions, filing headers, documents upsert +**권장 주기**: 평일 10~15분 간격 +**mode 지원**: poll, backfill, replay(제한적) + +예시: + +```bash +python -m apps.collector.sec_collector.main --job sec_submissions_poll --mode poll +``` + +### 2.2 `sec_filing_fetch` + +**source**: sec +**목적**: filing 본문, index, exhibit 다운로드 +**입력**: accession 목록 +**출력**: raw filing artifacts, artifact inventory +**권장 주기**: submissions poll 후 즉시 +**mode 지원**: poll, backfill, replay + +### 2.3 `sec_xbrl_extract` + +**source**: sec +**목적**: XBRL facts 추출 +**입력**: accession 목록 또는 pending xbrl queue +**출력**: xbrl_facts rows +**권장 주기**: filing fetch 후 배치 +**mode 지원**: poll, backfill, replay + +### 2.4 `alpaca_daily_bars_backfill` + +**source**: alpaca +**목적**: 일봉 수집/백필 +**입력**: symbol list, date range +**출력**: daily bars raw/staging/structured +**권장 주기**: 장 종료 후 1회 + 필요 시 backfill +**mode 지원**: poll, backfill, replay + +예시: + +```bash +python -m apps.collector.alpaca_collector.main \ + --job alpaca_daily_bars_backfill \ + --mode backfill \ + --symbols AAPL,MSFT \ + --start-date 2026-01-01 \ + --end-date 2026-01-31 +``` + +### 2.5 `alpaca_intraday_bars_poll` + +**source**: alpaca +**목적**: 분봉 수집 +**입력**: symbol list, trading day +**출력**: intraday bars raw/staging/structured +**권장 주기**: 거래일 종료 후 또는 장중 5~15분 간격 +**mode 지원**: poll, backfill, replay + +### 2.6 `fred_series_sync` + +**source**: fred +**목적**: 거시 시계열 수집 +**입력**: series_id 목록 +**출력**: series metadata, observation rows +**권장 주기**: 일 1회 +**mode 지원**: poll, backfill, replay + +예시: + +```bash +python -m apps.collector.fred_collector.main \ + --job fred_series_sync \ + --mode poll +``` + +### 2.7 `finra_short_volume_fetch` + +**source**: finra +**목적**: 일별 short volume 파일 수집 +**입력**: trade_date 또는 date range +**출력**: raw file, parsed rows, structured rows +**권장 주기**: 일 1회 +**mode 지원**: poll, backfill, replay + +### 2.8 `dq_validate_source_batch` + +**source**: internal +**목적**: source별 데이터 품질 검증 +**입력**: source, date range +**출력**: quality result rows, warnings/errors +**권장 주기**: 각 적재 후 후속 실행 +**mode 지원**: poll, replay + +### 2.9 `lineage_verify_batch` + +**source**: internal +**목적**: raw/staging/structured lineage 검증 +**입력**: source, run_id 또는 date range +**출력**: lineage verification result +**권장 주기**: 일 1회 또는 배포 후 1회 +**mode 지원**: poll, replay + +## 3. 우선순위 + +### P0 (반드시 구현) +- `sec_submissions_poll` +- `sec_filing_fetch` +- `alpaca_daily_bars_backfill` +- `fred_series_sync` +- `finra_short_volume_fetch` + +### P1 (Phase 2 내 구현 권장) +- `sec_xbrl_extract` +- `alpaca_intraday_bars_poll` +- `dq_validate_source_batch` + +### P2 (필요 시) +- `lineage_verify_batch` + +## 4. 운영자가 자주 쓰는 조합 + +### 시나리오 A: 평시 일일 배치 +1. `sec_submissions_poll` +2. `sec_filing_fetch` +3. `sec_xbrl_extract` +4. `alpaca_daily_bars_backfill` (당일) +5. `fred_series_sync` +6. `finra_short_volume_fetch` +7. `dq_validate_source_batch` + +### 시나리오 B: 과거 기간 백필 +1. `alpaca_daily_bars_backfill` +2. `sec_submissions_poll` (범위 제한) +3. `sec_filing_fetch` +4. `sec_xbrl_extract` +5. `fred_series_sync` +6. `finra_short_volume_fetch` + +### 시나리오 C: 버그 수정 후 재처리 +1. `sec_filing_fetch --mode replay` +2. `sec_xbrl_extract --mode replay` +3. `dq_validate_source_batch` +4. `lineage_verify_batch` + +## 5. 완료 기준 + +- 위 P0 job은 모두 실제 코드 entrypoint를 가져야 합니다. +- 각 job은 문서에 나온 입력/출력/상태 규칙을 따라야 합니다. +- 운영자는 job 이름만 보고 역할을 이해할 수 있어야 합니다. diff --git a/dev/phase2_deliverables/operator_runbook.md b/dev/phase2_deliverables/operator_runbook.md new file mode 100644 index 0000000..25b935b --- /dev/null +++ b/dev/phase2_deliverables/operator_runbook.md @@ -0,0 +1,191 @@ +# Phase 2 운영 Runbook + +## 1. 목적 + +이 문서는 운영자가 Phase 2 수집 파이프라인을 실행, 점검, 재시도, 복구할 때 따르는 절차를 정의합니다. + +## 2. 일일 운영 루틴 + +### 장 전 / 오전 + +- SEC submissions poll 정상 동작 여부 확인 +- 전일 FINRA 파일 수집 완료 여부 확인 +- FRED 최신 observation lag 확인 +- source freeze 설정이 의도치 않게 켜져 있지 않은지 확인 + +### 장 후 + +- Alpaca daily bars 적재 완료 여부 확인 +- SEC 신규 filing 유입량 점검 +- 실패 잡 및 quarantine 건수 확인 +- 데이터 품질 검증 리포트 확인 + +## 3. 기본 확인 명령 + +예시 명령은 프로젝트 CLI 이름에 맞게 조정합니다. + +```bash +python -m apps.ops.show_recent_runs --limit 20 +python -m apps.ops.show_failed_runs --since 24h +python -m apps.ops.show_checkpoints +python -m apps.ops.show_quarantine --since 7d +``` + +## 4. 특정 source 수동 실행 + +### SEC poll + +```bash +python -m apps.collector.sec_collector.main --mode poll --run-id MANUAL_SEC_POLL_001 +``` + +### Alpaca daily backfill + +```bash +python -m apps.collector.alpaca_collector.main \ + --mode backfill \ + --symbols AAPL,MSFT,NVDA \ + --start-date 2026-01-01 \ + --end-date 2026-01-31 +``` + +### FRED sync + +```bash +python -m apps.collector.fred_collector.main --mode poll +``` + +### FINRA fetch + +```bash +python -m apps.collector.finra_collector.main --mode poll +``` + +## 5. 실패 시 대응 절차 + +### 5.1 단일 run 실패 + +1. `job_runs`에서 상태와 오류 유형 확인 +2. raw 파일이 쓰였는지 확인 +3. checkpoint가 잘못 전진했는지 확인 +4. retriable 이면 동일 파라미터로 재시도 +5. terminal 이면 source payload와 schema drift 여부 확인 + +### 5.2 같은 잡이 연속 실패 + +1. source freeze 여부 확인 +2. 환경변수/자격정보/네트워크 상태 확인 +3. 최근 코드 변경 사항 확인 +4. 원문 payload 1건을 replay 하여 문제 재현 +5. 필요 시 해당 job freeze 후 다른 source는 계속 진행 + +## 6. Quarantine 처리 + +### 확인 항목 + +- 어떤 source인가 +- 어떤 object type인가 +- 언제부터 발생했는가 +- 동일 원인 반복인가 + +### 절차 + +1. quarantine raw와 sidecar 열람 +2. schema violation 또는 필수 필드 누락 원인 파악 +3. 파서/정규화 로직 수정 필요 여부 판단 +4. 수정 후 replay 수행 +5. 정상 결과 확인 시 quarantine 해제 또는 새 run으로 재처리 + +## 7. Checkpoint 복구 + +### 증상 + +- 이미 처리한 데이터를 계속 다시 가져옴 +- 새 데이터가 안 들어옴 +- 특정 source만 오래 lag 발생 + +### 절차 + +1. 현재 checkpoint 값 백업 +2. 최근 성공 run 기준 정상 위치 확인 +3. 필요한 경우 checkpoint reset 실행 +4. 작은 범위 backfill로 검증 +5. 문제 없으면 정상 poll 재개 + +주의: +- checkpoint reset은 운영자 수동 승인 후에만 수행 +- reset 전 snapshot 기록 필수 + +## 8. Replay 절차 + +Replay는 raw를 다시 해석하거나 구조화할 때 사용합니다. + +예: + +```bash +python -m apps.collector.sec_collector.main \ + --mode replay \ + --raw-path data/raw/sec/2026-03-12/.../filing.txt +``` + +확인할 것: +- replay run_id가 따로 생성되었는가 +- 외부 API 호출이 발생하지 않았는가 +- structured row가 기대한 대로 갱신되었는가 + +## 9. 데이터 이상 탐지 시 판단 기준 + +### SEC + +- 특정 대형 종목 filing이 비정상적으로 누락됨 +- filing_date 또는 accession 누락 +- 99.1 artifact가 갑자기 전부 사라짐 + +### Alpaca + +- 전일 bars가 없거나 너무 적음 +- OHLC 순서 이상 +- volume이 0 또는 과도하게 작음 + +### FRED + +- 최신 observation이 지나치게 오래 갱신되지 않음 +- series metadata가 바뀜 + +### FINRA + +- 파일 헤더 형식 변경 +- symbol row 급감 +- ratio 계산 불가 행 급증 + +## 10. 일시적 중단(Freeze/Drain) + +### Freeze + +신규 실행을 즉시 막습니다. + +### Drain + +현재 실행만 마무리하고 다음 스케줄부터 멈춥니다. + +사용 예: +- source 응답 포맷 변화 의심 +- 저장 계층 장애 +- 코드 배포 직후 이상 발견 + +## 11. 운영자가 반드시 남겨야 하는 기록 + +- 문제 발생 시각 +- 영향 받은 source/job +- 영향 범위(날짜/심볼/CIK) +- 임시 조치 +- 영구 수정 필요 여부 +- replay/backfill 수행 여부 + +## 12. 운영 종료 체크 + +- 실패 run이 남아 있지 않은가 +- checkpoint lag가 허용 범위 내인가 +- quarantine 신규 건이 있는가 +- raw/staging/structured count가 대체로 합리적인가 +- 다음 배치를 막는 freeze가 켜져 있지 않은가 diff --git a/dev/phase2_deliverables/orchestration_and_scheduling.md b/dev/phase2_deliverables/orchestration_and_scheduling.md new file mode 100644 index 0000000..1b8f2e4 --- /dev/null +++ b/dev/phase2_deliverables/orchestration_and_scheduling.md @@ -0,0 +1,244 @@ +# Phase 2 오케스트레이션 및 스케줄링 + +## 1. 목적 + +Phase 2에서는 복잡한 큐 시스템보다 **명시적 배치 실행과 체크포인트 기반 스케줄링**을 우선합니다. +이 문서는 각 잡의 실행 시점, 의존성, 백필 방식, 재처리 방식을 정의합니다. + +## 2. 스케줄링 원칙 + +1. **원천 데이터 수집은 source 특성에 맞춘다.** +2. **정규화/적재는 raw 저장 성공 이후에만 실행한다.** +3. **하루 운영 배치와 장기 백필 배치는 같은 코드 경로를 사용한다.** +4. **한 job 실패가 전체 DAG를 막지 않도록 source별 격리**한다. +5. **job 순서는 time-critical source를 먼저**, low-frequency source를 나중에 둔다. + +## 3. 권장 스케줄 표 + +### 3.1 일일/주기 스케줄 + +#### SEC submissions poll +- 주기: 평일 장중/장후 10~15분 간격 +- 목적: 신규 filing accession 탐지 +- 후속 작업: `sec_filing_fetch` + +#### SEC filing fetch +- 주기: submissions poll 성공 후 즉시 트리거 +- 목적: filing 본문 및 exhibit 저장 +- 후속 작업: `sec_xbrl_extract` + +#### SEC xbrl extract +- 주기: filing fetch 성공 후 지연 실행 가능 +- 목적: XBRL facts 추출 +- 비고: filing 원문 저장과 분리 가능 + +#### Alpaca daily bars +- 주기: 거래일 종료 후 1회 +- 목적: 일봉 적재 +- 비고: 운영 시각은 보수적으로 장 종료 이후 충분한 지연을 둠 + +#### Alpaca intraday bars +- 주기: 거래시간 동안 5~15분 간격 또는 거래일 종료 후 일괄 수집 +- 목적: 기본 분봉 적재 +- 비고: 무료 제약상 Phase 2는 일괄 수집 우선 + +#### FRED series sync +- 주기: 일 1회 +- 목적: 거시 레짐 데이터 업데이트 + +#### FINRA short volume +- 주기: 일 1회 +- 목적: 당일 게시된 직전 거래일 파일 적재 + +## 4. 잡 의존성 + +기본 DAG: + +```text +sec_submissions_poll + → sec_filing_fetch + → sec_xbrl_extract + → sec_document_header_write + +alpaca_daily_bars_backfill + → market_bars_daily_write + +alpaca_intraday_bars_poll + → market_bars_intraday_write + +fred_series_sync + → macro_series_write + +finra_short_volume_fetch + → short_volume_write +``` + +원칙: +- SEC 계열 DAG와 market 계열 DAG는 독립적으로 돌아야 합니다. +- FRED/FINRA 실패가 SEC/Alpaca 수집을 막으면 안 됩니다. + +## 5. 실행 모드 + +### 5.1 Poll + +운영 배치 모드입니다. +- 최신 데이터만 증분 수집 +- 체크포인트 사용 +- 기본 모드 + +### 5.2 Backfill + +과거 기간을 채우는 모드입니다. +- 날짜 범위 명시 +- 진행률 기록 필수 +- 중간 실패 후 resume 가능해야 함 + +### 5.3 Replay + +기존 raw를 다시 파싱/적재하는 모드입니다. +- 외부 source 호출 없음 +- parser drift / schema 변경 / 버그 수정 시 사용 + +## 6. 백필 정책 + +### 6.1 날짜 분할 + +백필은 반드시 작은 단위 chunk로 나눕니다. + +예: +- SEC: CIK batch 또는 accession batch +- Alpaca: 심볼 x 월 단위 +- FRED: series별 연 단위 +- FINRA: 거래일 단위 + +### 6.2 진행률 저장 + +`backfill_runs` 또는 `job_runs`에 아래를 남깁니다. + +- 전체 대상 수 +- 완료 수 +- 실패 수 +- 마지막 성공 chunk +- 재시작 포인터 + +### 6.3 재시작 규칙 + +중간 실패 시 마지막 성공 chunk 다음부터 재시작합니다. + +## 7. Replay 정책 + +Replay는 아래 경우에만 사용합니다. + +- parser 로직 변경 +- canonical schema 변경 +- structured write 버그 수정 +- source 응답 포맷 drift 대응 + +Replay의 기본 원칙: +- raw는 불변 +- staging/structured만 다시 생성 +- replay run_id를 별도로 부여 +- 기존 결과를 덮어쓸지, 버전 테이블로 남길지 사전에 결정 + +## 8. CLI 표준 + +모든 잡은 아래 형태의 CLI를 지원합니다. + +```bash +python -m apps.collector.sec_collector.main \ + --mode poll \ + --run-id + +python -m apps.collector.alpaca_collector.main \ + --mode backfill \ + --symbols AAPL,MSFT,NVDA \ + --start-date 2025-01-01 \ + --end-date 2025-03-31 \ + --run-id +``` + +필수 규칙: +- `run_id` 명시 가능 +- 없으면 시스템 생성 +- `dry_run` 지원 +- `force` 지원 + +## 9. Cron 예시 + +```cron +# SEC submissions poll every 15 minutes on weekdays +*/15 * * * 1-5 python -m apps.collector.sec_collector.main --mode poll + +# Daily bars after market close +30 22 * * 1-5 python -m apps.collector.alpaca_collector.main --mode poll --timeframe 1D + +# FRED once nightly +15 23 * * 1-5 python -m apps.collector.fred_collector.main --mode poll + +# FINRA once nightly +30 23 * * 1-5 python -m apps.collector.finra_collector.main --mode poll +``` + +실제 시각은 환경/타임존에 맞춰 config에서 오버라이드 가능해야 합니다. + +## 10. 실패 처리 + +### 10.1 Soft Failure + +예: +- 일부 심볼 실패 +- 일부 series 실패 +- 일부 accession 실패 + +처리: +- 실패 객체만 기록 +- 전체 job는 partial success 가능 +- 실패 목록은 후속 retry 대상 + +### 10.2 Hard Failure + +예: +- 인증/환경설정 오류 +- 저장소 write 불가 +- checkpoint load 실패 +- schema registry 로드 실패 + +처리: +- 전체 job 중단 +- `failed_terminal` 또는 `failed_retriable` + +## 11. Freeze / Drain 모드 + +운영자가 문제 source를 잠시 멈출 수 있어야 합니다. + +- `freeze`: 신규 poll 중단 +- `drain`: 현재 실행만 마무리 후 중단 + +설정 예시: + +```yaml +sources: + sec: + frozen: false + alpaca: + frozen: false +jobs: + sec_xbrl_extract: + frozen: true +``` + +## 12. 운영 알림 + +최소 알림 조건: +- 2회 이상 연속 실패 +- checkpoint lag가 허용치 초과 +- raw 저장 성공률 급락 +- structured validation 실패 +- quarantine 발생 + +## 13. 완료 기준 + +- poll/backfill/replay가 모두 작동해야 합니다. +- 체크포인트 기반 resume가 가능해야 합니다. +- source별 실패 격리가 동작해야 합니다. +- 운영자가 특정 잡만 선택 재실행할 수 있어야 합니다. diff --git a/dev/phase2_deliverables/source_adapter_specs.md b/dev/phase2_deliverables/source_adapter_specs.md new file mode 100644 index 0000000..76d2f9f --- /dev/null +++ b/dev/phase2_deliverables/source_adapter_specs.md @@ -0,0 +1,331 @@ +# Phase 2 Source Adapter 상세 명세 + +이 문서는 Phase 2에서 구현할 핵심 source adapter의 상세 규격을 정의합니다. +모든 adapter는 다음 공통 인터페이스를 따라야 합니다. + +## 1. 공통 인터페이스 + +### 1.1 입력 + +- `run_id: str` +- `mode: Literal["poll", "backfill", "replay"]` +- `start_date: date | None` +- `end_date: date | None` +- `symbols: list[str] | None` +- `force: bool = False` +- `dry_run: bool = False` + +### 1.2 출력 + +표준 실행 요약 객체: + +```json +{ + "run_id": "...", + "job_name": "...", + "source": "...", + "status": "completed", + "raw_objects_written": 0, + "staging_records_written": 0, + "structured_records_written": 0, + "warnings": [], + "errors": [] +} +``` + +### 1.3 예외 분류 + +- `RetriableSourceError` +- `TerminalSourceError` +- `SchemaValidationError` +- `CheckpointCorruptionError` +- `RawWriteError` +- `StructuredWriteError` + +## 2. SEC Adapter + +### 2.1 책임 + +- 회사 submissions JSON 수집 +- filing index / filing text / exhibit 문서 다운로드 +- accession / form_type / filing_date / acceptance_datetime 추출 +- XBRL facts 추출 대상 식별 + +### 2.2 세부 job + +#### A. `sec_submissions_poll` + +입력: +- CIK 목록 또는 symbol master 기반 CIK 맵 + +raw 출력: +- submissions JSON + +staging 출력: +- filing header row 목록 +- 신규 accession 후보 목록 + +structured 출력: +- `documents` +- `document_versions` + +#### B. `sec_filing_fetch` + +입력: +- accession 목록 + +raw 출력: +- filing text/html +- filing index +- exhibit 문서 + +staging 출력: +- exhibit inventory +- 99.1 / xbrl 관련 링크 + +structured 출력: +- `raw_objects` +- `document_artifacts` + +#### C. `sec_xbrl_extract` + +입력: +- XBRL 가능 accession + +staging 출력: +- canonical fact rows + +structured 출력: +- `xbrl_facts` + +### 2.3 체크포인트 + +- 마지막 submissions fetch 시각 +- accession별 fetch 완료 상태 +- accession별 xbrl extract 완료 상태 + +### 2.4 idempotency key + +- submissions: `(source, cik, fetched_at_date, checksum)` +- filing fetch: `(source, accession, artifact_name, checksum)` +- xbrl facts: `(accession, concept, period_end, unit, segment_hash)` + +### 2.5 구현 주의사항 + +- accession formatting을 내부 표준으로 통일 +- 동일 accession의 문서/artifact는 checksum이 다를 때만 새 버전 생성 +- raw write 전에 checksum 계산 +- 99.1, 8-K, 10-Q, 10-K, 6-K, 20-F 우선 + +### 2.6 실패 조건 + +retriable: +- 일시적 네트워크 오류 +- 응답 타임아웃 +- 일시적 source unavailable + +terminal: +- accession 식별 불가 +- 필수 filing metadata 누락 +- artifact MIME/type 해석 불가 + +## 3. Alpaca Adapter + +### 3.1 책임 + +- 일봉 수집 +- 분봉 수집(Phase 2에서는 1분/5분 중 하나만 선택) +- 심볼별 거래일 범위 backfill + +### 3.2 세부 job + +#### A. `alpaca_daily_bars_backfill` + +입력: +- symbol list +- date range + +raw 출력: +- 원본 bars payload + +staging 출력: +- canonical daily bars + +structured 출력: +- `market_bars_daily` +- Parquet partition `bars_daily/trading_date=YYYY-MM-DD/` + +#### B. `alpaca_intraday_bars_poll` + +입력: +- symbol list +- trading day + +raw 출력: +- intraday bars payload + +staging 출력: +- canonical intraday bars + +structured 출력: +- `market_bars_intraday` +- Parquet partition `bars_intraday/trading_date=YYYY-MM-DD/` + +### 3.3 체크포인트 + +- symbol / timeframe / date 단위 완료 플래그 +- intraday는 마지막 timestamp + +### 3.4 idempotency key + +- `(symbol, timeframe, timestamp)` + +### 3.5 구현 주의사항 + +- timezone normalization을 내부 표준으로 강제 +- 반일장 / 휴장일 처리 +- 동일 바 중복 수신 시 마지막 checksum만 허용 + +### 3.6 실패 조건 + +retriable: +- 네트워크 오류 +- 일시적 rate-limit + +terminal: +- symbol invalid +- timestamp parse 실패 +- 필수 OHLCV 필드 누락 + +## 4. FRED Adapter + +### 4.1 책임 + +- 지정 series_id 목록 sync +- daily/weekly/monthly frequency series 공통 적재 + +### 4.2 세부 job + +#### A. `fred_series_sync` + +입력: +- series_id 목록 +- optional date range + +raw 출력: +- series observations payload + +staging 출력: +- canonical series rows + +structured 출력: +- `macro_series_observations` + +### 4.3 체크포인트 + +- series_id별 마지막 observation date + +### 4.4 idempotency key + +- `(series_id, observation_date)` + +### 4.5 구현 주의사항 + +- 숫자/결측 문자열 처리 규칙 고정 +- revision이 가능한 시계열은 overwrite 가능한 upsert 허용 +- source metadata(series title, units, frequency)도 별도 저장 + +## 5. FINRA Adapter + +### 5.1 책임 + +- daily short sale volume 파일 다운로드 +- 파일 파싱 및 종목별 행 생성 + +### 5.2 세부 job + +#### A. `finra_short_volume_fetch` + +입력: +- trading date range + +raw 출력: +- 원본 txt/csv 파일 + +staging 출력: +- parsed rows + +structured 출력: +- `short_sale_volume_daily` + +### 5.3 체크포인트 + +- trading_date 파일 존재 여부 +- checksum + +### 5.4 idempotency key + +- `(trade_date, symbol)` + +### 5.5 구현 주의사항 + +- header drift 대응 +- symbol 정규화 +- total_volume == 0 또는 short_volume > total_volume 같은 비정상 레코드 검출 + +## 6. 공통 구현 요구사항 + +### 6.1 로그 + +모든 adapter는 최소 아래 필드를 구조화 로그로 남겨야 합니다. + +- `run_id` +- `job_name` +- `source` +- `mode` +- `status` +- `object_count` +- `duration_ms` +- `warning_count` +- `error_count` + +### 6.2 메트릭 + +최소 메트릭: +- fetch success rate +- fetch latency +- raw write latency +- staging rows written +- structured rows written +- checkpoint lag + +### 6.3 Dry Run + +모든 adapter는 `dry_run` 모드를 지원해야 합니다. + +`dry_run=True`일 때: +- 원격 요청은 수행 가능 +- raw/structured write는 하지 않음 +- validation과 실행 계획만 출력 + +### 6.4 Replay + +모든 adapter는 특정 raw object를 replay input으로 받을 수 있어야 합니다. + +예: +- `--raw-path data/raw/sec/.../filing.txt` +- `--raw-path data/raw/finra/.../file.txt` + +### 6.5 Source Freeze + +특정 source가 이상 동작할 때 운영자가 source별 수집을 중단할 수 있어야 합니다. + +예: +- `configs/source_freeze.yaml` +- source/job 단위 on/off + +## 7. 완료 기준 + +- 각 adapter는 poll/backfill/replay를 모두 지원해야 합니다. +- 동일 input에서 동일 output을 만들어야 합니다. +- raw sidecar, structured rows, checkpoint, job_runs가 모두 연결되어야 합니다. diff --git a/dev/phase2_deliverables/storage_layout_and_data_contracts.md b/dev/phase2_deliverables/storage_layout_and_data_contracts.md new file mode 100644 index 0000000..4546029 --- /dev/null +++ b/dev/phase2_deliverables/storage_layout_and_data_contracts.md @@ -0,0 +1,286 @@ +# Phase 2 저장 구조 및 데이터 계약 + +## 1. 목적 + +이 문서는 Phase 2 수집 파이프라인이 쓰는 **파일 경로 규칙**, **raw sidecar 형식**, **staging canonical schema**, **structured write 규칙**을 정의합니다. + +핵심 목표는 아래와 같습니다. + +1. 모든 raw 파일의 출처와 실행(run)을 추적할 수 있어야 한다. +2. 모든 staging/structured 레코드가 원문(raw)로 역추적 가능해야 한다. +3. 파일명/경로만 보고 source, date, object type을 알 수 있어야 한다. +4. 재실행 시 같은 경로/키 체계를 사용해야 한다. + +## 2. Raw 저장 규칙 + +### 2.1 공통 경로 형식 + +```text +data/raw/{source}/{ingestion_date}/{object_scope}/... +``` + +예시: + +```text +data/raw/sec/2026-03-12/cik_0000789019/0001193125-26-027198/submissions.json +data/raw/sec/2026-03-12/cik_0000789019/0001193125-26-027198/filing.txt +data/raw/sec/2026-03-12/cik_0000789019/0001193125-26-027198/exhibit_99_1.html + +data/raw/alpaca/2026-03-12/daily/AAPL.json + +data/raw/fred/2026-03-12/series/DGS10.json + +data/raw/finra/2026-03-12/short_volume/2026-03-11.txt +``` + +### 2.2 Sidecar 메타데이터 + +각 raw object마다 `.meta.json` sidecar를 생성합니다. + +예: + +```text +submissions.json +submissions.json.meta.json +``` + +### 2.3 Sidecar 필수 필드 + +```json +{ + "run_id": "2026-03-12T21:05:14Z_sec_submissions_poll_001", + "job_name": "sec_submissions_poll", + "source": "sec", + "source_url": "https://...", + "http_status": 200, + "fetched_at": "2026-03-12T21:05:14Z", + "content_type": "application/json", + "payload_bytes": 18293, + "sha256": "...", + "mode": "poll", + "source_identifier": { + "cik": "0000789019", + "accession": "0001193125-26-027198" + }, + "parser_hint": { + "object_type": "sec_submissions" + } +} +``` + +### 2.4 Raw 쓰기 원칙 + +- 임시 파일에 먼저 씁니다. +- checksum 계산 후 원자적 rename을 합니다. +- sidecar까지 성공해야 raw 저장 성공으로 간주합니다. +- sidecar 저장 전에는 `job_runs.status = raw_saved`로 올리지 않습니다. + +## 3. Staging 저장 규칙 + +staging은 파싱/정규화의 중간 결과입니다. + +경로 형식: + +```text +data/staging/{source}/{logical_date}/{object_type}/{entity_key}.jsonl +``` + +예시: + +```text +data/staging/sec/2026-03-12/filing_headers/cik_0000789019.jsonl +data/staging/sec/2026-03-12/exhibit_inventory/0001193125-26-027198.jsonl + +data/staging/alpaca/2026-03-12/daily_bars/AAPL.jsonl + +data/staging/fred/2026-03-12/series/DGS10.jsonl + +data/staging/finra/2026-03-12/short_volume/2026-03-11.jsonl +``` + +원칙: +- staging은 재생성 가능 데이터입니다. +- raw가 진실의 원천이고, staging은 파생 데이터입니다. +- staging은 overwrite 가능하지만 lineage가 남아야 합니다. + +## 4. Canonical Record 계약 + +### 4.1 SEC Document Header Record + +```json +{ + "document_id": "sec:0001193125-26-027198", + "source": "sec", + "cik": "0000789019", + "accession": "0001193125-26-027198", + "form_type": "8-K", + "filing_date": "2026-01-28", + "acceptance_datetime": "2026-01-28T16:13:02Z", + "primary_document": "msft-8k.htm", + "has_xbrl": true, + "raw_path": "data/raw/.../filing.txt", + "raw_sha256": "...", + "run_id": "..." +} +``` + +### 4.2 SEC Exhibit Record + +```json +{ + "artifact_id": "sec_artifact:0001193125-26-027198:ex99_1", + "document_id": "sec:0001193125-26-027198", + "artifact_name": "ex99_1.htm", + "artifact_type": "exhibit_99_1", + "mime_type": "text/html", + "raw_path": "data/raw/.../exhibit_99_1.html", + "raw_sha256": "...", + "run_id": "..." +} +``` + +### 4.3 Alpaca Canonical Bar Record + +```json +{ + "source": "alpaca", + "symbol": "AAPL", + "timeframe": "1D", + "ts": "2026-03-11T21:00:00Z", + "trading_date": "2026-03-11", + "open": 212.31, + "high": 214.05, + "low": 211.62, + "close": 213.98, + "volume": 48761234, + "trade_count": 302119, + "vwap": 213.12, + "raw_path": "data/raw/.../AAPL.json", + "raw_sha256": "...", + "run_id": "..." +} +``` + +### 4.4 FRED Observation Record + +```json +{ + "source": "fred", + "series_id": "DGS10", + "observation_date": "2026-03-11", + "value": 4.13, + "frequency": "Daily", + "units": "Percent", + "raw_path": "data/raw/.../DGS10.json", + "raw_sha256": "...", + "run_id": "..." +} +``` + +### 4.5 FINRA Short Volume Record + +```json +{ + "source": "finra", + "trade_date": "2026-03-11", + "symbol": "AAPL", + "short_volume": 14122112, + "total_volume": 42211884, + "short_volume_ratio": 0.3345, + "raw_path": "data/raw/.../2026-03-11.txt", + "raw_sha256": "...", + "run_id": "..." +} +``` + +## 5. Structured Write 규칙 + +### 5.1 PostgreSQL + +운영 상태/메타데이터는 PostgreSQL에 저장합니다. + +대표 테이블: +- `job_runs` +- `source_checkpoints` +- `raw_objects` +- `documents` +- `document_artifacts` +- `xbrl_facts` +- `macro_series_observations` +- `short_sale_volume_daily` + +규칙: +- 운영 테이블은 upsert 기반 +- natural key 또는 idempotency key를 반드시 둠 +- `created_at`, `updated_at`, `run_id` 필수 + +### 5.2 Parquet + +대량 시계열/분석용 데이터는 Parquet로 적재합니다. + +경로 예시: + +```text +data/parquet/market_bars_daily/trading_date=2026-03-11/part-000.parquet +data/parquet/market_bars_intraday/trading_date=2026-03-11/symbol=AAPL/part-000.parquet +data/parquet/fred/series_id=DGS10/part-000.parquet +data/parquet/finra_short_volume/trade_date=2026-03-11/part-000.parquet +``` + +규칙: +- partition overwrite는 날짜/심볼 범위 단위로 제한 +- late-arriving data는 해당 partition 재작성 +- write manifest로 어떤 partition이 언제 갱신됐는지 기록 + +## 6. Lineage 계약 + +모든 staging/structured 레코드는 최소 아래 필드를 가져야 합니다. + +- `source` +- `run_id` +- `raw_path` +- `raw_sha256` +- `ingested_at` + +이 5개가 없으면 downstream에서 사용 금지입니다. + +## 7. 상태 필드 규칙 + +`job_runs.status`는 아래 enum만 허용합니다. + +- `created` +- `running` +- `raw_saved` +- `staged` +- `structured_written` +- `validated` +- `completed` +- `failed_retriable` +- `failed_terminal` +- `quarantined` + +## 8. 중복 방지 + +중복 방지는 아래 3단계로 합니다. + +1. raw checksum 중복 검사 +2. staging natural key 중복 검사 +3. structured upsert key 검사 + +어느 단계에서도 중복이 발생하면 경고를 남기고 동일성 비교 후 no-op 처리합니다. + +## 9. 운영자가 빠르게 확인해야 할 경로 + +최소 아래 경로는 사람이 쉽게 찾을 수 있어야 합니다. + +- 최근 실패 잡 로그 +- quarantine raw +- 특정 accession 관련 raw/staging/structured lineage +- 특정 심볼/거래일 bars 파일 +- 특정 FRED series 최근 observation + +## 10. 완료 기준 + +- 임의의 structured row에서 raw_path를 따라가면 원문을 확인할 수 있어야 합니다. +- raw object checksum이 바뀌면 새 버전 또는 경고가 생성되어야 합니다. +- 동일 run을 다시 실행해도 동일 natural key에 대해 중복 row가 생기지 않아야 합니다. diff --git a/dev/phase2_deliverables/testing_checklist.md b/dev/phase2_deliverables/testing_checklist.md new file mode 100644 index 0000000..6534dd3 --- /dev/null +++ b/dev/phase2_deliverables/testing_checklist.md @@ -0,0 +1,114 @@ +# Phase 2 테스트 체크리스트 + +실제 구현 기준 재작성 (2026-03-12). +Phase 2에서 실제 구현된 기능 3가지 + Phase 1 Gap Coverage 기준. + +> Phase 2 원안(raw/staging/structured/checkpoint 아키텍처)은 채택되지 않음. +> 실제 구현은 Stock Oracle intermediary 아키텍처를 유지하며 아래 기능이 추가됨. + +--- + +## 1. Backfill CLI + +**대상:** `apps/pipeline/filing_poller/main.py`, `apps/sync/*.main.py` +**기능:** `--start-date` / `--end-date` 범위 지정 backfill 실행 + +### 단위 테스트 + +- [ ] `--start-date` / `--end-date` 파싱이 올바르게 동작한다 +- [ ] 날짜 범위가 역순이면 ValidationError가 발생한다 +- [ ] `--dry-run` 플래그가 실제 write를 억제한다 + +### 통합 테스트 (Docker postgres 필요) + +- [ ] `--start-date 2026-01-01 --end-date 2026-01-31`으로 polling이 수행된다 +- [ ] 이미 존재하는 구간을 재실행해도 중복 row가 없다 +- [ ] backfill 완료 후 job_runs 테이블에 succeeded 상태가 기록된다 + +### 수동 점검 + +- [ ] 로컬에서 샘플 symbol 1개로 backfill 1주 실행 성공 +- [ ] job_run_id가 로그에 일관되게 출력됨 + +--- + +## 2. with_retry 적용 (OracleClient.get / OracleClient.post) + +**대상:** `libs/oracle_client/client.py` +**기능:** `@with_retry(max_attempts=3)` 데코레이터 적용 + +### 단위 테스트 + +- [x] ConnectError 시 3회 재시도 후 OracleConnectionError가 발생한다 — `test_connection_error_raises_oracle_connection_error` +- [x] ReadTimeout 시 3회 재시도 후 OracleTimeoutError가 발생한다 — `test_timeout_raises_oracle_timeout_error` +- [x] 2회 실패 후 3회째 성공 시 정상 응답을 반환한다 — `test_get_retries_on_transient_error_then_succeeds` +- [x] 500 응답 3회 후 OracleServerError가 발생한다 — `test_server_error_raises_oracle_server_error` +- [x] context manager 없이 get() 호출 시 RuntimeError가 발생한다 — `test_client_without_context_manager_raises` +- [x] retry exhaustion 후 RetryableError가 최종 raise된다 — `test_with_retry_exhaustion` + +--- + +## 3. Financial Features + +**대상:** `libs/features/financial_features.py`, `libs/features/builder.py` +**기능:** `compute_financial_features` + `build_features_for_event`에 `financial_service` 연동 + +### 단위 테스트 + +- [x] 최신 period의 eps/gross_margin/operating_margin이 올바르게 추출된다 +- [x] 2개 period 기준 eps_growth_qoq가 올바르게 계산된다 +- [x] 2개 period 기준 revenue_growth_qoq가 올바르게 계산된다 +- [x] periods가 비어 있으면 빈 dict가 반환된다 +- [x] prior eps == 0 이면 eps_growth_qoq가 None이다 + +### 통합 테스트 (Docker postgres 필요) + +- [x] financial_service 제공 시 financial_v1 스냅샷이 추가 생성된다 — `test_financial_v1_snapshot_created` +- [x] financial_v1에 latest_eps / revenue_growth_qoq 필드가 포함된다 + +### 수동 점검 + +- [ ] 실제 Oracle에서 AAPL financial data를 받아 financial_v1 스냅샷 생성 확인 +- [ ] financial service 불가 시 market_v1/event_v1은 정상 생성됨을 확인 + +--- + +## 4. Gap Coverage (Phase 1 검증에서 발견된 미비 사항) + +Phase 1 테스트 작성 과정에서 커버되지 않았거나 버그가 발견된 항목. + +### 4.1 발견된 버그 수정 + +- [x] `FredService.get_observations`: `FredProxyResponse(series_id=series_id, **data)` 에서 series_id 중복 keyword argument 버그 수정 (`**{**data, "series_id": series_id}` 로 변경) + +### 4.2 신규 테스트로 커버된 항목 + +- [x] logging 모듈 3개 테스트 추가 (`tests/unit/test_logging.py`) +- [x] FredService 2개 테스트 추가 (`tests/unit/test_fred_service.py`) +- [x] LLMParserStub 2개 테스트 추가 (`tests/unit/test_llm_parser_stub.py`) +- [x] with_retry exhaustion 테스트 추가 (`tests/unit/test_retries.py`) +- [x] OracleClient connection/timeout/no-context-manager 테스트 추가 (`tests/unit/test_oracle_client.py`) + +--- + +## 5. 운영 전 점검 + +- [ ] 모든 sync job에 대해 sample run 로그가 있다 +- [ ] job_runs 테이블에 상태 전이(pending → running → succeeded/failed)가 정상 기록된다 +- [ ] Oracle 응답 실패 시 partial success가 명확히 기록된다 +- [ ] backfill과 poll 모드가 모두 성공한다 +- [ ] runbook(`dev/phase2_deliverables/operator_runbook.md`)에 있는 재시도 명령이 실제로 동작한다 +- [ ] 환경변수 누락 시 fail-fast 한다 + +--- + +## 6. 승인 기준 + +| 항목 | 상태 | 비고 | +|---|---|---| +| Backfill CLI 구현 | ⬜ | CLI 테스트 작성 및 통합 검증 필요 | +| with_retry 단위 테스트 | ✅ | 6개 테스트 통과 | +| Financial Features 단위 테스트 | ✅ | 5개 테스트 통과 | +| financial_v1 통합 테스트 | ✅ | Docker postgres 실제 연동, Oracle은 httpx_mock | +| Gap Coverage 버그 수정 | ✅ | FredService 버그 수정 + 12개 신규 테스트 | +| 운영 전 점검 | ⬜ | 실제 Oracle 환경에서 수행 필요 | diff --git a/dev/phase3_deliverables/README.md b/dev/phase3_deliverables/README.md new file mode 100644 index 0000000..6cfd30a --- /dev/null +++ b/dev/phase3_deliverables/README.md @@ -0,0 +1,114 @@ +# Phase 3 개발문서 패키지 + +이 문서는 **Phase 0의 전략/리스크/데이터 정책**, **Phase 1의 저장소 구조/DB/서비스 계약**, **Phase 2의 ingestion 계약**을 바탕으로, +AI 코딩 에이전트가 **문서 파서, feature builder, 라벨 생성, 품질검증 파이프라인**을 구현할 수 있도록 만든 **Phase 3 상세 개발문서**입니다. + +## 목표 + +Phase 3의 목표는 아래 8가지를 실제 코드 수준으로 구현하는 것입니다. + +1. **공시/첨부문서를 표준 이벤트 레코드로 변환하는 parser 계층**을 구현한다. +2. **규칙 기반 추출과 LLM 보강 추출의 역할 분담**을 고정한다. +3. **이벤트/문서/가격/레짐/attention 피처 생성 규칙**을 고정한다. +4. **1D/3D/5D forward outcome 라벨 생성 규칙**을 고정한다. +5. **수동 검수(review queue)와 품질 보증(QA) 절차**를 만든다. +6. **학습용/리서치용 데이터셋 스냅샷 생성 규칙**을 만든다. +7. **LLM 호출을 캐시/버전관리/재현 가능하게** 만든다. +8. **Phase 4 백테스터가 바로 사용할 수 있는 canonical feature dataset**을 제공한다. + +## 포함 문서 + +- `parser_and_feature_architecture.md` + - Phase 3 전체 아키텍처 + - parse → normalize → enrich → feature → label 흐름 + - 서비스 경계와 데이터 계약 +- `document_parser_design.md` + - 문서 파서 상세 설계 + - 규칙 기반/LLM 기반 역할 분리 + - 필드별 추출 규칙 + - fallback / retry / confidence 정책 +- `feature_catalog.md` + - 이벤트/문서/가격/레짐/attention feature 정의 + - 계산식 + - null 처리 + - 누수(leakage) 금지 규칙 +- `labeling_and_dataset_spec.md` + - 라벨 생성 규칙 + - reaction day / entry day 정의 + - 1D/3D/5D labels, MFE/MAE + - 학습/검증용 스냅샷 생성 규칙 +- `prompt_and_llm_policy.md` + - LLM 프롬프트 정책 + - JSON schema 사용 방식 + - 모델 버전/프롬프트 버전/캐시 키 + - 비용 통제와 fallback 원칙 +- `quality_assurance_and_review.md` + - 수동 검수 흐름 + - disagreement triage + - parser 품질 지표 + - gold set 운영 방식 +- `implementation_plan.md` + - AI 코딩 에이전트용 구현 순서 + - 작업 분할 + - 완료 조건 + - 금지사항 +- `testing_checklist.md` + - 단위 테스트 + - 통합 테스트 + - replay 테스트 + - 품질/운영 전 체크리스트 +- `review_queue_contract.md` + - review queue 스키마 + - 상태 전이 + - 검수 UI/CLI 요구사항 +- `feature_record.schema.json` + - canonical feature record JSON Schema +- `review_record.schema.json` + - 수동 검수 레코드 JSON Schema + +## Phase 3 범위 + +Phase 3에서는 아래까지만 구현합니다. + +- 문서 파서 +- LLM 호출 래퍼와 캐시 +- 파서 출력 정규화 +- feature builder +- labeling job +- review queue +- gold set 평가 +- canonical dataset export + +## Phase 3에서 일부러 하지 않는 것 + +- 모델 학습 자동화의 full pipeline +- 전략 점수식 최종 확정 +- portfolio optimizer +- live 주문 엔진 +- 대시보드 고도화 +- attention source 신규 수집기 구현 + +## 권장 실행 순서 + +1. `parser_and_feature_architecture.md` 읽기 +2. `document_parser_design.md` 읽기 +3. `prompt_and_llm_policy.md` 읽기 +4. `feature_catalog.md` 기반으로 feature builder 구현 +5. `labeling_and_dataset_spec.md` 기반으로 labeler 구현 +6. `review_queue_contract.md` 기반으로 review workflow 구현 +7. `implementation_plan.md` 순서대로 구현 +8. `testing_checklist.md`로 검증 +9. `quality_assurance_and_review.md`로 품질 점검 + +## 완료 기준 + +Phase 3 종료 시 아래가 가능해야 합니다. + +- SEC 이벤트 문서 하나를 canonical parser output으로 변환할 수 있다. +- parser output이 schema-valid JSON을 반환한다. +- 규칙 기반 추출과 LLM 보강 추출이 provenance와 confidence를 남긴다. +- feature builder가 event/document/market/regime/attention 피처를 한 레코드로 결합한다. +- 1D/3D/5D label과 MFE/MAE를 계산할 수 있다. +- 수동 검수 대상이 review queue에 자동 등록된다. +- gold set에 대해 parser 품질 리포트를 생성할 수 있다. +- canonical dataset을 Parquet/JSONL로 export할 수 있다. diff --git a/dev/phase3_deliverables/document_parser_design.md b/dev/phase3_deliverables/document_parser_design.md new file mode 100644 index 0000000..36b23bf --- /dev/null +++ b/dev/phase3_deliverables/document_parser_design.md @@ -0,0 +1,150 @@ +# Document Parser 상세 설계 + +## 1. 설계 원칙 + +1. 문서는 **규칙 기반 추출 → LLM 보강 → 병합** 순서로 처리한다. +2. 추출 결과는 **event taxonomy**와 모순되면 review 대상이다. +3. LLM은 숫자를 계산하거나 invent하지 않는다. +4. 모든 핵심 필드는 evidence span을 남긴다. + +## 2. 파서 입력 + +입력 필수 항목: +- filing_id +- accession_no +- cik +- issuer_name +- symbol +- form_type +- filing_ts +- document_id +- document_type +- normalized_text +- text_hash +- optional_xbrl_summary +- optional_prior_guidance_snapshot + +## 3. 단계별 파서 흐름 + +### Step A. Pre-classification +문서 종류 추정: +- earnings release +- shareholder letter +- contract announcement +- regulatory/approval update +- guidance update +- misc material event + +실패 시: +- `event_type = unknown` +- review queue로 보낼 수 있음 + +### Step B. Rule extraction +규칙 기반으로 먼저 뽑을 필드: +- item numbers +- guidance phrases (`raising`, `updating`, `expects`, `reaffirms`, `withdraws`) +- one-off markers (`tax benefit`, `gain on`, `fair value`, `impairment`, `restructuring`, `non-GAAP`) +- demand markers (`backlog`, `bookings`, `pipeline`, `orders`, `customers`) +- pricing markers (`pricing`, `price increase`, `higher ASP`, `price realization`) +- margin markers (`gross margin`, `operating margin`, `expanding margin`) +- direct numeric snippets for revenue/EPS/guidance if present + +### Step C. LLM enrichment +LLM이 판단할 필드: +- `event_direction`: bullish / bearish / mixed / neutral / unknown +- `guidance_direction`: raised / inline / lowered / withdrawn / ambiguous / unknown +- `quality_assessment`: high_quality / mixed_quality / low_quality / unknown +- `demand_strength`: strong / moderate / weak / unclear +- `pricing_power`: strong / present / absent / unclear +- `management_tone`: strong_positive / mild_positive / neutral / mild_negative / strong_negative / mixed +- `oneoff_suspicion`: none / possible / likely +- `customer_expansion`: yes / no / unclear +- `structural_change_flag`: yes / no / unclear + +### Step D. Canonical merge +병합 규칙 예시: +- 숫자: rule parser 우선 +- taxonomy/classification: rule strong hit가 있으면 rule 우선, 그 외는 llm +- quality/tone: llm 우선 +- one-off: rule hit와 llm 판단을 모두 보존, canonical은 더 보수적인 값 사용 + +## 4. 필수 출력 필드 + +최소 필수 출력: +- `event_instance_id` +- `source_filing_id` +- `symbol` +- `event_type` +- `event_direction` +- `guidance_direction` +- `quality_assessment` +- `oneoff_suspicion` +- `confidence_overall` +- `evidence_refs` +- `parser_version` +- `prompt_version` +- `schema_version` + +## 5. evidence span 정책 + +각 핵심 필드는 evidence span 1개 이상 권장: +- line offsets 또는 character offsets +- 최대 3개 span +- 증거 없는 강한 주장 금지 + +예시: +```json +{ + "guidance_direction": "raised", + "guidance_direction_confidence": 0.88, + "guidance_direction_evidence": [ + {"start": 1245, "end": 1320, "text": "raising full-year revenue guidance..."} + ] +} +``` + +## 6. 금지사항 + +- 문서에 없는 실적 추정치/컨센서스 생성 +- 문서에 없는 티커/세그먼트 생성 +- confidence가 낮은데도 단정적 레이블 출력 +- JSON schema를 어기는 자유형식 텍스트 반환 +- one-off suspicion이 높은데 high_quality로 단정 + +## 7. fallback 정책 + +### 규칙 파서 실패 +- null 허용 +- 실패 사유를 `parse_warnings`에 남김 +- LLM 보강을 시도하되 hallucination 금지 + +### LLM 실패 +- rule-only canonical record 생성 +- `llm_status = failed` +- review queue 생성 + +### JSON validation 실패 +- 1회 자동 수정 요청 가능 +- 그래도 실패면 `parse_failed` 상태로 저장 + +## 8. 수동 검수 트리거 + +다음은 반드시 검수 후보: +- `guidance_direction = ambiguous|unknown` +- `quality_assessment = unknown` +- `oneoff_suspicion = likely` +- `confidence_overall < 0.70` +- rule과 llm의 `event_direction` 충돌 + +## 9. 개발 시 구현 단위 + +추천 구현 순서: +1. text normalizer +2. rule-based keyword extractor +3. line/span mapper +4. llm client wrapper +5. schema validator +6. canonical merge function +7. review queue writer + +각 단계는 독립적으로 테스트 가능해야 합니다. diff --git a/dev/phase3_deliverables/feature_catalog.md b/dev/phase3_deliverables/feature_catalog.md new file mode 100644 index 0000000..efeee4f --- /dev/null +++ b/dev/phase3_deliverables/feature_catalog.md @@ -0,0 +1,149 @@ +# Feature Catalog + +## 1. 원칙 + +1. feature는 반드시 **point-in-time safe** 해야 한다. +2. 각 feature는 `as_of_ts`, `source`, `feature_version`을 가진다. +3. 미래 데이터를 암묵적으로 쓰는 rolling 통계는 금지한다. +4. missing value는 명시적으로 처리하고, silent fill 금지. + +## 2. Feature 그룹 + +### 2.1 Event features + +| feature | 타입 | 설명 | 계산/정의 | null 처리 | +|---|---|---|---|---| +| event_type | categorical | taxonomy 기반 이벤트 종류 | parser output | 불가 | +| event_direction | categorical | bullish/bearish/mixed | parser output | unknown 허용 | +| guidance_direction | categorical | raised/inline/lowered/... | parser output | unknown 허용 | +| quality_assessment | categorical | high/mixed/low/unknown | parser output | unknown 허용 | +| oneoff_suspicion | categorical | none/possible/likely | parser output | unknown 허용 | +| confidence_overall | float | parser 종합 confidence | parser output | 불가 | +| document_count | int | event에 연결된 문서 수 | count | 0 불가 | +| evidence_span_count | int | 핵심 필드 evidence 수 | count | 0 허용 | + +### 2.2 Document text features + +| feature | 타입 | 설명 | +|---|---|---| +| demand_strength_score | float | backlog/bookings/orders/customers 관련 점수 | +| pricing_power_score | float | pricing / ASP / price realization 점수 | +| margin_strength_score | float | margin expansion 관련 점수 | +| tone_score | float | management tone ordinal 매핑 | +| oneoff_keyword_count | int | 일회성 관련 키워드 출현 수 | +| guidance_statement_count | int | guidance 관련 문장 수 | +| doc_length_tokens | int | 정규화 문서 토큰 수 | +| qa_risk_flag | bool | ambiguous language 또는 parser disagreement 여부 | + +권장 ordinal mapping 예시: +- strong_positive = +2 +- mild_positive = +1 +- neutral = 0 +- mild_negative = -1 +- strong_negative = -2 +- mixed = 0 + +### 2.3 Numeric issuer features + +XBRL 또는 rule parser에서 추출: +- revenue_yoy_pct +- gross_margin_yoy_delta +- operating_margin_yoy_delta +- free_cash_flow_yoy_pct +- debt_to_cash_delta +- prior_company_guidance_surprise_pct + +주의: +- 컨센서스 surprise는 v1에 없음 +- 회사 가이던스 대비 actual만 허용 +- 분모 0 또는 누락 시 null + +### 2.4 Market reaction features + +반응일 기준: +- reaction_day_return_pct +- reaction_gap_pct +- reaction_volume_ratio_20d +- reaction_close_location +- reaction_intraday_range_pct +- reaction_close_vs_vwap (가능한 경우) +- reaction_close_vs_20d_ma +- reaction_sector_relative_return + +정의: +- `reaction_close_location = (close - low) / (high - low)` +- `reaction_volume_ratio_20d = volume / avg_volume_20d_prior` + +주의: +- moving average / ATR은 반드시 entry 이전 데이터만 사용 + +### 2.5 Regime features + +- spy_trend_20d +- qqq_trend_20d +- sector_etf_trend_20d +- vix_level +- vix_change_1d +- fred_rate_regime_bucket + +### 2.6 Attention features + +Phase 3에서는 ingestion이 아니라 결합/정규화만 구현: +- yahoo_headline_burst_6h +- yahoo_unique_publishers_24h +- wiki_pageview_zscore_1d +- youtube_influence_score_24h +- finra_short_volume_ratio +- finra_short_ratio_zscore_20d + +주의: +- attention feature는 core trigger 아님 +- missing source가 있어도 학습 레코드는 유지 + +## 3. Feature naming 규칙 + +- snake_case +- 단위 포함 (`_pct`, `_ratio`, `_count`, `_score`) +- 날짜창 포함 (`_1d`, `_3d`, `_20d`) +- categorical은 값 집합 문서화 필수 + +## 4. Leakage 금지 규칙 + +금지 예시: +- reaction day close 후 생성되어야 하는 feature를 same-day open entry 모델에 사용 +- future bar를 포함한 ATR/MA +- 거래 종료 후 게시되는 FINRA data를 당일 시초 진입 feature로 사용 + +필수 메타데이터: +- `feature_as_of_ts` +- `feature_available_ts` +- `entry_convention` + +## 5. Feature availability by entry convention + +### next_open_entry +허용: +- event filing timestamp까지 공개된 정보 +- reaction day 종가까지의 가격/거래량 +- previous day까지의 FINRA/FRED/attention + +금지: +- entry day 장중 가격 +- entry day 종가 + +### dayplus1_close_entry +허용: +- entry day 종가까지의 정보 + +## 6. Feature export 형식 + +Canonical feature record는 최소 아래를 포함: +- ids: event_instance_id, filing_id, symbol, reaction_date +- parser summary +- all feature columns +- availability metadata +- schema_version + +Parquet 저장 시: +- 파티션: `reaction_year=YYYY/reaction_month=MM` +- row group은 심볼 기준보다 날짜 기준 우선 diff --git a/dev/phase3_deliverables/feature_record.schema.json b/dev/phase3_deliverables/feature_record.schema.json new file mode 100644 index 0000000..97d7c12 --- /dev/null +++ b/dev/phase3_deliverables/feature_record.schema.json @@ -0,0 +1,58 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.local/schemas/feature_record.schema.json", + "title": "FeatureRecord", + "type": "object", + "required": [ + "event_instance_id", + "source_filing_id", + "symbol", + "reaction_date", + "entry_convention", + "feature_version", + "schema_version", + "as_of_ts", + "event_type", + "event_direction", + "confidence_overall" + ], + "properties": { + "event_instance_id": {"type": "string"}, + "source_filing_id": {"type": "string"}, + "symbol": {"type": "string"}, + "reaction_date": {"type": "string", "format": "date"}, + "entry_convention": { + "type": "string", + "enum": ["next_open_after_reaction_close", "dayplus1_close"] + }, + "feature_version": {"type": "string"}, + "schema_version": {"type": "string"}, + "as_of_ts": {"type": "string", "format": "date-time"}, + "available_ts": {"type": ["string", "null"], "format": "date-time"}, + "event_type": {"type": "string"}, + "event_direction": {"type": "string"}, + "guidance_direction": {"type": ["string", "null"]}, + "quality_assessment": {"type": ["string", "null"]}, + "oneoff_suspicion": {"type": ["string", "null"]}, + "confidence_overall": {"type": "number", "minimum": 0, "maximum": 1}, + "reaction_day_return_pct": {"type": ["number", "null"]}, + "reaction_volume_ratio_20d": {"type": ["number", "null"]}, + "reaction_close_location": {"type": ["number", "null"], "minimum": 0, "maximum": 1}, + "demand_strength_score": {"type": ["number", "null"]}, + "pricing_power_score": {"type": ["number", "null"]}, + "margin_strength_score": {"type": ["number", "null"]}, + "tone_score": {"type": ["number", "null"]}, + "wiki_pageview_zscore_1d": {"type": ["number", "null"]}, + "youtube_influence_score_24h": {"type": ["number", "null"]}, + "finra_short_volume_ratio": {"type": ["number", "null"]}, + "feature_sources": { + "type": "array", + "items": {"type": "string"} + }, + "warnings": { + "type": "array", + "items": {"type": "string"} + } + }, + "additionalProperties": true +} diff --git a/dev/phase3_deliverables/implementation_plan.md b/dev/phase3_deliverables/implementation_plan.md new file mode 100644 index 0000000..df28779 --- /dev/null +++ b/dev/phase3_deliverables/implementation_plan.md @@ -0,0 +1,156 @@ +# Phase 3 구현 계획 + +## 1. 선행조건 + +- Phase 0 승인 완료 +- Phase 1 저장소/DB/서비스 계약 구현 완료 +- Phase 2 ingestion 파이프라인이 raw/staging/structured 데이터를 안정적으로 제공 +- parser_event.schema.json 사용 가능 + +## 2. 구현 단위 + +### Workstream A. Parser foundation +목표: +- 문서 선택기 +- 텍스트 normalizer +- rule parser skeleton +- span mapper + +완료 조건: +- 8-K + EX-99.1 문서를 정규화 텍스트로 만들 수 있다. + +### Workstream B. LLM enrichment +목표: +- llm client wrapper +- schema validation +- prompt registry +- cache layer +- raw response logging + +완료 조건: +- 동일 문서 재호출 없이 schema-valid 응답을 저장할 수 있다. + +### Workstream C. Canonicalization +목표: +- rule + llm 병합 +- provenance 저장 +- disagreement 탐지 +- review queue 생성 + +완료 조건: +- canonical event record를 event_records에 적재할 수 있다. + +### Workstream D. Feature builder +목표: +- event/document/numeric/market/regime/attention feature 계산 +- as_of_ts / available_ts 저장 +- leakage validator + +완료 조건: +- event_feature_records 생성 가능 + +### Workstream E. Labeling +목표: +- reaction date 계산 +- entry convention별 label 생성 +- MFE/MAE 계산 +- snapshot export + +완료 조건: +- train/valid/test parquet snapshot 생성 가능 + +### Workstream F. QA and review +목표: +- gold set loader +- parser evaluator +- review queue tooling +- weekly quality report skeleton + +완료 조건: +- 골드셋 회귀 검증과 review queue triage 가능 + +## 3. 권장 작업 순서 + +1. document selector + normalizer 구현 +2. rule parser 기본 필드 구현 +3. line/span reference 구현 +4. llm wrapper + schema validator 구현 +5. canonical merge 구현 +6. event feature builder 구현 +7. market/regime feature join 구현 +8. labeler 구현 +9. dataset snapshot exporter 구현 +10. review queue / gold set evaluator 구현 + +## 4. 금지사항 + +- 규칙/LLM 출력 형식을 중간에 자주 바꾸기 +- evidence 없이 핵심 필드 저장 +- as_of_ts 없는 feature 저장 +- future leakage 검사 없이 label 생성 +- raw response를 버리고 normalized output만 저장 + +## 5. Task slicing 예시 + +### Task 1 +`apps/parser/document_selector.py` +- 입력: filing_documents +- 출력: parser_jobs +- 테스트: 8-K with EX-99.1 prioritization + +### Task 2 +`libs/common/text_normalizer.py` +- HTML to text +- disclaimer strip option +- hash generation + +### Task 3 +`apps/parser/rule_parser.py` +- guidance keywords +- one-off keywords +- demand/pricing/margin keywords + +### Task 4 +`libs/llm/client.py` +- invoke +- cache +- retry +- raw response persistence + +### Task 5 +`apps/parser/canonicalize.py` +- merge policy +- disagreement flags +- review queue write + +### Task 6 +`apps/feature_builder/build_event_features.py` +- parser-derived features +- availability metadata + +### Task 7 +`apps/feature_builder/build_market_features.py` +- reaction day price/volume features +- regime join + +### Task 8 +`apps/labeler/generate_labels.py` +- reaction_date +- entry_date +- 1D/3D/5D labels +- MFE/MAE + +### Task 9 +`apps/qa/evaluate_gold_set.py` +- parser metrics +- regression compare + +## 6. 완료 정의 + +Phase 3 완료는 아래를 모두 만족해야 합니다. +- parser output schema validation 100% +- gold set core metrics가 baseline 이상 +- feature leakage tests pass +- snapshot export reproducible +- review queue 생성/소진 흐름 작동 +- operator가 파싱 실패 원인과 review 대상을 추적 가능 diff --git a/dev/phase3_deliverables/labeling_and_dataset_spec.md b/dev/phase3_deliverables/labeling_and_dataset_spec.md new file mode 100644 index 0000000..3a0176e --- /dev/null +++ b/dev/phase3_deliverables/labeling_and_dataset_spec.md @@ -0,0 +1,130 @@ +# Labeling and Dataset Specification + +## 1. 목적 + +Phase 4 백테스터와 모델 학습을 위해 **누수 없는 event-level labels**를 생성한다. + +## 2. 핵심 정의 + +### filing timestamp +SEC filing 또는 허용된 원문이 실제로 공개된 시각. + +### reaction date +시장 참여자가 해당 이벤트를 정규장 가격으로 반영한 첫 거래일. + +규칙: +- 장 시작 전 공개 → 같은 거래일이 reaction date +- 정규장 중 공개 → 같은 거래일이 reaction date +- 장 종료 후 공개 → 다음 거래일이 reaction date + +### entry convention +Phase 3에서는 아래 2개만 공식 지원: +- `next_open_after_reaction_close` +- `dayplus1_close` + +기본 백테스트/학습 convention: +- `next_open_after_reaction_close` + +## 3. 라벨 종류 + +### return labels +- `fwd_return_1d` +- `fwd_return_3d` +- `fwd_return_5d` + +정의: +- entry price 대비 N 거래일 후 종가 수익률 + +### binary labels +- `hit_pos_1r_within_3d` +- `hit_neg_1r_within_3d` +- `close_up_after_3d` +- `close_up_after_5d` + +### path labels +- `mfe_3d` +- `mae_3d` +- `mfe_5d` +- `mae_5d` + +### timing labels +- `bars_to_mfe_3d` +- `bars_to_mae_3d` +- `days_to_peak_close_5d` + +## 4. Entry/Exit price 정의 + +### 기본 엔트리 +`next_open_after_reaction_close` +- entry_date = reaction_date 다음 거래일 +- entry_price = entry_date 공식 open + +### 보조 엔트리 +`dayplus1_close` +- entry_date = reaction_date 다음 거래일 +- entry_price = entry_date close + +### 라벨용 가상 exit +- N일 종가 exit +- stop/target path 계산용 intraday high/low 사용 가능 + +## 5. Stop/Target path rules + +연구용 path label에만 사용: +- risk unit `R`는 entry와 initial stop 차이 +- initial stop 기본값은 `reaction_day_low` 또는 `1 ATR`, 둘 다 보관 가능 + +필수 저장: +- `risk_model_name` +- `initial_stop_price` +- `initial_r_value` + +## 6. 결측 처리 + +- horizon 기간 내 상장폐지/거래정지 발생 시 `label_status = truncated` +- 데이터 누락 시 `label_status = unavailable` +- 반응일 자체가 형성되지 않으면 record drop 금지, `invalid_event_for_labeling = true` + +## 7. Dataset splits + +### 기본 split +- train: 과거 구간 +- validation: 그 다음 연속 구간 +- test: 가장 최근 연속 구간 + +### 금지 +- 랜덤 split +- 동일 issuer/event family가 train/test에 동시에 섞이는 split + +### 권장 +- walk-forward split +- earnings season 단위 성능 분리 +- bull/bear regime 분리 + +## 8. Snapshot 생성 규칙 + +스냅샷은 immutable artifact여야 함. + +필수 메타데이터: +- snapshot_id +- created_at +- code_commit_hash +- feature_version +- parser_version +- label_version +- split_policy +- included_sources + +출력 형식: +- `datasets/snapshots//train.parquet` +- `datasets/snapshots//valid.parquet` +- `datasets/snapshots//test.parquet` +- `manifest.json` + +## 9. QA 필수 점검 + +- reaction date가 filing timestamp와 일관적인가 +- entry_date가 거래일 달력상 유효한가 +- forward window에 future leakage가 없는가 +- delisting/halts 처리 규칙이 일관적인가 +- label 분포가 특정 연도/섹터에 치우치지 않는가 diff --git a/dev/phase3_deliverables/parser_and_feature_architecture.md b/dev/phase3_deliverables/parser_and_feature_architecture.md new file mode 100644 index 0000000..282d25b --- /dev/null +++ b/dev/phase3_deliverables/parser_and_feature_architecture.md @@ -0,0 +1,193 @@ +# Phase 3 아키텍처: Parser / Feature / Label Pipeline + +## 1. 목적 + +Phase 3의 목적은 Phase 2에서 수집된 원문과 시계열 데이터를 **연구/백테스트/실거래에서 공통으로 사용할 수 있는 구조화된 이벤트 레코드**로 바꾸는 것입니다. + +핵심 철학은 다음과 같습니다. + +1. **숫자는 규칙/계산으로 추출**하고, LLM은 숫자 계산기를 대체하지 않는다. +2. **LLM은 문맥 해석과 질 판단**에만 사용한다. +3. 모든 출력은 **schema-valid JSON**이어야 한다. +4. 모든 레코드는 **provenance, confidence, parser_version, prompt_version**을 가진다. +5. 파서 출력은 사람이 검수할 수 있어야 하며, 검수 결과가 다시 학습 데이터에 반영되어야 한다. + +## 2. 전체 흐름 + +```text +Phase 2 raw/staging data + ↓ +Document selector + ↓ +Rule parser + ↓ +LLM enrichment + ↓ +Canonical event record + ↓ +Feature builder + ↓ +Label generator + ↓ +Research dataset / review queue / gold set report +``` + +## 3. 서비스 경계 + +### 3.1 document_selector +책임: +- 어떤 문서가 parser 대상인지 결정 +- 8-K 본문, Exhibit 99.1, 10-Q/10-K, 6-K를 우선순위에 따라 고름 +- 동일 accession 내 여러 첨부가 있을 때 우선 파싱 대상을 선택 + +입력: +- `filings`, `filing_documents`, `filing_document_blobs` + +출력: +- `parser_jobs` + +정책: +- `8-K` + `EX-99.1` 조합을 최우선 +- `10-Q`/`10-K`는 event class에 따라 본문과 xbrl summary 모두 생성 +- 1 accession에 대해 parser target은 여러 개일 수 있으나, canonical event id는 하나 이상이 되지 않도록 dedupe 규칙 필요 + +### 3.2 rule_parser +책임: +- 문서 텍스트에서 deterministic하게 뽑을 수 있는 필드를 추출 +- item number, 숫자 패턴, guidance 문구, one-off 키워드, margin/growth 문구 등을 추출 + +입력: +- normalized document text +- filing metadata +- xbrl facts (선택) + +출력: +- `rule_parse_outputs` + +원칙: +- 숫자 필드는 가능하면 규칙 기반/파서 기반으로 추출 +- regex/keyword/section parser 실패는 null로 남기고 hallucination 금지 + +### 3.3 llm_enricher +책임: +- rule parser가 뽑은 결과를 보강하고 문맥 분류 수행 +- event direction, quality, guidance tone, one-off suspicion, demand_strength, pricing_power 등을 추출 + +입력: +- 문서 본문 일부/요약 +- rule parser output +- 이벤트 taxonomy 힌트 + +출력: +- schema-valid `llm_parse_outputs` + +정책: +- LLM은 숫자 값을 새로 발명하지 않는다. +- 숫자/날짜/티커의 신규 추론은 금지한다. +- 불확실하면 `unknown`, `mixed`, `low_confidence`를 사용한다. + +### 3.4 canonicalizer +책임: +- rule parse + llm output을 병합해 canonical event record 생성 +- 필드별 provenance를 저장 +- 충돌(disagreement)을 계산하고 review queue에 보냄 + +출력: +- `event_records` +- `review_queue` + +### 3.5 feature_builder +책임: +- event/document/market/regime/attention feature 생성 +- point-in-time safe feature만 허용 +- 각 feature의 `as_of_ts`와 source를 기록 + +출력: +- `event_feature_records` + +### 3.6 label_generator +책임: +- entry convention 기준으로 1D/3D/5D outcome 생성 +- MFE/MAE, stop-hit, target-hit 계산 +- leakage 방지 규칙 준수 + +출력: +- `event_labels` +- `dataset_snapshots` + +## 4. 데이터 플로우 상세 + +### 4.1 parser 대상 선정 + +문서 대상 우선순위: +1. 8-K의 EX-99.1 earnings release / shareholder letter +2. 8-K 본문 (Item 2.02 / 7.01 / 1.01 / 8.01) +3. 6-K 첨부 IR release +4. 10-Q / 10-K MD&A 요약 구간 +5. 추가 IR 보조자료(허용 source에 한함) + +### 4.2 텍스트 정규화 + +반드시 구현: +- HTML 제거와 line-break 정리 +- 표/머리글/꼬리말 단순화 +- repeated disclaimer 제거 옵션 +- 숫자 단위 정규화 (million, billion 등) +- Unicode normalize + +보존해야 할 것: +- 원문 raw blob +- 정규화 전 text hash +- 정규화 후 text hash + +### 4.3 provenance + +각 필드별 provenance 예시: +- `guidance_direction.source = rule|llm|manual` +- `guidance_direction.evidence = [document_span_refs...]` +- `guidance_direction.confidence = 0.82` + +Phase 3에서 필수로 남길 메타데이터: +- parser_version +- prompt_version +- schema_version +- model_name +- model_temperature +- document_hash +- source_filing_id +- created_at + +## 5. review queue 생성 규칙 + +다음 중 하나면 review queue 생성: +- LLM confidence < threshold +- rule/LLM 핵심 필드 충돌 +- one-off flag = suspected +- guidance direction = mixed/unknown +- parser JSON schema validation 실패 +- canonicalization에서 required field null 과다 + +## 6. canonical dataset 단위 + +기본 학습 단위는 **event x symbol x reaction_date** 입니다. + +이유: +- 하나의 filing이 여러 문서를 포함할 수 있음 +- 반응일 기준으로 entry/label이 정의되므로 event timestamp만으로는 부족함 +- same issuer/day multiple filings도 구분 가능해야 함 + +Primary key 제안: +- `event_instance_id` +- `symbol` +- `reaction_date` + +## 7. 성능보다 우선하는 것 + +Phase 3에서는 다음이 중요합니다. +- 재현 가능성 +- 추적 가능성 +- 수동 검수 가능성 +- leakage 방지 +- 비용 통제 + +따라서 처음부터 고성능 병렬화보다 **작동이 명확하고 audit 가능한 구현**을 우선합니다. diff --git a/dev/phase3_deliverables/prompt_and_llm_policy.md b/dev/phase3_deliverables/prompt_and_llm_policy.md new file mode 100644 index 0000000..8049bbd --- /dev/null +++ b/dev/phase3_deliverables/prompt_and_llm_policy.md @@ -0,0 +1,126 @@ +# Prompt and LLM Policy + +## 1. 역할 정의 + +LLM은 다음 역할만 수행한다. +- 문맥 분류 +- 이벤트의 질 평가 +- guidance 톤 분류 +- one-off 의심 플래그 +- 수요/가격결정력/고객확대/구조변화 문맥 추출 + +LLM이 하지 말아야 할 것: +- 숫자 계산 +- 컨센서스 추정치 생성 +- 문서에 없는 factual claim 생성 +- 포지션/매수/매도 추천 + +## 2. 입력 설계 + +입력은 다음 3개 블록으로 제한: +1. 문서 메타데이터 +2. 정규화 문서 본문 또는 핵심 section +3. rule parser가 추출한 structured hints + +절대 포함하지 말 것: +- 미래 수익률 +- 이후 가격 반응 요약 +- 목표 레이블 +- 백테스트 결과 + +## 3. 출력 설계 + +출력은 반드시 JSON Schema에 맞춰야 한다. + +필수 출력 필드: +- event_type +- event_direction +- guidance_direction +- quality_assessment +- oneoff_suspicion +- demand_strength +- pricing_power +- management_tone +- confidence_overall +- evidence_refs +- parse_warnings + +## 4. Prompt versioning + +필수 메타데이터: +- `prompt_name` +- `prompt_version` +- `model_name` +- `model_provider` +- `temperature` +- `max_tokens` +- `response_format` + +캐시 키 권장 구성: +```text +sha256( + normalized_text_hash + + parser_hint_hash + + prompt_version + + model_name + + schema_version +) +``` + +## 5. 비용 통제 + +반드시 구현: +- 동일 문서 재호출 방지 캐시 +- 긴 문서 section slicing +- low-priority 문서의 rule-only 모드 +- confidence 기반 selective retry + +권장 순서: +1. 규칙 파서만 실행 +2. 검수 가치가 있는 문서만 LLM 호출 +3. confidence 낮은 경우 1회 재시도 +4. 그래도 불명확하면 review queue + +## 6. 오류 처리 + +### 모델 오류 +- 타임아웃, rate limit, validation error를 구분 +- 재시도는 최대 2회 +- 실패해도 전체 파이프라인은 rule-only 결과로 진행 가능해야 함 + +### schema 오류 +- structured repair prompt 1회 허용 +- repair도 실패하면 parse_failed 저장 + +## 7. 재현 가능성 + +필수 저장: +- raw prompt +- raw response +- normalized response +- schema validation 결과 +- token usage +- elapsed_ms + +민감사항: +- 프롬프트에 외부 비밀값 포함 금지 +- PII 불필요 포함 금지 + +## 8. 골드셋 평가 + +gold set에 대해 최소 평가: +- event_type accuracy +- guidance_direction accuracy +- oneoff precision/recall +- quality_assessment macro F1 +- evidence presence ratio + +## 9. 모델 교체 정책 + +모델을 바꾸더라도 아래는 고정: +- 출력 schema +- provenance 필드 +- review queue 기준 +- null/unknown 정책 + +즉 모델 교체는 implementation detail이지 data contract 변경이 아니어야 한다. diff --git a/dev/phase3_deliverables/quality_assurance_and_review.md b/dev/phase3_deliverables/quality_assurance_and_review.md new file mode 100644 index 0000000..06b83e5 --- /dev/null +++ b/dev/phase3_deliverables/quality_assurance_and_review.md @@ -0,0 +1,119 @@ +# Quality Assurance and Manual Review + +## 1. 목표 + +Phase 3의 성공 기준은 parser가 돌아가는 것만이 아니라, **신뢰할 수 있는 구조화 레코드와 피처**를 만드는 것입니다. + +## 2. 품질 프레임워크 + +품질은 아래 4층으로 관리합니다. + +1. **Schema quality** + - JSON validation 통과율 + - required field completeness +2. **Extraction quality** + - 핵심 필드 정확도 + - evidence 일치율 +3. **Dataset quality** + - null rate + - distribution drift + - leakage 없음 +4. **Operational quality** + - 파이프라인 성공률 + - retry rate + - manual review backlog + +## 3. Gold set 운영 + +### 목적 +- parser 품질의 기준점 유지 +- prompt/model 변경 시 회귀 검출 + +### 구성 원칙 +- event_type별 대표 샘플 포함 +- high quality / low quality / ambiguous 케이스 혼합 +- earnings / contract / regulatory / misc 분산 +- issuer 규모와 섹터 다양성 확보 + +### 최소 컬럼 +- raw document reference +- human canonical labels +- evidence spans +- notes +- gold_version + +## 4. review queue 우선순위 + +### P0 +- schema invalid +- event_type unknown +- direction 충돌 +- filing timestamp 이상 + +### P1 +- guidance ambiguous +- one-off likely +- confidence 낮음 + +### P2 +- evidence 부족 +- minor categorical mismatch + +## 5. 수동 검수 workflow + +1. review item 생성 +2. reviewer가 원문/증거/span 확인 +3. canonical 수정 +4. reviewer note 작성 +5. disposition 저장 +6. gold set 후보이면 승격 + +필수 기록: +- reviewer_id +- review_started_at +- review_completed_at +- field_overrides +- root_cause +- resolution_type + +## 6. Root cause taxonomy + +필수 분류: +- rule_parser_bug +- llm_misclassification +- prompt_ambiguity +- source_document_noise +- taxonomy_gap +- timestamp_error +- xbrl_mapping_error +- feature_builder_bug + +## 7. 주간 품질 리포트 + +최소 포함 항목: +- parser success rate +- schema validation pass rate +- average confidence +- review queue inflow/outflow +- top root causes +- null rate by feature family +- label availability rate +- drift summary + +## 8. 배포 게이트 + +아래 중 하나라도 충족 못 하면 parser/feature 변경 배포 금지: +- gold set accuracy 하락 +- schema invalid 증가 +- review backlog 폭증 +- 핵심 feature null rate 급증 +- leakage 점검 실패 + +## 9. 테스트 외 수동 점검 + +매 릴리스 전 20~50개 샘플 수동 점검 권장: +- 실적 release +- 가이던스 상향/하향 +- one-off 착시 케이스 +- ambiguous press release +- 6-K 외국 발행사 케이스 diff --git a/dev/phase3_deliverables/review_queue_contract.md b/dev/phase3_deliverables/review_queue_contract.md new file mode 100644 index 0000000..13dcf18 --- /dev/null +++ b/dev/phase3_deliverables/review_queue_contract.md @@ -0,0 +1,73 @@ +# Review Queue Contract + +## 1. 목적 + +수동 검수가 필요한 parser/feature 레코드를 표준화된 방식으로 적재하고 처리한다. + +## 2. review item 최소 스키마 + +- `review_id` +- `entity_type` (`parser_event`, `feature_record`, `label_record`) +- `entity_id` +- `priority` (`P0`, `P1`, `P2`) +- `reason_codes` (array) +- `status` (`open`, `in_progress`, `resolved`, `wont_fix`) +- `assigned_to` (nullable) +- `created_at` +- `updated_at` +- `snapshot_refs` +- `suggested_overrides` (nullable) + +## 3. reason_codes + +- `schema_invalid` +- `low_confidence` +- `rule_llm_conflict` +- `guidance_ambiguous` +- `oneoff_likely` +- `timestamp_anomaly` +- `feature_null_spike` +- `label_generation_error` +- `taxonomy_gap` + +## 4. 상태 전이 + +```text +open -> in_progress -> resolved +open -> wont_fix +in_progress -> open +``` + +## 5. resolve 시 필수 입력 + +- `reviewer_id` +- `resolution_type` (`override`, `confirm`, `bug`, `source_issue`) +- `field_overrides` (optional) +- `root_cause` +- `notes` +- `resolved_at` + +## 6. UI/CLI 요구사항 + +최소 요구사항: +- 원문 문서 링크 열기 +- parser output diff 보기 +- evidence span 보기 +- override 입력 +- root cause 선택 +- resolve/wont_fix 처리 + +## 7. 파서와의 계약 + +review queue item은 parser/feature pipeline을 멈추지 않는다. +단, 아래 P0는 downstream model dataset export 전에 해결되어야 한다. +- schema_invalid +- timestamp_anomaly +- taxonomy_gap major + +## 8. 테스트 포인트 + +- 중복 review item 방지 +- 이미 resolve된 entity 재오픈 로직 +- priority escalation +- override 반영 후 canonical record 재생성 diff --git a/dev/phase3_deliverables/review_record.schema.json b/dev/phase3_deliverables/review_record.schema.json new file mode 100644 index 0000000..4a72163 --- /dev/null +++ b/dev/phase3_deliverables/review_record.schema.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.local/schemas/review_record.schema.json", + "title": "ReviewRecord", + "type": "object", + "required": [ + "review_id", + "entity_type", + "entity_id", + "priority", + "reason_codes", + "status", + "created_at", + "updated_at" + ], + "properties": { + "review_id": {"type": "string"}, + "entity_type": { + "type": "string", + "enum": ["parser_event", "feature_record", "label_record"] + }, + "entity_id": {"type": "string"}, + "priority": {"type": "string", "enum": ["P0", "P1", "P2"]}, + "reason_codes": { + "type": "array", + "items": {"type": "string"}, + "minItems": 1 + }, + "status": {"type": "string", "enum": ["open", "in_progress", "resolved", "wont_fix"]}, + "assigned_to": {"type": ["string", "null"]}, + "snapshot_refs": { + "type": "array", + "items": {"type": "string"} + }, + "suggested_overrides": {"type": ["object", "null"]}, + "resolution_type": {"type": ["string", "null"]}, + "root_cause": {"type": ["string", "null"]}, + "notes": {"type": ["string", "null"]}, + "reviewer_id": {"type": ["string", "null"]}, + "created_at": {"type": "string", "format": "date-time"}, + "updated_at": {"type": "string", "format": "date-time"}, + "resolved_at": {"type": ["string", "null"], "format": "date-time"} + }, + "additionalProperties": true +} diff --git a/dev/phase3_deliverables/testing_checklist.md b/dev/phase3_deliverables/testing_checklist.md new file mode 100644 index 0000000..ca2122b --- /dev/null +++ b/dev/phase3_deliverables/testing_checklist.md @@ -0,0 +1,81 @@ +# Phase 3 Testing Checklist + +## 1. 단위 테스트 + +### text normalizer +- HTML 문서가 안정적으로 plain text로 변환된다. +- 동일 문서에 대해 해시가 안정적으로 재생산된다. +- disclaimer strip 옵션이 본문을 과도하게 삭제하지 않는다. + +### rule parser +- guidance 키워드가 올바르게 분류된다. +- one-off 키워드가 검출된다. +- demand/pricing/margin 키워드가 검출된다. +- section parser가 없는 문서에서도 안전하게 실패한다. + +### span mapper +- evidence char offsets가 원문 구간과 일치한다. +- normalization 후에도 span reference가 추적 가능하다. + +### llm wrapper +- 캐시 히트 시 외부 호출이 발생하지 않는다. +- 타임아웃/레이트리밋 시 재시도 정책이 지켜진다. +- schema invalid 응답이 repair path를 탄다. + +### canonical merge +- rule/llm 충돌 시 보수적 merge가 적용된다. +- provenance 필드가 누락되지 않는다. +- review queue trigger가 올바르게 작동한다. + +### feature builder +- reaction_close_location 계산이 정확하다. +- rolling window가 미래 데이터를 보지 않는다. +- null feature가 정책대로 처리된다. +- available_ts가 entry convention과 맞는다. + +### labeler +- reaction date 계산이 장전/장중/장후에 맞게 동작한다. +- 1D/3D/5D forward return이 정확하다. +- MFE/MAE가 고저가 경로로 정확히 계산된다. +- 비거래일/휴일 처리에 오류가 없다. + +## 2. 통합 테스트 + +- SEC raw 문서 하나가 parser output까지 도달한다. +- parser output이 feature builder로 연결된다. +- feature + price data가 labeler로 연결된다. +- review queue item이 실제로 생성된다. +- snapshot export가 manifest 포함해 생성된다. + +## 3. Replay 테스트 + +- 동일 문서 재처리 시 canonical output이 동일하다. +- 동일 문서 + 동일 prompt_version에서 캐시 결과가 재현된다. +- parser_version 변경 시 이전 결과와 diff report 생성 가능하다. +- historical day replay가 live path와 같은 코드 경로를 탄다. + +## 4. Gold set 테스트 + +- event_type accuracy baseline 이상 +- guidance_direction accuracy baseline 이상 +- oneoff precision/recall baseline 이상 +- confidence calibration sanity check +- evidence presence ratio 기준 이상 + +## 5. Leakage 테스트 + +- next_open entry dataset에 entry day 장중 정보가 포함되지 않는다. +- FINRA post-close data가 당일 아침 feature로 들어가지 않는다. +- forward returns를 만드는 price bars가 feature 계산에 재사용되지 않는다. +- snapshot split이 시간 순서를 위반하지 않는다. + +## 6. 운영 전 체크리스트 + +- parser schema version 고정 +- prompt version 고정 +- gold set 리포트 생성 완료 +- review backlog acceptable +- null rate report 검토 완료 +- label distribution sanity check 완료 +- dataset manifest에 commit hash 포함 +- raw prompt/response 보관 정책 확인 diff --git a/dev/phase4_deliverables/README.md b/dev/phase4_deliverables/README.md new file mode 100644 index 0000000..8492291 --- /dev/null +++ b/dev/phase4_deliverables/README.md @@ -0,0 +1,107 @@ +# Phase 4 개발문서 패키지 + +이 문서는 **Phase 0의 전략/리스크/데이터 정책**, **Phase 1의 저장소 구조/DB/서비스 계약**, **Phase 2의 ingestion 파이프라인**, **Phase 3의 parser/feature/label 규칙**을 바탕으로, +AI 코딩 에이전트가 **누수 없는 event-driven 백테스터와 실험/평가 체계**를 구현할 수 있도록 만든 **Phase 4 상세 개발문서**입니다. + +## 목표 + +Phase 4의 목표는 아래 9가지를 실제 코드 수준으로 구현하는 것입니다. + +1. **event-driven backtest engine**을 구현한다. +2. **reaction day / entry day / holding horizon**을 정확히 처리한다. +3. **look-ahead bias / survivorship bias / timestamp leakage**를 방지한다. +4. **포트폴리오/리스크/자금 배분 규칙**을 백테스트에 반영한다. +5. **point-in-time feature snapshot**만으로 시뮬레이션한다. +6. **실험 정의(manifest), 결과 저장, 재현 가능한 실행 규칙**을 만든다. +7. **평가 지표와 리포트 산출물**을 고정한다. +8. **ablation / walk-forward / regime split** 실험 체계를 만든다. +9. **Phase 6 paper trading과 비교 가능한 output contract**를 만든다. + +## 포함 문서 + +- `backtest_architecture.md` + - Phase 4 전체 아키텍처 + - 입력/출력 데이터 계약 + - engine 경계와 상태 전이 +- `simulation_engine_design.md` + - 백테스터 내부 동작 상세 + - event queue / calendar / fill logic / price lookup 규칙 +- `portfolio_and_risk_model.md` + - 포지션 sizing + - 동시보유 수 + - 섹터 집중 제한 + - 손절/익절/시간청산 모델 +- `experiment_and_evaluation_plan.md` + - 실험 정의 방식 + - evaluation metrics + - ablation / walk-forward / split 규칙 +- `configuration_and_schemas.md` + - config 파일 구조 + - manifest 사용 규칙 + - 결과 저장 포맷 +- `implementation_plan.md` + - AI 코딩 에이전트용 구현 순서 + - 작업 분할 + - 완료 기준 + - 금지사항 +- `testing_checklist.md` + - 단위/통합/replay/통계/운영 전 테스트 체크리스트 +- `operator_research_runbook.md` + - 연구자가 백테스트를 실행하고 결과를 검증하는 절차 +- `backtest_config.schema.json` + - 백테스트 설정 JSON Schema +- `experiment_manifest.schema.json` + - 실험 실행 manifest JSON Schema + +## Phase 4 범위 + +Phase 4에서는 아래까지만 구현합니다. + +- event-driven 백테스터 +- point-in-time feature snapshot reader +- signal ranking / candidate selection +- portfolio / risk / sizing engine +- execution approximation (next open / close / stop / trailing / time exit) +- evaluation / report writer +- experiment registry / manifest runner +- walk-forward / split utilities + +Phase 4에서는 아직 아래를 구현하지 않습니다. + +- 실시간 주문 전송 +- broker API live execution +- paper trading orchestration +- human approval workflow +- 실전 알림/모니터링 + +## 핵심 원칙 + +1. **백테스트는 live가 나중에 따라와야 할 기준이 아니라, live와 같은 제약을 최대한 먼저 반영하는 장치**여야 한다. +2. **미래 정보는 단 1바이트도 사용하지 않는다.** +3. **event timestamp와 market session 경계를 정확히 처리한다.** +4. **문서/피처/라벨이 생성된 시각을 명시적으로 관리한다.** +5. **모든 실험은 manifest 기반으로 재현 가능해야 한다.** +6. **좋아 보이는 결과보다, 설명 가능한 결과를 우선한다.** +7. **Phase 0 정책(무료 데이터 전용, 공식 이벤트 우선, 소셜 overlay only)을 절대 깨지 않는다.** + +## 권장 구현 순서 + +1. `backtest_architecture.md` +2. `simulation_engine_design.md` +3. `portfolio_and_risk_model.md` +4. `configuration_and_schemas.md` +5. `backtest_config.schema.json` +6. `experiment_manifest.schema.json` +7. `implementation_plan.md` +8. `testing_checklist.md` + +## 완료 기준 + +Phase 4 완료의 최소 기준은 다음과 같습니다. + +- 1D / 3D / 5D horizon 전략을 같은 엔진에서 재현 가능하게 실행할 수 있다. +- event timestamp와 session alignment가 테스트로 검증되어 있다. +- candidate ranking → position sizing → fills → exits → metrics pipeline이 끝까지 동작한다. +- 하나의 manifest로 동일 실험을 다시 실행했을 때 같은 결과가 나온다. +- Phase 5 attention overlay 전/후 ablation이 비교 가능하다. +- 실험 리포트에 주요 KPI와 trade blotter, attribution, failure cases가 포함된다. diff --git a/dev/phase4_deliverables/backtest_architecture.md b/dev/phase4_deliverables/backtest_architecture.md new file mode 100644 index 0000000..c22e2e1 --- /dev/null +++ b/dev/phase4_deliverables/backtest_architecture.md @@ -0,0 +1,215 @@ +# Backtest Architecture + +## 1. 목적 + +Phase 4의 백테스터는 **Phase 3에서 생성된 canonical feature dataset**을 입력으로 받아, +**event-driven continuation 전략**을 시뮬레이션하고, 포트폴리오/리스크 규칙을 반영한 뒤, +정량 결과와 리포트를 산출하는 엔진입니다. + +이 엔진은 “가격만 넣고 대충 수익률 계산”하는 수준이 아니라, 다음을 반드시 만족해야 합니다. + +- point-in-time only +- session-aware event alignment +- deterministic execution +- reproducible experiment runs +- 동일 전략을 다른 horizon / regime split에 재사용 가능 + +## 2. 입력 계층 + +백테스터 입력은 아래 5개 레이어로 나뉩니다. + +### 2.1 Event snapshot + +Phase 3의 `canonical_event_record` + feature snapshot. +필수 필드: + +- `event_id` +- `symbol` +- `issuer_cik` +- `event_type` +- `event_timestamp_et` +- `availability_timestamp_et` +- `reaction_session_date` +- `entry_eligibility_date` +- `event_score_components` +- `attention_score_components` +- `final_candidate_score` + +### 2.2 Market bars + +일봉은 필수, 분봉은 선택입니다. +무료 데이터 제약상 v1의 기준 execution은 **next open / next close / stop-on-daily-bar approximation**입니다. +필수 필드: + +- `trade_date` +- `symbol` +- `open` +- `high` +- `low` +- `close` +- `volume` +- `vwap_optional` +- `adjustment_factor` + +### 2.3 Instrument master + +필수 필드: + +- `symbol` +- `asset_type` +- `listing_exchange` +- `sector` +- `industry` +- `is_tradable` +- `first_seen_date` +- `last_seen_date` + +### 2.4 Regime data + +필수 필드: + +- `trade_date` +- `spy_trend_state` +- `qqq_trend_state` +- `vix_level` +- `macro_regime` + +### 2.5 Experiment config + +전략, risk model, sizing model, slippage model, universe filter를 정의하는 manifest. + +## 3. 출력 계층 + +### 3.1 Trade blotter + +모든 체결/부분청산/최종청산을 row-level로 저장합니다. +필수 필드: + +- `run_id` +- `trade_id` +- `position_id` +- `symbol` +- `entry_date` +- `entry_price` +- `exit_date` +- `exit_price` +- `shares` +- `gross_pnl` +- `net_pnl` +- `exit_reason` +- `holding_days` + +### 3.2 Position timeline + +일자별 포지션 평가, 노출, MFE/MAE, stop distance. + +### 3.3 Daily equity curve + +일자별 자산곡선과 drawdown. + +### 3.4 Metrics report + +- CAGR-like annualized return +- average trade return +- expectancy +- hit rate +- payoff ratio +- max drawdown +- profit factor +- turnover +- avg holding period +- exposure +- sector concentration +- regime-by-regime performance + +### 3.5 Attribution report + +- by event type +- by guidance direction +- by sector +- by score bucket +- by attention overlay bucket + +## 4. 서비스 경계 + +### 4.1 Snapshot reader + +입력 데이터셋에서 **시점상 이용 가능한 feature만** 읽어옵니다. +이 계층은 절대 미래 row를 읽으면 안 됩니다. + +### 4.2 Candidate selector + +특정 날짜의 후보 종목을 정렬하고 상위 N개를 선택합니다. + +### 4.3 Portfolio allocator + +동시보유 수, 섹터 제한, 현금 제한, per-trade risk budget을 반영합니다. + +### 4.4 Execution simulator + +진입/청산 주문을 체결 가격으로 변환합니다. + +### 4.5 Report writer + +실험 결과를 표준 포맷으로 저장합니다. + +## 5. 실행 흐름 + +```text +experiment manifest +→ dataset snapshot resolve +→ calendar iteration by trade date +→ candidate selection for date D +→ eligibility filter +→ ranking / tie-break +→ portfolio allocation +→ entry fill simulation +→ open position management +→ exit checks +→ pnl / exposure update +→ end-of-day ledger snapshot +→ final metrics / artifacts write +``` + +## 6. 날짜/시간 처리 원칙 + +### 6.1 세션 경계 + +- 미국 동부시간(ET) 기준으로 판단합니다. +- premarket / regular / after-hours를 구분합니다. +- event timestamp가 장후면 reaction session은 다음 거래일입니다. +- event timestamp가 장중이면 reaction session은 당일이지만, feature availability timestamp를 반드시 확인합니다. + +### 6.2 entry eligibility + +기본 전략은 **reaction day 종료 후 점수 확정 → 다음 거래일 진입**입니다. +따라서 entry date는 최소한 reaction day + 1 trading session입니다. + +### 6.3 split/dividend adjustment + +가격계열은 조정값을 명시적으로 사용합니다. +단, 체결 시뮬레이션과 리포트는 같은 조정 기준을 사용해야 합니다. + +## 7. bias 방지 원칙 + +- future-adjusted universe 금지 +- delisted symbol 제거 금지 +- 나중에 정리된 event timestamp 사용 금지 +- later corrected filing 내용으로 과거 시점 feature overwrite 금지 +- final labels가 feature generation 단계에 들어가는 것 금지 + +## 8. backtest engine의 최소 클래스 경계 + +추천 Python 경계: + +- `BacktestRunner` +- `MarketCalendar` +- `SnapshotStore` +- `CandidateSelector` +- `PortfolioAllocator` +- `ExecutionSimulator` +- `PositionManager` +- `MetricsEngine` +- `ArtifactWriter` + +각 클래스는 side-effect를 최소화하고 deterministic 해야 합니다. diff --git a/dev/phase4_deliverables/backtest_config.schema.json b/dev/phase4_deliverables/backtest_config.schema.json new file mode 100644 index 0000000..c4fedf1 --- /dev/null +++ b/dev/phase4_deliverables/backtest_config.schema.json @@ -0,0 +1,227 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.local/schemas/backtest_config.schema.json", + "title": "BacktestConfig", + "type": "object", + "additionalProperties": false, + "required": [ + "strategy_name", + "dataset_snapshot_id", + "universe", + "signal", + "risk", + "execution", + "reporting" + ], + "properties": { + "strategy_name": { + "type": "string", + "minLength": 1 + }, + "dataset_snapshot_id": { + "type": "string", + "minLength": 1 + }, + "universe": { + "type": "object", + "additionalProperties": false, + "required": [ + "min_price", + "min_avg_dollar_volume", + "exclude_asset_types" + ], + "properties": { + "min_price": { + "type": "number", + "minimum": 0 + }, + "min_avg_dollar_volume": { + "type": "number", + "minimum": 0 + }, + "exclude_asset_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "allowed_exchanges": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "signal": { + "type": "object", + "additionalProperties": false, + "required": [ + "score_threshold", + "max_candidates_per_day", + "execution_timing" + ], + "properties": { + "score_threshold": { + "type": "number" + }, + "max_candidates_per_day": { + "type": "integer", + "minimum": 1 + }, + "execution_timing": { + "type": "string", + "enum": [ + "next_open", + "next_close" + ] + }, + "decision_timing": { + "type": "string", + "enum": [ + "reaction_close" + ] + }, + "ranking_fields": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "risk": { + "type": "object", + "additionalProperties": false, + "required": [ + "per_trade_risk_pct", + "max_daily_new_risk_pct", + "max_positions", + "max_positions_per_sector" + ], + "properties": { + "per_trade_risk_pct": { + "type": "number", + "exclusiveMinimum": 0 + }, + "max_daily_new_risk_pct": { + "type": "number", + "exclusiveMinimum": 0 + }, + "max_positions": { + "type": "integer", + "minimum": 1 + }, + "max_positions_per_sector": { + "type": "integer", + "minimum": 1 + }, + "max_position_value_pct": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "max_adv_fraction": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "cooldown_after_loss_streak": { + "type": "integer", + "minimum": 0 + }, + "cooldown_days": { + "type": "integer", + "minimum": 0 + } + } + }, + "execution": { + "type": "object", + "additionalProperties": false, + "required": [ + "entry_fill_model", + "exit_fill_model", + "slippage_bps_base", + "same_bar_priority" + ], + "properties": { + "entry_fill_model": { + "type": "string", + "enum": [ + "next_open" + ] + }, + "exit_fill_model": { + "type": "string", + "enum": [ + "daily_bar_approximation" + ] + }, + "slippage_bps_base": { + "type": "number", + "minimum": 0 + }, + "commission_per_share": { + "type": "number", + "minimum": 0 + }, + "same_bar_priority": { + "type": "string", + "enum": [ + "stop_first_conservative", + "target_first_aggressive" + ] + }, + "stop_model": { + "type": "string" + }, + "target_1_r": { + "type": "number", + "minimum": 0 + }, + "target_1_fraction": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "trailing_model": { + "type": "string" + }, + "max_holding_days": { + "type": "integer", + "minimum": 1 + } + } + }, + "reporting": { + "type": "object", + "additionalProperties": false, + "required": [ + "write_trade_blotter", + "write_equity_curve", + "write_metrics_summary" + ], + "properties": { + "write_trade_blotter": { + "type": "boolean" + }, + "write_equity_curve": { + "type": "boolean" + }, + "write_metrics_summary": { + "type": "boolean" + }, + "generate_plots": { + "type": "boolean" + }, + "attribution_buckets": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } +} diff --git a/dev/phase4_deliverables/configuration_and_schemas.md b/dev/phase4_deliverables/configuration_and_schemas.md new file mode 100644 index 0000000..0ed98d8 --- /dev/null +++ b/dev/phase4_deliverables/configuration_and_schemas.md @@ -0,0 +1,146 @@ +# Configuration and Schemas + +## 1. 목표 + +Phase 4는 모든 실험을 manifest 기반으로 실행해야 합니다. +설정 파일은 사람이 읽기 쉬운 YAML/JSON을 허용하되, +실행 직전 반드시 JSON Schema 검증을 통과해야 합니다. + +## 2. 설정 계층 + +### 2.1 Base strategy config + +전략 기본값. +예: 진입 규칙, exit 규칙, scoring threshold. + +### 2.2 Environment config + +데이터 위치, snapshot id, output base path. + +### 2.3 Experiment manifest + +실험 한 번을 완전히 정의하는 파일. + +## 3. 필수 설정 항목 + +### 3.1 Universe + +- allowed asset types +- min price +- min dollar volume +- exclude ADR/SPAC/ETF + +### 3.2 Signal + +- score threshold +- max candidates per day +- ranking fields +- decision timing +- execution timing + +### 3.3 Risk + +- per_trade_risk_pct +- max_daily_new_risk_pct +- max_positions +- max_positions_per_sector +- max_position_value_pct + +### 3.4 Execution + +- entry fill model +- exit fill model +- slippage model +- commission model +- stop/take-profit priority rule + +### 3.5 Reporting + +- output formats +- plot generation on/off +- attribution buckets + +## 4. 결과 디렉터리 구조 + +권장 구조: + +```text +runs/ + {run_id}/ + manifest.json + resolved_config.json + metadata.json + logs/ + metrics/ + metrics_summary.json + attribution_by_event_type.csv + attribution_by_sector.csv + score_bucket_report.csv + artifacts/ + trade_blotter.parquet + daily_equity_curve.parquet + position_timeline.parquet + plots/ + equity_curve.png + drawdown_curve.png + monthly_heatmap.png + notes/ + run_notes.md +``` + +## 5. config merge 규칙 + +1. base config +2. strategy profile override +3. experiment manifest override +4. CLI override (가능하면 최소화) + +최종 resolved config를 반드시 저장해야 합니다. + +## 6. versioning + +다음은 metadata에 반드시 남깁니다. + +- code commit hash +- dataset snapshot id +- feature schema version +- parser prompt version +- config hash +- run timestamp +- timezone + +## 7. schema 검증 정책 + +- 실행 전 manifest schema 검증 필수 +- config merge 후 resolved config schema 재검증 필수 +- unknown key는 기본적으로 에러 처리 +- 타입 coercion 자동 수행 금지 + +## 8. output contract + +실험 결과는 downstream Phase 5/6이 재사용할 수 있어야 합니다. +따라서 trade blotter와 metrics summary의 필드명은 안정적으로 유지합니다. + +## 9. CLI 예시 + +```bash +python -m apps.backtester.run \ + --manifest configs/experiments/baseline_v1.json \ + --snapshot-id snapshot_2026_03_20 \ + --output-root ./runs +``` + +## 10. 필수 metadata.json 예시 필드 + +```json +{ + "run_id": "bt_fgce_v1_snapshot_20260315_20260320_153000_a81c92", + "strategy_name": "fgce_v1", + "dataset_snapshot_id": "snapshot_2026_03_20", + "feature_schema_version": "1.0.0", + "config_hash": "...", + "git_commit": "...", + "started_at_et": "2026-03-20T15:30:00-04:00", + "completed_at_et": "2026-03-20T15:31:40-04:00" +} +``` diff --git a/dev/phase4_deliverables/experiment_and_evaluation_plan.md b/dev/phase4_deliverables/experiment_and_evaluation_plan.md new file mode 100644 index 0000000..c60a96b --- /dev/null +++ b/dev/phase4_deliverables/experiment_and_evaluation_plan.md @@ -0,0 +1,160 @@ +# Experiment and Evaluation Plan + +## 1. 목적 + +Phase 4의 실험 체계는 “좋은 결과 그림 만들기”가 아니라, +**전략이 어떤 조건에서 실제로 살아 있는지**를 검증하는 프레임입니다. + +실험은 모두 manifest 기반으로 정의하고, +입력 snapshot / 코드 버전 / config hash / output artifact를 함께 기록해야 합니다. + +## 2. 실험 종류 + +### 2.1 Baseline run + +문서 점수 + 가격 확인만 사용한 기본 전략. +이 실험이 기준선입니다. + +### 2.2 Ablation runs + +최소 아래 5개를 지원합니다. + +1. event only +2. event + price +3. event + price + regime +4. event + price + regime + attention +5. event + price + regime + attention + portfolio guardrails tuned + +### 2.3 Split runs + +다음 split을 지원합니다. + +- year-by-year +- bull / bear / mixed regime +- sector group +- market cap bucket +- liquidity bucket +- score bucket +- event type bucket + +### 2.4 Walk-forward runs + +권장 기본값: +- train 24 months +- validate 6 months +- test next 6 months +- rolling or expanding window 선택 가능 + +주의: Phase 4에서는 모델 학습보다 **threshold calibration / parameter sweep** 수준이면 충분합니다. + +### 2.5 Sensitivity runs + +- slippage sensitivity +- stop priority sensitivity +- holding days sensitivity +- max positions sensitivity +- risk budget sensitivity + +## 3. 필수 평가 지표 + +### 3.1 Trade-level metrics + +- trade count +- hit rate +- average winner / average loser +- payoff ratio +- expectancy +- median holding days +- median MFE / MAE + +### 3.2 Portfolio-level metrics + +- cumulative return +- annualized return +- max drawdown +- Calmar-like ratio +- Sharpe-like ratio (daily) +- Sortino-like ratio +- profit factor +- turnover +- average exposure + +### 3.3 Stability metrics + +- monthly hit rate dispersion +- rolling 3-month expectancy +- drawdown duration +- performance by score decile +- performance by regime bucket + +### 3.4 Practicality metrics + +- skipped trades due to constraints +- average gap from signal close to entry open +- average slippage cost +- portfolio slot utilization +- sector concentration usage + +## 4. 리포트 산출물 + +각 실험은 최소 아래 artifact를 생성해야 합니다. + +- `metrics_summary.json` +- `trade_blotter.parquet` +- `daily_equity_curve.parquet` +- `position_timeline.parquet` +- `attribution_by_event_type.csv` +- `attribution_by_sector.csv` +- `score_bucket_report.csv` +- `run_notes.md` +- `plots/` 디렉터리 + +플롯 최소 요구사항: + +- equity curve +- drawdown curve +- monthly returns heatmap +- return by score bucket +- return by holding period +- performance by regime + +## 5. 실험 naming 규칙 + +권장 run_id 포맷: + +```text +bt_{strategy_name}_{dataset_snapshot}_{yyyymmddhhmmss}_{short_hash} +``` + +예: +`bt_fgce_v1_snapshot_20260315_20260320_153000_a81c92` + +## 6. 해석 기준 + +좋은 전략으로 보기 위한 최소 기준 예시: + +- out-of-sample expectancy > 0 +- max drawdown이 감내 가능한 수준 +- score 상위 버킷일수록 성과가 우상향 +- regime split에서 완전히 무너지지 않음 +- 특정 한 이벤트 타입/한 해/한 섹터에만 의존하지 않음 + +## 7. 금지사항 + +- 테스트 구간을 보고 threshold를 수동으로 계속 바꾸기 +- 동일 기간을 train/validate/test로 동시에 사용하기 +- delisted symbols 제거하기 +- 나중에 보정된 문서 timestamp를 과거 실험에 반영하기 +- 결과가 좋지 않은 실험을 silently discard 하기 + +## 8. 결과 리뷰 템플릿 + +각 실험 후 아래 질문에 답할 수 있어야 합니다. + +1. 가장 큰 수익은 어떤 event type에서 나왔는가? +2. 손실은 특정 regime에 집중되었는가? +3. attention overlay가 실제로 도움이 되었는가? +4. 포트폴리오 제약 때문에 놓친 좋은 거래는 얼마나 되는가? +5. slippage와 stop 우선순위 가정이 결과를 얼마나 바꾸는가? +6. score가 높을수록 실제 성과가 증가하는가? +7. 1D/3D/5D horizon 중 어디가 가장 안정적인가? diff --git a/dev/phase4_deliverables/experiment_manifest.schema.json b/dev/phase4_deliverables/experiment_manifest.schema.json new file mode 100644 index 0000000..edada8d --- /dev/null +++ b/dev/phase4_deliverables/experiment_manifest.schema.json @@ -0,0 +1,67 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.local/schemas/experiment_manifest.schema.json", + "title": "ExperimentManifest", + "type": "object", + "additionalProperties": false, + "required": [ + "experiment_name", + "dataset_snapshot_id", + "base_config", + "overrides" + ], + "properties": { + "experiment_name": { + "type": "string", + "minLength": 1 + }, + "dataset_snapshot_id": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string" + }, + "base_config": { + "type": "string", + "minLength": 1 + }, + "overrides": { + "type": "object" + }, + "splits": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "year", + "regime", + "sector", + "walk_forward", + "score_bucket" + ] + }, + "params": { + "type": "object" + } + } + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "notes": { + "type": "string" + } + } +} diff --git a/dev/phase4_deliverables/implementation_plan.md b/dev/phase4_deliverables/implementation_plan.md new file mode 100644 index 0000000..f519740 --- /dev/null +++ b/dev/phase4_deliverables/implementation_plan.md @@ -0,0 +1,180 @@ +# Implementation Plan + +이 문서는 AI 코딩 에이전트가 Phase 4를 실제로 구현할 때의 권장 작업 순서와 완료 기준을 정의합니다. + +## 1. 작업 목표 + +아래 6개를 끝까지 구현합니다. + +1. manifest-driven backtest runner +2. snapshot reader +3. candidate selection / portfolio allocation +4. entry/exit simulation +5. metrics / artifact writer +6. experiment replay / walk-forward support + +## 2. 작업 분할 + +### Task 1. core domain models + +구현 대상: +- Candidate +- PlannedOrder +- FilledTrade +- OpenPosition +- DailyPortfolioState +- ExperimentResult +- MetricsBundle + +완료 기준: +- domain model 타입이 고정되고 직렬화 가능하다. +- blotter / equity curve / position timeline에 필요한 필드가 모두 있다. + +### Task 2. market calendar and date alignment + +구현 대상: +- trading calendar wrapper +- next trading day lookup +- reaction day / execution day resolver +- timezone utilities + +완료 기준: +- 장전/장중/장후 event timestamp를 정확히 trading session으로 매핑한다. +- 휴장일/조기폐장을 안전하게 처리한다. + +### Task 3. snapshot store + +구현 대상: +- feature snapshot loader +- market bar loader +- regime loader +- instrument master loader + +완료 기준: +- 특정 run이 같은 snapshot id로 동일 입력을 재현할 수 있다. +- future rows가 로드되지 않는다. + +### Task 4. candidate selector + +구현 대상: +- score threshold filter +- universe filters +- duplicate issuer/event cluster filter +- deterministic sort and tie-break + +완료 기준: +- 날짜별 후보 종목이 재현 가능하게 생성된다. +- skip reason이 모두 기록된다. + +### Task 5. portfolio allocator + +구현 대상: +- per-trade risk sizing +- max positions gate +- sector gate +- daily risk budget gate +- cash/exposure updates + +완료 기준: +- 같은 입력에서 항상 같은 주문 계획이 나온다. +- 거절 사유가 구조화되어 남는다. + +### Task 6. execution simulator + +구현 대상: +- next-open entry +- stop / target / trailing / time exit +- slippage / commission +- same-bar conservative priority rule + +완료 기준: +- 기본 전략 1D/3D/5D 실험이 끝까지 실행된다. +- 체결 규칙이 config 기반으로 바뀔 수 있다. + +### Task 7. metrics and artifacts + +구현 대상: +- trade blotter writer +- daily equity curve writer +- attribution reports +- metrics summary +- plot generation optional + +완료 기준: +- run directory가 표준 구조로 생성된다. +- metrics summary JSON이 schema를 만족한다. + +### Task 8. manifest runner and replay + +구현 대상: +- manifest parser +- config merge +- schema validation +- run metadata writer +- replay mode + +완료 기준: +- 동일 manifest 재실행 시 동일 결과가 나온다. + +## 3. 권장 구현 순서 + +1. domain models +2. calendar/date alignment +3. snapshot store +4. candidate selector +5. portfolio allocator +6. execution simulator +7. metrics writer +8. manifest runner +9. walk-forward and split helpers +10. optional plots + +## 4. 구현 규칙 + +- 순수 함수 중심으로 작성 +- hidden global state 금지 +- timezone naive datetime 금지 +- tie-break 랜덤화 금지 +- silent coercion 금지 +- future data fallback 금지 + +## 5. 최소 디렉터리 제안 + +```text +apps/backtester/ + run.py + replay.py +libs/backtest/ + domain.py + calendar.py + snapshot_store.py + selector.py + allocator.py + execution.py + metrics.py + artifacts.py + manifests.py + splits.py +tests/ + unit/backtest/ + integration/backtest/ + replay/ +``` + +## 6. 금지사항 + +- 결과를 좋게 보이게 하기 위한 parameter auto-tuning 루프를 기본 구현에 넣지 말 것 +- 평가 지표 계산 전에 손실 거래를 필터링하지 말 것 +- missing data를 0으로 임의 대체하지 말 것 +- event timestamp가 없는데 filing date로 임의 대체한 뒤 경고를 남기지 않는 것 금지 +- 같은 바에서 stop/target 순서를 유리하게 처리하는 것 금지 + +## 7. 완료 정의 + +다음이 모두 충족되면 Phase 4 구현 완료로 봅니다. + +- baseline experiment manifest를 한 번 실행할 수 있다. +- 결과 디렉터리에 metrics, blotter, equity curve가 생성된다. +- regression snapshot으로 결과가 고정된다. +- year split, regime split, ablation run을 각각 최소 1개씩 실행할 수 있다. +- testing checklist의 P0 항목이 모두 통과한다. diff --git a/dev/phase4_deliverables/operator_research_runbook.md b/dev/phase4_deliverables/operator_research_runbook.md new file mode 100644 index 0000000..a3aba79 --- /dev/null +++ b/dev/phase4_deliverables/operator_research_runbook.md @@ -0,0 +1,82 @@ +# Operator Research Runbook + +## 1. 목적 + +이 문서는 연구자/개발자가 Phase 4 백테스트를 실행하고 결과를 검토할 때의 표준 절차를 설명합니다. + +## 2. 실행 전 준비 + +1. Phase 0~3 문서를 모두 확인합니다. +2. 사용할 dataset snapshot id를 고정합니다. +3. 실험 목적을 한 줄로 정리합니다. + - 예: `event+price baseline vs event+price+attention ablation` +4. manifest를 작성하고 schema 검증을 통과시킵니다. +5. output root가 비어 있거나 새로운 run_id를 사용할 것을 확인합니다. + +## 3. 실행 절차 + +1. baseline run 실행 +2. metrics summary 확인 +3. blotter 샘플 확인 +4. drawdown 기간의 거래를 수동 검토 +5. score bucket 보고서 확인 +6. regime split 실행 +7. ablation 실행 +8. 차이점을 run notes에 기록 + +## 4. 결과 검토 우선순위 + +### 4.1 먼저 볼 것 + +- trade count가 충분한가 +- max drawdown이 과도하지 않은가 +- expectancy가 양수인가 +- score 상위 구간이 실제로 더 좋은가 + +### 4.2 그다음 볼 것 + +- 특정 이벤트 타입/섹터 편중 여부 +- attention overlay가 의미 있게 기여했는지 +- 포트폴리오 제약 때문에 너무 많은 기회를 놓쳤는지 +- 손절 우선순위에 민감한지 + +## 5. 이상 징후 체크 + +아래 중 하나라도 보이면 누수/leakage를 먼저 의심합니다. + +- 너무 매끈한 equity curve +- out-of-sample인데 hit rate가 비정상적으로 높음 +- slippage를 넣어도 성과가 거의 안 변함 +- score bucket monotonicity가 지나치게 완벽함 +- 특정 해/특정 섹터에서만 비정상적 초과수익 + +## 6. run notes 템플릿 + +```text +Run ID: +Objective: +Snapshot ID: +Main config deltas: +Key metrics: +Biggest winners: +Biggest losers: +Regime observations: +Leakage concerns: +Next actions: +``` + +## 7. 실패 시 우선 점검 순서 + +1. manifest validation +2. snapshot completeness +3. reaction day alignment +4. entry/exit price lookup +5. stop/target priority +6. portfolio gates +7. metrics aggregation + +## 8. 보관 정책 + +- 중요한 run은 run directory 전체를 보관합니다. +- release candidate run은 immutable로 취급합니다. +- 결과 비교용 CSV/JSON은 삭제하지 않습니다. diff --git a/dev/phase4_deliverables/portfolio_and_risk_model.md b/dev/phase4_deliverables/portfolio_and_risk_model.md new file mode 100644 index 0000000..77101bb --- /dev/null +++ b/dev/phase4_deliverables/portfolio_and_risk_model.md @@ -0,0 +1,177 @@ +# Portfolio and Risk Model + +## 1. 목적 + +백테스트에서 포트폴리오/리스크 모델은 전략보다 덜 중요해 보이지만, +실제로는 결과의 분포와 낙폭을 결정하는 핵심입니다. + +Phase 4에서는 아래 3가지를 반드시 반영합니다. + +1. **per-trade risk budget** +2. **portfolio-level exposure / concentration limits** +3. **kill-switch style risk guardrails** + +## 2. 기본 정책 + +Phase 0의 risk policy를 그대로 따릅니다. +기본 예시: + +- 거래당 계좌 리스크: 0.25% ~ 0.50% +- 하루 총 신규 리스크: 1.0% 이하 +- 동시 최대 포지션: 3개 +- 같은 섹터 최대 포지션: 2개 +- 연속 손실 임계치 도달 시 신규 진입 제한 + +Phase 4 구현은 이 규칙을 config로 표현하되, default는 위 정책을 사용합니다. + +## 3. sizing 모델 + +### 3.1 Risk-based sizing + +기본 공식: + +```text +risk_dollars = equity * per_trade_risk_pct +per_share_risk = entry_price - stop_price +shares = floor(risk_dollars / per_share_risk) +``` + +추가 제약: + +- `shares * entry_price <= available_cash` +- `shares <= max_position_value_pct * equity / entry_price` +- `shares`는 최소 lot 이상 + +### 3.2 Value cap + +risk-based sizing이 너무 커질 수 있으므로 별도 상한을 둡니다. + +예: +- single position max gross exposure = 20% equity + +### 3.3 Liquidity cap + +무료 데이터 기반 전략은 시가총액/평균거래대금 필터가 있어도, +개별 거래가 지나치게 큰 비중을 차지하지 않도록 cap을 둡니다. + +예: +- position value <= ADV dollar volume의 1% 미만 + +## 4. portfolio entry gates + +신규 포지션 진입은 아래 순서로 판단합니다. + +1. global kill switch 확인 +2. portfolio max positions 확인 +3. sector max positions 확인 +4. duplicate symbol / issuer check +5. daily risk budget 확인 +6. available cash / margin proxy 확인 +7. liquidity cap 확인 + +하나라도 실패하면 skip하고 이유 코드를 남깁니다. + +## 5. stop / target 규칙 + +### 5.1 Initial stop + +v1 기본: + +- `min(reaction_day_low breach rule, atr_multiple rule)` 대신 +- 구현 단순화를 위해 config에서 하나만 mandatory로 선택 +- 기본값: `reaction_day_low` 또는 `entry_price - 1.0 * ATR20` + +주의: Phase 4에서 stop 정의는 Phase 3 label 정의와 충돌하면 안 됩니다. + +### 5.2 Take-profit + +기본값: +- `target_1_r_multiple = 1.5` +- `target_1_fraction = 0.5` + +### 5.3 Trailing stop + +기본값: +- `previous_day_low_breach` +- daily bar 근사로 구현 + +### 5.4 Time exit + +기본값: +- `max_holding_days = 3 or 5` +- 해당 거래일 종가 청산 + +## 6. regime filters + +포트폴리오 엔진은 레짐이 불리할 때 신규 진입을 줄이거나 막을 수 있어야 합니다. + +예시: +- risk_off면 신규 포지션 수 50% 축소 +- VIX 급등일에는 신규 진입 금지 +- SPY trend down + sector weak면 후보 탈락 + +이 규칙은 signal ranking 전에 적용할 수도 있고, +portfolio allocation 단계에서 gating rule로 적용할 수도 있습니다. + +v1에서는 gating rule로 구현하는 편이 단순합니다. + +## 7. drawdown-based controls + +백테스트에서도 아래 risk guardrails를 반영합니다. + +- strategy drawdown > X%면 신규 진입 중단 +- rolling loss streak >= N이면 cooldown days 적용 +- daily equity drawdown > Y%면 당일 추가 진입 금지 + +이 기능은 live paper trading과 동일한 인터페이스로 구현해 두는 것이 좋습니다. + +## 8. cash ledger + +기본 백테스터는 margin-like 단순 cash ledger로 충분합니다. +하지만 아래 필드는 명시적으로 유지합니다. + +- `cash_available` +- `gross_exposure` +- `net_exposure` +- `reserved_risk_budget` +- `unrealized_pnl` +- `realized_pnl` + +나중에 cash account 전용 satellite 전략을 넣을 때 재사용할 수 있어야 합니다. + +## 9. attribution-friendly logging + +모든 진입/거절/청산은 아래 정보를 남겨야 합니다. + +- accepted/rejected reason +- sizing inputs +- stop distance +- sector gate result +- regime gate result +- portfolio slot usage + +그래야 나중에 “전략이 나빴는지, 포트폴리오 제약이 성과를 깎았는지” 구분할 수 있습니다. + +## 10. 추천 config 키 + +```yaml +risk: + per_trade_risk_pct: 0.0035 + max_daily_new_risk_pct: 0.01 + max_positions: 3 + max_positions_per_sector: 2 + max_position_value_pct: 0.20 + max_adv_fraction: 0.01 + cooldown_after_loss_streak: 3 + cooldown_days: 2 +exits: + stop_model: reaction_day_low + atr_multiple: 1.0 + target_1_r: 1.5 + target_1_fraction: 0.5 + trailing_model: previous_day_low + max_holding_days: 3 +regime: + block_on_vix_spike: true + reduce_slots_in_risk_off: true +``` diff --git a/dev/phase4_deliverables/simulation_engine_design.md b/dev/phase4_deliverables/simulation_engine_design.md new file mode 100644 index 0000000..79fb9b2 --- /dev/null +++ b/dev/phase4_deliverables/simulation_engine_design.md @@ -0,0 +1,187 @@ +# Simulation Engine Design + +## 1. 엔진 목적 + +이 엔진은 **candidate snapshot을 받아 실제 포트폴리오 성과로 연결하는 시뮬레이터**입니다. +핵심은 단순 수익률 계산이 아니라 **거래 가능 시점, 포트폴리오 제약, 체결 근사, 청산 로직**을 반영하는 것입니다. + +## 2. 기본 실행 단위 + +기본 실행 단위는 **거래일 단위**입니다. + +매 거래일마다 아래 순서를 수행합니다. + +1. 전일 종료 시점에 생성된 candidate 조회 +2. 당일 진입 가능한 종목만 필터링 +3. 포트폴리오 제약 반영 후 진입 대상 결정 +4. 당일 시초가 체결 시뮬레이션 +5. 기존 포지션에 대해 손절/익절/시간청산 평가 +6. 일말 평가금액, 노출, drawdown 갱신 + +## 3. signal 시점 정의 + +### 3.1 Event timestamp + +문서가 실제 시장에 이용 가능해진 시점. + +### 3.2 Availability timestamp + +feature snapshot이 전략에 사용 가능한 시점. +예: reaction day 종가를 사용하는 전략이라면 availability는 reaction day close 이후입니다. + +### 3.3 Decision date + +전략이 매수 여부를 결정한 날짜. + +### 3.4 Execution date + +실제 진입을 시뮬레이션한 거래일. +기본값은 next open입니다. + +## 4. 진입 로직 + +v1 기본 진입 규칙: + +- 후보는 `decision_date = reaction_session_date` +- `execution_date = next_trading_day` +- 진입 가격은 `execution_date open` +- open이 없으면 거래정지/누락 처리 후 skip + +### 4.1 ranking + +- `final_candidate_score` 내림차순 +- 동점이면 `liquidity_score` 우선 +- 그래도 동점이면 `symbol` 오름차순으로 deterministic tie-break + +### 4.2 eligibility filters + +- 유니버스 필터 통과 +- 최소 가격 조건 통과 +- 유동성 조건 통과 +- 이미 동일 종목 포지션 보유 중이면 중복 진입 금지 +- 같은 issuer에 같은 이벤트 cluster 중복 진입 금지 + +## 5. 체결 근사 모델 + +### 5.1 Entry fill model + +기본: +- price = next session open +- slippage = bps + liquidity penalty +- actual fill = open * (1 + slippage_bps/10000) + +롱 기준 slippage는 양수입니다. + +### 5.2 Exit fill model + +지원 exit types: +- take profit partial +- stop loss +- trailing exit by previous day low breach (daily approximation) +- time exit at close of holding day N +- regime forced exit + +### 5.3 Stop handling on daily bars + +무료 데이터 제약상 intraday exact path는 없으므로, +daily bar 기반 근사 원칙을 고정합니다. + +권장 규칙: +- stop price가 당일 low 이하이면 stop triggered로 간주 +- take-profit과 stop이 같은 날 동시에 충족되면 **보수적 우선순위**를 사용 +- 보수적 기본값: long position은 stop이 먼저 체결된 것으로 간주 + +이 우선순위는 config로 바꿀 수 있으나 기본값은 보수적으로 둡니다. + +## 6. 포지션 생명주기 + +```text +PLANNED +→ ENTERED +→ PARTIALLY_EXITED(optional) +→ OPEN +→ EXIT_PENDING(optional for close exits) +→ CLOSED +→ ARCHIVED +``` + +필수 상태 필드: + +- `position_id` +- `symbol` +- `entry_date` +- `entry_price` +- `initial_stop` +- `profit_target_1` +- `current_stop` +- `shares_open` +- `days_held` +- `exit_reason` + +## 7. 시간청산 규칙 + +기본 전략은 1~5일 보유를 전제로 하므로 아래 rule set을 지원합니다. + +- `max_holding_days = 3` 또는 `5` +- `close_on_day_n_close = true` +- `weak_close_count_exit = 2` optional +- `no_follow_through_exit = true` optional + +v1에서는 필수 규칙을 최소화합니다. + +## 8. partial exit 규칙 + +예시 기본값: + +- +1.5R 도달 시 50% 청산 +- 잔여분은 trailing rule 또는 time exit + +partial exit는 일별 bar 근사로 구현합니다. +동일 bar에서 partial target과 stop이 동시에 걸릴 수 있으므로, +우선순위 규칙을 명시적으로 config화해야 합니다. + +## 9. 수수료/슬리피지 + +무료 데이터 환경에서는 보수적인 비용 가정이 중요합니다. + +기본 파라미터 예시: + +- commission_per_share = 0.0 또는 broker-specific +- min_commission = 0 +- slippage_bps_base = 5 +- slippage_bps_low_liquidity_penalty = 5~20 +- gap risk adjustment optional + +v1에서는 모델 복잡도보다 **일관된 보수성**이 중요합니다. + +## 10. 누락/결측 처리 + +다음 상황에서는 기본적으로 no-trade 또는 forced skip입니다. + +- execution_date open missing +- non-tradable flag +- corporate action mismatch +- feature snapshot missing critical field +- duplicate event cluster + +모든 skip은 이유 코드와 함께 로그/리포트에 남겨야 합니다. + +## 11. 주요 함수 시그니처 권장안 + +```python +run_experiment(manifest_path: str) -> ExperimentResult +load_snapshot(snapshot_id: str) -> SnapshotBundle +select_candidates(date: str, snapshot: SnapshotBundle, config: BacktestConfig) -> list[Candidate] +allocate_portfolio(date: str, candidates: list[Candidate], portfolio_state: PortfolioState, config: BacktestConfig) -> list[PlannedOrder] +simulate_entries(date: str, planned_orders: list[PlannedOrder], market_bars: MarketBars, config: BacktestConfig) -> list[FilledEntry] +update_positions(date: str, positions: list[OpenPosition], market_bars: MarketBars, config: BacktestConfig) -> PositionUpdateResult +compute_metrics(run_id: str, blotter: pd.DataFrame, equity_curve: pd.DataFrame) -> MetricsBundle +``` + +## 12. deterministic 요구사항 + +- 동일 manifest + 동일 snapshot + 동일 코드 버전이면 결과가 같아야 합니다. +- 랜덤 tie-break 금지 +- floating rounding 정책 고정 +- timezone handling 고정 +- price lookup fallback 순서 고정 diff --git a/dev/phase4_deliverables/testing_checklist.md b/dev/phase4_deliverables/testing_checklist.md new file mode 100644 index 0000000..1a977a4 --- /dev/null +++ b/dev/phase4_deliverables/testing_checklist.md @@ -0,0 +1,117 @@ +# Testing Checklist + +## 목적 + +이 체크리스트는 Phase 4 백테스터의 **정확성, 재현성, 보수성**을 검증하기 위한 것입니다. + +테스트는 아래 5개 계층으로 나눕니다. + +1. 단위 테스트 +2. 통합 테스트 +3. 리플레이 / 재현성 테스트 +4. 통계/리포트 검증 +5. 운영 전 수동 검토 + +--- + +## P0 — 반드시 통과해야 하는 테스트 + +### 1. 날짜/시간 정렬 + +- [ ] 장전 event가 같은 날 reaction session으로 매핑되는지 +- [ ] 장후 event가 다음 거래일 reaction session으로 매핑되는지 +- [ ] 장중 event가 availability timestamp 규칙에 맞게 처리되는지 +- [ ] 휴장일 다음 거래일 계산이 정확한지 +- [ ] timezone aware datetime만 사용되는지 + +### 2. snapshot 무결성 + +- [ ] snapshot reader가 future rows를 읽지 않는지 +- [ ] feature availability timestamp 이후의 값만 사용되는지 +- [ ] 결측 critical field가 있으면 skip되는지 +- [ ] dataset snapshot id가 결과 metadata에 기록되는지 + +### 3. deterministic 실행 + +- [ ] 동일 manifest 재실행 시 blotter row 수가 같은지 +- [ ] 동일 manifest 재실행 시 metrics summary가 같은지 +- [ ] tie-break가 deterministic한지 +- [ ] floating rounding 정책이 고정되어 있는지 + +### 4. 체결/청산 규칙 + +- [ ] next-open entry가 정확히 open price 기반으로 계산되는지 +- [ ] stop breach 시 보수적 우선순위가 적용되는지 +- [ ] target + stop same-bar 충돌 시 기본 우선순위가 테스트되는지 +- [ ] time exit가 holding day close에 실행되는지 +- [ ] partial exit 후 잔여수량이 정확히 업데이트되는지 + +### 5. 포트폴리오 제약 + +- [ ] max positions 초과 진입이 차단되는지 +- [ ] sector gate가 적용되는지 +- [ ] daily risk budget이 초과되면 나머지 종목이 reject되는지 +- [ ] duplicate symbol / issuer 진입이 차단되는지 +- [ ] cash/exposure ledger가 음수로 깨지지 않는지 + +--- + +## P1 — 강하게 권장되는 테스트 + +### 6. 비용/슬리피지 + +- [ ] slippage bps가 fill price에 반영되는지 +- [ ] commission model on/off가 결과에 반영되는지 +- [ ] 저유동성 penalty가 sizing 또는 fill에 반영되는지 + +### 7. 리포트/산출물 + +- [ ] trade blotter 필수 컬럼이 모두 채워지는지 +- [ ] equity curve와 blotter realized pnl이 일치하는지 +- [ ] attribution report 합계가 전체 결과와 일치하는지 +- [ ] metrics summary JSON schema 검증이 통과하는지 +- [ ] plots 생성 실패가 run 전체를 깨지 않는지 + +### 8. split / walk-forward + +- [ ] year split이 정상 생성되는지 +- [ ] regime split이 정상 생성되는지 +- [ ] walk-forward window가 누수 없이 생성되는지 +- [ ] test 구간이 train 구간과 겹치지 않는지 + +--- + +## P2 — 리플레이 / 회귀 테스트 + +### 9. 회귀 스냅샷 + +- [ ] 소형 고정 데이터셋으로 golden run 결과를 저장했는지 +- [ ] 코드 변경 후 golden run 결과가 의도치 않게 바뀌지 않는지 +- [ ] 의도적 변경일 때는 run notes에 차이를 기록하는지 + +### 10. 에러 처리 + +- [ ] missing bar 데이터가 있을 때 no-trade로 안전하게 떨어지는지 +- [ ] unknown config key가 에러 처리되는지 +- [ ] schema invalid manifest가 실행 전에 차단되는지 +- [ ] corrupted artifact write가 적절히 에러를 내는지 + +--- + +## 수동 검토 체크리스트 + +- [ ] 샘플 20개 거래를 사람이 손으로 계산해 엔진 결과와 대조했는지 +- [ ] 장후 발표 실적 5개 케이스를 수동 검증했는지 +- [ ] same-bar stop/target 충돌 케이스를 수동 검증했는지 +- [ ] 섹터 제한 때문에 reject된 거래가 합리적인지 검토했는지 +- [ ] drawdown curve가 trade blotter와 정합적인지 확인했는지 +- [ ] 기대보다 너무 좋은 성과가 나올 때 leakage audit를 수행했는지 + +--- + +## 통과 기준 + +- P0 전항목 통과 +- P1 항목 중 치명적 실패 없음 +- 수동 검토 샘플에서 명백한 timestamp/price 오류 없음 +- regression snapshot 재현 성공 diff --git a/dev/phase5_deliverables/README.md b/dev/phase5_deliverables/README.md new file mode 100644 index 0000000..00170a3 --- /dev/null +++ b/dev/phase5_deliverables/README.md @@ -0,0 +1,44 @@ +# Phase 5 Deliverables — Attention Overlay Development Docs + +## 목적 +Phase 5는 Phase 0~4에서 구축한 **공식 이벤트 + 가격 확인 기반 코어 전략** 위에, 무료 attention/crowding 데이터를 **보조 신호(overlay)** 로 추가하는 단계입니다. + +핵심 원칙: +- 코어 신호 없이 overlay만으로 진입하지 않는다. +- overlay는 **후보 우선순위 조정, 보유기간 조절, 포지션 추가/축소 보조** 에만 사용한다. +- 무료 데이터 정책을 유지한다. +- 소스별 약관/쿼터/지연/신뢰도 차이를 명시적으로 반영한다. + +## 포함 문서 +- `attention_overlay_architecture.md` +- `source_integration_specs.md` +- `feature_design_and_entity_resolution.md` +- `overlay_scoring_policy.md` +- `implementation_plan.md` +- `testing_checklist.md` +- `operations_and_monitoring.md` +- `youtube_channel_registry_spec.md` +- `overlay_feature_record.schema.json` +- `overlay_config.schema.json` + +## 범위 +### 포함 +- Yahoo Finance RSS headline burst +- YouTube whitelist channel monitoring +- Wikimedia pageview shock +- Google Trends experimental theme heat +- FINRA crowding feature integration (Phase 2 데이터 재사용) +- attention overlay score 산출 +- backtester/live trader에 overlay score 연결 + +### 제외 +- X/Twitter API 연동 +- Reddit 라이브 핵심 의존성화 +- Stocktwits 신규 핵심 의존성화 +- 실시간 초단타 소셜 트리거 매매 +- 자막 전체 전수 수집 기반 파이프라인 + +## 성공 기준 +- overlay 미사용 대비 후보 정렬 품질이 개선되어야 한다. +- overlay 추가 후 코어 전략 수익성이 악화되더라도 원인을 attribution 가능해야 한다. +- overlay 장애 발생 시 자동으로 core-only 모드로 degrade 되어야 한다. diff --git a/dev/phase5_deliverables/attention_overlay_architecture.md b/dev/phase5_deliverables/attention_overlay_architecture.md new file mode 100644 index 0000000..d6b8113 --- /dev/null +++ b/dev/phase5_deliverables/attention_overlay_architecture.md @@ -0,0 +1,74 @@ +# Attention Overlay Architecture + +## 목표 +공식 문서와 가격 반응으로 생성된 `trade_candidates` 에 대해, 추가적인 **리테일 관심도 / 미디어 확산 / crowding** 신호를 계산해 `overlay_score` 를 부여한다. + +## 설계 원칙 +1. Overlay는 **후행 확인 신호** 다. +2. Overlay 데이터는 소스별 신뢰도 가중치를 갖는다. +3. Overlay는 결측이 많을 수 있으므로 sparse-friendly 하게 설계한다. +4. Overlay는 독립 장애 도메인으로 분리한다. +5. Overlay feature는 모두 타임스탬프와 source provenance를 남긴다. + +## 상위 구조 +```text +source adapters + ├── yahoo_rss_adapter + ├── youtube_overlay_adapter + ├── wikimedia_adapter + ├── google_trends_adapter (experimental) + └── finra_overlay_loader + +normalized events + ├── headline_mentions + ├── video_mentions + ├── pageview_timeseries + ├── trend_topic_timeseries + └── crowding_metrics + +entity resolution layer + ├── symbol ↔ company aliases + ├── symbol ↔ wikipedia page + ├── symbol ↔ youtube mention matcher + └── symbol ↔ trend topic map + +feature builder + ├── headline burst + ├── publisher breadth + ├── youtube influence score + ├── pageview shock + ├── theme heat + └── crowding stress + +overlay scorer + ├── overlay_score + ├── overlay_confidence + ├── hold_extension_hint + └── add_on_eligibility +``` + +## 데이터 흐름 +1. Phase 2/3에서 `trade_candidates` 생성. +2. overlay adapters가 소스별 raw 수집. +3. entity resolution이 종목 단위로 정규화. +4. feature builder가 observation window 기준 feature 생성. +5. overlay scorer가 각 후보에 score 부여. +6. backtester/live trader가 score를 사용해 ranking, sizing, holding rule 조정. + +## 장애 격리 +- overlay adapter 실패는 core signal 생성에 영향 주지 않는다. +- 특정 소스 실패 시 나머지 소스로 점수 계산 가능해야 한다. +- 모든 overlay feature가 누락되면 `overlay_mode=disabled` 로 자동 강등한다. + +## 추천 배치 순서 +- T day 18:30 ET: Yahoo RSS / FINRA 수집 완료 +- T day 20:00 ET: Wikimedia/YouTube/Trends 업데이트 +- T day 20:30 ET: overlay feature build +- T day 21:00 ET: candidate rerank + +## 사용 방식 +- `candidate_rank_score = core_score * 0.85 + overlay_score * 0.15` +- 또는 core score bucket 내 tie-breaker +- 또는 holding period extension/trim decision only + +v1 권장: **tie-breaker + hold adjustment only** diff --git a/dev/phase5_deliverables/feature_design_and_entity_resolution.md b/dev/phase5_deliverables/feature_design_and_entity_resolution.md new file mode 100644 index 0000000..471ef22 --- /dev/null +++ b/dev/phase5_deliverables/feature_design_and_entity_resolution.md @@ -0,0 +1,79 @@ +# Feature Design and Entity Resolution + +## 목표 +이종 소스의 noisy mentions를 종목 단위 overlay feature로 안정적으로 변환한다. + +## Entity Resolution 원칙 +### symbol matching 단계 +1. direct ticker token match +2. company canonical name match +3. approved alias match +4. fuzzy match (낮은 신뢰도, manual review 후보) + +### 금지 규칙 +- 1글자/2글자 일반 단어 ticker를 무조건 매칭하지 않는다. +- 맥락 없는 company substring match 금지. +- YouTube 제목에 `AI`, `App`, `ON`, `IT` 같은 일반 토큰을 티커로 해석하지 않는다. + +## reference tables +- `symbol_master` +- `company_aliases` +- `youtube_channel_registry` +- `wiki_page_map` +- `theme_topic_map` + +## Feature group 정의 +### A. Headline Burst +- 최근 6h/24h 헤드라인 수 +- publisher breadth +- deduped article count +- positive/negative heuristic headline count + +### B. YouTube Influence +- weighted views by channel weight +- unique channels mentioning symbol +- upload velocity +- comments-per-view ratio +- mention freshness decay + +### C. Wiki Attention +- 1d/3d/7d pageview changes +- rolling z-score +- percentile rank vs last 90d + +### D. Theme Heat +- mapped theme trend value +- acceleration +- saturation regime + +### E. Crowding +- short volume anomaly +- recent crowding persistence + +## Normalization +- 모든 source-local metric은 source별 robust z-score로 정규화 +- winsorization 적용 (1% / 99%) +- sparse source는 missing 그대로 두고 source confidence penalty 적용 + +## Time windows +- 6h burst: event propagation detection +- 24h burst: overnight attention +- 3d persistence: continuation support +- 7d trend: theme regime only + +## Leakage 방지 +- entry decision 시점 이후 수집 데이터 사용 금지 +- snapshot time 명시 +- re-run 시 동일 cutoff 적용 + +## 출력 예시 필드 +- symbol +- as_of_ts +- headline_burst_z +- youtube_influence_z +- wiki_attention_z +- theme_heat_z +- crowding_stress_z +- overlay_score +- overlay_confidence +- source_presence_mask diff --git a/dev/phase5_deliverables/implementation_plan.md b/dev/phase5_deliverables/implementation_plan.md new file mode 100644 index 0000000..d0ea6e4 --- /dev/null +++ b/dev/phase5_deliverables/implementation_plan.md @@ -0,0 +1,66 @@ +# Phase 5 Implementation Plan + +## 목표 +attention overlay 레이어를 구현하여 backtester와 live pipeline에 연결한다. + +## 작업 순서 +### Step 1. registry tables 구축 +- youtube_channel_registry +- company_aliases 보강 +- wiki_page_map +- theme_topic_map + +### Step 2. raw adapters 구현 +- yahoo_rss_adapter +- youtube_overlay_adapter +- wikimedia_adapter +- google_trends_adapter (feature flagged) + +### Step 3. normalization pipeline 구현 +- headline normalization +- video normalization +- pageview normalization +- trend timeseries normalization +- entity resolution jobs + +### Step 4. overlay feature builder 구현 +- per-source feature calculators +- missing data handling +- source confidence computation +- overlay_feature_record 생성 + +### Step 5. overlay scorer 구현 +- weighted scoring +- confidence adjustment +- band assignment +- recommendation flags 생성 + +### Step 6. backtester integration +- overlay on/off config +- tie-breaker mode +- weighted rank mode +- hold extension mode +- add-on eligibility mode + +### Step 7. live pipeline integration +- nightly overlay jobs +- candidate rerank job +- degraded mode handling +- source freshness checks + +## 구현 우선순위 +1. Yahoo RSS +2. Wikimedia +3. YouTube whitelist +4. FINRA overlay join +5. Google Trends experimental + +## 완료 조건 +- overlay feature record가 후보 종목에 대해 안정적으로 생성된다. +- backtester에서 overlay on/off ablation이 가능하다. +- source failure가 전체 파이프라인 실패로 이어지지 않는다. + +## non-goals +- high-frequency social trading +- full video transcript NLP pipeline +- real-time intraday social scraping diff --git a/dev/phase5_deliverables/operations_and_monitoring.md b/dev/phase5_deliverables/operations_and_monitoring.md new file mode 100644 index 0000000..2de9fa9 --- /dev/null +++ b/dev/phase5_deliverables/operations_and_monitoring.md @@ -0,0 +1,68 @@ +# Operations and Monitoring + +## 모니터링 목표 +- source freshness +- quota consumption +- entity resolution precision drift +- overlay coverage ratio +- overlay contribution stability + +## 핵심 메트릭 +### source health +- `overlay_source_success_rate` +- `overlay_source_last_success_ts` +- `overlay_source_latency_seconds` +- `overlay_source_quota_remaining` + +### pipeline health +- `overlay_records_generated_count` +- `overlay_records_missing_source_ratio` +- `overlay_degraded_mode_count` +- `overlay_review_queue_size` + +### quality +- `entity_match_precision_sampled` +- `headline_dedupe_rate` +- `youtube_false_positive_rate_sampled` +- `wiki_map_missing_ratio` + +### strategy impact +- `overlay_on_vs_off_delta_return` +- `overlay_on_vs_off_delta_sharpe` +- `overlay_hold_extension_hit_rate` +- `overlay_add_on_hit_rate` + +## 알림 규칙 +- source freshness SLA 초과 +- YouTube quota 80% 초과 +- review queue backlog threshold 초과 +- overlay coverage ratio 급락 +- overlay contribution delta 급변 + +## degraded mode +### trigger +- critical source 2개 이상 실패 +- entity matcher precision alarm +- stale overlay snapshot + +### behavior +- core-only mode로 전환 +- overlay-based add-on 금지 +- hold extension 비활성화 +- alert 발송 + +## 운영 점검 루틴 +### 일간 +- 실패한 source job 확인 +- overlay coverage 확인 +- review queue triage + +### 주간 +- source별 precision 샘플 검수 +- weight calibration 검토 +- channel registry 변경점 반영 + +### 월간 +- source keep/drop review +- topic map refresh +- schema/version audit diff --git a/dev/phase5_deliverables/overlay_config.schema.json b/dev/phase5_deliverables/overlay_config.schema.json new file mode 100644 index 0000000..1057f62 --- /dev/null +++ b/dev/phase5_deliverables/overlay_config.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "OverlayConfig", + "type": "object", + "required": ["enabled", "mode", "weights"], + "properties": { + "enabled": {"type": "boolean"}, + "mode": { + "type": "string", + "enum": ["tie_breaker", "weighted_rank", "hold_adjustment", "add_on_gate"] + }, + "weights": { + "type": "object", + "required": ["yahoo", "youtube", "wikimedia", "finra", "google_trends"], + "properties": { + "yahoo": {"type": "number", "minimum": 0, "maximum": 1}, + "youtube": {"type": "number", "minimum": 0, "maximum": 1}, + "wikimedia": {"type": "number", "minimum": 0, "maximum": 1}, + "finra": {"type": "number", "minimum": 0, "maximum": 1}, + "google_trends": {"type": "number", "minimum": 0, "maximum": 1} + }, + "additionalProperties": false + }, + "strong_threshold": {"type": "number", "minimum": 0, "maximum": 1}, + "weak_threshold": {"type": "number", "minimum": 0, "maximum": 1}, + "allow_degraded_mode": {"type": "boolean"}, + "experimental_sources_enabled": {"type": "boolean"} + }, + "additionalProperties": false +} diff --git a/dev/phase5_deliverables/overlay_feature_record.schema.json b/dev/phase5_deliverables/overlay_feature_record.schema.json new file mode 100644 index 0000000..d5031e6 --- /dev/null +++ b/dev/phase5_deliverables/overlay_feature_record.schema.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "OverlayFeatureRecord", + "type": "object", + "required": [ + "symbol", + "as_of_ts", + "overlay_score", + "overlay_confidence", + "source_presence_mask", + "feature_version" + ], + "properties": { + "symbol": {"type": "string"}, + "as_of_ts": {"type": "string", "format": "date-time"}, + "headline_burst_z": {"type": ["number", "null"]}, + "youtube_influence_z": {"type": ["number", "null"]}, + "wiki_attention_z": {"type": ["number", "null"]}, + "theme_heat_z": {"type": ["number", "null"]}, + "crowding_stress_z": {"type": ["number", "null"]}, + "overlay_score": {"type": "number", "minimum": 0, "maximum": 1}, + "overlay_confidence": {"type": "number", "minimum": 0, "maximum": 1}, + "source_presence_mask": { + "type": "object", + "properties": { + "yahoo": {"type": "boolean"}, + "youtube": {"type": "boolean"}, + "wikimedia": {"type": "boolean"}, + "google_trends": {"type": "boolean"}, + "finra": {"type": "boolean"} + }, + "additionalProperties": false + }, + "hold_extension_hint": {"type": ["string", "null"], "enum": ["extend", "neutral", "trim", null]}, + "add_on_eligibility": {"type": ["boolean", "null"]}, + "feature_version": {"type": "string"} + }, + "additionalProperties": false +} diff --git a/dev/phase5_deliverables/overlay_scoring_policy.md b/dev/phase5_deliverables/overlay_scoring_policy.md new file mode 100644 index 0000000..2fc7371 --- /dev/null +++ b/dev/phase5_deliverables/overlay_scoring_policy.md @@ -0,0 +1,58 @@ +# Overlay Scoring Policy + +## 원칙 +- overlay는 core score를 대체하지 않는다. +- overlay가 강해도 core score minimum threshold 미달이면 거래하지 않는다. +- overlay는 랭킹 재정렬, 보유기간 조정, add-on 허용 여부에만 사용한다. + +## 권장 v1 반영 방식 +### candidate ranking +```text +final_rank_score = core_rank_score * 0.90 + overlay_score * 0.10 +``` + +### optional tie-breaker mode +동일 core bucket 내에서 overlay score 높은 순으로 정렬. + +### holding adjustment +- overlay_score >= strong_threshold: max_holding_days +1 or +2 검토 +- overlay_score <= weak_threshold: time stop 보수화 + +### add-on eligibility +- strong core + strong overlay + market regime ok 일 때만 2차 진입 허용 + +## Source weight (v1 권장) +- Yahoo headline burst: 0.25 +- YouTube influence: 0.35 +- Wikimedia pageview shock: 0.20 +- FINRA crowding: 0.15 +- Google Trends experimental: 0.05 + +## Source reliability modifier +- stale data: penalty +- low entity confidence: penalty +- duplicated source cluster: penalty +- missing data: neutral, not punitive beyond confidence adjustment + +## Overlay score bands +- 0.75 ~ 1.00: strong +- 0.55 ~ 0.75: supportive +- 0.40 ~ 0.55: neutral +- < 0.40: weak/no support + +## Allowed actions by band +- strong: rank boost, hold extension candidate, add-on candidate +- supportive: tie-breaker advantage +- neutral: no action +- weak: no boost, may shorten hold + +## Explicit 금지 +- overlay-only entries +- overlay만으로 risk budget 상향 +- 소셜 burst만으로 stop widening +- 실험 소스(Google Trends) 단독 영향력 과대부여 + +## Calibration +- monthly recalibration +- source별 information coefficient와 hit rate 모니터링 +- unstable source는 weight 하향 또는 disable diff --git a/dev/phase5_deliverables/source_integration_specs.md b/dev/phase5_deliverables/source_integration_specs.md new file mode 100644 index 0000000..4722de0 --- /dev/null +++ b/dev/phase5_deliverables/source_integration_specs.md @@ -0,0 +1,135 @@ +# Source Integration Specs + +## 1. Yahoo Finance RSS +### 목적 +티커 관련 뉴스 headline burst와 publisher breadth 측정. + +### 수집 방식 +- RSS feed polling +- 종목 또는 회사명 기반 기사 매칭 +- 기사 URL canonicalization 및 deduplication + +### 저장 필드 +- source_name +- fetched_at +- article_guid +- url +- title +- publisher +- published_at +- matched_symbols[] +- confidence + +### 주요 feature +- `headline_count_6h` +- `headline_count_24h` +- `publisher_breadth_24h` +- `headline_burst_zscore` + +### 주의 +- 같은 보도자료가 여러 매체에 재유통될 수 있으므로 dedupe 필수 +- 본문 전체를 크롤링하지 말고 headline-level feature 중심 유지 + +## 2. YouTube Data API +### 목적 +유명 투자 채널의 종목 언급과 초기 확산 속도 측정. + +### 수집 방식 +- whitelist channel registry 기반 추적 +- `uploads` playlist polling 우선 +- 필요 시 제한적 search +- video metadata + comment count 수집 + +### 저장 필드 +- channel_id +- channel_title +- video_id +- published_at +- title +- description +- view_count +- like_count +- comment_count +- matched_symbols[] +- channel_weight +- collection_snapshot_at + +### 주요 feature +- `youtube_mentions_24h` +- `youtube_weighted_views_24h` +- `youtube_influence_score` +- `youtube_comment_velocity` + +### 주의 +- 자막 다운로드 의존 금지 +- 전체 유튜브 검색 전수화 금지 +- 채널 whitelist와 alias matcher 품질이 중요 + +## 3. Wikimedia Pageviews +### 목적 +리테일 관심 급증을 정량화. + +### 수집 방식 +- ticker/company ↔ wiki page map 유지 +- daily pageviews ingestion + +### 저장 필드 +- page_title +- date +- views +- project +- access +- agent +- mapped_symbol + +### 주요 feature +- `wiki_views_1d` +- `wiki_views_3d_change` +- `wiki_pageview_zscore_30d` +- `wiki_attention_shock_flag` + +### 주의 +- 다의어 페이지 매핑 오류 주의 +- 회사 페이지와 제품/인물 페이지 혼동 방지 + +## 4. Google Trends (Experimental) +### 목적 +개별 종목보다는 테마/섹터 관심도 측정. + +### 수집 방식 +- 제한된 topic registry 유지 +- 일/주 단위 시계열 수집 + +### 저장 필드 +- trend_topic_id +- topic_label +- geography +- granularity +- observed_at +- interest_value +- mapped_themes[] + +### 주요 feature +- `theme_heat_7d` +- `theme_acceleration_7d` +- `theme_heat_regime` + +### 주의 +- individual ticker trigger로 직접 사용 금지 +- experimental source로 분류 + +## 5. FINRA overlay loader +### 목적 +crowding/short pressure overlay 계산. + +### 입력 +Phase 2 적재 완료된 short sale volume dataset 재사용. + +### 주요 feature +- `short_volume_ratio` +- `short_volume_spike_zscore` +- `off_exchange_crowding_flag` (if available from OTC transparency datasets) + +### 주의 +- short interest와 혼동 금지 +- post-close feature only diff --git a/dev/phase5_deliverables/testing_checklist.md b/dev/phase5_deliverables/testing_checklist.md new file mode 100644 index 0000000..874bee1 --- /dev/null +++ b/dev/phase5_deliverables/testing_checklist.md @@ -0,0 +1,47 @@ +# Phase 5 Testing Checklist + +## A. Unit Tests +- [ ] RSS parser가 malformed item을 건너뛴다. +- [ ] URL canonicalizer가 tracking query를 제거한다. +- [ ] YouTube matcher가 approved alias만 매칭한다. +- [ ] Wikimedia page map lookup이 없는 symbol을 안전하게 skip 한다. +- [ ] Google Trends adapter가 disabled flag에서 no-op 한다. +- [ ] robust z-score 계산이 constant series에서 안전하다. +- [ ] overlay scorer가 missing source를 허용한다. + +## B. Entity Resolution Tests +- [ ] ambiguous ticker가 잘못 매칭되지 않는다. +- [ ] company alias override가 적용된다. +- [ ] manual denylist가 강제된다. +- [ ] wiki page redirect 처리 후 canonical title이 저장된다. +- [ ] video title과 description의 conflicting match를 review queue로 보낸다. + +## C. Integration Tests +- [ ] raw → normalized → feature → score 전체 체인이 동작한다. +- [ ] trade candidate가 없을 때 overlay pipeline이 정상 종료된다. +- [ ] 특정 source 실패 시 나머지 source로 partial score 생성 가능하다. +- [ ] overlay score가 backtester config에 따라 반영된다. +- [ ] live rerank job이 stale source를 탐지하고 degraded mode로 전환한다. + +## D. Replay Tests +- [ ] historical snapshot cutoff 이후 데이터가 사용되지 않는다. +- [ ] 동일 cutoff로 재실행 시 동일 overlay score가 재생성된다. +- [ ] source update 지연 시 fallback 결과가 일관적이다. + +## E. Quality Tests +- [ ] random sample 100건에서 symbol match precision 측정 +- [ ] YouTube whitelist 외 채널이 잘못 포함되지 않는다. +- [ ] duplicated Yahoo headlines가 burst를 과대추정하지 않는다. +- [ ] wiki attention shock가 명백한 비종목 이벤트에서 과도하게 오르지 않는다. + +## F. Strategy Impact Tests +- [ ] overlay on/off ablation 리포트 생성 +- [ ] tie-breaker only와 weighted-rank 결과 비교 +- [ ] hold-extension only 결과 비교 +- [ ] source별 incremental value 리포트 생성 + +## G. Ops Tests +- [ ] API quota exhaustion 시 경고 및 soft-fail +- [ ] source freshness SLA 위반 알림 발송 +- [ ] registry update 후 캐시 무효화 정상 동작 +- [ ] feature store write retry가 idempotent 하다. diff --git a/dev/phase5_deliverables/youtube_channel_registry_spec.md b/dev/phase5_deliverables/youtube_channel_registry_spec.md new file mode 100644 index 0000000..3a8cb19 --- /dev/null +++ b/dev/phase5_deliverables/youtube_channel_registry_spec.md @@ -0,0 +1,41 @@ +# YouTube Channel Registry Spec + +## 목적 +무분별한 search API 사용을 피하고, 신뢰 가능한 채널 subset만 추적하기 위한 registry. + +## registry 필드 +- channel_id +- channel_title +- category +- language +- region +- subscriber_band +- channel_weight +- active_flag +- watch_mode (`uploads_only` | `uploads_plus_comments`) +- symbol_focus_tags[] +- notes +- created_at +- updated_at + +## 포함 기준 +- 투자/시장 관련 공개 채널 +- 최근 일정 기간 내 활동 지속 +- 명확한 채널 정체성 +- 과도한 spam/scam 패턴 없음 + +## 제외 기준 +- 펌프 앤 덤프 의심 +- 저품질 클릭베이트 과다 +- 종목 언급은 많지만 실제 매칭 정밀도가 낮음 +- 정책/약관 리스크 높은 채널 + +## channel_weight 초기값 가이드 +- 대형/영향력 높음: 1.0 +- 중간: 0.6 +- 소형/니치: 0.3 + +## 운영 규칙 +- 신규 채널 추가는 수동 승인 +- 월 1회 registry review +- false positive 높은 채널은 weight 하향 또는 비활성화 diff --git a/dev/phase6_deliverables/README.md b/dev/phase6_deliverables/README.md new file mode 100644 index 0000000..526a872 --- /dev/null +++ b/dev/phase6_deliverables/README.md @@ -0,0 +1,121 @@ +# Phase 6 개발문서 패키지 + +이 문서는 **Phase 0의 전략/리스크/데이터 정책**, **Phase 1의 저장소 구조/DB/서비스 계약**, **Phase 2의 ingestion 파이프라인**, **Phase 3의 parser/feature/label 규칙**, **Phase 4의 백테스터**, **Phase 5의 attention overlay**를 바탕으로, +AI 코딩 에이전트가 **paper trading 및 live execution 운영 레이어**를 구현할 수 있도록 만든 **Phase 6 상세 개발문서**입니다. + +## 목표 + +Phase 6의 목표는 아래 10가지를 실제 코드 수준으로 구현하는 것입니다. + +1. **paper trading orchestrator**를 구현한다. +2. **broker abstraction layer**와 최소 1개 브로커(기본 reference: Alpaca paper)를 연동한다. +3. **candidate -> order plan -> order submit -> fill -> position -> exit** 전체 주문 생명주기를 상태머신으로 구현한다. +4. **risk guard / pre-trade checks / kill switch / day halt**를 구현한다. +5. **reconciliation**(브로커 상태와 내부 상태 대사)와 **idempotent recovery**를 구현한다. +6. **human approval mode / fully automatic mode / dry-run mode**를 지원한다. +7. **paper/live 공통 contract**를 정의하고, Phase 4 백테스터 결과와 비교 가능한 blotter를 산출한다. +8. **알림/모니터링/운영 대시보드**를 구현한다. +9. **장애 시 degraded mode / no-trade mode**로 안전하게 전환한다. +10. **실전 운영 전 테스트, paper gate, small-capital live gate**를 문서화한다. + +## 포함 문서 + +- `paper_trading_and_live_architecture.md` + - Phase 6 전체 아키텍처 + - paper/live 모드 구분 + - 상태 저장과 복구 전략 +- `broker_integration_and_order_lifecycle.md` + - broker abstraction layer + - 주문/체결/취소/교체 규칙 + - client order id 규칙 +- `risk_guard_and_approval_workflow.md` + - pre-trade / in-trade / post-trade risk guard + - human approval workflow + - kill switch / trading halt 정책 +- `state_machine_and_reconciliation.md` + - 실행 상태머신 + - reconciliation loop + - restart / crash recovery 절차 +- `monitoring_alerting_and_ops.md` + - 운영 모니터링 + - alert routing + - degraded mode와 no-trade mode +- `configuration_and_schemas.md` + - live/paper 설정 구조 + - schema 파일 설명 +- `implementation_plan.md` + - AI 코딩 에이전트용 구현 순서 + - 작업 분할 + - 완료 기준 + - 금지사항 +- `testing_checklist.md` + - 단위/통합/replay/failover/UAT 체크리스트 +- `operator_runbook.md` + - 운영자가 장 시작 전/중/후에 수행할 절차 +- `live_config.schema.json` + - Phase 6 런타임 설정 스키마 +- `order_event.schema.json` + - 주문/체결 이벤트 canonical schema +- `approval_ticket.schema.json` + - human approval ticket schema + +## Phase 6 범위 + +Phase 6에서는 아래를 구현합니다. + +- broker abstraction layer +- paper trading runner +- optional live trading runner +- pre-trade checks +- order submit/cancel/replace +- fill handling / partial fill handling +- position lifecycle manager +- reconciliation job +- approval workflow +- kill switch / daily halt / degraded mode +- trade blotter / execution journal / alerts + +Phase 6에서는 아직 아래를 구현하지 않습니다. + +- multi-broker 동시 생산 운영 +- 옵션/선물/숏셀 자동화 +- 초단타 ORB 전용 execution stack +- 완전한 웹 기반 OMS/EMS +- 멀티유저 권한 체계 +- 24/7 NOC 수준의 운영 자동화 + +## 핵심 원칙 + +1. **실행은 항상 보수적이어야 한다.** 좋은 체결보다 잘못된 체결을 피하는 것이 우선이다. +2. **내부 상태와 브로커 상태는 항상 대사 가능해야 한다.** +3. **모든 실행 액션은 재시도 가능해야 하지만, 중복 주문은 절대 허용하지 않는다.** +4. **장애 시에는 자동으로 더 안전한 모드로 degrade 되어야 한다.** +5. **paper와 live는 최대한 같은 코드 경로를 사용한다.** +6. **human approval 없이도 돌아갈 수 있게 설계하되, 초기 운영은 approval mode를 기본으로 한다.** +7. **Phase 0 정책(무료 데이터 전용, 공식 이벤트 우선, 소셜 overlay only)을 절대 깨지 않는다.** + +## 권장 구현 순서 + +1. `paper_trading_and_live_architecture.md` +2. `broker_integration_and_order_lifecycle.md` +3. `state_machine_and_reconciliation.md` +4. `risk_guard_and_approval_workflow.md` +5. `monitoring_alerting_and_ops.md` +6. `configuration_and_schemas.md` +7. `live_config.schema.json` +8. `order_event.schema.json` +9. `approval_ticket.schema.json` +10. `implementation_plan.md` +11. `testing_checklist.md` +12. `operator_runbook.md` + +## 완료 기준 + +Phase 6 완료의 최소 기준은 다음과 같습니다. + +- 같은 candidate set으로 dry-run / paper / live-simulated 실행이 같은 order plan을 생성한다. +- broker adapter를 mocking 했을 때 주문/체결/부분체결/취소/교체/거부 전이가 모두 검증된다. +- restart 이후에도 실행 상태를 복구하고 reconciliation이 가능하다. +- approval mode와 auto mode가 동일한 risk checks를 통과한 주문만 제출한다. +- 장중 장애가 발생하면 degraded mode 또는 no-trade mode로 전환할 수 있다. +- 종가 후 trade blotter, exception log, execution journal, risk summary가 생성된다. diff --git a/dev/phase6_deliverables/approval_ticket.schema.json b/dev/phase6_deliverables/approval_ticket.schema.json new file mode 100644 index 0000000..cfb5a97 --- /dev/null +++ b/dev/phase6_deliverables/approval_ticket.schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "phase6/approval_ticket.schema.json", + "title": "ApprovalTicket", + "type": "object", + "required": [ + "approval_ticket_id", + "candidate_id", + "order_plan_id", + "symbol", + "side", + "qty", + "status", + "decision_deadline" + ], + "properties": { + "approval_ticket_id": {"type": "string"}, + "candidate_id": {"type": "string"}, + "order_plan_id": {"type": "string"}, + "symbol": {"type": "string"}, + "side": {"type": "string", "enum": ["buy", "sell"]}, + "qty": {"type": "number"}, + "entry_window": {"type": ["string", "null"]}, + "planned_stop": {"type": ["number", "null"]}, + "planned_time_exit": {"type": ["string", "null"]}, + "score_total": {"type": ["number", "null"]}, + "risk_summary": {"type": ["object", "null"]}, + "status": { + "type": "string", + "enum": ["created", "pending_review", "approved", "rejected", "expired", "superseded"] + }, + "operator_comment": {"type": ["string", "null"]}, + "decision_deadline": {"type": "string", "format": "date-time"} + }, + "additionalProperties": true +} diff --git a/dev/phase6_deliverables/broker_integration_and_order_lifecycle.md b/dev/phase6_deliverables/broker_integration_and_order_lifecycle.md new file mode 100644 index 0000000..0991ce2 --- /dev/null +++ b/dev/phase6_deliverables/broker_integration_and_order_lifecycle.md @@ -0,0 +1,175 @@ +# Broker Integration & Order Lifecycle + +## 1. 목적 + +이 문서는 브로커 연동 레이어와 주문 생명주기 규칙을 정의합니다. +Phase 6에서는 최소 1개 브로커 reference 구현이 필요하며, 기본 reference는 **Alpaca paper** 입니다. +그러나 상위 코드가 특정 브로커 SDK에 직접 결합되면 안 되므로, 내부적으로는 **broker abstraction layer**를 사용합니다. + +## 2. Broker Adapter 인터페이스 + +모든 adapter는 아래 메서드를 제공해야 합니다. + +```python +class BrokerAdapter(Protocol): + def get_health(self) -> BrokerHealth: ... + def get_account(self) -> BrokerAccountSnapshot: ... + def get_positions(self) -> list[BrokerPositionSnapshot]: ... + def get_open_orders(self) -> list[BrokerOrderSnapshot]: ... + def submit_order(self, order: PlannedOrder) -> SubmitResult: ... + def cancel_order(self, broker_order_id: str) -> CancelResult: ... + def replace_order(self, broker_order_id: str, patch: ReplacePatch) -> ReplaceResult: ... + def fetch_order(self, broker_order_id: str) -> BrokerOrderSnapshot: ... + def fetch_fills(self, since: datetime) -> list[BrokerFill]: ... + def stream_events(self, on_event: Callable[[RawBrokerEvent], None]) -> None: ... +``` + +### 금지사항 +- adapter 바깥에서 브로커 SDK raw object를 직접 참조하지 않는다. +- business logic이 브로커별 order status string에 의존하지 않는다. +- adapter 내부에서 전략 점수나 리스크 결정을 하지 않는다. + +## 3. Canonical Order Types + +Phase 6 v1에서 허용하는 주문 타입은 아래로 제한한다. + +- `market` +- `limit` +- `stop` +- `stop_limit` +- `market_on_open` (브로커 지원 시) +- `market_on_close` (브로커 지원 시) + +v1에서는 다음을 금지한다. + +- 복합 브래킷 주문에 전략 로직을 위임하는 방식 +- 옵션/멀티레그 주문 +- 알고리즘 주문(TWAP/VWAP 등) +- after-hours 진입 전략 주문 + +## 4. Client Order ID 규칙 + +내부 시스템은 반드시 **deterministic client_order_id**를 생성해야 한다. +형식 예시: + +```text +{env}-{strategy_id}-{trade_date}-{symbol}-{leg}-{attempt} +``` + +예시: + +```text +paper-corelong-2026-03-12-NVDA-entry-01 +paper-corelong-2026-03-12-NVDA-exit-half-01 +``` + +규칙: +- 같은 logical order를 재전송할 때는 같은 id를 재사용할지, 새 시퀀스를 부여할지 명확히 해야 한다. +- submit timeout 후 실제로 브로커가 주문을 받았는지 불명확할 경우, 무조건 새 주문을 보내지 말고 먼저 `fetch by client id` 또는 reconciliation을 수행한다. + +## 5. PlannedOrder 구조 + +PlannedOrder는 최소 아래 필드를 가져야 한다. + +- `order_plan_id` +- `candidate_id` +- `symbol` +- `side` +- `qty` +- `notional` (optional) +- `order_type` +- `time_in_force` +- `limit_price` (optional) +- `stop_price` (optional) +- `submission_window_start` +- `submission_window_end` +- `expiry_policy` +- `broker_route_hint` (optional) +- `reason_code` +- `risk_snapshot_id` + +## 6. Order Lifecycle + +### 6.1 내부 상태 + +```text +planned +-> approved +-> submitted +-> accepted +-> partially_filled +-> filled +-> cancel_requested +-> cancelled +-> replace_requested +-> replaced +-> rejected +-> expired +-> busted +``` + +### 6.2 전이 규칙 + +- `planned -> approved` : approval mode에서 승인 완료 또는 auto mode에서 risk guard 통과 +- `approved -> submitted` : broker adapter submit 성공 +- `submitted -> accepted` : broker가 ack 반환 +- `accepted -> partially_filled` : 부분체결 event 수신 +- `partially_filled -> filled` : 남은 수량이 0 +- `accepted -> cancel_requested` : TTL 만료 / 운영자 cancel / risk halt +- `accepted -> rejected` : broker reject +- `accepted -> expired` : broker day order expired +- `partially_filled -> cancel_requested` : 잔량 취소 요청 +- `replace_requested -> replaced` : 새 broker order 또는 수정 반영 확인 + +## 7. Entry / Exit 주문 정책 + +### 7.1 Entry +v1 entry는 아래 정책만 허용한다. +- 다음 시초가 근처 market/limit 진입 +- 장중 제한된 window 안의 limit 진입 +- stop-limit 진입은 paper에서 충분히 검증된 전략에만 허용 + +### 7.2 Stop Exit +- 하드 stop을 브로커에 즉시 상주시키는지, 내부 synthetic stop으로 운용하는지 전략별로 고정한다. +- v1에서는 gap risk를 고려해 synthetic stop + session-based exit를 기본으로 한다. +- 단, 연결 장애가 잦은 환경에서는 broker-native stop을 허용할 수 있다. + +### 7.3 Time Exit +- planned_time_exit 시각 또는 session close 기준으로 자동 청산 계획을 생성한다. +- time exit는 별도의 logical order leg로 기록한다. + +## 8. Partial Fill 처리 + +partial fill은 예외가 아니라 정상 흐름이다. + +정책: +- 평균 체결가를 지속 업데이트한다. +- 남은 수량이 min_fill_threshold 미만이면 즉시 취소/시장가 전환 여부를 전략별로 결정한다. +- exit 주문은 filled qty를 기준으로만 생성한다. +- stop/target leg는 filled qty와 정합해야 한다. + +## 9. Cancel / Replace 정책 + +v1 원칙: +- replace는 실제로 필요한 경우에만 사용한다. +- submit 직후 수 초 내 교체를 반복하는 logic은 금지한다. +- 취소와 교체가 동시에 걸릴 수 있으므로, reconciliation 전 finality를 가정하지 않는다. + +## 10. 브로커 event canonicalization + +브로커 raw event는 아래 canonical event로 변환한다. + +- `order_submitted` +- `order_accepted` +- `order_rejected` +- `order_partially_filled` +- `order_filled` +- `order_cancel_requested` +- `order_cancelled` +- `order_replaced` +- `order_expired` +- `trade_bust` +- `heartbeat_lost` +- `heartbeat_restored` + +모든 event는 `event_id`, `event_time`, `source`, `broker_order_id`, `client_order_id`, `payload_hash`를 포함해야 한다. diff --git a/dev/phase6_deliverables/configuration_and_schemas.md b/dev/phase6_deliverables/configuration_and_schemas.md new file mode 100644 index 0000000..f2d1175 --- /dev/null +++ b/dev/phase6_deliverables/configuration_and_schemas.md @@ -0,0 +1,60 @@ +# Configuration & Schemas + +## 1. 목적 + +이 문서는 Phase 6 런타임 설정 파일과 JSON schema 파일의 역할을 설명합니다. + +## 2. 주요 설정 파일 구조 + +권장 파일 구조: + +```text +configs/ + live/ + paper.yaml + live_approval.yaml + live_auto.yaml + brokers/ + alpaca_paper.yaml + alpaca_live.yaml + risk/ + default.yaml + alerts/ + default.yaml +``` + +## 3. live_config 구성 섹션 + +- `env` +- `mode` +- `broker` +- `session` +- `order_submission` +- `risk_limits` +- `approval` +- `reconciliation` +- `monitoring` +- `alerts` +- `degraded_modes` + +## 4. order_event.schema.json 역할 + +모든 broker raw event를 canonical execution event로 변환했을 때 검증에 사용한다. +이 schema는 다음 용도에 사용한다. +- adapter contract validation +- replay test fixture validation +- event store write validation + +## 5. approval_ticket.schema.json 역할 + +approval workflow가 있을 때 human review 객체를 검증한다. +이 schema는 다음 용도에 사용한다. +- UI/API 입력 검증 +- audit log 저장 검증 +- session restart 후 pending approval 복원 검증 + +## 6. 설정 변경 원칙 + +- 장중에는 live_config의 핵심 위험 파라미터를 임의 수정하지 않는다. +- 수정이 필요한 경우 새 session 또는 명시적 operator action event를 남긴다. +- schema version을 명시하고 backward compatibility를 관리한다. diff --git a/dev/phase6_deliverables/implementation_plan.md b/dev/phase6_deliverables/implementation_plan.md new file mode 100644 index 0000000..19b4063 --- /dev/null +++ b/dev/phase6_deliverables/implementation_plan.md @@ -0,0 +1,98 @@ +# Phase 6 구현 계획 + +## 1. 구현 목표 + +AI 코딩 에이전트는 Phase 6에서 아래 8개의 구현 묶음을 순서대로 완료해야 합니다. + +1. 실행 세션/상태 저장 모델 구현 +2. broker abstraction 및 reference adapter 구현 +3. order planner -> risk guard -> approval gate -> broker submit 흐름 구현 +4. canonical order event 저장 및 상태머신 projection 구현 +5. reconciliation loop 구현 +6. monitoring/alerts/degraded mode 구현 +7. dry-run / paper UAT 시나리오 통과 +8. live readiness 문서와 gate check 구현 + +## 2. 작업 분할 + +### Workstream A — 실행 데이터 모델 +- execution_session +- order_intent +- broker_order +- fill_ledger +- position_snapshot +- approval_ticket +- risk_decision +- operator_action + +### Workstream B — broker adapter +- adapter protocol +- Alpaca paper adapter +- raw event canonicalizer +- adapter error mapping + +### Workstream C — 실행 코어 +- order planner runner +- risk guard service +- approval gate service +- submit/cancel/replace service +- position manager + +### Workstream D — 상태관리 +- event store +- projection builder +- reconciliation worker +- restart recovery flow + +### Workstream E — 운영 +- alerts +- dashboard query layer +- EOD reports +- session control API + +## 3. 권장 구현 순서 + +### Step 1 +execution_session, order_intent, broker_order, canonical order event 모델 구현. + +### Step 2 +BrokerAdapter interface와 mock adapter 구현. +mock만으로 상태머신 테스트를 먼저 통과시킨다. + +### Step 3 +risk guard와 approval workflow 구현. +브로커 없이도 deny/defer/allow가 잘 동작해야 한다. + +### Step 4 +Alpaca paper adapter 구현. +submit/fetch/cancel/stream 최소 경로를 연결한다. + +### Step 5 +state machine projection 및 reconciliation 구현. +재시작 복구 테스트를 먼저 통과시킨다. + +### Step 6 +alerts / degraded mode / no-trade mode 구현. + +### Step 7 +paper UAT 시나리오를 운영 runbook 기준으로 통과시킨다. + +### Step 8 +small-capital live readiness gate만 구현하고, 실제 live enable은 operator explicit flag 없이는 불가하게 둔다. + +## 4. 완료 기준 + +- mock adapter로 전체 주문 생명주기 테스트가 통과한다. +- Alpaca paper에서 실제 submit/partial fill/cancel 경로가 검증된다. +- reconciliation mismatch를 의도적으로 만들었을 때 탐지 및 해결 루프가 동작한다. +- approval required 모드에서 승인 전 주문 제출이 일어나지 않는다. +- no-trade mode에서 signal 계산은 유지되지만 주문은 전송되지 않는다. +- EOD blotter와 execution journal이 자동 생성된다. + +## 5. 금지사항 + +- 브로커 SDK raw response를 business logic에 직접 사용하지 말 것. +- 주문 제출과 상태 업데이트를 하나의 giant function에 몰아넣지 말 것. +- restart 이후 내부 메모리 상태를 신뢰해 즉시 신규 주문을 보내지 말 것. +- 예외를 무시하고 다음 루프로 넘어가는 코드 작성 금지. +- approval workflow를 UI 의존적으로 설계하지 말 것. ticket API/DB 기반으로 구현할 것. diff --git a/dev/phase6_deliverables/live_config.schema.json b/dev/phase6_deliverables/live_config.schema.json new file mode 100644 index 0000000..598ed11 --- /dev/null +++ b/dev/phase6_deliverables/live_config.schema.json @@ -0,0 +1,80 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "phase6/live_config.schema.json", + "title": "Phase6LiveConfig", + "type": "object", + "required": [ + "env", + "mode", + "broker", + "session", + "risk_limits", + "approval", + "reconciliation", + "alerts" + ], + "properties": { + "env": {"type": "string", "enum": ["dev", "staging", "prod"]}, + "mode": {"type": "string", "enum": ["dry_run", "paper", "live"]}, + "broker": { + "type": "object", + "required": ["name", "account_profile"], + "properties": { + "name": {"type": "string"}, + "account_profile": {"type": "string"}, + "stream_enabled": {"type": "boolean", "default": true} + }, + "additionalProperties": true + }, + "session": { + "type": "object", + "required": ["timezone", "entry_start", "entry_end"], + "properties": { + "timezone": {"type": "string"}, + "entry_start": {"type": "string"}, + "entry_end": {"type": "string"}, + "allow_half_day": {"type": "boolean", "default": false} + }, + "additionalProperties": false + }, + "risk_limits": { + "type": "object", + "required": ["max_positions", "max_daily_loss_pct"], + "properties": { + "max_positions": {"type": "integer", "minimum": 1}, + "max_daily_loss_pct": {"type": "number", "minimum": 0}, + "max_sector_positions": {"type": "integer", "minimum": 1}, + "max_gross_exposure_pct": {"type": "number", "minimum": 0} + }, + "additionalProperties": true + }, + "approval": { + "type": "object", + "required": ["mode"], + "properties": { + "mode": {"type": "string", "enum": ["approval_required", "approval_optional", "auto"]}, + "ticket_ttl_seconds": {"type": "integer", "minimum": 1} + }, + "additionalProperties": false + }, + "reconciliation": { + "type": "object", + "required": ["interval_seconds"], + "properties": { + "interval_seconds": {"type": "integer", "minimum": 1}, + "full_sync_on_startup": {"type": "boolean", "default": true} + }, + "additionalProperties": false + }, + "alerts": { + "type": "object", + "required": ["enabled"], + "properties": { + "enabled": {"type": "boolean"}, + "channels": {"type": "array", "items": {"type": "string"}} + }, + "additionalProperties": true + } + }, + "additionalProperties": false +} diff --git a/dev/phase6_deliverables/monitoring_alerting_and_ops.md b/dev/phase6_deliverables/monitoring_alerting_and_ops.md new file mode 100644 index 0000000..13d5873 --- /dev/null +++ b/dev/phase6_deliverables/monitoring_alerting_and_ops.md @@ -0,0 +1,114 @@ +# Monitoring, Alerting & Operational Controls + +## 1. 목적 + +Phase 6 운영은 단순 로그 수집이 아니라 **실제 거래 리스크를 제어하는 관제 레이어**가 필요합니다. +이 문서는 모니터링 지표, 알림 정책, degraded mode, 운영 통제 기준을 정의합니다. + +## 2. 주요 모니터링 영역 + +### 2.1 시스템 상태 +- collector/parser snapshot freshness +- order planner latency +- broker adapter heartbeat +- DB write/read latency +- queue backlog +- reconciliation lag + +### 2.2 거래 상태 +- today planned orders count +- submitted orders count +- rejected orders count +- cancel ratio +- partial fill ratio +- execution slippage estimate +- open positions count +- sector exposure + +### 2.3 리스크 상태 +- daily pnl +- daily realized loss +- gross exposure +- planned risk remaining +- kill switch 상태 +- no-trade mode 상태 + +### 2.4 운영 상태 +- pending approval tickets +- unresolved mismatches +- manual override count +- operator acknowledgement overdue + +## 3. Alert Severity + +### P1 +즉시 운영자 확인 필요. 자동으로 hard halt 또는 no-trade 고려. +예: +- broker unreachable +- position mismatch with non-zero exposure +- duplicate order suspicion +- flatten all 실패 + +### P2 +빠른 확인 필요. 신규 주문 제한 가능. +예: +- data freshness breach +- repeated order rejects +- reconciliation mismatch unresolved for N minutes +- approval queue backlog + +### P3 +참고용. +예: +- 예상보다 높은 partial fill ratio +- overlay source 일부 지연 +- 비핵심 배치 실패 + +## 4. Degraded Mode 규칙 + +### degraded_mode_level_1 +- overlay 비활성화 +- auto approval 비활성화 +- 신규 주문 수량 축소 + +### degraded_mode_level_2 +- 신규 진입 중단 +- 기존 포지션 관리만 유지 + +### no_trade_mode +- 주문 제출 전면 중단 +- signal 계산과 관측만 유지 + +## 5. 운영 대시보드 최소 요구사항 + +대시보드는 최소 아래 패널을 제공해야 한다. + +- current session status +- broker/account status +- open positions +- working orders +- pending approvals +- recent order events +- alerts / exceptions +- risk summary +- reconciliation summary + +## 6. End-of-day 산출물 + +매 거래일 종료 후 자동 생성: +- execution summary +- trade blotter +- risk summary +- exception report +- reconciliation report +- operator actions log +- session status report + +## 7. 감사 추적 + +아래는 반드시 immutable 또는 append-only에 가깝게 보관한다. +- approval decisions +- manual overrides +- kill switch activations +- order submissions / cancels / replaces +- reconciliation mismatch and resolution diff --git a/dev/phase6_deliverables/operator_runbook.md b/dev/phase6_deliverables/operator_runbook.md new file mode 100644 index 0000000..9dcb50d --- /dev/null +++ b/dev/phase6_deliverables/operator_runbook.md @@ -0,0 +1,55 @@ +# Phase 6 운영 Runbook + +## 1. 장 시작 전 + +1. 실행 세션 생성 +2. market calendar 확인 +3. broker health / account 상태 확인 +4. latest candidate snapshot 확인 +5. stale data 여부 확인 +6. approval queue 생성 여부 확인 +7. kill switch가 해제 상태인지 확인 +8. no-trade mode / degraded mode 상태 확인 + +## 2. 장중 운영 + +### approval required 모드 +- pending approval 티켓을 확인한다. +- 승인/거절/수정 사유를 기록한다. +- 승인 마감 시각이 지난 티켓은 만료 처리한다. + +### 장애 발생 시 +- P1 alert면 신규 주문 즉시 중단 검토 +- broker 상태 확인 +- reconciliation 수동 실행 +- 필요시 soft halt / hard halt / flatten all 수행 + +## 3. 장 종료 전/후 + +1. 예정된 time exit 주문 확인 +2. working order 잔존 여부 확인 +3. 장 종료 후 final reconciliation 실행 +4. blotter / risk summary / exception report 생성 확인 +5. operator manual action을 로그에 반영 +6. session closed 상태 전환 확인 + +## 4. 긴급 대응 + +### 중복 주문 의심 +- hard halt +- 해당 symbol open orders 조회 +- 내부 order intent와 대조 +- 필요시 operator cancel 후 manual action 기록 +- reconciliation 완료 전 신규 주문 금지 + +### 포지션 불일치 +- no-trade mode 전환 +- broker positions를 truth로 삼아 mismatch 범위 확인 +- 내부 projection 재구성 +- 차이가 남으면 flatten 또는 operator adjudication + +### flatten all +- 신규 주문 중단 +- working entry orders 취소 +- open position 전량 청산 order 계획 +- 체결 완료 후 full reconciliation diff --git a/dev/phase6_deliverables/order_event.schema.json b/dev/phase6_deliverables/order_event.schema.json new file mode 100644 index 0000000..5b61e77 --- /dev/null +++ b/dev/phase6_deliverables/order_event.schema.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "phase6/order_event.schema.json", + "title": "CanonicalOrderEvent", + "type": "object", + "required": [ + "event_id", + "event_type", + "event_time", + "source", + "env", + "mode", + "client_order_id" + ], + "properties": { + "event_id": {"type": "string"}, + "event_type": { + "type": "string", + "enum": [ + "order_submitted", + "order_accepted", + "order_rejected", + "order_partially_filled", + "order_filled", + "order_cancel_requested", + "order_cancelled", + "order_replaced", + "order_expired", + "trade_bust", + "heartbeat_lost", + "heartbeat_restored" + ] + }, + "event_time": {"type": "string", "format": "date-time"}, + "source": {"type": "string"}, + "env": {"type": "string"}, + "mode": {"type": "string", "enum": ["dry_run", "paper", "live"]}, + "client_order_id": {"type": "string"}, + "broker_order_id": {"type": ["string", "null"]}, + "symbol": {"type": ["string", "null"]}, + "side": {"type": ["string", "null"], "enum": [null, "buy", "sell"]}, + "qty": {"type": ["number", "null"]}, + "filled_qty": {"type": ["number", "null"]}, + "fill_price": {"type": ["number", "null"]}, + "reason": {"type": ["string", "null"]}, + "payload_hash": {"type": ["string", "null"]} + }, + "additionalProperties": true +} diff --git a/dev/phase6_deliverables/paper_trading_and_live_architecture.md b/dev/phase6_deliverables/paper_trading_and_live_architecture.md new file mode 100644 index 0000000..4cf1ee6 --- /dev/null +++ b/dev/phase6_deliverables/paper_trading_and_live_architecture.md @@ -0,0 +1,174 @@ +# Phase 6 아키텍처 — Paper Trading & Live Execution + +## 1. 목적 + +Phase 6의 목적은 Phase 4의 백테스트 신호와 Phase 5의 overlay 결과를 **실제 주문 가능한 execution plan**으로 변환하고, +이를 **paper trading**에서 충분히 검증한 뒤 **small-capital live execution**으로 이행할 수 있는 운영 레이어를 구현하는 것입니다. + +핵심은 다음과 같습니다. + +- 전략 신호 생성과 주문 실행을 분리한다. +- 실행 시스템은 신호를 맹신하지 않고 별도의 risk guard를 통과한 주문만 보낸다. +- paper/live의 코드 경로를 최대한 동일하게 유지한다. +- 실행 실패, 네트워크 장애, 브로커 지연, 부분체결, 재시작을 모두 가정한다. + +## 2. 상위 구성 + +```text +signal pipeline (Phase 2~5) + -> candidate store + -> order planner + -> risk guard + -> approval gate (optional) + -> broker adapter + -> execution event bus + -> position manager + -> reconciliation loop + -> alerts / blotter / dashboard +``` + +### 2.1 구성 요소 + +#### candidate reader +- Phase 4/5 산출물에서 실행 가능한 후보를 읽는다. +- 후보는 `trade_date`, `symbol`, `side`, `entry_window`, `planned_stop`, `planned_time_exit`, `score`, `portfolio_bucket`을 가져야 한다. + +#### order planner +- 후보를 실제 주문 단위로 변환한다. +- sizing 결과, 주문 타입, limit/stop 가격, TTL, submission window를 계산한다. +- planner는 브로커 API를 직접 호출하지 않는다. + +#### risk guard +- 계좌 기준, 종목 기준, 전략 기준, 운영 기준 제한을 체크한다. +- pre-trade 단계에서만 끝나지 않고 in-trade/post-trade 검사도 포함한다. + +#### approval gate +- human-in-the-loop가 필요한 초기 운영 모드용 모듈이다. +- 승인/거절/수정 요청을 ticket 기반으로 기록한다. + +#### broker adapter +- 최소 인터페이스만 노출한다. +- submit, cancel, replace, fetch order, fetch positions, fetch fills, heartbeat. +- Phase 6의 기본 reference broker는 Alpaca paper이다. live 지원은 adapter 수준에서 optional로 둔다. + +#### execution event bus +- 주문 제출 결과, 체결, 취소, 거부, 연결 장애, 재동기화 이벤트를 canonical event로 변환한다. +- 내부 상태머신은 브로커별 raw payload가 아니라 canonical event를 소비한다. + +#### position manager +- 실행 포지션, 평균단가, 남은 수량, stop 상태, time exit 상태를 관리한다. +- 백테스터와 동일한 청산 규칙을 실전용으로 근사 적용한다. + +#### reconciliation loop +- 주기적으로 브로커 상태와 내부 상태를 비교한다. +- 누락 이벤트, 중복 체결, orphan order, stale position을 탐지한다. + +## 3. 실행 모드 + +### 3.1 dry-run mode +- 주문을 브로커에 보내지 않는다. +- order plan과 risk decision만 생성한다. +- 모든 알림/로그/리포트는 동일하게 생성한다. +- Phase 6 초반 개발 단계의 기본 모드다. + +### 3.2 paper mode +- broker paper endpoint에만 주문을 보낸다. +- 체결과 취소 이벤트를 실시간으로 소비한다. +- 운영 프로세스 검증과 상태머신 검증용이다. + +### 3.3 live mode +- 실제 주문을 보낸다. +- 기본은 human approval required. +- auto mode는 충분한 paper 성과와 운영 안정성 확인 후에만 허용한다. + +## 4. 세션 기준 실행 타임라인 + +### T-1 (전일 종가 후) +- 최종 candidate snapshot 확정 +- overlay score 반영 완료 +- order plan preview 생성 +- 리스크 및 자금 사용 가능성 점검 + +### T (장 시작 전) +- broker/account health check +- market calendar / holiday / half-day 확인 +- latest candidate consistency check +- approval mode일 경우 주문 티켓 생성 + +### T (장중) +- entry window 도달 시 주문 제출 +- fill / partial fill / reject / cancel 이벤트 처리 +- intraday risk guard 점검 +- 일일 손실 또는 시스템 장애 발생 시 신규 주문 중단 + +### T (장 종료 직전/후) +- time exit 대상 주문 처리 +- 종가 기반 상태 업데이트 +- blotter / execution summary / reconciliation 수행 + +## 5. 상태 저장 원칙 + +상태는 메모리에만 두지 않는다. 아래는 반드시 durable storage에 기록한다. + +- candidate snapshot id +- order plan id +- approval ticket +- client_order_id +- broker_order_id +- canonical order event +- position snapshot +- exception / operator action + +## 6. 공통 코드 경로 원칙 + +아래 모듈은 dry-run / paper / live에서 공통 코드 경로를 사용해야 한다. + +- candidate selection input reader +- order planner +- risk guard +- state machine +- blotter generator +- reconciliation core logic + +브로커 endpoint 차이는 broker adapter와 credential/config 계층에서만 나뉘어야 한다. + +## 7. Phase 4와 연결되는 contract + +Phase 4에서 생성한 backtest candidate와 Phase 6에서 실행하는 candidate는 최소한 다음 필드를 공유해야 한다. + +- `candidate_id` +- `symbol` +- `side` +- `strategy_id` +- `event_id` +- `trade_date` +- `entry_policy` +- `planned_stop_policy` +- `planned_time_exit_policy` +- `score_total` +- `score_components` +- `sizing_inputs` + +Phase 6는 이 contract를 받아 live-specific 필드를 추가한다. + +- `execution_session_id` +- `broker_name` +- `broker_account_id` +- `client_order_id` +- `approval_ticket_id` +- `risk_decision` + +## 8. 실패에 대비한 설계 + +Phase 6는 반드시 아래 실패를 정상 흐름으로 가정해야 한다. + +- broker websocket disconnect +- HTTP timeout +- duplicate callback +- order accepted 후 fill event 지연 +- cancel requested 후 already filled +- replace requested 후 partial fill 선행 +- process restart 중 이벤트 손실 +- operator가 수동으로 브로커 화면에서 주문을 변경한 경우 + +이 실패들에 대한 대응은 `state_machine_and_reconciliation.md`에서 구체화한다. diff --git a/dev/phase6_deliverables/risk_guard_and_approval_workflow.md b/dev/phase6_deliverables/risk_guard_and_approval_workflow.md new file mode 100644 index 0000000..5a3393c --- /dev/null +++ b/dev/phase6_deliverables/risk_guard_and_approval_workflow.md @@ -0,0 +1,155 @@ +# Risk Guard & Approval Workflow + +## 1. 목적 + +이 문서는 Phase 6 실행 시스템에서 필수적인 **리스크 가드**, **운영 제약**, **approval workflow**를 정의합니다. +전략 신호가 아무리 좋아도, 실행 시스템은 이 문서의 제약을 통과하지 못하면 주문을 제출하면 안 됩니다. + +## 2. Risk Guard 계층 + +리스크 가드는 4개 계층으로 나눕니다. + +### 2.1 계좌 레벨 +- 계좌 총 자산 +- buying power / cash / margin 상태 +- 일중 실현/미실현 손익 +- 일일 손실 한도 초과 여부 +- 신규 주문 허용 상태 + +### 2.2 포트폴리오 레벨 +- 동시 보유 종목 수 +- 섹터 집중도 +- 이벤트 유형 집중도 +- 총 gross exposure +- 총 planned risk + +### 2.3 종목 레벨 +- 유동성 기준 유지 여부 +- 최근 halt / LULD / 비정상 변동성 +- 가격 제한(예: min price) +- 오늘 이미 포지션/주문이 있는 종목인지 + +### 2.4 운영 레벨 +- broker health +- market data freshness +- parser snapshot freshness +- reconciliation lag +- alert backlog +- operator acknowledgement requirement + +## 3. Pre-trade Checks + +주문 제출 직전에는 최소 아래를 확인한다. + +1. candidate snapshot이 최신 세션 기준으로 확정되었는가 +2. market calendar가 현재 세션과 일치하는가 +3. broker health가 healthy인가 +4. market data freshness가 기준 이하인가 +5. 동일 candidate/order plan으로 이미 제출된 주문이 없는가 +6. buying power / cash가 충분한가 +7. 전략/리스크 정책상 신규 진입 허용 상태인가 +8. approval mode일 경우 승인 상태인가 + +모든 pre-trade check 결과는 로그가 아니라 **구조화된 risk_decision record**로 저장한다. + +## 4. Risk Guard 결정 결과 + +결정 타입은 아래 셋 중 하나다. + +- `allow` +- `deny` +- `defer` + +### `defer`가 필요한 예시 +- broker websocket reconnect 중 +- market data freshness 임계치 초과 +- approval 응답 대기 중 +- sector limit이 장중 청산 결과에 따라 해제될 수 있음 + +## 5. 일일 손실/중단 규칙 + +v1 기본 예시: +- 일일 순손실이 계좌의 `X%` 초과 시 신규 진입 중단 +- 연속 `N`회 실패 주문/체결 이상 시 신규 제출 중단 +- reconciliation mismatch가 `M`건 이상이면 no-trade mode 전환 +- 데이터 freshness가 `T`초 초과하면 신규 주문 중단 + +정확한 수치는 `live_config`에서 설정한다. + +## 6. Approval Workflow + +### 6.1 모드 +- `approval_required` +- `approval_optional` +- `auto` + +### 6.2 Approval Ticket 필드 +- `approval_ticket_id` +- `candidate_id` +- `order_plan_id` +- `symbol` +- `side` +- `qty` +- `entry_window` +- `planned_stop` +- `planned_time_exit` +- `score_total` +- `score_summary` +- `risk_summary` +- `decision_deadline` +- `status` +- `operator_comment` + +### 6.3 상태 전이 + +```text +created +-> pending_review +-> approved +-> rejected +-> expired +-> superseded +``` + +### 6.4 운영 규칙 +- 승인 만료 시점이 지나면 자동 제출하지 않는다. +- operator가 수량/가격을 수정할 수 있는 범위를 제한한다. +- 전략 의미를 바꾸는 수정은 허용하지 않는다. +- 승인/거절/수정 사유는 필수 기록이다. + +## 7. Kill Switch + +Kill switch는 최소 3종을 지원한다. + +### 7.1 soft halt +- 신규 주문만 중단 +- 기존 포지션 관리는 계속 + +### 7.2 hard halt +- 신규 주문 중단 +- 기존 미체결 주문 취소 +- 기존 포지션은 별도 exit policy 유지 + +### 7.3 flatten all +- 신규 주문 중단 +- 미체결 주문 취소 +- 가능한 한 빠르게 모든 포지션 청산 + +## 8. No-trade / Degraded Mode + +### no-trade mode +- signal 계산은 계속하지만 주문은 보내지 않는다. +- 이유 예: 브로커 장애, 구성 오류, 심각한 reconciliation mismatch. + +### degraded mode +- 신규 진입은 제한적으로 허용 +- overlay 비활성화, auto mode 비활성화, replace 사용 금지 등 축소 운영 + +## 9. Post-trade Risk Checks + +장 종료 후 최소 아래를 검증한다. +- 브로커 포지션과 내부 포지션 일치 여부 +- 미체결 주문 잔존 여부 +- 계획된 stop/target/time-exit 상태 누락 여부 +- trade blotter와 fill ledger 정합성 +- operator manual action 기록 반영 여부 diff --git a/dev/phase6_deliverables/state_machine_and_reconciliation.md b/dev/phase6_deliverables/state_machine_and_reconciliation.md new file mode 100644 index 0000000..c1e7838 --- /dev/null +++ b/dev/phase6_deliverables/state_machine_and_reconciliation.md @@ -0,0 +1,199 @@ +# State Machine & Reconciliation + +## 1. 목적 + +이 문서는 Phase 6 실행 엔진의 상태머신과 reconciliation 절차를 정의합니다. +실전 시스템에서 가장 위험한 문제는 **내부 상태와 브로커 상태가 어긋나는 것**이므로, +reconciliation은 보조 작업이 아니라 핵심 기능입니다. + +## 2. 핵심 엔티티 + +### 2.1 Execution Session +하루의 운영 세션 단위. + +필수 필드: +- `execution_session_id` +- `env` +- `trade_date` +- `mode` (`dry_run`, `paper`, `live`) +- `strategy_scope` +- `status` +- `started_at` +- `ended_at` + +### 2.2 Order Plan +전략 후보를 실행 가능한 주문 계획으로 변환한 결과. + +### 2.3 Order Intent +실제로 한 번 제출하려는 logical action. +예: entry, exit_half, exit_final, stop_exit, cancel_all. + +### 2.4 Broker Order +브로커가 인식하는 주문. +하나의 Order Intent가 broker replace 과정에서 여러 broker order로 이어질 수 있다. + +### 2.5 Position State +실제 체결 기반 포지션 상태. + +## 3. 상위 상태머신 + +### 3.1 Candidate Execution State + +```text +candidate_ready +-> planned +-> risk_checked +-> awaiting_approval +-> approved +-> entry_submitted +-> entry_working +-> entry_partially_filled +-> position_open +-> exit_pending +-> partially_exited +-> fully_closed +-> abandoned +-> error +``` + +### 3.2 Position Lifecycle + +```text +no_position +-> opening +-> open +-> reducing +-> closed +-> orphaned +``` + +### 3.3 Session Lifecycle + +```text +booting +-> pre_open_checks +-> active +-> soft_halt +-> hard_halt +-> closing +-> reconciliation +-> closed +-> failed +``` + +## 4. Event Sourcing 원칙 + +가능하면 상태를 직접 수정하기보다 event를 append하고 projection으로 현재 상태를 만든다. + +필수 이벤트: +- candidate_loaded +- risk_decision_recorded +- approval_ticket_created +- approval_decided +- order_intent_created +- order_submitted +- order_acknowledged +- order_partially_filled +- order_filled +- order_cancel_requested +- order_cancelled +- order_replaced +- order_rejected +- position_opened +- position_reduced +- position_closed +- reconciliation_mismatch_detected +- reconciliation_resolved +- manual_operator_action +- session_halted +- session_resumed + +## 5. Reconciliation 유형 + +### 5.1 주문 대사 +비교 대상: +- 내부 open orders +- 브로커 open orders +- 최근 fill ledger + +체크 항목: +- broker_order_id 누락 +- client_order_id 중복 +- 내부는 working인데 브로커엔 없음 +- 브로커는 filled인데 내부는 accepted 상태 +- cancel 요청 후 실제 잔존 여부 + +### 5.2 포지션 대사 +비교 대상: +- 내부 position state +- 브로커 position snapshot + +체크 항목: +- 수량 불일치 +- 평균단가 불일치 +- symbol 누락 +- 브로커에만 존재하는 orphan position + +### 5.3 세션 대사 +- 장 종료 후 미처리 ticket 존재 여부 +- 미완료 order intent 존재 여부 +- kill switch 상태 복구 여부 + +## 6. Reconciliation 주기 + +- 장 시작 전: full sync +- 장중: 짧은 interval incremental sync +- submit/cancel/replace 직후: targeted sync +- 장 종료 후: final full sync +- 재시작 직후: mandatory full sync + +## 7. 충돌 해결 규칙 + +원칙적으로 **브로커 체결 사실**을 가장 강한 truth로 본다. +다만 브로커 API 지연/일시 불일치가 있을 수 있으므로, 아래 우선순위를 따른다. + +1. confirmed fill / position snapshot +2. fetch order by broker id +3. stream event +4. 내부 optimistic state + +### 예시 1: submit timeout +- 내부 상태는 `submitted_pending_confirmation` +- 즉시 재제출하지 않는다. +- 먼저 client_order_id 기반 조회 또는 full reconciliation 수행 + +### 예시 2: cancel 요청 후 fill 도착 +- fill 이벤트가 cancel보다 우선한다. +- 남은 수량만 취소되도록 재계산 + +### 예시 3: 프로세스 재시작 +- 마지막 checkpoint 이후 미확정 intent를 모두 reconciliation queue에 올린다. +- broker 상태를 먼저 읽고 projection을 복구한 뒤에만 신규 주문 허용 + +## 8. Checkpoint / Recovery + +다음은 checkpoint 대상이다. +- current session state +- active candidate executions +- open positions +- working orders +- latest broker cursor / stream cursor +- kill switch 상태 +- approval pending 목록 + +복구 절차: +1. durable state load +2. broker full sync +3. mismatch resolution +4. projection rebuild +5. safe-to-trade 판단 +6. 신규 주문 재개 여부 결정 + +## 9. 운영자 수동 개입 + +운영자가 브로커 화면에서 직접 주문/취소/청산한 경우를 고려해야 한다. +필수 규칙: +- manual action은 별도 event로 기록 +- reconciliation 시 수동 개입 감지 시 operator note 요구 +- 내부 상태는 브로커 상태로 수렴 +- 수동 개입이 잦은 전략/구간은 별도 review 대상 diff --git a/dev/phase6_deliverables/testing_checklist.md b/dev/phase6_deliverables/testing_checklist.md new file mode 100644 index 0000000..9543b94 --- /dev/null +++ b/dev/phase6_deliverables/testing_checklist.md @@ -0,0 +1,66 @@ +# Phase 6 테스트 체크리스트 + +## 1. 단위 테스트 + +### Broker Adapter +- [ ] health/account/positions/open orders 조회가 contract대로 직렬화된다. +- [ ] raw broker status가 canonical status로 올바르게 매핑된다. +- [ ] timeout / network / auth / rate limit 예외가 내부 오류 타입으로 매핑된다. + +### Risk Guard +- [ ] buying power 부족 시 `deny` +- [ ] daily loss 초과 시 `deny` +- [ ] data freshness breach 시 `defer` 또는 `deny` +- [ ] degraded mode level별 허용 범위가 다르게 적용된다. + +### Approval Workflow +- [ ] ticket 생성/승인/거절/만료 상태 전이가 맞다. +- [ ] 승인 없는 주문은 제출되지 않는다. +- [ ] 수정 허용 범위를 벗어난 operator action은 거부된다. + +### State Machine +- [ ] accepted -> partial fill -> filled 전이 +- [ ] accepted -> cancel_requested -> cancelled 전이 +- [ ] partial fill 후 cancel 전이 +- [ ] submit timeout 후 reconciliation 대기 상태 전이 +- [ ] reject / expire / bust 전이 + +## 2. 통합 테스트 + +- [ ] dry-run 모드에서 order plan만 생성되고 브로커 호출이 없다. +- [ ] paper 모드에서 submit -> fill -> position open -> time exit 흐름이 끝까지 실행된다. +- [ ] broker stream disconnect 후 reconnect 시 event duplication 없이 복구된다. +- [ ] 장중 재시작 후 working orders와 positions를 복구한다. +- [ ] manual broker-side cancel 후 reconciliation이 mismatch를 탐지한다. + +## 3. 리플레이 테스트 + +- [ ] 과거 canonical order event 로그로 projection을 재구성할 수 있다. +- [ ] 같은 이벤트 스트림을 두 번 재생해도 최종 상태가 동일하다. +- [ ] out-of-order event가 들어와도 안전하게 처리된다. + +## 4. 장애/복구 테스트 + +- [ ] submit 직후 프로세스 강제 종료 -> 재시작 복구 +- [ ] partial fill 직후 websocket 종료 -> HTTP poll 기반 복구 +- [ ] broker fetch timeout 반복 -> no-trade mode 전환 +- [ ] DB 일시 장애 -> event write 재시도 / 세션 halt + +## 5. 운영 전 UAT + +- [ ] operator runbook 기준 pre-open checklist 완료 +- [ ] approval required paper session 5일 이상 무중단 운영 +- [ ] EOD blotter / reconciliation report 자동 생성 확인 +- [ ] kill switch soft/hard/flatten_all 실습 확인 +- [ ] degraded mode 수동 전환과 자동 전환 모두 검증 + +## 6. Live readiness gate + +아래 항목이 모두 참이어야 live enable을 검토할 수 있다. + +- [ ] 최근 paper 세션에서 미해결 mismatch 0 +- [ ] 중복 주문 사고 0 +- [ ] operator action이 모두 감사 로그에 남음 +- [ ] risk guard false negative 사례 없음 +- [ ] 하루 종료 시 브로커/내부 포지션 불일치 0 +- [ ] emergency flatten runbook 리허설 완료 diff --git a/libs/oracle_client/client.py b/libs/oracle_client/client.py index d085945..f7c435c 100644 --- a/libs/oracle_client/client.py +++ b/libs/oracle_client/client.py @@ -1,4 +1,5 @@ """Base httpx async client for Stock Oracle.""" + from __future__ import annotations from typing import Any @@ -46,13 +47,9 @@ class OracleClient: try: response = await client.get(path, params=params) except httpx.ConnectError as exc: - raise OracleConnectionError( - str(exc), source="oracle", entity=path - ) from exc + raise OracleConnectionError(str(exc), source="oracle", entity=path) from exc except httpx.TimeoutException as exc: - raise OracleTimeoutError( - str(exc), source="oracle", entity=path - ) from exc + raise OracleTimeoutError(str(exc), source="oracle", entity=path) from exc return self._handle_response(response, path) @@ -62,13 +59,9 @@ class OracleClient: try: response = await client.post(path, json=json) except httpx.ConnectError as exc: - raise OracleConnectionError( - str(exc), source="oracle", entity=path - ) from exc + raise OracleConnectionError(str(exc), source="oracle", entity=path) from exc except httpx.TimeoutException as exc: - raise OracleTimeoutError( - str(exc), source="oracle", entity=path - ) from exc + raise OracleTimeoutError(str(exc), source="oracle", entity=path) from exc return self._handle_response(response, path) diff --git a/libs/oracle_client/exceptions.py b/libs/oracle_client/exceptions.py index 0341fe4..6ce0c6e 100644 --- a/libs/oracle_client/exceptions.py +++ b/libs/oracle_client/exceptions.py @@ -1,4 +1,5 @@ """Oracle client exception hierarchy.""" + from __future__ import annotations from libs.common.retries import NonRetryableError, RetryableError diff --git a/libs/oracle_client/filings.py b/libs/oracle_client/filings.py index beea109..18f7185 100644 --- a/libs/oracle_client/filings.py +++ b/libs/oracle_client/filings.py @@ -1,10 +1,13 @@ """Filing-related Oracle service methods.""" + from __future__ import annotations from libs.oracle_client.client import OracleClient from libs.oracle_client.models import ( + ExhibitDocument, ExhibitResponse, FilingDocumentsResponse, + FilingEntry, FilingSearchResponse, ) @@ -27,16 +30,40 @@ class FilingsService: params["start_date"] = start_date if end_date: params["end_date"] = end_date - data = await self._client.get(f"/filings/search/{ticker}", params=params) - return FilingSearchResponse.model_validate(data) + data = await self._client.get(f"/api/v1/filings/search/{ticker}", params=params) + # Real Oracle: {"ticker": ..., "filings": [{accession_number, form_type, ...}], "total_count": ...} + filings = [ + FilingEntry( + accession_no=f["accession_number"], + form_type=f["form_type"], + filing_date=f["filing_date"], + primary_document=f.get("primary_document"), + description=f.get("filing_description"), + ) + for f in data.get("filings", []) + ] + return FilingSearchResponse( + ticker=data.get("ticker", ticker), + filings=filings, + total=data.get("total_count", len(filings)), + ) async def get_documents(self, accession_no: str) -> FilingDocumentsResponse: - data = await self._client.get(f"/filings/documents/{accession_no}") - return FilingDocumentsResponse.model_validate(data) + data = await self._client.get(f"/api/v1/filings/documents/{accession_no}") + exhibits = [ExhibitDocument.model_validate(e) for e in data.get("exhibits", [])] + return FilingDocumentsResponse( + accession_no=data.get("accession_number", accession_no), + exhibits=exhibits, + ) async def get_exhibit( self, accession_no: str, exhibit_type: str = "EX-99.1" ) -> ExhibitResponse: params = {"exhibit_type": exhibit_type} - data = await self._client.get(f"/filings/exhibit/{accession_no}", params=params) - return ExhibitResponse.model_validate(data) + data = await self._client.get(f"/api/v1/filings/exhibit/{accession_no}", params=params) + # Real Oracle: {"accession_number": ..., "exhibit_type": ..., "content": ...} + return ExhibitResponse( + accession_no=data.get("accession_number", accession_no), + exhibit_type=data.get("exhibit_type", exhibit_type), + content=data.get("content", ""), + ) diff --git a/libs/oracle_client/financial.py b/libs/oracle_client/financial.py index 722a924..2a6d52d 100644 --- a/libs/oracle_client/financial.py +++ b/libs/oracle_client/financial.py @@ -1,21 +1,46 @@ """Financial data Oracle service methods.""" + from __future__ import annotations from libs.oracle_client.client import OracleClient -from libs.oracle_client.models import CompanyInfo, FinancialDataResponse +from libs.oracle_client.models import CompanyInfo, FinancialDataResponse, FinancialPeriod class FinancialService: def __init__(self, client: OracleClient) -> None: self._client = client - async def get_financial_data( - self, ticker: str, quarters: int = 8 - ) -> FinancialDataResponse: - params: dict[str, str | int] = {"ticker": ticker, "quarters": quarters} - data = await self._client.get("/financial/data", params=params) - return FinancialDataResponse.model_validate(data) + async def get_financial_data(self, ticker: str, quarters: int = 8) -> FinancialDataResponse: + # Oracle accepts period param (e.g. "2y"), convert quarters to approximate years + years = max(1, (quarters + 3) // 4) + data = await self._client.get( + f"/api/v1/financial/data/{ticker}", params={"period": f"{years}y"} + ) + # Real Oracle: {"company": {...}, "financial_data": [{period_date, ...}]} + periods = [] + for p in data.get("financial_data", []): + period_date = p.get("period_date", "") + period_end = period_date[:10] if period_date else "" + periods.append( + FinancialPeriod( + period=period_end, + period_end=period_end, + revenue=p.get("revenue"), + net_income=p.get("net_income"), + eps=p.get("eps"), + gross_margin=p.get("gross_margin"), + operating_margin=p.get("operating_margin"), + ) + ) + return FinancialDataResponse(ticker=ticker, periods=periods) async def get_company_info(self, ticker: str) -> CompanyInfo: - data = await self._client.get(f"/financial/company/{ticker}") - return CompanyInfo.model_validate(data) + data = await self._client.get(f"/api/v1/financial/data/{ticker}", params={"period": "1y"}) + company = data.get("company", {}) + return CompanyInfo( + ticker=company.get("ticker", ticker), + name=company.get("name"), + cik=company.get("cik"), + sector=company.get("sector"), + industry=company.get("industry"), + ) diff --git a/libs/oracle_client/finra.py b/libs/oracle_client/finra.py index 324e693..1b2ad38 100644 --- a/libs/oracle_client/finra.py +++ b/libs/oracle_client/finra.py @@ -1,24 +1,39 @@ """FINRA short volume Oracle service methods.""" + from __future__ import annotations from libs.oracle_client.client import OracleClient -from libs.oracle_client.models import ShortRatioResponse, ShortVolumeResponse +from libs.oracle_client.models import ( + ShortRatioPoint, + ShortRatioResponse, + ShortVolumeEntry, + ShortVolumeResponse, +) class FinraService: def __init__(self, client: OracleClient) -> None: self._client = client - async def get_short_volume( - self, symbol: str, days: int = 30 - ) -> ShortVolumeResponse: + async def get_short_volume(self, symbol: str, days: int = 30) -> ShortVolumeResponse: params: dict[str, str | int] = {"days": days} - data = await self._client.get(f"/finra/short-volume/{symbol}", params=params) - return ShortVolumeResponse.model_validate(data) + data = await self._client.get(f"/api/v1/finra/short-volume/{symbol}", params=params) + # Real Oracle: {"symbol": ..., "entries": [{date, short_volume, ...}], "total_count": ...} + entries = [ + ShortVolumeEntry( + date=e["date"], + short_volume=int(e["short_volume"]), + short_exempt_volume=int(e["short_exempt_volume"]) + if e.get("short_exempt_volume") is not None + else None, + total_volume=int(e["total_volume"]) if e.get("total_volume") is not None else None, + ) + for e in data.get("entries", []) + ] + return ShortVolumeResponse(symbol=data.get("symbol", symbol), data=entries) - async def get_short_ratio( - self, symbol: str, days: int = 30 - ) -> ShortRatioResponse: + async def get_short_ratio(self, symbol: str, days: int = 30) -> ShortRatioResponse: params: dict[str, str | int] = {"days": days} - data = await self._client.get(f"/finra/short-ratio/{symbol}", params=params) - return ShortRatioResponse.model_validate(data) + data = await self._client.get(f"/api/v1/finra/short-ratio/{symbol}", params=params) + points = [ShortRatioPoint.model_validate(p) for p in data.get("data", [])] + return ShortRatioResponse(symbol=data.get("symbol", symbol), data=points) diff --git a/libs/oracle_client/fred.py b/libs/oracle_client/fred.py index c8b0d43..261344d 100644 --- a/libs/oracle_client/fred.py +++ b/libs/oracle_client/fred.py @@ -1,8 +1,9 @@ """FRED data Oracle service methods.""" + from __future__ import annotations from libs.oracle_client.client import OracleClient -from libs.oracle_client.models import FredProxyResponse, FredSeriesInfo +from libs.oracle_client.models import FredObservation, FredProxyResponse, FredSeriesInfo class FredService: @@ -20,18 +21,30 @@ class FredService: params["observation_start"] = start if end: params["observation_end"] = end - data = await self._client.get( - "/fred/proxy/series/observations", params=params + data = await self._client.get("/api/v1/fred/proxy/series/observations", params=params) + # Real Oracle: {"success": true, "data": {"realtime_start": ..., "observations": [{"date": ..., "value": "4.06"}]}} + inner = data.get("data", {}) + obs_raw = inner.get("observations", []) + observations = [ + FredObservation( + date=o["date"], + value=float(o["value"]) if o.get("value") not in (None, ".", "") else None, + ) + for o in obs_raw + ] + return FredProxyResponse( + series_id=series_id, + observations=observations, + realtime_start=inner.get("realtime_start"), + realtime_end=inner.get("realtime_end"), ) - # Normalize to FredProxyResponse - if "observations" in data: - return FredProxyResponse(series_id=series_id, **data) - return FredProxyResponse(series_id=series_id, observations=data.get("data", [])) async def get_series_info(self, series_id: str) -> FredSeriesInfo: params = {"series_id": series_id} - data = await self._client.get("/fred/proxy/series", params=params) - seriess = data.get("seriess", [data]) + data = await self._client.get("/api/v1/fred/proxy/series", params=params) + # Real Oracle: {"success": true, "data": {"seriess": [{id, title, frequency, ...}]}} + inner = data.get("data", {}) + seriess = inner.get("seriess", []) if seriess: info = seriess[0] return FredSeriesInfo.model_validate({**info, "id": info.get("id", series_id)}) diff --git a/libs/oracle_client/models.py b/libs/oracle_client/models.py index 6a3af2b..9783f75 100644 --- a/libs/oracle_client/models.py +++ b/libs/oracle_client/models.py @@ -1,4 +1,5 @@ """Pydantic response models for Stock Oracle API.""" + from __future__ import annotations from typing import Any diff --git a/libs/oracle_client/price.py b/libs/oracle_client/price.py index 256dca1..b035ea5 100644 --- a/libs/oracle_client/price.py +++ b/libs/oracle_client/price.py @@ -1,9 +1,12 @@ """Price-related Oracle service methods.""" + from __future__ import annotations from libs.oracle_client.client import OracleClient from libs.oracle_client.models import ( + IntradayBar, IntradayResponse, + PriceBar, PriceDataResponse, PriceQuote, ) @@ -19,22 +22,46 @@ class PriceService: start: str | None = None, end: str | None = None, ) -> PriceDataResponse: - params: dict[str, str] = {"ticker": ticker} + params: dict[str, str] = {} if start: - params["start"] = start + params["start_date"] = start if end: - params["end"] = end - data = await self._client.get("/price/data", params=params) - return PriceDataResponse.model_validate(data) + params["end_date"] = end + data = await self._client.get(f"/api/v1/price/data/{ticker}", params=params) + # Real Oracle: {"ticker": ..., "interval": "1d", "data": [...bars...], "metadata": {...}} + bars = [ + PriceBar( + date=b["date"], + open=b["open"], + high=b["high"], + low=b["low"], + close=b["close"], + volume=int(b["volume"]), + ) + for b in data.get("data", []) + ] + return PriceDataResponse(ticker=data.get("ticker", ticker), bars=bars) async def get_quote(self, ticker: str) -> PriceQuote: - data = await self._client.get(f"/price/quote/{ticker}") + data = await self._client.get(f"/api/v1/price/quote/{ticker}") return PriceQuote.model_validate(data) async def get_intraday(self, ticker: str) -> IntradayResponse: - data = await self._client.get(f"/price/intraday/{ticker}") - return IntradayResponse.model_validate(data) + data = await self._client.get(f"/api/v1/price/intraday/{ticker}") + bars = [IntradayBar.model_validate(b) for b in data.get("data", [])] + return IntradayResponse(ticker=data.get("ticker", ticker), bars=bars) async def get_today(self, ticker: str) -> PriceDataResponse: - data = await self._client.get(f"/price/today/{ticker}") - return PriceDataResponse.model_validate(data) + data = await self._client.get(f"/api/v1/price/today/{ticker}") + bars = [ + PriceBar( + date=b["date"], + open=b["open"], + high=b["high"], + low=b["low"], + close=b["close"], + volume=int(b["volume"]), + ) + for b in data.get("data", []) + ] + return PriceDataResponse(ticker=data.get("ticker", ticker), bars=bars) diff --git a/tests/fixtures/exhibit_content.json b/tests/fixtures/exhibit_content.json index e5b4ebb..41d18c0 100644 --- a/tests/fixtures/exhibit_content.json +++ b/tests/fixtures/exhibit_content.json @@ -1,5 +1,5 @@ { - "accession_no": "0000320193-26-000001", + "accession_number": "0000320193-26-000001", "exhibit_type": "EX-99.1", "content": "Apple Inc. Reports First Quarter Results\n\nItem 2.02 Results of Operations\n\nApple today announced financial results for its fiscal 2026 first quarter ended December 28, 2025. The Company posted quarterly revenue of $124.3 billion, up 9 percent year over year.\n\nGuidance raised for Q2: The Company expects revenue to be between $125 billion and $131 billion. Demand remains strong across all product categories. Gross margin is expected to be between 46.5 percent and 47.5 percent, reflecting margin expansion driven by Services mix.\n\nCustomer expansion in enterprise continues. Backlog increased significantly. Pricing strength maintained.\n\nAdjusted EPS excludes certain non-GAAP items.", "content_type": "text/plain" diff --git a/tests/fixtures/filing_search.json b/tests/fixtures/filing_search.json index 1aa3431..67f8aee 100644 --- a/tests/fixtures/filing_search.json +++ b/tests/fixtures/filing_search.json @@ -1,24 +1,27 @@ { "ticker": "AAPL", - "total": 2, + "total_count": 2, "filings": [ { - "accession_no": "0000320193-26-000001", + "accession_number": "0000320193-26-000001", "form_type": "8-K", "filing_date": "2026-01-29", - "accepted_at": "2026-01-29T21:05:00Z", "primary_document": "d123456d8k.htm", - "description": "Results of Operations and Financial Condition", - "items": ["2.02", "9.01"] + "filing_description": "Results of Operations and Financial Condition", + "documents_count": 9 }, { - "accession_no": "0000320193-26-000002", + "accession_number": "0000320193-26-000002", "form_type": "8-K", "filing_date": "2026-02-15", - "accepted_at": "2026-02-15T16:30:00Z", "primary_document": "d234567d8k.htm", - "description": "Other Events", - "items": ["8.01"] + "filing_description": "Other Events", + "documents_count": 4 } - ] + ], + "metadata": { + "limit": 10, + "offset": 0, + "form_types": ["8-K"] + } } diff --git a/tests/fixtures/financial_data.json b/tests/fixtures/financial_data.json index 2d39ce7..d18aad4 100644 --- a/tests/fixtures/financial_data.json +++ b/tests/fixtures/financial_data.json @@ -1,23 +1,37 @@ { - "ticker": "AAPL", - "periods": [ + "company": { + "ticker": "AAPL", + "name": "Apple Inc.", + "cik": "0000320193", + "sector": "Technology", + "industry": "Consumer Electronics" + }, + "financial_data": [ { - "period": "2026-Q1", - "period_end": "2025-12-28", - "revenue": 124300000000, - "net_income": 36000000000, + "period_date": "2025-12-28T00:00:00Z", + "period_type": "quarterly", + "filing_type": "10-Q", + "revenue": 124300000000.0, + "net_income": 36000000000.0, "eps": 2.34, "gross_margin": 0.472, - "operating_margin": 0.315 + "operating_margin": 0.315, + "gross_profit": 58668000000.0, + "data_source": "SEC_EDGAR", + "is_estimated": false }, { - "period": "2025-Q4", - "period_end": "2025-09-27", - "revenue": 119600000000, - "net_income": 34900000000, + "period_date": "2025-09-27T00:00:00Z", + "period_type": "quarterly", + "filing_type": "10-Q", + "revenue": 119600000000.0, + "net_income": 34900000000.0, "eps": 2.26, "gross_margin": 0.461, - "operating_margin": 0.308 + "operating_margin": 0.308, + "gross_profit": 55135000000.0, + "data_source": "SEC_EDGAR", + "is_estimated": false } ] } diff --git a/tests/fixtures/fred_observations.json b/tests/fixtures/fred_observations.json index 526796b..a9a5be3 100644 --- a/tests/fixtures/fred_observations.json +++ b/tests/fixtures/fred_observations.json @@ -1,11 +1,14 @@ { - "series_id": "DGS10", - "realtime_start": "2026-01-01", - "realtime_end": "2026-03-12", - "observations": [ - {"date": "2026-03-10", "value": 4.32}, - {"date": "2026-03-09", "value": 4.28}, - {"date": "2026-03-06", "value": 4.35}, - {"date": "2026-03-05", "value": 4.41} - ] + "success": true, + "message": "FRED API proxy: series/observations -> 4 records", + "data": { + "realtime_start": "2026-01-01", + "realtime_end": "2026-03-12", + "observations": [ + {"date": "2026-03-10", "value": "4.32"}, + {"date": "2026-03-09", "value": "4.28"}, + {"date": "2026-03-06", "value": "4.35"}, + {"date": "2026-03-05", "value": "4.41"} + ] + } } diff --git a/tests/fixtures/price_data.json b/tests/fixtures/price_data.json index d141876..dcadc5b 100644 --- a/tests/fixtures/price_data.json +++ b/tests/fixtures/price_data.json @@ -1,12 +1,17 @@ { "ticker": "AAPL", - "source": "yfinance", - "bars": [ - {"date": "2026-01-23", "open": 225.0, "high": 228.5, "low": 224.0, "close": 227.0, "volume": 52000000}, - {"date": "2026-01-26", "open": 227.5, "high": 230.0, "low": 226.0, "close": 229.5, "volume": 48000000}, - {"date": "2026-01-27", "open": 229.0, "high": 232.0, "low": 228.0, "close": 231.0, "volume": 55000000}, - {"date": "2026-01-28", "open": 231.5, "high": 234.0, "low": 230.0, "close": 233.0, "volume": 60000000}, - {"date": "2026-01-29", "open": 240.0, "high": 245.0, "low": 238.0, "close": 243.0, "volume": 120000000}, - {"date": "2026-01-30", "open": 243.5, "high": 246.0, "low": 241.0, "close": 244.5, "volume": 75000000} - ] + "interval": "1d", + "data": [ + {"date": "2026-01-23", "open": 225.0, "high": 228.5, "low": 224.0, "close": 227.0, "volume": 52000000.0, "adjusted_close": 227.0, "data_source": "YAHOO_FINANCE"}, + {"date": "2026-01-26", "open": 227.5, "high": 230.0, "low": 226.0, "close": 229.5, "volume": 48000000.0, "adjusted_close": 229.5, "data_source": "YAHOO_FINANCE"}, + {"date": "2026-01-27", "open": 229.0, "high": 232.0, "low": 228.0, "close": 231.0, "volume": 55000000.0, "adjusted_close": 231.0, "data_source": "YAHOO_FINANCE"}, + {"date": "2026-01-28", "open": 231.5, "high": 234.0, "low": 230.0, "close": 233.0, "volume": 60000000.0, "adjusted_close": 233.0, "data_source": "YAHOO_FINANCE"}, + {"date": "2026-01-29", "open": 240.0, "high": 245.0, "low": 238.0, "close": 243.0, "volume": 120000000.0, "adjusted_close": 243.0, "data_source": "YAHOO_FINANCE"}, + {"date": "2026-01-30", "open": 243.5, "high": 246.0, "low": 241.0, "close": 244.5, "volume": 75000000.0, "adjusted_close": 244.5, "data_source": "YAHOO_FINANCE"} + ], + "metadata": { + "request_id": "AAPL", + "data_points": 6, + "interval": "1d" + } } diff --git a/tests/fixtures/short_volume.json b/tests/fixtures/short_volume.json index 6468a03..86aec70 100644 --- a/tests/fixtures/short_volume.json +++ b/tests/fixtures/short_volume.json @@ -1,8 +1,14 @@ { "symbol": "AAPL", - "data": [ - {"date": "2026-03-10", "short_volume": 5000000, "short_exempt_volume": 50000, "total_volume": 45000000}, - {"date": "2026-03-09", "short_volume": 4800000, "short_exempt_volume": 48000, "total_volume": 42000000}, - {"date": "2026-03-06", "short_volume": 5200000, "short_exempt_volume": 52000, "total_volume": 48000000} - ] + "entries": [ + {"date": "2026-03-10", "symbol": "AAPL", "short_volume": 5000000.0, "short_exempt_volume": 50000.0, "total_volume": 45000000.0, "market": "B,Q,N", "short_ratio": 0.111}, + {"date": "2026-03-09", "symbol": "AAPL", "short_volume": 4800000.0, "short_exempt_volume": 48000.0, "total_volume": 42000000.0, "market": "B,Q,N", "short_ratio": 0.114}, + {"date": "2026-03-06", "symbol": "AAPL", "short_volume": 5200000.0, "short_exempt_volume": 52000.0, "total_volume": 48000000.0, "market": "B,Q,N", "short_ratio": 0.108} + ], + "total_count": 3, + "metadata": { + "days_requested": 5, + "start_date": "2026-03-06", + "end_date": "2026-03-10" + } } diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 5bfafed..cd36188 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -1,7 +1,7 @@ """Integration test fixtures using the running postgres from docker-compose.""" + from __future__ import annotations -import pytest import pytest_asyncio from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine diff --git a/tests/integration/test_db_migration.py b/tests/integration/test_db_migration.py index b5a9755..4e4cc65 100644 --- a/tests/integration/test_db_migration.py +++ b/tests/integration/test_db_migration.py @@ -1,4 +1,5 @@ """Integration test: verify all tables exist after migration.""" + import pytest from sqlalchemy import text @@ -25,10 +26,7 @@ EXPECTED_TABLES = [ async def test_all_tables_exist(db_session): """All expected tables should exist after migration.""" result = await db_session.execute( - text( - "SELECT table_name FROM information_schema.tables " - "WHERE table_schema = 'public'" - ) + text("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'") ) existing = {row[0] for row in result} for table in EXPECTED_TABLES: @@ -39,4 +37,5 @@ async def test_all_tables_exist(db_session): @pytest.mark.asyncio async def test_db_health(db_session): from libs.db.helpers import health_check + assert await health_check(db_session) is True diff --git a/tests/integration/test_feature_pipeline.py b/tests/integration/test_feature_pipeline.py index b55d155..7bf3ab7 100644 --- a/tests/integration/test_feature_pipeline.py +++ b/tests/integration/test_feature_pipeline.py @@ -1,14 +1,16 @@ -"""Integration test: feature pipeline (event + mock price → feature snapshot).""" +"""Integration test: feature pipeline with real Oracle (localhost:18001).""" + import datetime as dt import pytest -from pytest_httpx import HTTPXMock + +ORACLE_URL = "http://localhost:18001" @pytest.mark.integration @pytest.mark.asyncio -async def test_feature_snapshot_created(db_session, httpx_mock: HTTPXMock, price_data_fixture, sample_parser_output): - """Given a valid event + parse, feature snapshots should be created.""" +async def test_feature_snapshot_created(db_session, sample_parser_output): + """Real Oracle price data → market_v1/event_v1 snapshots created in DB.""" from libs.db.models import ( Document, @@ -21,10 +23,6 @@ async def test_feature_snapshot_created(db_session, httpx_mock: HTTPXMock, price from libs.oracle_client.client import OracleClient from libs.oracle_client.price import PriceService - # Mock price endpoint - httpx_mock.add_response(json=price_data_fixture) - - # Setup DB records issuer = IssuerMaster(issuer_id="ISSUER::0000320193", issuer_name="Apple Inc.", ticker="AAPL") db_session.add(issuer) @@ -71,7 +69,7 @@ async def test_feature_snapshot_created(db_session, httpx_mock: HTTPXMock, price db_session.add(parse) await db_session.flush() - async with OracleClient("http://oracle:18001") as client: + async with OracleClient(ORACLE_URL) as client: price_svc = PriceService(client) result = await build_features_for_event(db_session, event, price_svc) @@ -81,3 +79,97 @@ async def test_feature_snapshot_created(db_session, httpx_mock: HTTPXMock, price assert event_snap.snapshot_name == "event_v1" assert "reaction_day_return" in market_snap.feature_json assert "guidance_direction_score" in event_snap.feature_json + + +@pytest.mark.integration +@pytest.mark.asyncio +async def test_financial_v1_snapshot_created(db_session, sample_parser_output): + """Real Oracle price + financial data → financial_v1 snapshot created in DB.""" + + from sqlalchemy import select + + from libs.db.models import ( + Document, + Event, + EventParse, + FeatureSnapshot, + IssuerMaster, + SymbolMaster, + ) + from libs.features.builder import build_features_for_event + from libs.oracle_client.client import OracleClient + from libs.oracle_client.financial import FinancialService + from libs.oracle_client.price import PriceService + + issuer = IssuerMaster(issuer_id="ISSUER::0000320193", issuer_name="Apple Inc.", ticker="AAPL") + db_session.add(issuer) + + symbol = SymbolMaster( + symbol_id="SYM::AAPL::XNYS", + issuer_id="ISSUER::0000320193", + ticker="AAPL", + venue="XNYS", + ) + db_session.add(symbol) + + doc = Document( + document_id="DOC::sec::ISSUER::0000320193::2026-01-29::ACC002", + source_name="sec", + form_type="8-K", + filing_date=dt.date(2026, 1, 29), + accession_no="ACC002", + parsed_status="succeeded", + ) + db_session.add(doc) + await db_session.flush() + + event = Event( + event_id="EVT::test::earnings_release::fin", + primary_document_id=doc.document_id, + symbol_id="SYM::AAPL::XNYS", + event_type="earnings_release", + event_direction="bullish", + event_date=dt.date(2026, 1, 29), + parser_version="rule-1.0.0", + status="pending", + ) + db_session.add(event) + await db_session.flush() + + parse = EventParse( + event_id=event.event_id, + parser_kind="rule", + parser_version="rule-1.0.0", + schema_version="1.0.0", + output_json=sample_parser_output, + validation_status="valid", + ) + db_session.add(parse) + await db_session.flush() + + async with OracleClient(ORACLE_URL) as client: + price_svc = PriceService(client) + fin_svc = FinancialService(client) + result = await build_features_for_event( + db_session, event, price_svc, financial_service=fin_svc + ) + + assert result is not None + market_snap, event_snap = result + + rows = ( + ( + await db_session.execute( + select(FeatureSnapshot).where( + FeatureSnapshot.event_id == event.event_id, + FeatureSnapshot.snapshot_name == "financial_v1", + ) + ) + ) + .scalars() + .all() + ) + assert len(rows) == 1 + fin_snap = rows[0] + assert "latest_eps" in fin_snap.feature_json + assert "revenue_growth_qoq" in fin_snap.feature_json diff --git a/tests/integration/test_filing_pipeline.py b/tests/integration/test_filing_pipeline.py index 1b76570..2aeb8db 100644 --- a/tests/integration/test_filing_pipeline.py +++ b/tests/integration/test_filing_pipeline.py @@ -1,4 +1,5 @@ """Integration test: filing pipeline (poll → fetch → parse → event in DB).""" + import datetime as dt from pathlib import Path @@ -63,9 +64,7 @@ async def test_document_upsert_idempotency(db_session): await db_session.flush() # Try to insert again (should conflict on unique accession+form_type) - result = await db_session.execute( - select(Document).where(Document.document_id == doc_id) - ) + result = await db_session.execute(select(Document).where(Document.document_id == doc_id)) rows = result.scalars().all() assert len(rows) == 1 diff --git a/tests/integration/test_sync_jobs.py b/tests/integration/test_sync_jobs.py index c24b3cd..a16023a 100644 --- a/tests/integration/test_sync_jobs.py +++ b/tests/integration/test_sync_jobs.py @@ -1,13 +1,13 @@ """Integration test: sync jobs (FRED/FINRA → DB rows).""" + import datetime as dt import pytest -from pytest_httpx import HTTPXMock @pytest.mark.integration @pytest.mark.asyncio -async def test_macro_series_insert(db_session, httpx_mock: HTTPXMock, fred_observations_fixture): +async def test_macro_series_insert(db_session, fred_observations_fixture): """FRED sync should create macro_series + macro_observations rows.""" from sqlalchemy import select @@ -24,7 +24,7 @@ async def test_macro_series_insert(db_session, httpx_mock: HTTPXMock, fred_obser await db_session.flush() # Insert observations - for obs in fred_observations_fixture["observations"]: + for obs in fred_observations_fixture["data"]["observations"]: ob = MacroObservation( series_id="DGS10", observation_date=dt.date.fromisoformat(obs["date"]), @@ -48,7 +48,7 @@ async def test_short_sale_daily_insert(db_session, short_volume_fixture): from libs.db.models import ShortSaleDaily - for entry in short_volume_fixture["data"]: + for entry in short_volume_fixture["entries"]: row = ShortSaleDaily( ticker_raw="AAPL", trade_date=dt.date.fromisoformat(entry["date"]), diff --git a/tests/unit/test_fred_service.py b/tests/unit/test_fred_service.py new file mode 100644 index 0000000..5096ecc --- /dev/null +++ b/tests/unit/test_fred_service.py @@ -0,0 +1,56 @@ +"""Unit tests for oracle_client.fred (FredService).""" + +from __future__ import annotations + +import pytest +from pytest_httpx import HTTPXMock + + +@pytest.mark.asyncio +async def test_get_observations(httpx_mock: HTTPXMock, fred_observations_fixture): + """FredService.get_observations가 FredProxyResponse를 올바르게 반환한다.""" + from libs.oracle_client.client import OracleClient + from libs.oracle_client.fred import FredService + from libs.oracle_client.models import FredProxyResponse + + httpx_mock.add_response(json=fred_observations_fixture) + + async with OracleClient("http://oracle:18001") as client: + svc = FredService(client) + result = await svc.get_observations("DGS10") + + assert isinstance(result, FredProxyResponse) + assert result.series_id == "DGS10" + assert len(result.observations) == 4 + assert result.observations[0].value == pytest.approx(4.32) + + +@pytest.mark.asyncio +async def test_get_series_info(httpx_mock: HTTPXMock): + """FredService.get_series_info가 FredSeriesInfo를 올바르게 반환한다.""" + from libs.oracle_client.client import OracleClient + from libs.oracle_client.fred import FredService + from libs.oracle_client.models import FredSeriesInfo + + series_response = { + "success": True, + "data": { + "seriess": [ + { + "id": "DGS10", + "title": "Market Yield on U.S. Treasury Securities at 10-Year Constant Maturity", + "frequency": "Daily", + "units": "Percent Per Year", + } + ] + }, + } + httpx_mock.add_response(json=series_response) + + async with OracleClient("http://oracle:18001") as client: + svc = FredService(client) + result = await svc.get_series_info("DGS10") + + assert isinstance(result, FredSeriesInfo) + assert result.id == "DGS10" + assert result.frequency == "Daily" diff --git a/tests/unit/test_llm_parser_stub.py b/tests/unit/test_llm_parser_stub.py new file mode 100644 index 0000000..9212cb2 --- /dev/null +++ b/tests/unit/test_llm_parser_stub.py @@ -0,0 +1,31 @@ +"""Unit tests for parser.llm_parser_stub (LLMParserStub).""" + +from __future__ import annotations + +import pytest + + +def test_disabled_returns_none(): + """enabled=False 인 경우 parse()는 None을 반환한다.""" + from libs.parser.llm_parser_stub import LLMParserStub + + stub = LLMParserStub(enabled=False) + result = stub.parse( + document_id="DOC::test", + form_type="8-K", + text="Apple Inc. reports strong quarterly earnings.", + ) + assert result is None + + +def test_enabled_raises_not_implemented(): + """enabled=True 인 경우 parse()는 NotImplementedError를 발생시킨다.""" + from libs.parser.llm_parser_stub import LLMParserStub + + stub = LLMParserStub(enabled=True) + with pytest.raises(NotImplementedError): + stub.parse( + document_id="DOC::test", + form_type="8-K", + text="Apple Inc. reports strong quarterly earnings.", + ) diff --git a/tests/unit/test_logging.py b/tests/unit/test_logging.py new file mode 100644 index 0000000..2cfa791 --- /dev/null +++ b/tests/unit/test_logging.py @@ -0,0 +1,32 @@ +"""Unit tests for common.logging module.""" + +from __future__ import annotations + + +def test_configure_logging_no_error(): + """configure_logging("DEBUG") 호출 시 예외가 발생하지 않아야 한다.""" + from libs.common.logging import configure_logging + + configure_logging("DEBUG") + configure_logging("INFO") # reset to INFO after + + +def test_bind_job_run_id_in_context(): + """bind_job_run_id 후 ContextVar에 run_id가 설정된다.""" + from libs.common.logging import _job_run_id, bind_job_run_id + + bind_job_run_id("RUN::test-001") + assert _job_run_id.get() == "RUN::test-001" + # cleanup + bind_job_run_id("") + + +def test_get_logger_returns_bound_logger(): + """get_logger("name") 결과가 logging 메서드를 가진 객체를 반환한다.""" + from libs.common.logging import get_logger + + logger = get_logger("test.module") + assert hasattr(logger, "info") + assert hasattr(logger, "debug") + assert hasattr(logger, "warning") + assert hasattr(logger, "error") diff --git a/tests/unit/test_oracle_client.py b/tests/unit/test_oracle_client.py index 820e35b..9dc87b4 100644 --- a/tests/unit/test_oracle_client.py +++ b/tests/unit/test_oracle_client.py @@ -1,4 +1,5 @@ """Unit tests for Oracle client using pytest-httpx.""" + import json from pathlib import Path @@ -18,7 +19,7 @@ async def test_search_filings(httpx_mock: HTTPXMock): from libs.oracle_client.filings import FilingsService data = load_fixture("filing_search.json") - httpx_mock.add_response(json=data, url="http://oracle:18001/filings/search/AAPL") + httpx_mock.add_response(json=data, url="http://oracle:18001/api/v1/filings/search/AAPL") async with OracleClient("http://oracle:18001") as client: svc = FilingsService(client) @@ -99,9 +100,9 @@ async def test_get_retries_on_transient_error_then_succeeds(httpx_mock: HTTPXMoc from libs.oracle_client.filings import FilingsService data = load_fixture("exhibit_content.json") - httpx_mock.add_response(status_code=500) # attempt 1 fails - httpx_mock.add_response(status_code=500) # attempt 2 fails - httpx_mock.add_response(json=data) # attempt 3 succeeds + httpx_mock.add_response(status_code=500) # attempt 1 fails + httpx_mock.add_response(status_code=500) # attempt 2 fails + httpx_mock.add_response(json=data) # attempt 3 succeeds async with OracleClient("http://oracle:18001") as client: svc = FilingsService(client) @@ -140,3 +141,48 @@ async def test_get_financial_data(httpx_mock: HTTPXMock): assert result.ticker == "AAPL" assert len(result.periods) == 2 + + +@pytest.mark.asyncio +async def test_connection_error_raises_oracle_connection_error(httpx_mock: HTTPXMock): + """ConnectError 시 OracleConnectionError가 발생한다 (3회 재시도 후).""" + import httpx + + from libs.oracle_client.client import OracleClient + from libs.oracle_client.exceptions import OracleConnectionError + + # 3번 모두 ConnectError (max_attempts=3) + httpx_mock.add_exception(httpx.ConnectError("connection refused")) + httpx_mock.add_exception(httpx.ConnectError("connection refused")) + httpx_mock.add_exception(httpx.ConnectError("connection refused")) + + async with OracleClient("http://oracle:18001") as client: + with pytest.raises(OracleConnectionError): + await client.get("/health") + + +@pytest.mark.asyncio +async def test_timeout_raises_oracle_timeout_error(httpx_mock: HTTPXMock): + """ReadTimeout 시 OracleTimeoutError가 발생한다 (3회 재시도 후).""" + import httpx + + from libs.oracle_client.client import OracleClient + from libs.oracle_client.exceptions import OracleTimeoutError + + httpx_mock.add_exception(httpx.ReadTimeout("read timeout")) + httpx_mock.add_exception(httpx.ReadTimeout("read timeout")) + httpx_mock.add_exception(httpx.ReadTimeout("read timeout")) + + async with OracleClient("http://oracle:18001") as client: + with pytest.raises(OracleTimeoutError): + await client.get("/health") + + +@pytest.mark.asyncio +async def test_client_without_context_manager_raises(): + """context manager 없이 get() 호출 시 RuntimeError가 발생한다.""" + from libs.oracle_client.client import OracleClient + + client = OracleClient("http://oracle:18001") + with pytest.raises(RuntimeError, match="async context manager"): + await client.get("/health") diff --git a/tests/unit/test_retries.py b/tests/unit/test_retries.py index e42e80a..638a0bc 100644 --- a/tests/unit/test_retries.py +++ b/tests/unit/test_retries.py @@ -1,4 +1,5 @@ """Unit tests for retries module.""" + import pytest @@ -10,6 +11,7 @@ def test_exception_hierarchy(): RetryableError, ValidationError, ) + assert issubclass(RetryableError, ACEFError) assert issubclass(NonRetryableError, ACEFError) assert issubclass(ValidationError, ACEFError) @@ -18,6 +20,7 @@ def test_exception_hierarchy(): def test_error_fields(): from libs.common.retries import RetryableError + err = RetryableError("msg", source="sec", entity="CIK123", context={"url": "x"}) assert err.source == "sec" assert err.entity == "CIK123" @@ -75,3 +78,21 @@ async def test_with_retry_does_not_retry_non_retryable(): with pytest.raises(NonRetryableError): await func() assert call_count == 1 + + +@pytest.mark.asyncio +async def test_with_retry_exhaustion(): + """max_attempts 모두 소진 후 RetryableError가 최종 raise된다.""" + from libs.common.retries import RetryableError, with_retry + + call_count = 0 + + @with_retry(max_attempts=3, min_wait=0.01, max_wait=0.1) + async def func(): + nonlocal call_count + call_count += 1 + raise RetryableError("always fails") + + with pytest.raises(RetryableError): + await func() + assert call_count == 3