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:
@@ -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 없음.
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# req-083: KRX 호가단위(tick) 산출 유틸 — 가격→tick 순수 함수 + 배수 내림/올림
|
||||
|
||||
- Status: Implemented
|
||||
- Type: Integration
|
||||
- Priority: High
|
||||
- Owner: project maintainers
|
||||
|
||||
## Intent
|
||||
- 가격대별 KRX 주식 호가단위(2023.1.25~)를 산출하는 순수 함수와 tick 배수 내림/올림 헬퍼를 제공해, slippage 보호 지정가 스냅(REQ-070)과 이후 호가단위가 필요한 지점이 단일 검증된 유틸을 재사용하게 한다. (dcy-030 FR-1)
|
||||
|
||||
## Requirement
|
||||
- 가격(int/float, 원) → 해당 가격대의 KRX 주식 호가단위(tick, int)를 반환하는 순수 함수를 제공한다. 구간표(2023.1.25~):
|
||||
- `< 2,000` → 1
|
||||
- `2,000 ≤ p < 5,000` → 5
|
||||
- `5,000 ≤ p < 20,000` → 10
|
||||
- `20,000 ≤ p < 50,000` → 50
|
||||
- `50,000 ≤ p < 200,000` → 100
|
||||
- `200,000 ≤ p < 500,000` → 500
|
||||
- `≥ 500,000` → 1,000
|
||||
- 가격을 해당 tick의 **내림 배수**(`floor_to_tick`)와 **올림 배수**(`ceil_to_tick`)로 정규화하는 순수 헬퍼를 제공한다. 결과는 항상 tick의 정수배다.
|
||||
- 구간 경계는 하한 포함/상한 미만(`[lo, hi)`)으로 처리한다(예: 정확히 50,000원은 100원 tick).
|
||||
- 주식 전용이다. ETF/ETN 등 비주식 호가체계는 이번 범위 밖이며(dcy-030 OQ-1), 종목 유형 분기는 두지 않는다(known limitation: 2,000원 미만 비주식은 미지원).
|
||||
|
||||
## Acceptance Criteria
|
||||
1. Given 가격 1,500, When tick 산출, Then 1.
|
||||
2. Given 가격 3,000 / 12,000 / 35,000 / 147,700 / 300,000 / 800,000, When tick 산출, Then 각각 5 / 10 / 50 / 100 / 500 / 1,000.
|
||||
3. Given 경계값 2,000 / 5,000 / 20,000 / 50,000 / 200,000 / 500,000, When tick 산출, Then 각각 5 / 10 / 50 / 100 / 500 / 1,000 (하한 포함).
|
||||
4. Given 가격 148,439, When `floor_to_tick`, Then 148,400 (tick 100의 내림 배수).
|
||||
5. Given 가격 54,773, When `floor_to_tick`, Then 54,700; When `ceil_to_tick`, Then 54,800.
|
||||
6. Given 임의 가격 p, When `floor_to_tick(p)`/`ceil_to_tick(p)`, Then 결과는 tick(p)의 정수배이고 `floor ≤ p ≤ ceil`.
|
||||
|
||||
## Impacted Area
|
||||
- Docs: docs/runtime-flows.md(주문 집행 슬리피지 스냅 단계 — REQ-070 change와 공동), docs/project-structure.md(신규 tick 유틸 모듈 책임)
|
||||
- Modules: 신규 순수 모듈(후보: `trex_core/domain/pricing.py` 또는 `trex_core/application/tick_size.py` — design에서 확정), REQ-070 dispatch에서 소비
|
||||
- Tests: 신규 단위 테스트(각 가격 구간 경계값 + floor/ceil 불변식)
|
||||
|
||||
## Notes
|
||||
- 출처: dcy-030 FR-1, OQ-1(주식 전용).
|
||||
- tick 표는 단일 상수 테이블로 모아 KRX 제도 변경 시 변경 지점을 1곳으로 한다(dcy-030 R-3).
|
||||
- 모듈 배치는 순수 함수이므로 domain 계층 후보. 정확한 위치는 batch design에서 확정.
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260616-01
|
||||
- Date: 2026-06-16
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
- dcy-030 FR-1로부터 최초 생성(Proposed).
|
||||
|
||||
### CHG-20260616-02
|
||||
- Date: 2026-06-16
|
||||
- Author: Justin Ko
|
||||
- Type: status
|
||||
- Summary: Proposed → **Approved** (confirm-req). 필수 필드·구체 AC(경계값 6 + floor/ceil 불변식) 충족, source dcy-030(confirmed, Open OQ 0) 정합. REQ-070 change-req와 함께 dcy-030 단일 batch 편성 예정.
|
||||
|
||||
### CHG-20260616-03
|
||||
- Date: 2026-06-16
|
||||
- Author: Justin Ko
|
||||
- Type: status
|
||||
- Summary: Approved → **Implemented** (confirm-req-implemented, BAT-044 release-candidate, dcy-030). 근거: `trex_core/domain/pricing.py` 구현 + `tests/domain/test_pricing.py` 전 AC 매핑(대표값/경계값/floor·ceil 불변식). 47 passed(pricing+execution), 전체 516 passed/47 skipped 회귀 0, ruff clean. Blocking Issues 없음.
|
||||
@@ -0,0 +1,56 @@
|
||||
# req-085: 주문 결과 로깅 — submit/체결/거부 일관 tailable 로그
|
||||
|
||||
- Status: Implemented
|
||||
- Type: Integration
|
||||
- Priority: High
|
||||
- Owner: project maintainers
|
||||
|
||||
## Intent
|
||||
- 주문 결과(체결/거부)가 산발적 로그·DB(`slot_transition_log`)에만 남아 호가단위 거부(#28)를 수 시간 못 봤다. 모든 주문 submit과 그 결과(체결가·수량·수수료·세금, 또는 거부+사유)를 타임스탬프 달린 **일관(tailable) 로그 라인**으로 출력해 `grep`/`tail`로 당일 체결·거부를 즉시 전수 추출할 수 있게 한다. (dcy-029 FR-2)
|
||||
|
||||
## Requirement
|
||||
- 집행 루프(`execution.py`)의 주문 dispatch/poll 경로에서 다음 이벤트를 일관된 안정적 키를 가진 한 줄 로그로 남긴다:
|
||||
- **submit**: side, symbol, slot_id, qty, limit_price(또는 시장가), order_type.
|
||||
- **fill**: side, symbol, slot_id, 체결가, 체결수량, fee, tax.
|
||||
- **reject**: side, symbol, slot_id, 거부 사유.
|
||||
- 로그 라인은 안정적 키(예: `event=order_submit|order_fill|order_reject`, `symbol=`, `slot_id=`)를 포함해 grep 필터가 가능해야 한다. JSON 포맷(REQ-084) 시 동일 필드가 구조화 키로 나간다.
|
||||
- correlation id(REQ-084)가 설정돼 있으면 submit/fill/reject 로그가 같은 id를 공유한다.
|
||||
- 기존 슬롯 전이/원장 기록 동작은 변경하지 않는다(로그 추가만, additive).
|
||||
|
||||
## Acceptance Criteria
|
||||
1. Given 매수 주문 dispatch, When submit, Then `event=order_submit` + side/symbol/slot_id/qty/limit_price를 포함한 한 줄 로그가 남는다.
|
||||
2. Given 체결, When poll fill, Then `event=order_fill` + 체결가/체결수량/fee/tax를 포함한 로그가 남는다.
|
||||
3. Given 거부, When poll/ dispatch reject, Then `event=order_reject` + 사유를 포함한 로그가 남는다.
|
||||
4. Given `TREX_LOG_FORMAT=json`, When 위 이벤트, Then 해당 키들이 JSON 구조화 필드로 출력된다.
|
||||
5. Given 일련의 체결/거부, When `grep "event=order_reject"`, Then 당일 거부 주문을 전수 추출할 수 있다.
|
||||
6. Given correlation id 설정 컨텍스트, When submit→fill, Then 두 로그가 동일 correlation id를 가진다.
|
||||
|
||||
## Impacted Area
|
||||
- Docs: docs/runtime-flows.md(주문 집행 루프 — 결과 로깅)
|
||||
- Modules: trex_core/application/execution.py(`_dispatch`/`_poll`/`_on_filled`/`_rollback` 로깅), REQ-084 로깅 기반 소비
|
||||
- Tests: tests/application/test_execution* (submit/fill/reject 로그 caplog 단위)
|
||||
|
||||
## Notes
|
||||
- FR-4(거부 ERROR 레벨)는 REQ-087에서 다룬다. 본 REQ는 submit/fill/reject의 **일관 구조·가시성**(레벨 무관)에 집중하되, reject 로그 라인은 REQ-087과 동일 지점에서 협조한다.
|
||||
- 출처: dcy-029 FR-2.
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260616-01
|
||||
- Date: 2026-06-16
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
- dcy-029 FR-2로부터 최초 생성(Proposed).
|
||||
|
||||
### CHG-20260616-02
|
||||
- Date: 2026-06-16
|
||||
- Author: Justin Ko
|
||||
- Type: status
|
||||
- Summary: Proposed → **Approved** (confirm-req). 필수 필드·구체 AC 충족, source dcy-029(confirmed, Open OQ 0) 정합. dcy-029 P0/P1 batch 편성 예정.
|
||||
|
||||
### CHG-20260616-IMPL
|
||||
- Date: 2026-06-16
|
||||
- Author: Justin Ko
|
||||
- Type: status
|
||||
- Summary: Approved → **Implemented** (confirm-req-implemented, BAT-045 release-candidate, dcy-029 P0). 근거: 단위 AC 매핑(logging_setup text/json/KST/cid/idempotent, execution 주문 결과 submit/fill/reject + cid=slot_id + 거부 ERROR). 35 passed(logging+execution_policy), 전체 529 passed/47 skipped 회귀 0, ruff clean. 실 Loki 적재는 release 운영 검증 위임. Blocking Issues 없음.
|
||||
Reference in New Issue
Block a user