feat(execution,obs): 호가단위 스냅(dcy-030/#28) + 운영 관측성(dcy-029/#29) + REL-007 환류

dcy-030 (issue #28, P0 체결 복구):
- REQ-083 KRX 호가단위(tick) 순수 유틸 (domain/pricing.py)
- REQ-070 CHG: 슬리피지 실효가 호가단위 스냅 — 상한 준수(매수 floor/매도 ceil)
  + band<1tick 하한/상한 보장 + 부동소수 epsilon 가드. 147700→148400/54500→54700.
- BAT-044 release-candidate

dcy-029 (issue #29, 운영 관측성):
- REQ-084 구조화 로깅 기반 (runtime/logging_setup.py): ISO8601 KST·레벨·Loki JSON
  ·correlation id(slot_id)
- REQ-085 주문 결과 로깅 (event=order_submit|fill|reject)
- REQ-087 거부 ERROR 가시성 (조용한 롤백 제거)
- REQ-086 청산 reason+actor 동반 노출 (안전망/전략 구분, 신규 필드 없음)
- REQ-088 운영 조회 CLI report (application/operator_report.py, 보유·오늘 체결
  ·실현 PnL net·대기, direct-DB)
- REQ-089 운영자 런북 docs/operations.md
- REQ-090 baseline 정합 (runtime-flows·project-structure·interface-contract)
- BAT-045(P0)·BAT-046(P1) release-candidate

REL-007: confirmed → feedback-captured (운영 인스턴스 라이브 검증 + #28/#29 환류
정규화). BAT-037~041 released.

검증: 537 passed, 47 skipped, 회귀 0, ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-16 13:19:31 +09:00
co-authored by Claude Opus 4.8
parent e0d4e71304
commit d8d9021260
48 changed files with 2461 additions and 58 deletions
@@ -9,25 +9,30 @@
- `slippage_pct` 가 지정된 주문을 dispatch 할 때 `entry_price*(1±pct)` 실효 지정가로 제출해 과도한 불리 체결을 막는다. `entry_price` 없으면 무시(순수 시장가는 슬리피지 기준 없음). (dcy-022 FR-3)
## Requirement
- `_dispatch` 가 슬롯의 `slippage_pct` 를 읽어 실효 지정가를 산출한다: 매수 = `limit_price * (1 + slippage_pct/100)` (올림, ceil), 매도 = `limit_price * (1 - slippage_pct/100)` (내림, floor). `limit_price`(= `entry_price`)가 None 이면 `slippage_pct` 를 무시하고 시장가 주문 제출(기존 동작 보존). KRX 호가단위 반올림은 체결 보장 방향(매수=상위 단위, 매도=하위 단위)으로 적용한다(단, 테스트에서는 간단한 int() 사용 가능 — 정밀 호가단위 테이블은 후속). `slippage_pct` 없는 슬롯은 기존 `limit_price` 직접 사용(회귀 없음).
- `_dispatch` 가 슬롯의 `slippage_pct` 를 읽어 실효 지정가를 산출한 뒤 **KRX 호가단위(REQ-083)로 스냅**한다. 방향은 **상한 준수**: 매수 = `floor_to_tick(limit_price * (1 + slippage_pct/100))` (내림 — slippage 상한 미초과), 매도 = `ceil_to_tick(limit_price * (1 - slippage_pct/100))` (올림 — 매도 하한 미초과). band<1tick 엣지에서 미체결을 막기 위해 하한/상한을 보장한다: 매수 실효가 = `max(floor_to_tick(...), ceil_to_tick(limit_price))`, 매도 실효가 = `min(ceil_to_tick(...), floor_to_tick(limit_price))`. `limit_price`(= `entry_price`)가 None 이면 `slippage_pct` 를 무시하고 시장가 주문 제출(기존 동작 보존, 스냅 없음). `slippage_pct` 없는 슬롯은 기존 `limit_price` 직접 사용(회귀 없음). 주식 전용 — ETF/ETN 비주식 호가체계는 범위 밖(dcy-030 OQ-1).
## Acceptance Criteria
1. Given 슬롯에 `limit_price=10000`, `slippage_pct=1.0`, side=buy, When dispatch, Then OrderRequest.limit_price = `ceil(10000*1.01)` = 10100.
2. Given 슬롯에 `limit_price=10000`, `slippage_pct=1.0`, side=sell, When dispatch, Then OrderRequest.limit_price = `floor(10000*0.99)` = 9900.
3. Given `limit_price=None`(시장가), `slippage_pct=1.0`, When dispatch, Then OrderRequest.limit_price = None(시장가 — slippage 무시).
1. Given 슬롯에 `limit_price=10000`, `slippage_pct=1.0`, side=buy, When dispatch, Then OrderRequest.limit_price = `floor_to_tick(10000*1.01)` = `floor_to_tick(10100)` = 10100 (상한 준수 내림).
2. Given 슬롯에 `limit_price=10000`, `slippage_pct=1.0`, side=sell, When dispatch, Then OrderRequest.limit_price = `ceil_to_tick(10000*0.99)` = `ceil_to_tick(9900)` = 9900 (하한 준수 올림).
3. Given `limit_price=None`(시장가), `slippage_pct=1.0`, When dispatch, Then OrderRequest.limit_price = None(시장가 — slippage·스냅 무시).
4. Given `slippage_pct=None`, `limit_price=10000`, When dispatch, Then OrderRequest.limit_price = 10000(기존 동작, 회귀 없음).
5. Given `slippage_pct=0.0`, When dispatch, Then 실효 지정가 = limit_price(0% 슬리피지 = 기준가 그대로).
5. Given `slippage_pct=0.0`, When dispatch, Then 실효 지정가 = `floor_to_tick(limit_price)`(0% 슬리피지 = 기준가의 유효 호가).
6. Given 기존 매수/매도 슬롯(slippage_pct 없음), When dispatch, Then 기존 동작과 동일(전체 회귀 없음).
7. Given `limit_price=147700`, `slippage_pct=0.5`, side=buy, When dispatch, Then `floor_to_tick(148438.5)` = 148400 (issue #28 케이스, 유효 호가·상한 미초과).
8. Given `limit_price=54500`, `slippage_pct=0.5`, side=buy, When dispatch, Then `floor_to_tick(54772.5)` = 54700 (issue #28 케이스).
9. Given band<1tick (예: `limit_price=147700`, `slippage_pct=0.01`, buy), When dispatch, Then 매수 실효가 = `max(floor_to_tick(147714.77), ceil_to_tick(147700))` ≥ entry 유효 호가 (미체결 방지), 그리고 slippage 상한 미초과.
10. Given 임의 매수/매도 + slippage, When dispatch, Then 결과는 유효 KRX 호가단위 배수이고 매수 ≤ `limit*(1+pct/100)` / 매도 ≥ `limit*(1-pct/100)`.
## Impacted Area
- Docs: docs/runtime-flows.md(slippage dispatch 흐름)
- Modules: trex_core/application/execution.py(`_dispatch` slippage 계산)
- Tests: tests/application/test_execution.py(slippage dispatch 단위)
- Docs: docs/runtime-flows.md(slippage dispatch + 호가단위 스냅 흐름), docs/project-structure.md(신규 tick 유틸 — REQ-083)
- Modules: trex_core/application/execution.py(`_dispatch` slippage 계산 + 호가단위 스냅), REQ-083 tick 유틸 소비
- Tests: tests/application/test_execution.py(slippage dispatch + 스냅 방향·경계·band<tick 하한 보장 단위)
## Notes
- `entry_price` = `limit_price` 로 슬롯에 저장되는 설계를 그대로 사용(REQ-013 CHG-20260608-01 선례). 슬리피지는 dispatch-time 변환만 — 슬롯의 `limit_price` 자체를 덮어쓰지 않는다.
- KRX 정밀 호가단위 반올림(500원 미만 → 1원, 500~2000 → 5원 등)은 후속 주기. 현재 구현은 int(math.ceil/floor) 로 충분(테스트 범위 내).
- 출처: dcy-022 FR-3, OQ-2.
- (CHG-20260616-01, dcy-030) KRX 정밀 호가단위 스냅을 REQ-083 유틸로 구현. 반올림 방향 = 상한 준수(매수 내림/매도 올림). 원래 메모의 "체결 보장(매수 상위/매도 하위)" 방향에서 전환 — `slippage_pct`를 리스크 상한으로 취급하기 때문(상한 1틱 초과 방지). band<1tick 하한/상한 보장으로 미체결 방지.
- 주식 전용. ETF/ETN 비주식 호가체계 및 2,000원 미만 비주식은 미지원(dcy-030 OQ-1, known limitation).
- 출처: dcy-022 FR-3, OQ-2 / dcy-030 FR-2·2b·3·4.
## Change Log
@@ -87,3 +92,52 @@
- Author: Justin Ko
- Type: status
- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-032(release-candidate) verification — 실행 정책(timeout_sec·slippage_pct) 계약·집행·운영자 cancel 백스톱 전 AC acceptance mapping 존재(신규 71 tests), 단위 425 passed 회귀 없음, baseline 2문서 수동 확인(M5). Blocking Issues 없음.
### CHG-20260616-01
- Date: 2026-06-16
- Author: Justin Ko
#### Change Summary
- slippage 실효 지정가에 **KRX 호가단위 스냅**을 추가하고, 반올림 방향을 원래 메모의 "체결 보장(매수=상위/매도=하위)"에서 **"상한 준수(매수=하위/매도=상위)"** 로 전환한다. band<1tick 엣지에 하한(매수)/상한(매도) 보장을 추가한다. (dcy-030 FR-2/FR-2b/FR-3/FR-4)
#### Intent
- 현재 구현은 `int(ceil/floor)`만 적용하고 KRX 호가단위로 스냅하지 않아, slippage_pct>0인 매수 한계가가 호가단위를 위반해 KIS `주식주문호가단위 오류`로 전량 거부됨(issue #28). 호가단위 스냅으로 거부를 제거하되, `slippage_pct`를 리스크 상한으로 취급해 스냅이 상한을 넘기지 않도록 매수=내림/매도=올림으로 정정한다.
#### Acceptance Criteria Delta
- Updated:
- AC-1: 매수 = `floor_to_tick(limit*(1+pct/100))` (상한 준수 내림). 예: `limit=10000,pct=1.0` → `floor_to_tick(10100)=10100`.
- AC-2: 매도 = `ceil_to_tick(limit*(1-pct/100))` (하한 준수 올림). 예: `limit=10000,pct=1.0` → `ceil_to_tick(9900)=9900`.
- Added:
- AC-7: `limit=147700,pct=0.5,buy` → `floor_to_tick(148438.5)=148400` (issue #28 케이스).
- AC-8: `limit=54500,pct=0.5,buy` → `floor_to_tick(54772.5)=54700` (issue #28 케이스).
- AC-9 (band<1tick 하한 보장): 매수 실효가 = `max(floor_to_tick(limit*(1+pct/100)), ceil_to_tick(limit))`; 매도 실효가 = `min(ceil_to_tick(limit*(1-pct/100)), floor_to_tick(limit))`. slippage<1tick이어도 최소 entry 유효 호가 보장.
- AC-10: 스냅 결과는 항상 유효 호가단위 배수이고, 매수 ≤ `limit*(1+pct/100)` / 매도 ≥ `limit*(1-pct/100)` (상한·하한 미초과).
- Removed:
- (없음 — AC-3·4·5·6 시장가/무slippage/0% 회귀 기준 유지)
#### Impacted Area Delta
- Docs:
- docs/runtime-flows.md(주문 집행 슬리피지 dispatch에 호가단위 스냅 단계 추가), docs/project-structure.md(신규 tick 유틸 — REQ-083)
- Modules:
- trex_core/application/execution.py(`_dispatch` 스냅 적용), REQ-083 tick 유틸 소비
- Tests:
- tests/application/test_execution.py(슬리피지 스냅 방향·경계·band<tick 하한 보장), REQ-083 tick 단위 테스트
#### Delivery Trace Delta
- Batches: BAT-032(REQ-070 최초 구현) — 본 변경은 신규 batch(dcy-030)에서 재구현·재검증.
- Releases: REL-006(BAT-032 released) — 운영 영향(매수 거부 해소)은 dcy-030 batch 배포 시 신규 release로 추적.
#### Revalidation Impact
- Reverification Needed: yes
- Existing Implementation Invalidated: yes (tick 스냅 부재 → 매수 거부; 반올림 방향도 변경)
- Status Recommendation: revert-to-approved
- Follow-up Action: dcy-030 batch에서 REQ-083 + REQ-070 재구현·검증 후 confirm-req-implemented로 재전환.
#### Validation Plan
- REQ-083 tick 유틸 단위 + execution.py 슬리피지 스냅 방향/경계/band<tick 하한 보장 단위 + 기존 AC-3~6 회귀.
### CHG-20260616-IMPL
- Date: 2026-06-16
- Author: Justin Ko
- Type: status
- Summary: Approved → **Implemented** (confirm-req-implemented, BAT-044 release-candidate, dcy-030/issue #28). 근거: `execution.py` `_dispatch` 호가단위 스냅 구현(상한 준수 매수 floor/매도 ceil + band<tick 보장 + epsilon 가드, REQ-083 소비). AC-1·2·5·7·8·9·10 단위 매핑(issue #28 케이스 147700→148400/54500→54700, 35000@0.7%→35200, band<tick), AC-3·4·6 회귀. 47 passed(pricing+execution_policy), 전체 516 passed/47 skipped 회귀 0, ruff clean. Blocking Issues 없음.