주식 지식그래프 StockClone
WORK IN PROGRESSA Neo4j knowledge graph of the KOSPI/KOSDAQ market that fuses disclosures, news, order flow, and macro data into 134 ML features, scored daily by a gradient-boosting ensemble.
공시·뉴스·수급·매크로를 하나의 Neo4j 지식그래프로 잇고, 여기서 뽑은 134개 feature를 매일 앙상블 모델로 스코어링하는 한국 주식 분석 시스템
Role · Sole architect & engineer — ontology, feature engineering, Alpha Engine, recommendation pipeline
Key Metrics
Why it matters
The knowledge graph is the feature factory: 134 features spanning news, order-flow, and supply chains feed an Alpha Engine that outputs daily 5-stock picks (D+3) and monthly volatility-weighted baskets (D+20).
지식그래프를 feature 팩토리로 삼아, 뉴스·수급·공급망을 아우르는 134개 feature를 Alpha Engine에 학습시켜 일 5종목(D+3)·월간 변동성 가중 바스켓(D+20) 추천을 산출합니다.
Problem
Korean equity signals are semantic and relational, not tabular. A single DART disclosure can ripple from a subsidiary to its parent to an entire sector — multi-hop relationships a graph traverses naturally but SQL forces into brittle joins. The harder problem isn't storing the data; it's keeping a domain ontology rigorous enough to reason over (FIBO-aligned, SHACL-validated) while staying fast enough to query. Ticks never live in Neo4j — 1 year ≈ 1.2B nodes the query planner pays for on every traversal. I designed a hot/warm/cold tiering (ring buffer → SQLite → Parquet → Neo4j aggregates) and recorded every trade-off in 31 ADRs.
한국 주식 신호는 표(table)가 아니라 의미·관계 데이터입니다. 하나의 DART 공시가 자회사 → 모회사 → 섹터 전체로 파급되는 다중 홉 관계는 그래프에서는 자연스럽지만 SQL에서는 깨지기 쉬운 조인이 됩니다. 진짜 어려운 건 데이터 저장이 아니라, '추론이 가능할 만큼 엄밀하면서도 질의가 빠른' 도메인 온톨로지를 유지하는 것. 틱은 Neo4j에 두지 않습니다. 1년치 틱 ≈ 12억 노드라 모든 탐색마다 플래너 비용이 발생합니다. hot/warm/cold 계층화(링버퍼 → SQLite → Parquet → Neo4j 집계)를 설계하고 모든 결정을 31개 ADR로 기록했습니다.
What I built
4-tier data pipeline: Hot ring buffer (5min) → Warm SQLite WAL (intra-day) → Cold Parquet (archive) → Neo4j aggregates + events
4단계 데이터 파이프라인: Hot 링버퍼(5분) → Warm SQLite WAL(당일) → Cold Parquet(아카이브) → Neo4j 집계+이벤트
6 data sources fused: DART disclosures · Kiwoom ticks · KRX master · news RSS · daily flows · macro indicators
6개 데이터 소스 융합: DART 공시·키움 틱·KRX 마스터·뉴스 RSS·일별 유동·거시지표
Ontology layer: FIBO-aligned OWL classes, SHACL closed-world validation, PROV-O lineage tracking
온톨로지 레이어: FIBO 정렬 OWL 클래스, SHACL 폐쇄세계 검증, PROV-O 계보 추적
Hypothesis registry: 12 backtested hypotheses as parameterized Cypher queries — same query serves research and live firing (no train/serve skew)
가설 레지스트리: 12개 백테스트 가설을 파라미터화 Cypher로. 동일 쿼리가 연구·실시간 발화 양쪽에 사용(train/serve skew 없음)
31 ADRs: every significant architectural choice documented with context, decision, alternatives, and consequences — including rejected modules and failed attempts
31개 ADR: 모든 주요 아키텍처 결정을 맥락·결정·대안·결과로 문서화 — 미채택 모듈·실패한 시도까지 기록
Alpha Engine: 134 graph-derived features → three parallel models (weekly LGBM D+5, monthly LGBM D+20, neural forecaster) → daily 5-stock picks (D+3) and volatility-inverse-weighted monthly baskets (D+20)
Alpha Engine: 그래프에서 뽑은 134개 feature → 세 모델 병렬(주간 LGBM D+5, 월간 LGBM D+20, 신경망 예측기) → 일간 5종목 추천(D+3)과 변동성 역가중 월간 바스켓(D+20)
Demo surface: per-model win rate on a shared chart, plus a graph-grounded assistant that explains why a given ticker was selected
데모: 모델별 승률을 한 차트에 올리고, 특정 종목이 왜 선정됐는지 그래프 근거로 설명하는 어시스턴트를 함께 제공
Key Decisions (trade-offs)
Ticks don't live in Neo4j (ADR-001)
A year of ticks ≈ 1.2B nodes the query planner pays for on every traversal. I gave up a single unified store for hot/warm/cold tiering — prices in a columnar tier, only entities and relationships in the graph — keeping traversal latency bounded as data grows.
1년치 틱은 약 12억 노드. '단일 통합 저장소'를 포기하고 hot/warm/cold 계층화를 택해 그래프엔 엔티티·관계만 두었습니다.
SHACL for validation, not OWL (ADR-003)
OWL reasons under an open-world assumption — it can't enforce 'a ticker must be exactly 6 digits.' I scoped OWL/FIBO to semantics and pushed all closed-world integrity rules into SHACL, so validation actually fails on bad data instead of silently inferring around it.
OWL은 개방세계 가정이라 '티커는 정확히 6자리'를 강제하지 못합니다. OWL/FIBO는 의미 표현에만 쓰고, 폐쇄세계 무결성 규칙은 전부 SHACL로 분리했습니다.
Three models, three horizons, one comparison surface
A single model forced a choice between reacting to today's catalyst and holding a position for a month. Instead of tuning one model toward the middle, the engine runs three in parallel on the same feature set: a weekly LGBM (D+5), a monthly LGBM variant (D+20), and a neural forecaster. The demo renders them on one chart with per-model win rate, so the horizons are compared rather than blended. The cost is three training paths to maintain; the return is that the D+3 momentum pick and the D+20 volatility-weighted basket stop competing for the same loss function.
모델이 하나면 당일 호재에 반응하는 것과 한 달을 보유하는 것 사이에서 하나를 골라야 합니다. 중간값으로 튜닝하는 대신 같은 feature 위에서 세 모델을 병렬로 돌립니다. 주간 LGBM(D+5), 월간 LGBM(D+20), 신경망 예측기입니다. 데모는 셋을 한 차트에 올리고 모델별 승률을 함께 보여줘, 기간을 섞지 않고 비교하게 했습니다. 학습 경로를 셋 유지하는 비용이 들지만, D+3 모멘텀 추천과 D+20 변동성 가중 바스켓이 같은 손실 함수를 두고 경쟁하지 않게 됩니다.
Make the recommendation answer 'why', not just 'what'
A ranked list of five tickers is unusable if the reader cannot tell whether a pick came from momentum, order flow, or a same-day disclosure. The graph already holds those relationships, so the demo exposes them through an assistant that answers questions like "why was this picked today?" against the same graph and model signals that produced the list. The recommendation and its explanation come from one source, which means the explanation cannot drift from the actual ranking logic.
티커 다섯 개를 순위로 나열해도, 그 종목이 모멘텀에서 왔는지 수급에서 왔는지 당일 공시에서 왔는지 알 수 없으면 쓸 수 없습니다. 그 관계는 이미 그래프에 들어 있으므로, 데모에서는 "오늘 추천 왜 골랐어?" 같은 질문에 답하는 어시스턴트로 노출합니다. 추천과 설명이 같은 그래프·모델 신호에서 나오므로 설명이 실제 랭킹 로직과 어긋날 수 없습니다.
One hypothesis registry for backtest and live (ADR-010)
Most setups duplicate logic: one query for research, another for production. I made each hypothesis a single parameterized Cypher in a registry, serving both historical evaluation and real-time firing — eliminating train/serve skew.
각 가설을 레지스트리의 단일 파라미터화 Cypher로 만들어 백테스트와 실시간 발화를 같은 로직으로 처리. train/serve skew를 제거했습니다.