# Simulation Engine Design ## 1. 엔진 목적 이 엔진은 **candidate snapshot을 받아 실제 포트폴리오 성과로 연결하는 시뮬레이터**입니다. 핵심은 단순 수익률 계산이 아니라 **거래 가능 시점, 포트폴리오 제약, 체결 근사, 청산 로직**을 반영하는 것입니다. ## 2. 기본 실행 단위 기본 실행 단위는 **거래일 단위**입니다. 매 거래일마다 아래 순서를 수행합니다. 1. 전일 종료 시점에 생성된 candidate 조회 2. 당일 진입 가능한 종목만 필터링 3. 포트폴리오 제약 반영 후 진입 대상 결정 4. 당일 시초가 체결 시뮬레이션 5. 기존 포지션에 대해 손절/익절/시간청산 평가 6. 일말 평가금액, 노출, drawdown 갱신 ## 3. signal 시점 정의 ### 3.1 Event timestamp 문서가 실제 시장에 이용 가능해진 시점. ### 3.2 Availability timestamp feature snapshot이 전략에 사용 가능한 시점. 예: reaction day 종가를 사용하는 전략이라면 availability는 reaction day close 이후입니다. ### 3.3 Decision date 전략이 매수 여부를 결정한 날짜. ### 3.4 Execution date 실제 진입을 시뮬레이션한 거래일. 기본값은 next open입니다. ## 4. 진입 로직 v1 기본 진입 규칙: - 후보는 `decision_date = reaction_session_date` - `execution_date = next_trading_day` - 진입 가격은 `execution_date open` - open이 없으면 거래정지/누락 처리 후 skip ### 4.1 ranking - `final_candidate_score` 내림차순 - 동점이면 `liquidity_score` 우선 - 그래도 동점이면 `symbol` 오름차순으로 deterministic tie-break ### 4.2 eligibility filters - 유니버스 필터 통과 - 최소 가격 조건 통과 - 유동성 조건 통과 - 이미 동일 종목 포지션 보유 중이면 중복 진입 금지 - 같은 issuer에 같은 이벤트 cluster 중복 진입 금지 ## 5. 체결 근사 모델 ### 5.1 Entry fill model 기본: - price = next session open - slippage = bps + liquidity penalty - actual fill = open * (1 + slippage_bps/10000) 롱 기준 slippage는 양수입니다. ### 5.2 Exit fill model 지원 exit types: - take profit partial - stop loss - trailing exit by previous day low breach (daily approximation) - time exit at close of holding day N - regime forced exit ### 5.3 Stop handling on daily bars 무료 데이터 제약상 intraday exact path는 없으므로, daily bar 기반 근사 원칙을 고정합니다. 권장 규칙: - stop price가 당일 low 이하이면 stop triggered로 간주 - take-profit과 stop이 같은 날 동시에 충족되면 **보수적 우선순위**를 사용 - 보수적 기본값: long position은 stop이 먼저 체결된 것으로 간주 이 우선순위는 config로 바꿀 수 있으나 기본값은 보수적으로 둡니다. ## 6. 포지션 생명주기 ```text PLANNED → ENTERED → PARTIALLY_EXITED(optional) → OPEN → EXIT_PENDING(optional for close exits) → CLOSED → ARCHIVED ``` 필수 상태 필드: - `position_id` - `symbol` - `entry_date` - `entry_price` - `initial_stop` - `profit_target_1` - `current_stop` - `shares_open` - `days_held` - `exit_reason` ## 7. 시간청산 규칙 기본 전략은 1~5일 보유를 전제로 하므로 아래 rule set을 지원합니다. - `max_holding_days = 3` 또는 `5` - `close_on_day_n_close = true` - `weak_close_count_exit = 2` optional - `no_follow_through_exit = true` optional v1에서는 필수 규칙을 최소화합니다. ## 8. partial exit 규칙 예시 기본값: - +1.5R 도달 시 50% 청산 - 잔여분은 trailing rule 또는 time exit partial exit는 일별 bar 근사로 구현합니다. 동일 bar에서 partial target과 stop이 동시에 걸릴 수 있으므로, 우선순위 규칙을 명시적으로 config화해야 합니다. ## 9. 수수료/슬리피지 무료 데이터 환경에서는 보수적인 비용 가정이 중요합니다. 기본 파라미터 예시: - commission_per_share = 0.0 또는 broker-specific - min_commission = 0 - slippage_bps_base = 5 - slippage_bps_low_liquidity_penalty = 5~20 - gap risk adjustment optional v1에서는 모델 복잡도보다 **일관된 보수성**이 중요합니다. ## 10. 누락/결측 처리 다음 상황에서는 기본적으로 no-trade 또는 forced skip입니다. - execution_date open missing - non-tradable flag - corporate action mismatch - feature snapshot missing critical field - duplicate event cluster 모든 skip은 이유 코드와 함께 로그/리포트에 남겨야 합니다. ## 11. 주요 함수 시그니처 권장안 ```python run_experiment(manifest_path: str) -> ExperimentResult load_snapshot(snapshot_id: str) -> SnapshotBundle select_candidates(date: str, snapshot: SnapshotBundle, config: BacktestConfig) -> list[Candidate] allocate_portfolio(date: str, candidates: list[Candidate], portfolio_state: PortfolioState, config: BacktestConfig) -> list[PlannedOrder] simulate_entries(date: str, planned_orders: list[PlannedOrder], market_bars: MarketBars, config: BacktestConfig) -> list[FilledEntry] update_positions(date: str, positions: list[OpenPosition], market_bars: MarketBars, config: BacktestConfig) -> PositionUpdateResult compute_metrics(run_id: str, blotter: pd.DataFrame, equity_curve: pd.DataFrame) -> MetricsBundle ``` ## 12. deterministic 요구사항 - 동일 manifest + 동일 snapshot + 동일 코드 버전이면 결과가 같아야 합니다. - 랜덤 tie-break 금지 - floating rounding 정책 고정 - timezone handling 고정 - price lookup fallback 순서 고정