feat(cli): enter --forced-close-at 시간 기반 자동청산 옵션 (issue #17, BAT-024)
trex-core enter 에 --forced-close-at <ISO8601>(절대시간) 옵션 추가. 지정 시 buy intent payload 의 기존 forced_close_at 에 매핑(계약·도메인· forced-close 루프 REQ-037 불변, CLI 노출 갭만 보완). 잘못된 형식은 제출 전 거부, 미지정 시 미포함(하위호환). 상대시간 옵션은 미채택. REQ-032 change-req(CHG-20260609-01/02) → BAT-024(batch-lite) → Implemented. 단위 353 passed, ruff clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# BAT-024 Implementation
|
||||
|
||||
- Status: completed
|
||||
- Batch: BAT-024
|
||||
|
||||
## Plan Summary
|
||||
- 운영자 CLI `enter` 에 `--forced-close-at <ISO8601>`(절대시간) 옵션을 추가하고, 지정 시 buy intent payload 의 기존 `forced_close_at` 필드에 매핑한다(REQ-032 AC-5/6). 잘못된 형식은 제출 전 거부, 미지정 시 payload 미포함(하위호환). 도메인/계약/forced-close 루프는 불변.
|
||||
|
||||
## Changed Files
|
||||
- `trex_core/runtime/cli.py`
|
||||
- `enter` 서브파서에 `--forced-close-at`(dest `forced_close_at`) 인자 추가.
|
||||
- `_cmd_enter`: 제출 **전** `datetime.fromisoformat` 으로 검증(실패 → `_fail` + rc=1), 정규화된 ISO8601 문자열을 `summary` 와(지정 시) payload `forced_close_at` 에 포함. 미지정 시 payload 키 미포함.
|
||||
- `tests/runtime/test_cli_operator.py`
|
||||
- `test_enter_with_forced_close_at_maps_to_payload`(AC-5), `test_enter_without_forced_close_at_omits_field`(AC-6 하위호환), `test_enter_rejects_malformed_forced_close_at_before_submit`(AC-6 형식 거부) 추가.
|
||||
- `docs/interface-contract.md`
|
||||
- buy payload `forced_close_at` 항목에 CLI `--forced-close-at` 노출 설명 추가, 운영자 CLI 매핑 표 `enter` 행에 `--forced-close-at` 반영.
|
||||
|
||||
## Execution Log
|
||||
1. `_build_parser` 의 `enter` 서브파서에 `--forced-close-at` 인자 추가(help: ISO8601 절대시각).
|
||||
2. `_cmd_enter` 에 제출 전 파싱·검증 + payload 조건부 포함 로직 추가(기존 `datetime` import 재사용).
|
||||
3. 단위 테스트 3종 추가.
|
||||
4. interface-contract 정합 갱신.
|
||||
5. `pytest tests/runtime/test_cli_operator.py` → 20 passed. 전체 회귀 `pytest`(KIS 샌드박스 env 테스트 deselect) → 353 passed / 35 skipped. `ruff check` → All checks passed.
|
||||
|
||||
## Validation
|
||||
- 단위: `tests/runtime/test_cli_operator.py` 20 passed(신규 3종 포함).
|
||||
- 회귀: 전체 단위 353 passed, 35 skipped(인프라 게이트 `TREX_POSTGRESQL_TEST_DSN`/`TREX_RABBITMQ_TEST_URL` 미설정), 1 deselected(KIS 샌드박스 env, 본 batch 무관).
|
||||
- lint: `ruff check trex_core/runtime/cli.py tests/runtime/test_cli_operator.py` All passed.
|
||||
|
||||
## Remaining Risks
|
||||
- naive(타임존 없는) timestamp 입력 시 핸들러 `_opt_dt`(`datetime.fromisoformat`) 규약을 그대로 따른다 — CLI 는 노출·형식 검증만 담당하고 타임존 정규화는 도입하지 않음(범위 밖). 운영자가 KST 의도를 명확히 하려면 오프셋 포함(`+09:00`) 권장(help 예시에 명시).
|
||||
- 실 데몬 e2e(enter → admitted 슬롯 `forced_close_at` 설정 → REQ-037 만기 매도)는 verification 에서 다룬다.
|
||||
@@ -0,0 +1,33 @@
|
||||
# BAT-024: 운영자 `enter` 시간 기반 자동청산 옵션 — `--forced-close-at` (절대시간) 노출 (issue #17)
|
||||
|
||||
- Status: release-candidate
|
||||
- Profile: batch-lite
|
||||
- Source Discovery: dcy-016 (issue #17 change-req)
|
||||
- Included REQ: REQ-032 (CHG-20260609-01)
|
||||
- Excluded REQ: 없음
|
||||
|
||||
## Summary
|
||||
- issue #17(`trex-core enter` 에 시간 기반 자동청산 옵션 부재) 를 batch-lite 로 delivery 한다. `enter` 는 `--stop-loss`/`--take-profit` 만 노출하고 `forced_close_at` 을 넣을 인자가 없어, 시간 기반 청산은 별도 수동/스케줄 `exit` 가 필요했다.
|
||||
- 핵심: `trex_core/runtime/cli.py` `_cmd_enter` 에 **`--forced-close-at <ISO8601>` (절대시간)** 옵션 추가 + buy intent payload 에 파싱된 `forced_close_at` 매핑. buy intent 계약(REQ-007/021, 핸들러가 이미 `forced_close_at` optional 수용)·도메인(`Slot.forced_close_at`, REQ-066)·forced-close 만기 매도 루프(REQ-037)는 **이미 구현·불변** — 본 batch 는 CLI 노출 갭만 보완한다. 상대시간(`--hold-minutes`)은 미채택(사용자 결정).
|
||||
|
||||
## Scope
|
||||
- In Scope:
|
||||
- `_cmd_enter` 에 `--forced-close-at` 인자 추가(절대시간, ISO8601 파싱) + 잘못된 형식 제출 전 거부 (REQ-032 AC-5/6)
|
||||
- buy intent payload 에 지정 시 `forced_close_at` 포함, 미지정 시 미포함(하위호환)
|
||||
- `docs/interface-contract.md` enter 옵션 표 + payload `forced_close_at` 노출 정합
|
||||
- `tests/runtime/test_cli_operator.py` enter `--forced-close-at` payload 매핑·형식 거부·미지정 회귀
|
||||
- Out of Scope:
|
||||
- 상대시간 옵션(`--hold-minutes`) — 미채택
|
||||
- buy intent 계약/도메인/forced-close 루프 변경(이미 구현·불변)
|
||||
- `exit`/`update_thresholds` 의 forced_close_at 노출(별도 주제)
|
||||
|
||||
## Documents
|
||||
- Planning: [planning.md](./planning.md)
|
||||
- Design: none (batch-lite with no architecture impact)
|
||||
- Implementation: [implementation.md](./implementation.md)
|
||||
- Verification: [verification.md](./verification.md)
|
||||
|
||||
## Notes
|
||||
- Profile **batch-lite**: 단일 파일(`cli.py`) 국소 변경 + 테스트/문서, 신규 계약/도메인/런타임 흐름 없음(기존 `forced_close_at` 경로 재사용), DB/스레드/마이그레이션 없음 → **Design Gate: no**(design.md 불요).
|
||||
- REQ-032 는 CHG-20260609-01 로 **Approved**(신규 AC-5/6 미구현) 상태. 본 batch release-candidate + confirm-req-implemented 로 **Implemented** 복귀 예정.
|
||||
- 발견 근거: GitHub issue #17.
|
||||
@@ -0,0 +1,33 @@
|
||||
# BAT-024 Planning
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-024
|
||||
- Profile: batch-lite
|
||||
|
||||
## Included REQ
|
||||
- REQ-032 (CHG-20260609-01) — 운영자 `enter` `--forced-close-at` (절대시간) 옵션
|
||||
|
||||
## Delivery Plan
|
||||
1. `trex_core/runtime/cli.py` `_build_parser` 의 `enter` 서브파서에 `--forced-close-at`(dest `forced_close_at`, ISO8601 문자열) 인자 추가.
|
||||
2. `_cmd_enter` 에서 `args.forced_close_at` 가 주어지면 `datetime.fromisoformat` 으로 파싱(잘못된 형식은 제출 전 `_fail` + 비정상 종료), buy intent payload 에 `forced_close_at`(ISO8601 문자열)으로 포함. 미지정 시 payload 에 키 미포함(하위호환).
|
||||
3. `docs/interface-contract.md` 의 enter 옵션 목록/매핑에 `--forced-close-at` 추가.
|
||||
4. `tests/runtime/test_cli_operator.py` 테스트 추가/갱신.
|
||||
|
||||
## Dependencies
|
||||
- Internal:
|
||||
- buy intent 핸들러 `_handle_buy_intent`(messaging_handlers.py:87) 가 이미 `forced_close_at` 수용 — 변경 불필요.
|
||||
- 도메인 `Slot.forced_close_at`(REQ-066), forced-close 만기 매도 루프(REQ-037) — 변경 불필요.
|
||||
- External:
|
||||
- 없음.
|
||||
|
||||
## Design Gate
|
||||
- Design Required Before Implementation: no
|
||||
- Reason: 단일 파일(`cli.py`) 국소 변경. 신규 계약 필드/도메인/런타임 흐름 없음(기존 `forced_close_at` 경로 재사용). 구조·인터페이스 구조 변경 없음(옵션 additive). 설계 판단이 change-req(CHG-20260609-01) AC-5/6 에 완비.
|
||||
|
||||
## Milestones
|
||||
1. CLI 인자 + payload 매핑 + 형식 검증 구현.
|
||||
2. 단위 테스트(payload 매핑/형식 거부/미지정 회귀) green + interface-contract 정합.
|
||||
|
||||
## Risks
|
||||
- R-1: timestamp 파싱 모호성(타임존 없는 naive vs aware). 대응: `datetime.fromisoformat` 로 파싱하되 payload 에는 입력 문자열을 ISO8601 로 전달(핸들러 `_opt_dt` 와 동일 규약). naive/aware 처리는 기존 핸들러 규약을 따른다 — 본 batch 는 CLI 노출만 담당.
|
||||
- R-2: 잘못된 형식이 데몬까지 가서 admission 실패로 나타나는 회귀. 대응: CLI 제출 **전** 파싱 검증 후 거부(AC-6).
|
||||
@@ -0,0 +1,39 @@
|
||||
# BAT-024 Verification
|
||||
|
||||
- Status: approved
|
||||
- Batch: BAT-024
|
||||
- Profile: batch-lite
|
||||
|
||||
## Acceptance Mapping
|
||||
|
||||
### REQ-032 (CHG-20260609-01) — 운영자 `enter --forced-close-at`
|
||||
|
||||
| AC | 검증 | Evidence |
|
||||
|----|------|----------|
|
||||
| AC-5: `--forced-close-at <ISO8601>` → buy intent payload `forced_close_at` 매핑 | `enter PF-1 --forced-close-at 2026-06-09T15:20:00+09:00 --yes` → payload `forced_close_at` 동일값 | `tests/runtime/test_cli_operator.py::test_enter_with_forced_close_at_maps_to_payload` → PASS |
|
||||
| AC-6a: 미지정 시 payload 미포함(하위호환) | `enter PF-1 --yes` → payload 에 `forced_close_at` 키 없음 | `::test_enter_without_forced_close_at_omits_field` → PASS |
|
||||
| AC-6b: 잘못된 형식 제출 전 거부 | `enter PF-1 --forced-close-at notatime --yes` → rc=1, RPC 미호출 | `::test_enter_rejects_malformed_forced_close_at_before_submit` → PASS |
|
||||
|
||||
### 회귀 (기존 AC-1~4 + enter/exit/universe/slots 불변)
|
||||
- `tests/runtime/test_cli_operator.py` 20 passed(기존 17 + 신규 3) — pause/resume/universe/slots/enter/exit/안전장치 전부 green.
|
||||
- 전체 단위 회귀 353 passed / 35 skipped / 1 deselected.
|
||||
|
||||
## 다운스트림 경로 (이미 구현·검증, 본 batch 무변경)
|
||||
- buy intent 핸들러가 payload `forced_close_at` 수용: `trex_core/application/messaging_handlers.py:87`(`_opt_dt`) — REQ-007/021, 기존 구현.
|
||||
- 도메인 `Slot.forced_close_at`(REQ-066) + forced-close 만기 매도 루프(REQ-037, BAT-010/011 검증 완료). 본 batch 는 CLI 노출만 추가하므로 다운스트림 재검증 불요.
|
||||
|
||||
## 검증 명령/결과
|
||||
- `pytest tests/runtime/test_cli_operator.py -q` → **20 passed**.
|
||||
- `pytest -q --deselect tests/adapters/broker/test_kis.py::test_sandbox_token_and_submit` → **353 passed, 35 skipped, 1 deselected**.
|
||||
- `ruff check trex_core/runtime/cli.py tests/runtime/test_cli_operator.py` → All checks passed.
|
||||
|
||||
## Baseline 문서 동기화
|
||||
- `docs/interface-contract.md` — buy payload `forced_close_at` 의 CLI `--forced-close-at` 노출 + 운영자 CLI 매핑 표 `enter` 행 갱신. (runtime-flows 는 흐름 무변경 — forced-close 경로 기존 그대로, 추가 갱신 불요.)
|
||||
|
||||
## 검증 결과 요약
|
||||
- AC-5/6 전부 단위 PASS, 기존 AC·명령 회귀 green, baseline 정합 완료.
|
||||
- **Ready for Release: true.**
|
||||
|
||||
## 미검증/한계
|
||||
- 실 데몬 + LIVE/paper full-stack e2e(enter `--forced-close-at` → admitted 슬롯 `forced_close_at` 설정 → REQ-037 만기 시 시장가 청산)는 본 batch 의 책임 경계(=올바른 payload 필드 방출) 밖이며, 다운스트림(REQ-037)은 BAT-010/011 에서 이미 검증됨. CLI 책임은 payload 매핑 단위로 직접 커버.
|
||||
- pytest 인프라 게이트(`TREX_POSTGRESQL_TEST_DSN`/`TREX_RABBITMQ_TEST_URL`)는 본 변경과 무관(CLI 는 RPC 주입 단위 테스트, DB/브로커 불요) — skipped 35 는 본 batch 범위 밖.
|
||||
@@ -31,3 +31,4 @@
|
||||
| BAT-021 | standard | release-candidate | REQ-034 (CHG-20260608-02), REQ-021 (CHG-20260608-03), REQ-037 (CHG-20260608-01), REQ-038 (CHG-20260608-01), REQ-039 (CHG-20260608-01), REQ-032 (CHG-20260608-02), REQ-042 (CHG-20260608-01) | dcy-017 | [bat-021](./bat-021_20260608_watchdog-ownership-visibility/) |
|
||||
| BAT-022 | standard | release-candidate | REQ-028 (CHG-20260608-01), REQ-032 (CHG-20260608-03) | dcy-016 | [bat-022](./bat-022_20260608_slot-state-snapshot-visibility/) |
|
||||
| BAT-023 | standard | release-candidate | REQ-017 (CHG-20260608-02), REQ-019 (CHG-20260608-02), REQ-029 (CHG-20260608-01), REQ-030 (CHG-20260608-01, Approved→Implemented) | dcy-018 | [bat-023](./bat-023_20260608_ledger-cash-position-writeback-option-d/) |
|
||||
| BAT-024 | batch-lite | release-candidate | REQ-032 (CHG-20260609-01) | dcy-016 (issue #17) | [bat-024](./bat-024_20260609_operator-enter-forced-close-at/) |
|
||||
|
||||
Reference in New Issue
Block a user