리팩토링 기획안

2026-04-13 | by Lucky 상태: Dan 합의 대기 중


목표

기존 백테스팅 시스템의 핵심 로직은 유지하면서, 중복 제거 + 구조 통일 + 확장성 확보를 달성한다. 리팩토링 후에도 기존 백테스트 결과를 동일하게 재현할 수 있어야 한다.


Phase 1: 공통 유틸 추출 (중복 제거)

1-1. simulate_portfolio_dedupedengine/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_phase4runners/universe.py (유니버스 필터 검증)
runners/phase_b + auto_pipeline/run_phase1runners/signal.py (시그널 탐색)
auto_pipeline/run_phase2runners/ensemble.py (앙상블 조합)
runners/phase_c + auto_pipeline/run_phase3runners/exit_opt.py (출구 최적화)
runners/phase_drunners/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_D

Phase 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/ — 현재 구조 유지

리스크 & 주의사항

  1. 결과 재현성: 리팩토링 전후로 동일 config → 동일 결과 나오는지 검증 필수
  2. auto_pipeline state.json: 기존 상태 추적 로직을 러너 시스템에 어떻게 녹일지 결정 필요
  3. phase_b_followup.py: phase_b 내부 함수에 의존 — 통합 시 같이 처리
  4. experiments 폴더: 일회성 실험 스크립트들 보존할지 정리할지 결정 필요