이 스킬
Quant scanBacktest top-level helper
dartlab.quant.scanBacktest(scanResult, signalFn=, style=, topN=, weighting=) 형태로 scan 결과 DataFrame 의 universe 를 받아 종목별 Rule 빌드 → multiAssetBacktest 호출 → BacktestResult.scanContext 에 universe 출처 SHA-1 기록. axis 미등록 (top-level helper). 트리거 — 'scanBacktest', 'scan→quant', 'universe → backtest'.
이어 가기
절차
실행 순서
- 1
scanResult = dartlab.scan(...) (사용자 사전 sort/filter)
- 2
dartlab.quant.scanBacktest(scanResult, style=..., topN=20, weighting="equal")
- 3
result.scanContext 로 universe 출처 추적 + result.sharpe / mdd / dsr 인용
- 4
기간 (BacktestResult.period), 수수료 (fee_bps), 슬리피지 (slip_bps), 리밸런싱 (weighting) 명시
예시
이런 질문이 들어오면 이 skill 을 쓴다
- scan("valuation") → 등급 A → topN=20 → trendFollow 스타일 백테스트
- scan("ranking") → top10 → forecast signalFn 변환 → 백테스트
- c > moving_avg(c, 20) (단순 모멘텀)
엔진 역할
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 | supported | — |
| Pyodide | limited | — |
실패 회피
흔한 실패 · 절대 금지
- 빈 scanResult → BacktestResult(status="error", reason="empty scanResult")
- signalFn 과 style 둘 다 미지정 → error
- universe 컬럼 미감지 → error (후보 4종 알려줌)
- 미등록 style → KeyError + 후보 목록
- 수수료/슬리피지/리밸런싱 가정 누락 인용 — 백테스트 가정 명시 의무
- axis dispatch 호출 시도 (`dartlab.quant("scanBacktest", ...)`) — 미등록
- quant → scan 금지)
- 백테스트 결과 미래 성과 보장처럼 표현
- scanContext.scanResultHash 누락 인용 (universe 출처 추적 깨짐)