한국어 · English
탐색 비용을 스스로 정산하는 미국 주식 팩터 연구 — 성과는 walk-forward 만, 관문은 Deflated Sharpe 와 PBO, 기각한 것까지 함께 공개한다.
세로축은 로그다 — 기울기가 같으면 수익률이 같다. 2008·2012·2022 의 평평한 구간은 200일 이평이 현금으로 뺀 자리다. 그 세 번이 SPY 와 낙폭이 갈리는 지점이다.
이 저장소가 흔치 않은 이유 셋
make install # uv sync --extra dev — uv 필요 (https://docs.astral.sh/uv/)
make test # pytest + coverage — 정상 설치 검증
저장소에 이미 커밋된 결과를 확인하는 데에는 API 키가 필요 없다:
# 전략 탐색 비용(DSR/PBO) 재현 — 벤더 데이터 없이 results/oos/ 만 읽음
uv run python scripts/strategy_search_cost.py
# VAA 원본 전략 — 무료 데이터(yfinance), 대화형 메뉴
make run
팩터 엔진이나 TAA 를 처음부터 다시 돌리려면(최신 데이터 적재, 신규 설정
최적화) Sharadar 구독이 필요하다 — 아래 사용법.
이 README 의 숫자는 전부 2026-08-14 까지의 데이터로 쟀고, 산출물이
results/ 에 함께 커밋되어 있어 벤더 데이터 없이도 대조할 수 있다.
세 개의 서브시스템이 서로 다른 질문에 답하고, 같은 과최적화 관문(Deflated Sharpe + PBO)으로 잰다.
팩터 엔진 (factor/) |
TAA 자산배분 (taa/) |
VAA 원본 (strategies/) |
|
|---|---|---|---|
| 질문 | 어떤 종목을 살까 | 어떤 자산군으로 갈아탈까 | (같음 — 첫 시도) |
| 대상 | 미국 개별주 (20,931종목, 1997~2026) | ETF 18종 | ETF 7~11종 |
| 데이터 | Sharadar 직판 (PIT · 상장폐지 포함) | Sharadar 펀드 벌크 (closeadj) |
yfinance 일간 종가 |
| 진입점 | opt-factor · opt-factor-tui |
scripts/run_taa.py |
make run · run.py |
| 결과 | 채택 1건 (대형주 E안) | 채택 0건 — 9개 전부 PBO 관문 탈락 | 보존 — 왜 실패했는지의 기록 |
세 시스템은 서로를 import 하지 않는다. 예외는 taa/ →
factor.research.overfitting(DSR·PBO) 하나뿐이다.
docs/factor-library.ko.md.configs/ 에 채택한 것, 기각한 것,
폐기한 것을 운용 파라미터까지 전부 둔다.PITStore, DuckDB) — 최초 공시 우선, 상장폐지 종목 보존.운용 후보 · walk-forward 검증 구간 · 2002-12 – 2026-08 (23.6년)
대형주 5팩터 + 200일 이평 타이밍 (configs/strategy_lean_timed.json)
| 지표 | 슬리피지 15bps | 슬리피지 50bps | SPY (같은 구간) |
|---|---|---|---|
| 연평균 수익률 | 16.34% | 14.91% | 11.66% |
| 최대낙폭 | −24.3% | −24.3% | −55.2% |
| 변동성 | 15.7% | 15.7% | 18.6% |
| Sharpe | 0.727 | 0.648 | 0.418 |
| Calmar | 0.67 | 0.61 | 0.21 |
| Deflated Sharpe (파라미터 시도 72회) | 0.996 ✓ | 0.988 ✓ | — |
| Deflated Sharpe (전략 탐색 35회) | 0.982 ✓ | 0.957 ✓ | — |
| PBO (35개 구성 CSCV · 월별 · S=16) | 0.303 ✓ | — | — |
이 전략은 방어 장치를 켠 채로 측정됐다 — 최소 주가 $5 · 최소 거래대금 $1M · 슬리피지. 유니버스가 역대 S&P500 이라 용량 제약이 없다. 슬리피지를 50bps 로 올려도 낙폭과 변동성이 그대로고 수익만 1.4%p 깎인다.
마지막 두 줄은 walk-forward 바깥을 정산한 값이다 — “35개 전략을 훑어 하나를
골랐다”는 상위 탐색까지 시도 횟수에 넣었다. 관문 설정(월별 · S=16)을 고른 근거는
아래에 있다. 재현: uv run python scripts/strategy_search_cost.py — 벤더 데이터
없이 results/oos/ 만으로 돈다.
퀀트 백테스트가 실패하는 방식은 대개 정해져 있고, 이 엔진은 그 각각을 구조로 막는다. 원칙은 하나다 — 조용히 틀리지 않는다.
| 흔한 실패 | 이 엔진의 대응 |
|---|---|
| 생존편향 — 지금 살아남은 종목만 본다 | 상장폐지 종목 포함 (엔론·구 아메리칸항공 등 실제 확인) |
| Look-ahead — 발표 전 숫자를 쓴다 | 표현식이 원시 테이블에 접근 못 하고 PanelContext 만 통과, datekey 정렬 강제 |
| 재공시 오염 — 정정된 숫자를 쓴다 | 최초 공시 우선 — 시장이 처음 본 값만 저장 |
| 조용한 절단 — 데이터가 덜 왔는데 성공 처리 | 페이지네이션이 기대 범위 미달 시 TruncatedDataError |
| 과최적화 — 수백 번 돌려 최고를 고른다 | DSR(Deflated Sharpe) + PBO 로 시도 횟수를 정산 |
| 인샘플 성과 보고 | 공식 성과는 walk-forward 뿐. 단일 백테스트는 참고용 명시 |
같은 전략, 같은 구간. 차이는 슬리피지·최소 주가·최소 거래대금뿐이다. 파란 선이 2026-08-16 까지 이 README 의 표제였다.
CAGR 23.78% 의 초소형주 8팩터 전략이 이 자리에 있었다. 설계 문서가 필수라고 적은 방어 장치 셋을 켜자 무너졌다 — Sharpe 1.047 → −0.224. 방어를 켜면 유니버스의 98% 가 사라지기 때문이다. 표제는 대형주 전략으로 옮겼다. 그쪽은 처음부터 방어를 켠 채로 쟀고, 그래서 두 전략을 같은 조건에서 비교한 적이 없었다.
DSR 관문에서 기각한 후보가 스무 개를 넘고, PBO 는 제대로 재기 전까지 두 번 틀린 채로
발표됐다. 그 자취를 전부 남겼다 — docs/journal/.
개별 종목 대신 ETF 18종을 매월 교체한다. 9개 구성을 사전등록했고 9개 전부 기각했다 — PBO 0.770. 관문을 완화하지 않았다.
아무것도 안 통한다는 뜻은 아니다. BAA(Keller 2022) 변형 6개는 예외 없이 Calmar 에서 60/40 을 이긴다(0.535~0.812 대 0.354). 다만 그 6개만으로 잰 PBO 가 0.861 이라 그중 어느 것이 최선인지는 이 데이터로 정할 수 없다. 두 문장은 함께 서야 한다.
9개 구성과 이 과정에서 잡은 결함 8건, VAA 가 논문의 16~17% 대신 여기서 6.07% 로
측정된 이유는 docs/taa/01-results.md 와
docs/journal/ 에 있다.
flowchart LR
subgraph Factor engine["팩터 엔진 (factor/)"]
SH[(Sharadar<br/>sf1 · sep · daily · actions)] -->|"opt-factor ingest"| STORE[(PITStore<br/>us.duckdb)]
STORE --> CTX["PanelContext<br/>(datekey 정렬, look-ahead 차단)"]
CTX --> DSL["Factor DSL<br/>158개 팩터, factor/library"]
DSL --> UNIV["유니버스 필터<br/>가격 · 거래대금 · 섹터"]
UNIV --> PIPE["FactorPipeline.run<br/>factor/pipeline.py"]
PIPE --> BT["백테스트 엔진<br/>factor/backtest"]
BT --> WF["walk-forward 최적화<br/>opt-factor optimize"]
WF --> DSRF["DSR + PBO 게이트<br/>factor/research/overfitting.py"]
DSRF -->|"통과 시"| HOLD["opt-factor holdings<br/>매매 계획"]
end
subgraph TAA allocation["TAA 자산배분 (taa/)"]
FUNDS[(Sharadar funds bulk<br/>closeadj, 18개 ETF)] -->|"taa/data.py"| SIG["시그널<br/>13612w · sma13"]
SIG --> STRAT["StrategySpec 레지스트리<br/>9개 사전등록 설정"]
STRAT --> TBT["월간 백테스트<br/>taa/backtest.py"]
TBT --> EVAL["evaluate_all + 판정<br/>taa/evaluate.py"]
EVAL --> DSRF
end
subgraph Original VAA["VAA 원본 (strategies/)"]
YF[(yfinance<br/>일간 종가)] --> VAA["VAAStrategy<br/>strategies/vaa.py"]
VAA --> RUNPY["run.py / make run<br/>백테스트 + 리포트"]
end
아래 적재·최적화 명령을 다시 돌리려면 Sharadar 구독이 필요하다. PIT 재무제표와 폐지 종목 커버리지를 함께 주는 유일한 소매 가격 소스다. 어댑터는 중립적인
Provider프로토콜 뒤에 격리돼 있어 소스 교체는 파일 하나를 다시 쓰는 일이다.
# 데이터 적재 (Sharadar 구독 필요)
export SHARADAR_API_KEY=...
opt-factor ingest --store us.duckdb --provider sharadar \
--tables sf1,sep,daily,actions,sp500,tickers
# 팩터 예측력 검증 — 10분할 · IC · 회전율
uv run python scripts/factor_lab.py --store us.duckdb --factors GP_A,PER,SIZE
# 공식 성과 (walk-forward + DSR)
opt-factor optimize --store us.duckdb --config configs/strategy_lean_timed.json \
--space configs/space.json --objective calmar
# 오늘 살 종목 (현재 보유를 주면 매매 계획까지)
opt-factor holdings --store us.duckdb \
--config configs/strategy_lean_timed.json --current my_holdings.csv
# TAA: 9개 구성 · PBO · 판정표
uv run python scripts/run_taa.py
make lint # ruff check + format --check
make typecheck # mypy src/
패키지 관리는 uv(uv.lock)다. pip install 하지 않는다. 코드는
src/opt_portfolio/ 아래 factor/·taa/·strategies/ 로 나뉘고 analysis/·core/
를 공유한다.
설계 문서는 docs/factor-system/ 에 있다 — 명세,
데이터 계약(스토어 스키마 · PIT 규약 · 벤더 실측), walk-forward 수식, 그리고
기각 목록이 담긴 실험 로그. 연구 저널은 docs/journal/,
팩터 카탈로그는 docs/factor-library.ko.md.
quant-airflow 공유 수집 DAG 를 껐다), 스토어
~/data/us_micro.duckdb 는 2026-09-10 가격에서 동결돼 더는 재구축되지 않는다 — 지금
ingest 를 다시 돌려도 API 에서 받아오지 못한다. 세금은 반영하지 않았다.strategies/ 의 VAA 원본은 다른 데이터
소스에서 in-sample 로 최적화한 것이다 — 비교하려면 taa/ 숫자를 쓴다.⚠️ 모든 백테스트는 과거 데이터 기반이며 미래 수익을 보장하지 않는다.
MIT
한국·미국 주식과 암호화폐를 아우르는 오픈소스 스택입니다. 각 저장소는 독립적으로 쓸 수 있습니다.
| 축 | 프로젝트 | 설명 |
|---|---|---|
| 🇰🇷 한국 주식 | kiwoom-client | 키움증권 REST API Python 라이브러리 — 국내주식 엔드포인트 전수·실시간 WebSocket, sync + async (pip install kiwoom-client) |
| 🇰🇷 한국 주식 | krx-fundamentals-client | 국내 기업 펀더멘탈 Python 클라이언트 라이브러리 — 재무제표·투자지표·배당·종목 스크리닝 (DART + KRX + 네이버) |
| 🇰🇷 한국 주식 | krx-news-client | 한국 주식 뉴스·공시를 한 스키마로 내주는 Python 클라이언트 라이브러리 (DART + 토스증권) |
| 🇰🇷 한국 주식 | krx-quant-core | 한국 주식 퀀트 시스템 공통 Python 코어 — KRX 호가·가격제한폭·세션 규칙, 시행일별 거래세 비용모델, 키움 주문 가드, DART 중대공시 위험 분류, 체결 시뮬레이션, Deflated Sharpe·purged CV 통계 |
| 🇰🇷 한국 주식 | fin-checkup | 관심종목 위험 공시 텔레그램 알림 + DART·SEC 재무 건강검진 — 측정값과 사실만 전달한다 |
| 🇰🇷 한국 주식 | quant-airflow | 시세·수급·실적을 TimescaleDB 로 수집하는 Airflow 파이프라인 — 상장폐지 종목까지 담아 생존편향을 막는다 |
| 🇰🇷 한국 주식 | swing-it | 코스피·코스닥 알파 심사 프레임워크 — 개별 트레이드 분포로 판정하고 랜덤 음성대조·purged CV·Deflated Sharpe 를 CI 가드레일로 강제 |
| 🇺🇸 미국 주식 | macro-sector-agent | 하향식 미국 산업 사이클 리서치 — "어느 산업이 잊혔나"를 묻고 기계는 제외만 한다 |
| 🇺🇸 미국 주식 | automated-stock-trading-systems | Bensdorp 의 7개 비상관 트레이딩 시스템 백테스터 (교육용 재구현) |
| ₿ 암호화폐 | binance-quant-engine | 암호화폐 선물 백테스트·실행 엔진 — 룩어헤드 0, 백테스트↔실거래 일체화 |
| ₿ 암호화폐 | crypto-quant-insights | 실거래 선물 시스템을 검증하며 나온 차트 연구 노트 — 폭락 스트레스 테스트·Kelly 레버리지·체결 간극 |
채영환 (Younghwan Chae) · GitHub @younghwan91 · LinkedIn
전체 오픈소스 퀀트 스택은 프로필에서 한눈에 볼 수 있습니다.