이 스킬
Quant Scan Backtest
scan rule 의 historical universe 적용 backtest — slippage/cost 명시 + walk-forward.
절차
실행 순서
- 1
quant → scan import **금지** (역방향). scan 결과는 사용자가 호출자에서 추출해 ``pl.DataFrame`` 입력으로 전달.
- 2
본 helper 는 ``dartlab.scan`` 을 import 하지 않음 — 단지 stockCode 컬럼이 있는 DataFrame 만 받는다.
- 3
axis 미등록 — registry dispatcher 의 ``fn(stockCode=stockCode, **kwargs)`` 계약은 첫 인자가 stockCode. scanBacktest 의 첫 인자는 DataFrame 이라 어긋남. ``dartlab.quant("scanBacktest", ...)`` 호출 X.
- 4
target: universe 종목 리스트 (BacktestResult.trades 의 stock_code 컬럼 or scanContext)
- 5
period: BacktestResult.period
- 6
benchmark: signalFn 또는 style 명시
- 7
metric: sharpe / mdd / dsr (cpcv 있으면 PBO 도)
- 8
가정: fee_bps, slip_bps, weighting
- 9
scanContext.scanResultHash: universe 출처
- 10
빈 scanResult → BacktestResult(status="error", reason="empty scanResult")
- 11
signalFn / style 둘 다 미지정 → error
- 12
같은 universe 두 번 호출 → 같은 scanResultHash
엔진 역할
scanBacktest 는 scan 결과 universe + signalFn (또는 style) → multiAssetBacktest 호출의 wrapper. 내부 로직 0 — 모든 백테스트는 multiAssetBacktest SSOT 가 처리. 본 helper 의 책임은 ① universe 추출, ② signalFn → Rule 변환, ③ scanContext SHA-1 기록.
architecture 룰 준수
- quant → scan import 금지 (역방향). scan 결과는 사용자가 호출자에서 추출해
pl.DataFrame입력으로 전달. - 본 helper 는
dartlab.scan을 import 하지 않음 — 단지 stockCode 컬럼이 있는 DataFrame 만 받는다. - axis 미등록 — registry dispatcher 의
fn(stockCode=stockCode, **kwargs)계약은 첫 인자가 stockCode. scanBacktest 의 첫 인자는 DataFrame 이라 어긋남.dartlab.quant("scanBacktest", ...)호출 X.
공개 호출 방식
import dartlab as dl
import polars as pl
# scan 으로 valuation 등급 A 추리고 trendFollow 스타일 백테스트
top = dl.scan("valuation").filter(pl.col("등급") == "A").sort("PER").head(20)
result = dl.quant.scanBacktest(top, style="trendFollow", topN=20)
result.sharpe, result.mdd, result.scanContext
# signalFn 직접 정의 — 단순 momentum 시그널
import numpy as np
def momentum_signal(close):
sma_short = np.convolve(close, np.ones(10) / 10, mode="same")
sma_long = np.convolve(close, np.ones(50) / 50, mode="same")
return sma_short > sma_long
result = dl.quant.scanBacktest(top, signalFn=momentum_signal, topN=20) 호출 동작
dartlab.quant.scanBacktest(scanResult, ...) 가 진입. 다음 순서:
- scanResult 빈 DataFrame / 누락 시 error 반환
- signalFn 또는 style 둘 중 하나 필수 — 미지정 시 error
- universeCol 자동 감지 (
stockCode→종목코드→stock_code→corp_code) - scanResult.head(topN) 로 universe 추출 — 사용자가 사전 sort/filter 책임
- signalFn 우선, fallback 으로 style → STYLE_REGISTRY 의 build 함수
- multiAssetBacktest 호출 (weighting=equal/inv_vol/risk_parity)
- BacktestResult.scanContext 에 universe 출처 SHA-1 + signalSource 기록 후 dataclasses.replace
signalFn / style 우선순위
signalFn명시 → 우선 (signalFn 으로 Rule 빌드)- signalFn 미지정 +
style명시 → STYLE_REGISTRY (trendFollow/meanReversion/breakout/dipBuy/eventDriven/flowFollow/lowVolDefensive/seasonalKR) - 둘 다 미지정 → error
universe 컬럼 자동 감지
universeCol="auto" (default) 시 다음 우선순위로 첫 매칭 컬럼 사용:
stockCode종목코드stock_codecorp_code
명시 override: universeCol="myCustomCol".
대표 반환 형태
BacktestResult(
equity=np.ndarray, # 누적 자산 시계열
returns=np.ndarray, # 일별 포트폴리오 수익률
trades=pl.DataFrame | None, # 종목별 trade 이력 (stock_code 컬럼 포함)
sharpe=float, # Sharpe ratio
sortino=float,
mdd=float, # 최대낙폭 (음수)
dsr=float, # Probabilistic Sharpe Ratio (Lopez de Prado)
pbo=float | None,
style=str, # "style:trendFollow" 또는 "signalFn"
scanContext=dict, # universe 출처 추적 — 본 helper 신규 필드
status="ok" | "error",
reason=str | None,
) 빈 universe / 미지정 signal / 잘못된 style → BacktestResult(status="error", reason=...) 반환.
scanContext (BacktestResult 신규 필드)
{
"universeSize": 20,
"universeCol": "stockCode",
"topN": 20,
"scanResultHash": "a3b1c2d4e5f60718", # 결정적 SHA-1 (16 자)
"signalSource": "style:trendFollow", # 또는 "signalFn"
"weighting": "equal"
} 같은 universe 입력 → 같은 hash. 사용자가 다른 sort/filter 적용 → 다른 hash. universe 출처 추적 가능.
evidence 기준
- target: universe 종목 리스트 (BacktestResult.trades 의 stock_code 컬럼 or scanContext)
- period: BacktestResult.period
- benchmark: signalFn 또는 style 명시
- metric: sharpe / mdd / dsr (cpcv 있으면 PBO 도)
- 가정: fee_bps, slip_bps, weighting
- scanContext.scanResultHash: universe 출처
자기 검증 노트
- 빈 scanResult → BacktestResult(status=“error”, reason=“empty scanResult”)
- signalFn / style 둘 다 미지정 → error
- 같은 universe 두 번 호출 → 같은 scanResultHash
- multiAssetBacktest 직접 호출 vs scanBacktest 의 결과 sharpe ε 이내 일치 (회귀 가드)
한계 및 비목표
- universe 의 등급/sort 자동 추출 X — 사용자가 사전에
scanResult.filter(...).sort(...).head(N)책임 - multi-period 백테스트 (월별 리밸런싱) 는 본 helper 범위 밖 —
multiAssetBacktest가 정적 가중치만 지원 - forecast 모델의 fold 마다 재학습 (walk-forward refit) 은 후속 PR
기본 검증
스킬 변경 시 본 파일 + engines.quant SKILL.md 의 top-level helper 섹션 + tests/test_quant_scanBacktest.py + Quant.scanBacktest 메서드 (__init__.py) + BacktestResult.scanContext 필드 5 곳을 같은 변경에서 갱신한다.
런타임
실행 환경별 호환성
| 환경 | 상태 | 비고 / 제한 |
|---|---|---|
| Local Python | supported | · |
| Server | supported | · |
| MCP | supported | · |
| Web AI | limited | · |
| Pyodide | limited | · |