feat(execution): 체결가 0/None 시 지정가 백필 가드 + WARN 로깅 (dcy-032/BAT-048)

REQ-095(슬롯 평단·원장 price 지정가 백필, 0 오염 방지) + REQ-096(백필/미해결 WARN 로깅) 구현.
지정가 부재 시 skip+강WARN. application 단일화(_resolve_fill_price), wire/LedgerTrade 스키마 불변.
575 passed/0 fail, opus QC PASS. REQ-097 알람=스파이크 산출물만(impl 후속).

- _resolve_fill_price 헬퍼 추가: avg_price 검증, limit_price 백필, 미해결 WARN
- _on_filled/_on_partial_cancel 통합: 백필 적용, 슬롯/원장 일관성
- _record_ledger 시그니처: resolved_price 파라미터 (backward compat)
- test_execution_fill_price_backfill.py: 21 시나리오 (avg=None/0, limit=있음/없음 등)
- docs: dcy-032, req-095/096/097, bat-048 설계/검증

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
This commit is contained in:
2026-06-21 13:34:31 +09:00
co-authored by Claude Opus 4.8
parent d182464b7a
commit f1b649cf2a
15 changed files with 2134 additions and 11 deletions
+50 -2
View File
@@ -149,7 +149,54 @@ trex-core는 발행 연결의 idle 종료를 예방하기 위해 `trex-execution
---
## 5. 디버깅 / 트러블슈팅
## 5. 체결가 백필 WARN 발생 시 운영자 점검 절차 (REQ-095/096 / dcy-032 / BAT-048)
KIS 체결조회가 체결수량은 보고하면서 평균체결가(`avg`)를 None 또는 0으로 반환하는 순간 레이스가 발생하면, trex-core는 주문 지정가로 백필해 슬롯 평단·원장 cost basis에 0이 저장되지 않도록 보호하고 WARN 로그를 발행한다.
### WARN 유형 및 로그 식별
| WARN 유형 | `reason` | 발생 조건 | 의미 |
|---|---|---|---|
| `avg_price_backfill` | `reason=avg_price_backfill` | avg_price=None/0, limit_price>0 | 지정가로 백필 성공 — 값은 보정됐으나 추적 필요 |
| `fill_price_unresolvable` | `reason=fill_price_unresolvable` | avg_price=None/0, limit_price=None/≤0 | 백필 불가 — 체결 skip, 슬롯 PENDING 유지 |
```bash
# avg_price_backfill 확인 (백필 성공)
grep "avg_price_backfill" /tmp/trex-core.log
# fill_price_unresolvable 확인 (백필 불가, 운영자 즉시 개입)
grep "fill_price_unresolvable" /tmp/trex-core.log
```
Loki LogQL:
```logql
{job="trex-core"} | json | line_contains "avg_price_backfill"
{job="trex-core"} | json | line_contains "fill_price_unresolvable"
```
### `avg_price_backfill` WARN 발생 시 점검 항목
1. **로그에서 슬롯·주문 정보 확인**: `slot_id`, `symbol`, `broker_order_id`, `avg_price`(원 값, None 또는 0), `backfill_price`(대체 지정가)를 로그에서 추출.
2. **원장 레코드 확인**: 해당 `slot_id`의 `ledger_outbox` price가 백필값(지정가)으로 기록됐는지 확인.
```bash
psql "$TREX_POSTGRESQL_DSN" -c "SELECT * FROM ledger_outbox WHERE correlation_id LIKE '%<broker_order_id>%'"
```
3. **슬롯 평단 확인**: `trex-core slots` 또는 DB에서 해당 슬롯의 `avg_price`가 0이 아닌 지정가 기반 값인지 확인.
4. **판단**: 백필 지정가는 "0보다 합리적인 근사"지만 실제 체결가와 다를 수 있다. 중요한 포지션이라면 KIS HTS/API에서 실제 체결가를 확인해 `ledger_outbox` 수기 보정 여부를 검토한다(Discovery R-1).
### `fill_price_unresolvable` WARN 발생 시 즉시 조치
1. **로그에서 슬롯 정보 확인**: `slot_id`, `symbol`, `broker_order_id` 추출.
2. **슬롯 상태 확인**: `trex-core slots --status pending` — 해당 슬롯이 PENDING_BUY 상태로 고착됐는지 확인.
3. **KIS HTS/API에서 실제 체결 여부와 가격 확인**.
4. **운영자 조치 선택**:
- 체결됐다면: `trex-core cancel <slot_id>`로 슬롯을 롤백 후 재진입 매수.
- 미체결이라면: `trex-core cancel <slot_id>`로 주문 취소 → 슬롯 rollback(다음 폴링에서 is_terminal_reject 경로).
5. **상황이 반복된다면**: Discovery 또는 이슈 트래커에 기록 — `limit_price=None` 매수 주문이 들어온 경로 조사 필요(정상 운용 시 orb 전략은 지정가 주문을 제출한다, Discovery A-1).
---
## 6. 디버깅 / 트러블슈팅
| 증상 | 원인 | 조치 |
|---|---|---|
@@ -159,7 +206,8 @@ trex-core는 발행 연결의 idle 종료를 예방하기 위해 `trex-execution
| intent `rejected: capacity_full` | `max_active_slots`(M) 도달 | 매도 후 재시도 또는 M 상향 |
| 매도 미발생 + WARN `sell dispatch skipped` | 브로커 잔고 0(divergence) | reconcile(운영자) — 미러 fallback 없음(설계) |
| `RpcTimeoutError` | 데몬 미기동/소비 스레드 크래시 | `/tmp/trex-core.log` traceback 확인 |
| 미체결 `pending_*` 고착 | 취소 감지 누락/timeout 미설정 | `trex-core cancel <slot_id>` 백스톱; pending stale WARN 확인 |
| 미체결 `pending_*` 고착 | 취소 감지 누락/timeout 미설정/fill_price_unresolvable | `trex-core cancel <slot_id>` 백스톱; pending stale WARN + `fill_price_unresolvable` WARN 확인(§5) |
| `avg_price_backfill` WARN | KIS avg_price 순간 레이스(None/0 반환) | 슬롯 평단·원장 price는 지정가로 보정됨 — §5 점검 절차 참고 |
### DB 직접 조회
```bash