# 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 순서 고정
