리팩토링 기획안
2026-04-13 | by Lucky 상태: Dan 합의 대기 중
목표
기존 백테스팅 시스템의 핵심 로직은 유지하면서, 중복 제거 + 구조 통일 + 확장성 확보를 달성한다. 리팩토링 후에도 기존 백테스트 결과를 동일하게 재현할 수 있어야 한다.
Phase 1: 공통 유틸 추출 (중복 제거)
1-1. simulate_portfolio_deduped → engine/portfolio.py로 이동
현황: auto_pipeline/run_phase1,2,3.py에 거의 동일한 함수가 3벌 복붙
변경: engine/portfolio.py(현재 스텁)에 통합 구현
engine/portfolio.py
├── simulate_portfolio_deduped(trades_df, ...) → 기존 3곳 대체
├── evaluate_strategy(metrics_dict, ...) → phase1,3 중복 대체
└── (향후 Phase D용 PortfolioSimulator 확장 여지)
1-2. 유니버스 빌더 통합
현황: 유니버스 구축 로직이 5곳에 각각 다른 방식으로 존재
- phase_a:
filter_universe_monthly()+ params dict - phase_b:
_build_universe(),_build_config2_universe()커스텀 - auto_pipeline: 직접 precompute 함수 호출 + 인라인 파라미터
변경: engine/universe.py에 통합 빌더 함수 추가
def build_universe(prices, config: dict, conn) -> dict[date, list[str]]:
"""config dict 기반으로 유니버스 구축. 모든 runner/pipeline이 이걸 쓴다."""1-3. DuckDB 쿼리 중앙화
현황: data.py에 getter 있지만, universe.py/phase_b.py/auto_pipeline에서 직접 duckdb.connect() 호출
변경:
- IPO first dates, pipeline thickness 등 누락된 쿼리를
data.py에 추가 - 다른 모듈에서
duckdb직접 import 제거
Phase 2: 러너 인터페이스 통일
2-1. 공통 러너 인터페이스
현황:
- runners/: 함수 기반 (
run_phase_a(params)) → dict 리턴 - auto_pipeline/: 스크립트 기반 (if name) → 파일에 직접 저장
- 파라미터 소스가 다름 (config.py vs 인라인 하드코딩)
변경: 모든 러너를 통일된 인터페이스로 리팩토링
class BaseRunner:
def __init__(self, config: dict, output_dir: Path):
...
def run(self) -> RunResult:
"""실행 + 결과 반환"""
def save(self, result: RunResult):
"""통일된 포맷으로 저장"""
def report(self, result: RunResult) -> str:
"""마크다운 서머리 생성"""2-2. auto_pipeline ↔ runners 통합
현황: 두 시스템이 같은 엔진을 다르게 감싸서 중복 변경:
runners/하나로 통합 (auto_pipeline 폴더 제거)- 기존 auto_pipeline의 고유 기능(state.json, 10개 시그널, 앙상블 OR 로직)은 러너 config로 흡수
- 통합 매핑:
| 기존 | 통합 후 |
|---|---|
| runners/phase_a + auto_pipeline/run_phase4 | runners/universe.py (유니버스 필터 검증) |
| runners/phase_b + auto_pipeline/run_phase1 | runners/signal.py (시그널 탐색) |
| auto_pipeline/run_phase2 | runners/ensemble.py (앙상블 조합) |
| runners/phase_c + auto_pipeline/run_phase3 | runners/exit_opt.py (출구 최적화) |
| runners/phase_d | runners/portfolio.py (포트폴리오 시뮬레이션) |
Phase 3: Config 중앙화
3-1. config.py 확장
현황:
- config.py에 PHASE_A~D 정의
- auto_pipeline은 config.py 무시하고 인라인 파라미터 사용
- 날짜 포맷 불일치 (string vs date object)
변경:
# config.py
DEFAULT_START = date(2015, 1, 2) # 통일: date 객체
DEFAULT_END = date(2025, 12, 31)
OOS_DATE = date(2024, 1, 1)
UNIVERSE = {
"baseline": { ... }, # 기존 PHASE_A
"optimized": { ... }, # 기존 phase_b CONFIG_2
}
SIGNALS = {
"all": [...], # 10개 시그널 목록
"eval_pass": {"sharpe": 0.5},
}
EXIT = {
"fixed_default": {"tp": 1.0, "sl": -0.5, "max_days": 180},
"fixed_wide": {"tp": 0.5, "sl": -0.9, "max_days": 365},
...
}
PORTFOLIO = { ... } # 기존 PHASE_DPhase 4: 결과 저장 체계화
4-1. 통일된 결과 구조
현황:
- phase_a: 타임스탬프 디렉토리 (
run_2026-03-17_001/) - phase_b: 고정 디렉토리 (
phase_b/, 덮어쓰기) - auto_pipeline: 별도 경로 + state.json
변경:
results/
├── YYYY-MM-DD_HHmm_{runner_name}/
│ ├── config.json # 실행 파라미터
│ ├── trades.csv # 전체 거래 내역 (재검증용)
│ ├── metrics.json # 핵심 메트릭
│ ├── summary.md # 마크다운 리포트
│ └── charts/ # 시각화
└── latest/ # 최신 결과 심링크
핵심: trades.csv에 모든 거래 내역 저장 → Dan이 언제든 재검증 가능
Phase 5: 데이터 레이어 보강
5-1. Split adjustment 함수 추가
현황: adj_close가 split 미반영 (P0 이슈)
변경: engine/data.py에 split adjustment 유틸 추가
def get_split_adjusted_prices(symbols=None, start=None, end=None) -> pd.DataFrame:
"""stock_splits 테이블 기반으로 close/high/low/open 보정"""5-2. Clinical trials 커버리지 개선
현황: 43.8% 커버리지 변경: 매칭 로직 개선 (fuzzy match) 또는 missing → “unknown” 처리 옵션
실행 순서 & 의존성
Phase 1 (공통 유틸 추출)
↓
Phase 2 (러너 통합) ← Phase 1 완료 필요
↓
Phase 3 (config 중앙화) ← Phase 2와 병행 가능
↓
Phase 4 (결과 저장 체계화) ← Phase 2 완료 필요
↓
Phase 5 (데이터 레이어) ← 독립 실행 가능, 언제든
리팩토링 후 디렉토리 구조 (예상)
biotech/backtest/
├── config.py # 통합 설정
├── engine/
│ ├── data.py # DuckDB + split adjustment
│ ├── universe.py # 필터 + 통합 빌더
│ ├── entry.py # 진입 시그널
│ ├── exit.py # 출구 시뮬레이션
│ ├── portfolio.py # 포트폴리오 시뮬레이션 (deduped 포함)
│ ├── metrics.py # 메트릭 계산
│ └── reversal_patterns.py # 반전 패턴
├── runners/
│ ├── base.py # BaseRunner 인터페이스
│ ├── universe.py # 유니버스 검증
│ ├── signal.py # 시그널 탐색
│ ├── ensemble.py # 앙상블 조합
│ ├── exit_opt.py # 출구 최적화
│ ├── portfolio.py # 포트폴리오 레벨 시뮬
│ └── run.py # CLI 진입점
├── charts/ # 시각화 (유지)
├── data/ # DuckDB (유지)
└── results/ # 통일된 결과 저장
안 건드리는 것들
docs/research/— 투자 thesis, 스펙 문서 그대로 유지engine/entry.py,engine/exit.py— 핵심 로직 변경 없음 (인터페이스만 정리)- 기존
results/폴더의 과거 결과 — 삭제하지 않음 charts/— 현재 구조 유지
리스크 & 주의사항
- 결과 재현성: 리팩토링 전후로 동일 config → 동일 결과 나오는지 검증 필수
- auto_pipeline state.json: 기존 상태 추적 로직을 러너 시스템에 어떻게 녹일지 결정 필요
- phase_b_followup.py: phase_b 내부 함수에 의존 — 통합 시 같이 처리
- experiments 폴더: 일회성 실험 스크립트들 보존할지 정리할지 결정 필요