feat(safety-sell): dcy-007 core 자율 안전 매도 — BAT-010 기반 + BAT-011 트리거
dcy-007(core 자율 안전 매도) 전체 delivery. BAT-010 기반 인프라가 미커밋 상태였고 BAT-011 트리거 레이어와 공유 파일에서 섞여 있어 한 커밋으로 묶음. BAT-010 (REQ-034/035/036/041, release-candidate): - WatchdogStore(application/watchdog.py) + snapshot RPC 워치독 갱신 - Slot stop_loss_price/take_profit_price 필드 + 004_add_price_thresholds.sql - SlotService.sell() 자율 매도 내부 경로 + idempotency - SafetySellConfig env 5종 파싱 BAT-011 (REQ-037/038/039/040/042, release-candidate): - SafetySellService(application/safety_sell.py): forced_close 만기 / stop-loss· take-profit KIS polling / 장 마감 강제 청산 + trex-safety-sell 7번째 스레드 - BrokerPort.get_current_price + KIS inquire-price(TTTC8397R/VTTC8397R, stck_prpr) + paper stub - SlotService.update_thresholds() + intent RPC update_thresholds 액션 (부분 갱신/null 해제, 워치독 last_seen_at 리셋) - baseline 3문서 정합(interface-contract/runtime-flows/project-structure) 거버넌스: REQ-034~042 Implemented 동기화, BAT-010/011 release-candidate, dcy-007 discovery 포함. 검증: 신규 38 테스트 green, 단위 236 passed/23 skipped, 인프라 게이트(PG+RabbitMQ) 259 passed(마이그레이션 001~004 round-trip 포함), ruff clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
# BAT-010 Design
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-010
|
||||
|
||||
## Architecture Summary
|
||||
BAT-010은 safety-sell 기능의 기반 레이어 세 가지를 추가한다.
|
||||
|
||||
1. **WatchdogStore** (`application/watchdog.py` 신규): `dict[str, datetime]` in-memory store. snapshot RPC handler가 응답 슬롯의 `last_seen_at`을 갱신하고 BAT-011 트리거 루프가 만료 판정에 사용한다.
|
||||
2. **도메인 필드 확장** (`models.py`, `postgresql.py`, SQL 마이그레이션): `stop_loss_price`, `take_profit_price` `Optional[Decimal]` additive 추가. BAT-011 KIS polling이 트리거 기준으로 사용한다.
|
||||
3. **SlotService.sell()** (`domain/slot/service.py`): RabbitMQ 우회 내부 매도 경로. BAT-011의 모든 트리거 루프(forced_close, KIS polling, 장 마감)가 이 메서드를 호출한다.
|
||||
|
||||
## Changed Areas
|
||||
- Docs:
|
||||
- 없음 (baseline 문서 갱신은 REQ-042 / BAT-011에서 처리)
|
||||
- Modules:
|
||||
- `trex_core/runtime/config.py` — `DaemonConfig`에 safety-sell env vars 5종 파싱 추가
|
||||
- `trex_core/domain/slot/models.py` — `stop_loss_price: Optional[Decimal]`, `take_profit_price: Optional[Decimal]` 추가
|
||||
- `trex_core/adapters/persistence/postgresql.py` — 두 필드 read/write 포함
|
||||
- `trex_core/adapters/persistence/sql/` — 마이그레이션 스크립트 신규 추가
|
||||
- `trex_core/application/intents.py` — `IntentIn`에 `stop_loss_price`, `take_profit_price` Optional 필드 추가
|
||||
- `trex_core/application/messaging_handlers.py` — `make_snapshot_rpc_handler` 확장(`last_seen_at` 갱신) + intent RPC payload 파싱 확장(price fields)
|
||||
- `trex_core/application/watchdog.py` (신규) — `WatchdogStore` class
|
||||
- `trex_core/domain/slot/service.py` — `SlotService.sell(slot_id, actor, reason)` 추가
|
||||
- Interfaces:
|
||||
- `SlotService.sell(slot_id: str, actor: str, reason: str) -> None` (신규 내부 인터페이스)
|
||||
- `WatchdogStore.update(slot_id: str) -> None` (신규)
|
||||
- `WatchdogStore.is_expired(slot_id: str, timeout_s: int) -> bool` (신규 — BAT-011 트리거 루프 계약)
|
||||
- `WatchdogStore.in_grace_period(grace_s: int) -> bool` (신규)
|
||||
|
||||
## Key Decisions
|
||||
1. **WatchdogStore를 `watchdog.py` 신규 모듈로 분리**: `portfolio.py`는 snapshot 조회·직렬화 책임이 있어 watchdog 관심사(타임스탬프 추적·만료 판정)를 추가하면 단일 책임 위반. 신규 모듈이 독립 테스트와 BAT-011 참조에 유리하다.
|
||||
2. **REQ-041 env vars 5종 전체를 BAT-010에서 파싱 완성**: `TREX_FORCED_CLOSE_CHECK_INTERVAL`·`TREX_STOP_LOSS_POLL_INTERVAL`·`TREX_MARKET_CLOSE_LIQUIDATION_TIME`은 BAT-010에서 사용하지 않지만, REQ-041을 두 batch에 분리하면 REQ 단위 거버넌스 trace가 깨진다. 파싱만 완성하고 BAT-011이 config 값을 참조한다.
|
||||
3. **WatchdogStore 동시성은 Python GIL에 의존**: `dict[str, datetime]` 단일 dict의 Python 레벨 read/write는 GIL 보장 하에 원자적. 별도 `threading.Lock` 불필요. grace period 판정: `datetime.utcnow() - self.started_at < timedelta(seconds=grace_s)` 단순 비교.
|
||||
4. **SlotService.sell()은 transit_slot() 재사용**: 기존 `transit_slot(slot_id, from_state=HOLDING, to_state=PENDING_SELL, ...)` advisory lock + outbox 경로를 그대로 재사용. 신규 로직을 최소화해 BAT-004 검증 coverage를 그대로 이어받는다.
|
||||
|
||||
## Edge Cases
|
||||
- grace period 중 `is_expired()` 호출: `in_grace_period()` True이면 store에 없는 슬롯 포함 전체 만료 판정 유예.
|
||||
- 데몬 재시작 시 watchdog store: in-memory 초기화 + `started_at` 재설정으로 grace period 자동 재시작.
|
||||
- `sell()` 호출 시 이미 `pending_sell` 상태: `transit_slot` 호출 전 상태 조회 → silent return (중복 매도 intent 제출 없음).
|
||||
- `sell()` 호출 시 `idle`·`cancelled` 등 비정상 상태: `SlotStateError`(또는 동등 예외) raise, 슬롯 상태 불변.
|
||||
- 마이그레이션 기존 row: `ALTER TABLE slots ADD COLUMN IF NOT EXISTS stop_loss_price NUMERIC` 패턴으로 멱등성 보장. 기존 row `NULL` 유지.
|
||||
|
||||
## Architecture Impact
|
||||
- runtime-flows: snapshot RPC handler → watchdog `last_seen_at` 갱신 흐름 추가 (BAT-011 REQ-042에서 문서화)
|
||||
- project-structure: `watchdog.py` 신규 모듈 추가 (BAT-011 REQ-042에서 문서화)
|
||||
|
||||
## Reference Doc Update Plan
|
||||
- project-structure.md: BAT-011 REQ-042에서 처리 (`watchdog.py` 신규 모듈 반영)
|
||||
- runtime-flows.md: BAT-011 REQ-042에서 처리 (snapshot RPC → watchdog update + sell() 내부 경로 반영)
|
||||
@@ -0,0 +1,43 @@
|
||||
# BAT-010 Implementation
|
||||
|
||||
- Status: completed
|
||||
- Batch: BAT-010
|
||||
|
||||
## Plan Summary
|
||||
- REQ-041 → REQ-035 → REQ-034 → REQ-036 순서로 구현. 각 단계 unit test green 확인 후 다음으로 진행.
|
||||
|
||||
## Changed Files
|
||||
|
||||
### 신규 파일
|
||||
- `trex_core/application/watchdog.py` — WatchdogStore class (REQ-034)
|
||||
- `trex_core/adapters/persistence/sql/004_add_price_thresholds.sql` — additive DB migration (REQ-035)
|
||||
- `tests/runtime/test_safety_sell_config.py` — REQ-041 AC 검증 4종
|
||||
- `tests/application/test_watchdog.py` — REQ-034 AC 검증 11종
|
||||
- `tests/application/test_snapshot_rpc_watchdog.py` — REQ-034 handler 통합 4종
|
||||
- `tests/domain/slot/test_service_sell.py` — REQ-036 AC 검증 6종
|
||||
- `tests/domain/slot/test_price_threshold_fields.py` — REQ-035 AC 검증 7종
|
||||
|
||||
### 기존 파일 수정
|
||||
- `trex_core/runtime/config.py` — SafetySellConfig dataclass 추가 + DaemonConfig 5종 env var 파싱 (REQ-041)
|
||||
- `trex_core/domain/slot/models.py` — stop_loss_price / take_profit_price Optional[Decimal] 필드 추가 + EXPECTED_SLOT_FIELDS 갱신 (REQ-035)
|
||||
- `trex_core/adapters/persistence/postgresql.py` — _SLOT_COLUMNS 갱신, update_slot SET절 확장, _row_to_slot 인덱스 조정 (REQ-035)
|
||||
- `trex_core/application/intents.py` — BuyIntent에 price fields + Decimal import (REQ-035)
|
||||
- `trex_core/domain/slot/service.py` — transit_slot에 stop_loss_price / take_profit_price 파라미터 추가, _apply_transition_fields 확장(reset + preserve), sell() 메서드 신설, Decimal import (REQ-035, REQ-036)
|
||||
- `trex_core/application/messaging_handlers.py` — WatchdogStore import + SlotState import + make_snapshot_rpc_handler watchdog_store= 파라미터 + 홀딩 슬롯 update 로직 + buy intent payload _opt_decimal 파싱 (REQ-034, REQ-035)
|
||||
|
||||
## Execution Log
|
||||
1. M1 — REQ-041: SafetySellConfig + DaemonConfig.from_env() 5종 파싱. test_safety_sell_config.py 4종 green.
|
||||
2. M2 — REQ-035: models.py 필드 추가 + EXPECTED_SLOT_FIELDS 갱신 → test_contract.py 통과. postgresql.py _SLOT_COLUMNS / update_slot / _row_to_slot 갱신. SQL 마이그레이션 004 생성. intents.py BuyIntent 확장. service.py transit_slot + _apply_transition_fields 확장. test_price_threshold_fields.py 7종 green.
|
||||
3. M3 — REQ-034: watchdog.py 신규. messaging_handlers.py make_snapshot_rpc_handler 확장. test_watchdog.py 11종 green. test_snapshot_rpc_watchdog.py 4종 green.
|
||||
4. M4 — REQ-036: service.py sell() 신설. test_service_sell.py 6종 green.
|
||||
5. M5 — 전체 회귀 95 passed (httpx 미설치 환경 기인 test_daemon_no_http_surface 1건 기존 실패, BAT-010 무관).
|
||||
|
||||
## Validation
|
||||
- 신규 테스트: 32종 100% green
|
||||
- 회귀 테스트: 95 passed (domain/ + runtime/ + application/ 대상, httpx 불필요 범위)
|
||||
- test_contract.py: EXPECTED_SLOT_FIELDS ↔ dataclasses.fields(Slot) 계약 통과 (price fields 포함)
|
||||
- `WatchdogStore.in_grace_period()` + `is_expired()` 조합 패턴 단위 테스트로 고정
|
||||
|
||||
## Remaining Risks
|
||||
- R-2 (Medium → Low): DB 마이그레이션은 실 PostgreSQL 없이 검증 불가. 004 migration이 `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` 패턴 사용 → 멱등 보장. 실 DB 검증은 BAT-011 환경 포함 후 수행.
|
||||
- R-3 (Low): test_daemon_no_http_surface 기존 실패 — httpx 미설치 환경 이슈. BAT-010 변경 무관. 런타임 환경에서 httpx 설치 시 해소.
|
||||
@@ -0,0 +1,35 @@
|
||||
# BAT-010: safety-sell 기반 인프라 (watchdog + 가격 임계값 필드 + SlotService.sell)
|
||||
|
||||
- Status: release-candidate
|
||||
- Profile: standard
|
||||
- Source Discovery: dcy-007
|
||||
- Included REQ: REQ-034, REQ-035, REQ-036, REQ-041
|
||||
- Excluded REQ: REQ-037, REQ-038, REQ-039, REQ-040, REQ-042 (safety-sell 트리거 레이어 — BAT-011 예정)
|
||||
|
||||
## Summary
|
||||
- dcy-007(core 자율 안전 매도) 기반 인프라 레이어를 delivery한다. per-slot 워치독 store(REQ-034), 슬롯 가격 임계값 도메인 필드 + DB 마이그레이션(REQ-035), 자율 매도 내부 경로 `SlotService.sell()`(REQ-036), safety-sell 환경 변수 전체 파싱(REQ-041)을 포함한다. 트리거 루프(forced_close/KIS polling/장 마감)와 `update_thresholds` RPC는 BAT-011에서 처리한다.
|
||||
|
||||
## Scope
|
||||
- In Scope:
|
||||
- `WatchdogStore` 신규 모듈(`application/watchdog.py`) + `make_snapshot_rpc_handler` 확장 (REQ-034)
|
||||
- `stop_loss_price`, `take_profit_price` Optional[Decimal] 도메인 필드 + PostgreSQL additive 마이그레이션 (REQ-035)
|
||||
- `SlotService.sell(slot_id, actor, reason)` + idempotency (REQ-036)
|
||||
- `DaemonConfig` env vars 5종 파싱: `TREX_WATCHDOG_TIMEOUT`, `TREX_WATCHDOG_GRACE_PERIOD`, `TREX_FORCED_CLOSE_CHECK_INTERVAL`, `TREX_STOP_LOSS_POLL_INTERVAL`, `TREX_MARKET_CLOSE_LIQUIDATION_TIME` (REQ-041)
|
||||
- Out of Scope:
|
||||
- forced_close_at 루프 — BAT-011 (REQ-037)
|
||||
- KIS 현재가 polling — BAT-011 (REQ-038)
|
||||
- 장 마감 청산 루프 — BAT-011 (REQ-039)
|
||||
- update_thresholds intent RPC — BAT-011 (REQ-040)
|
||||
- baseline 문서 정렬 — BAT-011 (REQ-042)
|
||||
- 가격 임계값 논리 유효성 검증(take_profit > stop_loss 등) — REQ-035 Notes 명시
|
||||
|
||||
## Documents
|
||||
- Planning: [planning.md](./planning.md)
|
||||
- Design: [design.md](./design.md)
|
||||
- Implementation: [implementation.md](./implementation.md)
|
||||
- Verification: [verification.md](./verification.md)
|
||||
|
||||
## Notes
|
||||
- BAT-010은 외부 API(KIS) 호출이 없고 신규 daemon thread를 추가하지 않는다. 단위 테스트와 persistence 통합 테스트만으로 모든 AC를 검증 가능한 레이어다.
|
||||
- REQ-041의 env vars 5종 중 BAT-010에서 실제 사용되는 것은 `TREX_WATCHDOG_TIMEOUT` + `TREX_WATCHDOG_GRACE_PERIOD` 2개다. 나머지 3종(`TREX_FORCED_CLOSE_CHECK_INTERVAL`, `TREX_STOP_LOSS_POLL_INTERVAL`, `TREX_MARKET_CLOSE_LIQUIDATION_TIME`)은 파싱만 완성하고 BAT-011에서 참조한다. REQ-041을 두 batch에 분리하지 않기 위한 결정(design.md Key Decision 2 참조).
|
||||
- WatchdogStore 구현 위치: `trex_core/application/watchdog.py` 신규 모듈(design.md Key Decision 1 참조).
|
||||
@@ -0,0 +1,42 @@
|
||||
# BAT-010 Planning
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-010
|
||||
- Profile: standard
|
||||
|
||||
## Included REQ
|
||||
- REQ-041 (Configuration): safety-sell 환경 변수 5종 파싱 — `TREX_WATCHDOG_TIMEOUT`(s, 기본 120), `TREX_WATCHDOG_GRACE_PERIOD`(s, 기본 60), `TREX_FORCED_CLOSE_CHECK_INTERVAL`(s, 기본 60), `TREX_STOP_LOSS_POLL_INTERVAL`(s, 기본 30), `TREX_MARKET_CLOSE_LIQUIDATION_TIME`(HH:MM KST, optional)
|
||||
- REQ-035 (Data): `stop_loss_price` · `take_profit_price` `Optional[Decimal]` 도메인 필드 + PostgreSQL additive 마이그레이션
|
||||
- REQ-034 (Interface): per-slot 워치독 — `WatchdogStore(dict[slot_id, datetime])` + snapshot RPC handler 확장 + grace period
|
||||
- REQ-036 (Interface): `SlotService.sell(slot_id, actor, reason)` — `holding→pending_sell` 전이 + idempotency
|
||||
|
||||
## Delivery Plan
|
||||
1. REQ-041: `runtime/config.py`에 safety-sell env vars 5종 파싱 추가 (`DaemonConfig` 확장). 파싱 단위 테스트.
|
||||
2. REQ-035: `domain/slot/models.py`에 두 필드 추가 → `adapters/persistence/postgresql.py` read/write 포함 → SQL 마이그레이션 스크립트 작성 → `application/intents.py` + `messaging_handlers.py` payload 파싱 추가. 필드 저장·조회 round-trip 테스트 + 마이그레이션 멱등성 테스트.
|
||||
3. REQ-034: `application/watchdog.py` 신규 작성 (`WatchdogStore` class, grace period 판정) → `make_snapshot_rpc_handler` 확장 (응답 슬롯 `last_seen_at` 갱신). 워치독 판정 단위 테스트 (만료/비만료/grace period 시나리오).
|
||||
4. REQ-036: `domain/slot/service.py`에 `sell(slot_id, actor, reason)` 추가 (`transit_slot` 기반, idempotency). 단위 테스트 3종(holding→pending_sell, pending_sell 중복 호출, idle 오류).
|
||||
5. 전체 회귀 테스트 green + ruff lint clean.
|
||||
|
||||
## Dependencies
|
||||
- Internal:
|
||||
- BAT-007/REQ-022: `make_snapshot_rpc_handler` 기존 구현 — 확장 대상
|
||||
- BAT-008/REQ-027: `SlotService` 기존 구조 + `transit_slot` 패턴 — `sell()` 기반
|
||||
- BAT-004/REQ-011~012: `PaperBroker` execution 경로 — `sell()` 통합 검증 대상
|
||||
- External:
|
||||
- 없음 (KIS API 호출 없음, 신규 daemon thread 없음)
|
||||
|
||||
## Design Gate
|
||||
- Design Required Before Implementation: yes
|
||||
- Reason: `WatchdogStore`는 신규 in-memory 컴포넌트로 snapshot RPC handler thread에서 write, BAT-011 트리거 루프 thread에서 read가 발생한다. 동시성 모델(GIL 의존 vs. explicit lock)과 모듈 배치(`watchdog.py` 분리 vs. `portfolio.py` 확장)를 설계 단계에서 확정해야 BAT-011 트리거 루프 설계 시 인터페이스 충돌이 없다. `SlotService.sell()`과 `transit_slot` advisory lock 통합 방식도 설계에서 명시한다.
|
||||
|
||||
## Milestones
|
||||
1. M1 — REQ-041: env vars 파싱 + unit test green
|
||||
2. M2 — REQ-035: 도메인 필드 + 마이그레이션 + persistence round-trip 테스트 green
|
||||
3. M3 — REQ-034: `WatchdogStore` + snapshot RPC handler 확장 + 워치독 판정 unit test green
|
||||
4. M4 — REQ-036: `SlotService.sell()` + idempotency unit test green (3종)
|
||||
5. M5 — 전체 회귀 + ruff lint clean
|
||||
|
||||
## Risks
|
||||
- R-1 (Medium): WatchdogStore 동시성 — BAT-011 트리거 루프가 watchdog store를 read하는 시점에 snapshot RPC handler가 동시 write할 수 있음. 대응: Python GIL 보장 하에 단순 `dict` 사용; 별도 `threading.Lock` 회피. 단위 테스트에서 판정 로직 고정.
|
||||
- R-2 (Medium): DB 마이그레이션 기존 row 영향. 대응: `NULL` 허용 additive 컬럼 추가 전용(`ALTER TABLE … ADD COLUMN IF NOT EXISTS`). 기존 row `NULL` 유지. migration 멱등성 테스트.
|
||||
- R-3 (Low): `SlotService.sell()` advisory lock 통합 — `transit_slot` 기존 advisory lock과 충돌 가능성. 대응: 기존 `transit_slot()` 코드 경로를 최대한 재사용하고 신규 로직을 최소화한다.
|
||||
@@ -0,0 +1,78 @@
|
||||
# BAT-010 Verification
|
||||
|
||||
- Status: verified
|
||||
- Batch: BAT-010
|
||||
|
||||
## Acceptance Mapping
|
||||
|
||||
### REQ-041 (SafetySellConfig env vars)
|
||||
|
||||
| AC | Evidence |
|
||||
|----|---------|
|
||||
| AC-1: 기본값 120/60/60/30/None | `test_safety_sell_config.py::test_safety_sell_config_defaults` — 5개 기본값 모두 assert |
|
||||
| AC-2: TREX_WATCHDOG_TIMEOUT 파싱 | `test_safety_sell_config_env_tuning` — 180 파싱 확인 |
|
||||
| AC-3: TREX_WATCHDOG_GRACE_PERIOD 파싱 | 동일 테스트 — 90 파싱 확인 |
|
||||
| AC-4: TREX_FORCED_CLOSE_CHECK_INTERVAL 파싱 | 동일 테스트 — 45 파싱 확인 |
|
||||
| AC-5: TREX_STOP_LOSS_POLL_INTERVAL 파싱 | 동일 테스트 — 15 파싱 확인 |
|
||||
| TREX_MARKET_CLOSE_LIQUIDATION_TIME Optional | `test_safety_sell_config_env_tuning` "15:30" 파싱 + `test_market_close_liquidation_time_empty_string_becomes_none` None 변환 |
|
||||
|
||||
### REQ-035 (stop_loss_price / take_profit_price 필드)
|
||||
|
||||
| AC | Evidence |
|
||||
|----|---------|
|
||||
| AC-1: 기본값 None | `test_price_threshold_fields.py::test_price_fields_default_to_none` |
|
||||
| AC-2: Decimal 저장·조회 round-trip | `test_price_fields_set_on_pending_buy` — Decimal("48000") round-trip via in-memory repo |
|
||||
| AC-3: 마이그레이션 멱등성 | `004_add_price_thresholds.sql` `IF NOT EXISTS` 패턴. 실 PostgreSQL round-trip은 Remaining Risks R-2 참조 |
|
||||
| AC-4: 매수 intent payload 포함 시 저장 | `test_price_fields_preserved_through_holding` — pending_buy→holding 전이 후 price fields 유지 |
|
||||
| AC-5: 미포함 시 None | `test_no_price_fields_sets_none` |
|
||||
| 계약 가드 | `test_contract.py::test_slot_fields_match_expected_contract` — EXPECTED_SLOT_FIELDS 포함 확인 |
|
||||
|
||||
### REQ-034 (per-slot WatchdogStore)
|
||||
|
||||
| AC | Evidence |
|
||||
|----|---------|
|
||||
| AC-1: snapshot RPC 처리 시 holding slot last_seen_at 갱신 | `test_snapshot_rpc_watchdog.py::test_holding_slot_watchdog_updated_on_snapshot_rpc` |
|
||||
| AC-2: timeout 초과 슬롯 만료 판정 | `test_watchdog.py::TestIsExpired::test_old_update_is_expired` (200s > 120s timeout) |
|
||||
| AC-3: timeout 미초과 슬롯 비만료 | `test_just_within_timeout_not_expired` (100s < 120s timeout) |
|
||||
| AC-4: grace period 중 만료 유예 | `test_grace_period_defers_expiry_check` — in_grace_period+is_expired 조합 패턴 검증 |
|
||||
| AC-5: 재시작 시 store 초기화 | `test_new_store_is_empty` |
|
||||
| AC-6: 정상 poll 주기 슬롯 비만료 | `test_recently_updated_slot_not_expired` — update 직후 is_expired=False |
|
||||
| holding만 갱신, idle은 제외 | `test_idle_slot_watchdog_not_updated` |
|
||||
| 복수 holding 슬롯 모두 갱신 | `test_multiple_holding_slots_all_updated` |
|
||||
| backward compat (watchdog_store=None) | `test_no_watchdog_store_does_not_raise` |
|
||||
|
||||
### REQ-036 (SlotService.sell() + idempotency)
|
||||
|
||||
| AC | Evidence |
|
||||
|----|---------|
|
||||
| AC-1: holding → pending_sell 전이 | `test_service_sell.py::test_sell_holding_transitions_to_pending_sell` |
|
||||
| AC-2: pending_sell 중복 호출 → 무전이 | `test_sell_idempotent_on_pending_sell` — transition_log PENDING_SELL 항목 1건만 확인 |
|
||||
| AC-3: idle 호출 → 오류, 상태 불변 | `test_sell_idle_slot_raises_transition_error` |
|
||||
| AC-4: execution 경로 재사용 | `test_sell_records_transition_log` — transit_slot 기반으로 log append 확인 |
|
||||
| AC-5: transport 독립 | `sell()` 구현이 RabbitMQ 의존 없는 service.transit_slot() 직접 호출 — 코드 검사로 확인 |
|
||||
| SlotNotFoundError | `test_sell_unknown_slot_raises_not_found` |
|
||||
| price fields 보존 | `test_sell_preserves_stop_loss_price` |
|
||||
|
||||
## Evidence
|
||||
|
||||
- Test:
|
||||
- 신규 테스트 32종 all green (pytest 출력 확인)
|
||||
- `tests/domain/ + tests/runtime/ + 대상 tests/application/` 회귀 95 passed
|
||||
- `test_contract.py` 통과 — EXPECTED_SLOT_FIELDS 계약 유지
|
||||
- Manual:
|
||||
- 없음 (단위 테스트로 모든 AC 커버)
|
||||
- Logs:
|
||||
- `32 passed in 0.09s` (신규 테스트 단독)
|
||||
- `95 passed in 0.13s` (회귀 포함)
|
||||
|
||||
## Architecture Impact Note
|
||||
|
||||
- BAT-010은 project-structure.md / runtime-flows.md 업데이트를 BAT-011 REQ-042에 위임
|
||||
- design.md Reference Doc Update Plan과 일치: 현 batch는 구현 evidence 확보 범위
|
||||
- `test_daemon_no_http_surface` 기존 실패는 httpx 미설치 환경 이슈로 BAT-010 변경과 무관
|
||||
|
||||
## Result
|
||||
|
||||
- Ready for Release: true
|
||||
- Blocking Issues:
|
||||
- R-2: 실 PostgreSQL migration round-trip 미검증 — `IF NOT EXISTS` 패턴으로 안전성 확보, BAT-011 환경에서 검증 예정. release gate에서는 수용(additive-only schema 변경, 기존 row 영향 없음).
|
||||
@@ -0,0 +1,55 @@
|
||||
# BAT-011 Design
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-011
|
||||
|
||||
## Architecture Summary
|
||||
BAT-011은 BAT-010 기반 위에 자동 매도를 실제로 발동하는 트리거 레이어를 추가한다.
|
||||
|
||||
1. **`trex-safety-sell` worker 스레드** (`runtime/daemon.py`): 기존 6 스레드(execution/ledger/intent-rpc/snapshot-rpc/pause-rpc/snapshot-scheduler)에 7번째로 추가. `_safety_sell_loop()`가 단일 통합 루프에서 forced_close_at 만기(REQ-037), stop-loss·take-profit 가격 체크(REQ-038), 장 마감 청산(REQ-039)을 순차 평가한다.
|
||||
2. **KIS 현재가 polling 어댑터** (`adapters/broker/kis.py` 확장): `get_current_price(symbol) -> Decimal` — `TTTC8397R`(실전)/`VTTC8397R`(모의) 시세 endpoint 호출, `stck_prpr`(직전 체결가) 파싱. paper 환경 stub.
|
||||
3. **`update_thresholds` intent RPC** (`application/messaging_handlers.py` + `domain/slot/service.py`): `make_intent_rpc_handler`에 action 라우팅 추가, `SlotService.update_thresholds()`로 부분 갱신 + 워치독 `last_seen_at` 리셋.
|
||||
4. **baseline 문서 정합** (REQ-042): 구현 완료 후 3문서 갱신.
|
||||
|
||||
## Changed Areas
|
||||
- Docs:
|
||||
- `docs/interface-contract.md` — `update_thresholds` action 계약 + intent payload `stop_loss_price`/`take_profit_price` 선택 필드 + snapshot RPC 워치독 갱신 신호 명시 (REQ-040, REQ-042)
|
||||
- `docs/runtime-flows.md` — 워치독 갱신·forced_close 체크·가격 체크·장 마감 청산 흐름 추가 + `:114` "A-5 미구현" 주석 갱신 (REQ-042)
|
||||
- `docs/project-structure.md` — `trex-safety-sell` 스레드 + env 5종 + KIS 현재가 polling/watchdog 모듈 반영 (REQ-042)
|
||||
- Modules:
|
||||
- `trex_core/runtime/daemon.py` — `trex-safety-sell` worker 등록 + `_safety_sell_loop()` 구현(forced_close/가격/장 마감 통합)
|
||||
- `trex_core/adapters/broker/kis.py` — `get_current_price(symbol)` 추가(`TTTC8397R`/`VTTC8397R`, `stck_prpr`)
|
||||
- `trex_core/application/broker_port.py` — (필요 시) `get_current_price` 포트 시그니처 추가; paper 더블 stub 구현
|
||||
- `trex_core/adapters/broker/paper.py` — `get_current_price` stub(고정/주입 가능 현재가) — 테스트·모의용
|
||||
- `trex_core/domain/slot/service.py` — `SlotService.update_thresholds(slot_id, stop_loss_price, take_profit_price, forced_close_at)` 추가 + 트리거 조건 평가 헬퍼(또는 worker 인라인)
|
||||
- `trex_core/application/messaging_handlers.py` — `make_intent_rpc_handler`에 `action: "update_thresholds"` 라우팅 + holding-only 응답
|
||||
- Interfaces:
|
||||
- `Broker.get_current_price(symbol: str) -> Decimal | None` (신규 포트 메서드)
|
||||
- `SlotService.update_thresholds(slot_id, stop_loss_price=..., take_profit_price=..., forced_close_at=...) -> str` (부분 갱신, sentinel로 "미지정" vs "null 해제" 구분)
|
||||
- intent RPC `action: "update_thresholds"` payload/response 계약 (REQ-040)
|
||||
|
||||
## Key Decisions (초안 — 구현 전 확정 대상)
|
||||
1. **단일 통합 worker 루프 vs. 분리 스레드**: REQ-037 Notes 권고에 따라 `trex-safety-sell` 단일 스레드 통합 루프를 기본안으로 한다. 가격 polling 주기(`TREX_STOP_LOSS_POLL_INTERVAL`, 기본 30s)와 forced_close 주기(`TREX_FORCED_CLOSE_CHECK_INTERVAL`, 기본 60s)가 다르므로, 루프 base tick을 짧은 쪽(또는 GCD)으로 두고 각 체크에 last-run 타임스탬프로 sub-scheduling한다. (확정 필요)
|
||||
2. **KIS 현재가 어댑터 배치**: `kis.py`에 `get_current_price` 추가(별도 `kis_price.py` 분리 대신) — 토큰/세션/`_account` 재사용 이점. paper는 `paper.py` stub. (확정 필요)
|
||||
3. **WatchdogStore read 동시성**: BAT-010 Key Decision 3 계약 승계 — GIL 보장 단일 dict, lock 없음. worker는 `is_expired()`/`in_grace_period()` read만 수행.
|
||||
4. **장 마감 1회 발동 가드**: in-memory `last_liquidation_date: date | None`. 재시작 시 초기화(REQ-039 Notes). 같은 거래일 재발동 방지. 발동 시각 도래 판정은 `now(KST).time() >= liquidation_time and last_liquidation_date != today`.
|
||||
5. **`update_thresholds` 부분 갱신 sentinel**: payload에 키가 없으면 "유지", 키가 `null`이면 "해제(NULL)". `_UNSET` sentinel로 두 경우를 구분(REQ-040 AC-4/AC-5).
|
||||
6. **루프 예외 격리**: tick 단위 try/except로 polling/sell 예외가 worker 스레드를 죽이지 않게 한다. 예외는 로그 후 다음 tick 지속(REQ-024 degrade 정책 정합).
|
||||
|
||||
## Edge Cases
|
||||
- 가격 polling 중 KIS 거부/타임아웃: 해당 슬롯 skip + 경고 로그, 다음 tick 재시도(R-4).
|
||||
- forced_close와 stop-loss가 동시 충족: 둘 다 `sell()` 호출하나 REQ-036 idempotency로 1회만 전이.
|
||||
- 장 마감 청산 시각이 forced_close 체크 사이에 도래: 통합 루프 tick에서 장 마감 평가가 우선 실행되도록 순서 고정.
|
||||
- `update_thresholds`로 `forced_close_at`만 갱신: 나머지 필드 유지(sentinel), `last_seen_at` 리셋(워치독 타이머 재시작, REQ-034).
|
||||
- non-holding 슬롯에 `update_thresholds`: 필드 불변 + `{status: ignored, reason: "not_holding"}` (REQ-040 AC-6).
|
||||
- 쿼터 상한 초과: 경고 로그만(본 batch 범위) — 동적 간격 조정은 out of scope.
|
||||
|
||||
## Architecture Impact
|
||||
- runtime-flows: safety-sell worker 4종 흐름(워치독 갱신은 snapshot RPC 측) 추가 — REQ-042에서 문서화.
|
||||
- project-structure: 7번째 스레드 `trex-safety-sell` + KIS 현재가 polling 어댑터 메서드 — REQ-042에서 문서화.
|
||||
- broker_port 계약 확장(`get_current_price`)이 paper/kis 양 더블에 반영되어야 한다.
|
||||
|
||||
## Reference Doc Update Plan
|
||||
- `interface-contract.md`: `update_thresholds` action + price 선택 필드 + snapshot RPC 워치독 신호 (REQ-042 AC-1/AC-2)
|
||||
- `runtime-flows.md`: 4종 safety-sell 흐름 + `:114` A-5 주석 갱신 (REQ-042 AC-3/AC-4)
|
||||
- `project-structure.md`: `trex-safety-sell` 스레드 + env 5종 + 신규 모듈 (REQ-042 AC-5)
|
||||
@@ -0,0 +1,43 @@
|
||||
# BAT-011 Implementation
|
||||
|
||||
- Status: completed
|
||||
- Batch: BAT-011
|
||||
|
||||
## Plan Summary
|
||||
- 구현 순서: REQ-038 KIS 현재가 어댑터 → `SafetySellService`(REQ-037/038/039) + `trex-safety-sell` worker 배선 → REQ-040 `update_thresholds` RPC → REQ-042 baseline 문서 정합. 각 단계 unit test green 확인 후 진행.
|
||||
|
||||
## Changed Files
|
||||
|
||||
### 신규 파일
|
||||
- `trex_core/application/safety_sell.py` — `SafetySellService`: forced_close(REQ-037) / stop-loss·take-profit(REQ-038) / 장 마감 청산(REQ-039) + run_once 캐던스. watchdog read-only, sell() 통해 매도, tick 예외 격리.
|
||||
- `tests/adapters/broker/test_kis_price.py` — KIS 현재가 어댑터 7종(tr_id 분기, stck_prpr 파싱, 에러→None, paper stub).
|
||||
- `tests/application/test_safety_sell_worker.py` — 트리거 서비스 23종(forced_close 6 / 가격 10 / 장 마감 6 / run_once 2).
|
||||
- `tests/application/test_update_thresholds_handler.py` — update_thresholds RPC 8종(부분 갱신/null 해제/non-holding/last_seen 리셋/미존재).
|
||||
|
||||
### 기존 파일 수정
|
||||
- `trex_core/application/broker_port.py` — `BrokerPort.get_current_price(symbol) -> Optional[Decimal]` 포트 추가 (REQ-038).
|
||||
- `trex_core/adapters/broker/kis.py` — `_tr_price()`(TTTC8397R/VTTC8397R) + `get_current_price()`(`inquire-price`, `stck_prpr` 파싱, 에러→None) (REQ-038).
|
||||
- `trex_core/adapters/broker/paper.py` — `set_current_price()`/`get_current_price()` 프로그래머블 stub (REQ-038).
|
||||
- `trex_core/domain/slot/service.py` — `SlotService.update_thresholds()` + `_UNSET` sentinel(유지 vs 해제 구분) (REQ-040).
|
||||
- `trex_core/application/messaging_handlers.py` — `make_intent_rpc_handler` 에 `slot_service`/`watchdog_store` 인자 + `update_thresholds` action 라우팅(`_handle_update_thresholds`) + `last_seen_at` 리셋 (REQ-040).
|
||||
- `trex_core/runtime/daemon.py` — `WatchdogStore` 인스턴스 + `SafetySellService` 빌드 + `trex-safety-sell` worker(7번째 스레드) + snapshot/intent RPC handler 에 watchdog 배선 + docstring 갱신 (REQ-034/037/038/039/040).
|
||||
- `docs/interface-contract.md`, `docs/runtime-flows.md`, `docs/project-structure.md` — baseline 정합 (REQ-042).
|
||||
- `tests/domain/slot/test_price_threshold_fields.py` — 중복 import 정리(ruff clean).
|
||||
|
||||
## Execution Log
|
||||
1. broker_port + kis + paper 현재가 구현 → `test_kis_price.py` 7 passed.
|
||||
2. `safety_sell.py` 구현 → `test_safety_sell_worker.py` 23 passed.
|
||||
3. daemon 배선(watchdog + worker + handler) → import 회귀(test_daemon_no_http_surface) green.
|
||||
4. `update_thresholds` 구현 → `test_update_thresholds_handler.py` 8 passed.
|
||||
5. baseline 3문서 정합(REQ-042).
|
||||
6. 전체 회귀: 단위 236 passed / 23 skipped, `ruff check` All passed.
|
||||
7. 인프라 게이트 포함(PG+RabbitMQ): 259 passed (마이그레이션 001~004 적용 + PG repo round-trip 포함).
|
||||
|
||||
## Validation
|
||||
- 단위: `pytest -q --deselect ...::test_sandbox_token_and_submit` → 236 passed / 23 skipped.
|
||||
- 인프라 게이트: `TREX_RABBITMQ_TEST_URL=... TREX_POSTGRESQL_TEST_DSN=...(trex_core_test) pytest -q` → 259 passed.
|
||||
- lint: `ruff check trex_core tests` → All checks passed.
|
||||
|
||||
## Remaining Risks
|
||||
- R-1 (운영 실증 이월): stop-loss·take-profit·forced_close·장 마감의 **실 KIS 라이브 트리거**는 휴장/운영 환경 제약으로 단위(mock)·정적 검증까지 수행. 차기 거래일 운영 관찰 필요(verification Carried-Over).
|
||||
- R-5 해소: BAT-010 R-2(실 PostgreSQL `004_add_price_thresholds.sql` round-trip)는 인프라 게이트 259 passed 로 적용 확인(마이그레이션 idempotency + PG repo 테스트 green).
|
||||
@@ -0,0 +1,35 @@
|
||||
# BAT-011: safety-sell 트리거 레이어 (forced_close·KIS polling·장 마감 청산 + update_thresholds RPC + baseline 정합)
|
||||
|
||||
- Status: release-candidate
|
||||
- Profile: standard
|
||||
- Source Discovery: dcy-007
|
||||
- Included REQ: REQ-037, REQ-038, REQ-039, REQ-040, REQ-042
|
||||
- Excluded REQ: REQ-034, REQ-035, REQ-036, REQ-041 (safety-sell 기반 인프라 — BAT-010에서 delivery 완료)
|
||||
|
||||
## Summary
|
||||
- dcy-007(core 자율 안전 매도)의 트리거 레이어를 delivery한다. BAT-010이 제공한 기반(WatchdogStore, `stop_loss_price`/`take_profit_price` 도메인 필드, `SlotService.sell()`, safety-sell env vars 5종 파싱) 위에 실제 자동 매도를 발동하는 백그라운드 루프와 임계값 갱신 RPC를 추가한다. 구성: `forced_close_at` 만기 루프(REQ-037), stop-loss·take-profit KIS 현재가 polling + 트리거(REQ-038), 장 마감 강제 청산(REQ-039), `update_thresholds` intent RPC 액션(REQ-040), 그리고 dcy-007 전체(REQ-034~041)의 baseline 문서 정합(REQ-042).
|
||||
|
||||
## Scope
|
||||
- In Scope:
|
||||
- 신규 `trex-safety-sell` 데몬 worker 스레드 — 기존 6 스레드에 1개 추가. forced_close_at 체크(REQ-037), stop-loss·take-profit 가격 체크(REQ-038), 장 마감 청산(REQ-039)을 통합 루프로 처리.
|
||||
- forced_close_at 만기 감지: `holding` + 워치독 만료 + `forced_close_at <= now(KST)` → `SlotService.sell()` (REQ-037)
|
||||
- KIS 현재가 polling 어댑터(`TTTC8397R` 실전 / `VTTC8397R` 모의, `stck_prpr` 파싱) + 손절/익절 조건 평가 + 쿼터 상한 경고 (REQ-038)
|
||||
- 장 마감 강제 청산: `TREX_MARKET_CLOSE_LIQUIDATION_TIME` 도래 시 워치독 무관 전체 `holding` 청산 + 거래일 1회 발동 가드 (REQ-039)
|
||||
- `update_thresholds` intent RPC 액션: `stop_loss_price`/`take_profit_price`/`forced_close_at` 부분 갱신 + 워치독 `last_seen_at` 리셋 + holding-only (REQ-040)
|
||||
- baseline 3문서(`interface-contract.md`, `runtime-flows.md`, `project-structure.md`) 정합 — dcy-007 REQ-034~041 반영 (REQ-042)
|
||||
- Out of Scope:
|
||||
- BAT-010이 완료한 기반 인프라(REQ-034 WatchdogStore, REQ-035 도메인 필드/마이그레이션, REQ-036 `SlotService.sell()`, REQ-041 env 파싱) 재구현 — 본 batch는 호출/참조만 한다.
|
||||
- 가격 임계값 논리 유효성 검증(take_profit > stop_loss 등) — dcy-007/REQ-035 범위 밖.
|
||||
- KIS 쿼터 초과 시 polling 간격 자동 조정의 동적 튜닝 정책 — 본 batch는 상한 산정 + 경고 로그까지(REQ-038 AC-8). 동적 조정은 후속 후보.
|
||||
|
||||
## Documents
|
||||
- Planning: [planning.md](./planning.md)
|
||||
- Design: [design.md](./design.md)
|
||||
- Implementation: [implementation.md](./implementation.md)
|
||||
- Verification: [verification.md](./verification.md)
|
||||
|
||||
## Notes
|
||||
- Source discovery는 단일(dcy-007). BAT-010과 같은 Discovery의 후속 batch이며, BAT-010 index의 "BAT-011 예정" 분할 계획과 정확히 일치한다(REQ-037/038/039/040/042).
|
||||
- 본 batch는 BAT-010과 달리 **외부 API(KIS 현재가 polling) 호출과 신규 daemon thread**를 도입하므로 standard profile + design 선행이 필요하다(planning.md Design Gate 참조).
|
||||
- BAT-010 verification의 잔여 R-2(실 PostgreSQL migration `004_add_price_thresholds.sql` round-trip 미검증)를 본 batch 환경에서 함께 검증한다(verification에 evidence 기록 예정).
|
||||
- REQ-042(문서 정합)는 REQ-037~040 구현 완료 후 verification 단계에서 최신 구현 상태를 반영해 수행한다(REQ-042 Notes).
|
||||
@@ -0,0 +1,54 @@
|
||||
# BAT-011 Planning
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-011
|
||||
- Profile: standard
|
||||
|
||||
## Included REQ
|
||||
- REQ-037 (Integration): `forced_close_at` 만기 자동 매도 루프 — `holding` + 워치독 만료 + `forced_close_at <= now(KST)` 슬롯에 `SlotService.sell()`. `TREX_FORCED_CLOSE_CHECK_INTERVAL` 주기. KIS 호출 없음(DB + in-memory watchdog 비교).
|
||||
- REQ-038 (Integration): stop-loss·take-profit KIS 현재가 polling + 트리거 — `TTTC8397R`(실전)/`VTTC8397R`(모의) `stck_prpr` 기준, `현재가 <= stop_loss_price`(손절)/`현재가 >= take_profit_price`(익절) 시 `sell()`. `TREX_STOP_LOSS_POLL_INTERVAL` 주기. 쿼터 상한 `N × (3600/interval)` 경고.
|
||||
- REQ-039 (Integration): 장 마감 강제 청산 — `TREX_MARKET_CLOSE_LIQUIDATION_TIME`(HH:MM KST) 도래 시 워치독 무관 전체 `holding` 청산. 거래일 1회 발동 가드(`last_liquidation_date`).
|
||||
- REQ-040 (Interface): `update_thresholds` intent RPC 액션 — `{action, slot_id, stop_loss_price?, take_profit_price?, forced_close_at?}` 부분 갱신 + 워치독 `last_seen_at` 리셋 + holding-only. `reply_to` 시 `{status: updated|ignored, slot_id}` 응답.
|
||||
- REQ-042 (Documentation): baseline 3문서 정합 — `interface-contract.md`(update_thresholds + price 필드), `runtime-flows.md`(워치독 갱신·forced_close·가격 체크·장 마감 흐름 + `:114` A-5 주석 갱신), `project-structure.md`(`trex-safety-sell` 스레드 + env 5종 + KIS polling/watchdog 모듈).
|
||||
|
||||
## Delivery Plan
|
||||
1. REQ-038 KIS 현재가 polling 어댑터: `get_current_price(symbol)` (`TTTC8397R`/`VTTC8397R`, `stck_prpr` 파싱, 실전/모의 분기). paper 환경 stub. 어댑터 단위 테스트.
|
||||
2. REQ-037 + REQ-038 + REQ-039 통합 worker: `daemon.py`에 `trex-safety-sell` 스레드 + `_safety_sell_loop()` 추가. 루프 1 tick에서 (a) forced_close_at 만기, (b) stop-loss/take-profit 가격 체크, (c) 장 마감 청산 시각 평가를 순차 수행. 각 경로 `SlotService.sell()` 호출. mock 기반 단위 테스트(만기/손절/익절/미충족/워치독 보호/idempotency/장 마감 전체·중복가드).
|
||||
3. REQ-040 `update_thresholds`: `SlotService.update_thresholds(...)` + `make_intent_rpc_handler` action 라우팅 + `last_seen_at` 리셋 + holding-only 응답. handler 단위 테스트(부분 갱신/null 해제/non-holding ignored/last_seen_at 리셋).
|
||||
4. REQ-039 config: `TREX_MARKET_CLOSE_LIQUIDATION_TIME` 활용 확인(REQ-041에서 파싱 완료 — 본 batch는 소비).
|
||||
5. REQ-042 baseline 문서 3종 정합 — 구현 완료 상태 반영.
|
||||
6. 전체 회귀 green + ruff lint clean + BAT-010 잔여 R-2(실 PG 마이그레이션 round-trip) 검증.
|
||||
|
||||
## Dependencies
|
||||
- Internal:
|
||||
- BAT-010/REQ-034: `WatchdogStore.is_expired()` / `in_grace_period()` — 트리거 루프의 워치독 만료 판정 계약(read 측).
|
||||
- BAT-010/REQ-035: `stop_loss_price`/`take_profit_price` 도메인 필드 — 가격 체크 기준값.
|
||||
- BAT-010/REQ-036: `SlotService.sell(slot_id, actor, reason)` — 모든 트리거의 매도 제출 경로.
|
||||
- BAT-010/REQ-041: safety-sell env vars 5종 파싱(`DaemonConfig`) — 본 batch가 값을 소비.
|
||||
- BAT-007/REQ-022: `make_intent_rpc_handler` — `update_thresholds` action 확장 대상.
|
||||
- BAT-004/REQ-012: `KISBroker`(`kis.py`) — 현재가 polling 어댑터 추가/확장 대상.
|
||||
- External:
|
||||
- KIS Open API 현재가 시세 endpoint(`TTTC8397R`/`VTTC8397R`). 실 호출은 운영(`broker=kis`) 환경; 테스트는 mock/stub.
|
||||
|
||||
## Design Gate
|
||||
- Design Required Before Implementation: yes
|
||||
- Reason: 본 batch는 (a) **신규 daemon worker 스레드(`trex-safety-sell`)**를 추가하고 BAT-010의 WatchdogStore를 새로운 스레드에서 read하므로 동시성 계약(GIL 의존 read vs. snapshot RPC handler write)을 명시해야 한다. (b) **신규 외부 I/O(KIS 현재가 polling)** 어댑터의 배치(`kis.py` 확장 vs. 신규 `kis_price.py`), 실전/모의 endpoint 분기, paper stub 전략, 쿼터 상한 산정을 설계에서 확정해야 한다. (c) forced_close·가격 체크·장 마감을 **단일 통합 루프**로 묶는 구조와 장 마감 1회 발동 가드(`last_liquidation_date`) 상태 모델을 설계에서 고정해야 구현·검증 drift가 없다. → `design.md`를 동일 턴에 scaffold한다.
|
||||
|
||||
## Milestones
|
||||
1. M1 — REQ-038 KIS 현재가 polling 어댑터 + 단위 테스트 green (실전/모의 분기, `stck_prpr` 파싱)
|
||||
2. M2 — `trex-safety-sell` worker + forced_close_at 만기 루프(REQ-037) + 단위 테스트 green
|
||||
3. M3 — stop-loss·take-profit 가격 체크(REQ-038) worker 통합 + mock 단위 테스트 green(손절/익절/미충족/워치독 보호/쿼터 산정)
|
||||
4. M4 — 장 마감 강제 청산(REQ-039) + 거래일 1회 가드 + 단위 테스트 green
|
||||
5. M5 — `update_thresholds` RPC(REQ-040) + handler 단위 테스트 green
|
||||
6. M6 — baseline 3문서 정합(REQ-042) + 전체 회귀 + ruff clean + BAT-010 R-2 검증
|
||||
|
||||
## Risks
|
||||
- R-1 (Medium): KIS 현재가 polling 쿼터 — 워치독 만료 슬롯 N개 × (3600/`TREX_STOP_LOSS_POLL_INTERVAL`) 호출이 KIS 분당/일일 쿼터를 초과할 수 있음. 대응: REQ-038 AC-8 상한 산정 + 임계 초과 시 경고 로그. 폴링 대상은 "워치독 만료" 슬롯으로 한정해 평시 호출 최소화.
|
||||
- R-2 (Medium): WatchdogStore cross-thread read — safety-sell worker가 snapshot RPC handler thread의 write와 동시 접근. 대응: BAT-010 Key Decision 3(GIL 보장 단일 dict, lock 없음) 계약을 그대로 따르고 read-only 판정만 수행. 단위 테스트로 만료 판정 고정.
|
||||
- R-3 (Medium): 장 마감 청산 발동 정밀도/중복 — worker 루프 주기에 의존하는 시각 평가 + 재시작 시 in-memory `last_liquidation_date` 초기화로 동일 거래일 재발동 위험. 대응: 발동 가드 + (필요 시) 발동 시각 경계 처리 설계에서 확정.
|
||||
- R-4 (Low): KIS 거부/네트워크 예외 시 루프 안정성 — polling 또는 sell 제출 중 예외가 worker 스레드를 죽이면 안 됨. 대응: tick 단위 try/except + 로그 후 다음 tick 지속(REQ-024 degrade 정책 정합).
|
||||
- R-5 (Low, 이월): BAT-010 잔여 R-2 — `004_add_price_thresholds.sql` 실 PostgreSQL round-trip 미검증. 대응: 본 batch verification에서 실 PG 적용 + round-trip 확인.
|
||||
|
||||
## Notes
|
||||
- safety-sell worker는 REQ-037 Notes의 권고(forced_close·stop-loss/take-profit·장 마감을 하나의 통합 루프로 처리)를 따른다. 루프 주기와 가격 체크 주기가 다를 경우(`TREX_FORCED_CLOSE_CHECK_INTERVAL` vs `TREX_STOP_LOSS_POLL_INTERVAL`) tick 내 sub-scheduling 방식은 design에서 확정한다.
|
||||
- REQ-040 OQ-6(non-holding 슬롯 처리)는 REQ-040 AC-6에서 `{status: ignored, reason: "not_holding"}` 응답으로 이미 결정됨 — 구현은 이를 따른다.
|
||||
@@ -0,0 +1,82 @@
|
||||
# BAT-011 Verification
|
||||
|
||||
- Status: verified
|
||||
- Batch: BAT-011
|
||||
|
||||
## Acceptance Mapping
|
||||
|
||||
### REQ-037 (forced_close_at 만기 자동 매도 루프)
|
||||
| AC | Evidence |
|
||||
|----|---------|
|
||||
| AC-1: 만기+워치독 만료 → sell() | `test_safety_sell_worker.py::TestForcedClose::test_expired_past_due_sells` |
|
||||
| AC-2: 미만기(now+1h) → 미호출 | `test_future_not_due_no_sell` |
|
||||
| AC-3: 워치독 활성 → 미호출(보호) | `test_watchdog_active_protected` |
|
||||
| AC-4: forced_close_at None → 제외 | `test_no_forced_close_excluded` |
|
||||
| AC-5: pending_sell 중복 미호출 | `test_idempotent_on_pending_sell` (transition_log PENDING_SELL 1건) |
|
||||
| AC-6: interval 주기 | `TestRunOnce::test_base_tick_is_finest_interval` + `safety_sell.base_tick_sec`/`run_once` `_due` 캐던스(코드) |
|
||||
| grace 보호 | `test_grace_period_defers` |
|
||||
|
||||
### REQ-038 (stop-loss·take-profit KIS polling + 트리거)
|
||||
| AC | Evidence |
|
||||
|----|---------|
|
||||
| AC-1: stck_prpr ≤ stop_loss → 손절 | `TestPriceTriggers::test_stop_loss_triggers` |
|
||||
| AC-2: stck_prpr ≥ take_profit → 익절 | `test_take_profit_triggers` |
|
||||
| AC-3: 두 조건 미충족 → 미호출 | `test_neither_condition_no_sell` |
|
||||
| AC-4: 워치독 활성 → KIS 호출 0 | `test_watchdog_active_no_kis_call` (broker.calls == []) |
|
||||
| AC-5: 임계값 None → 제외 | `test_no_thresholds_excluded` |
|
||||
| AC-6: pending_sell 중복 미호출 | `test_idempotent_on_pending_sell` |
|
||||
| AC-7: 실전 TTTC8397R / 모의 VTTC8397R | `test_kis_price.py::test_tr_price_live_vs_mock` + `test_get_current_price_parses_stck_prpr` |
|
||||
| AC-8: 시간당 호출 ≤ N×(3600/interval) | `test_quota_estimate`(N=3,interval=30→360) + `test_only_eligible_slots_polled`(워치독 만료+임계 슬롯만 poll) |
|
||||
| 가격 미수신 → skip | `test_price_unavailable_skips` |
|
||||
|
||||
### REQ-039 (장 마감 강제 청산)
|
||||
| AC | Evidence |
|
||||
|----|---------|
|
||||
| AC-1: 시각 도래 → 전체 holding sell() | `TestMarketClose::test_time_reached_liquidates_all` |
|
||||
| AC-2: 워치독 활성 포함 청산 | `test_watchdog_active_included` |
|
||||
| AC-3: 미설정 → 미동작 | `test_not_configured_no_sell` |
|
||||
| AC-4: 거래일 1회 발동 가드 | `test_once_per_day_guard` |
|
||||
| AC-5: pending_sell 중복 미호출 | REQ-036 idempotency(`SlotService.sell`) — `test_service_sell.py::test_sell_idempotent_on_pending_sell` 승계, 청산 경로 동일 sell() 사용 |
|
||||
| AC-6: grace period 무관 청산 | `test_grace_period_does_not_block` |
|
||||
| 발동 시각 이전 → 미동작 | `test_before_time_no_sell` |
|
||||
|
||||
### REQ-040 (update_thresholds intent RPC)
|
||||
| AC | Evidence |
|
||||
|----|---------|
|
||||
| AC-1: stop_loss_price 갱신 | `test_update_thresholds_handler.py::test_updates_stop_loss` |
|
||||
| AC-2: take_profit + forced_close 갱신 | `test_updates_take_profit_and_forced_close` |
|
||||
| AC-3: last_seen_at 리셋 | `test_resets_watchdog_last_seen` |
|
||||
| AC-4: 부분 갱신(미포함 유지) | `test_partial_update_keeps_other_fields` |
|
||||
| AC-5: null → 해제 | `test_explicit_null_clears` |
|
||||
| AC-6: non-holding → ignored/not_holding | `test_non_holding_ignored` |
|
||||
| AC-7: reply_to → updated 응답 | `test_reply_updated_status` |
|
||||
| 미존재 슬롯 → ignored/slot_not_found | `test_unknown_slot_ignored` |
|
||||
|
||||
### REQ-042 (baseline 문서 정합)
|
||||
| AC | Evidence |
|
||||
|----|---------|
|
||||
| AC-1: interface-contract update_thresholds 계약 | `docs/interface-contract.md` `trex.intent.req update_thresholds 액션` 블록 + Patterns 행 |
|
||||
| AC-2: interface-contract price 선택 필드 | buy payload 에 `stop_loss_price`/`take_profit_price` 추가 |
|
||||
| AC-3: runtime-flows 4종 흐름 | `docs/runtime-flows.md` 워치독 갱신/forced_close/가격 체크/장 마감 흐름 4종 추가 |
|
||||
| AC-4: runtime-flows A-5 주석 갱신 | broker degrade 흐름의 "A-5 미구현" → dcy-007 REQ-037~041 해소로 갱신 |
|
||||
| AC-5: project-structure trex-safety-sell 스레드 | `docs/project-structure.md` 7 스레드 목록 + safety_sell.py/watchdog.py 모듈 + 변경 로그 |
|
||||
|
||||
## Evidence
|
||||
- Test:
|
||||
- 신규 테스트 38종 green (test_kis_price 7 + test_safety_sell_worker 23 + test_update_thresholds_handler 8).
|
||||
- 단위 전체 회귀: `236 passed / 23 skipped`(`::test_sandbox_token_and_submit` deselect — `.env=LIVE` 산물).
|
||||
- 인프라 게이트(PG trex_core_test + RabbitMQ): `259 passed`(마이그레이션 001~004 적용 + PG repo round-trip 포함).
|
||||
- `ruff check trex_core tests` → All checks passed.
|
||||
- Manual:
|
||||
- 없음(트리거 로직은 결정적 단위 테스트로 커버; 실 KIS 라이브 트리거는 운영 실증 이월).
|
||||
- Logs:
|
||||
- `236 passed, 23 skipped, 1 deselected in ~0.8s` / `259 passed ... in ~11s`.
|
||||
|
||||
## Carried-Over Items
|
||||
- BAT-010 R-2 (실 PostgreSQL `004_add_price_thresholds.sql` round-trip): **해소** — 인프라 게이트 259 passed(마이그레이션 적용 + PG repo 테스트 green)로 확인.
|
||||
- 운영 실증 이월(차기 거래일): stop-loss·take-profit·forced_close·장 마감의 실 KIS 라이브 트리거 1건 관찰. 휴장/운영 제약으로 본 batch 는 mock·정적 검증까지. release feedback 으로 추적.
|
||||
|
||||
## Result
|
||||
- Ready for Release: true
|
||||
- Blocking Issues:
|
||||
- 없음. 잔여(실 KIS 라이브 트리거 운영 실증)는 blocker 아님 — paper/mock 결정적 검증 + KIS 어댑터 tr_id/파싱 단위 검증으로 release gate 충족. 차기 거래일 운영 관찰로 보강(REL feedback).
|
||||
@@ -17,3 +17,5 @@
|
||||
| BAT-007 | standard | released | REQ-021, REQ-022, REQ-023, REQ-024, REQ-025, REQ-026 | dcy-006 | [bat-007](./bat-007_20260605_strategy-module-rabbitmq-transport/) |
|
||||
| BAT-008 | standard | released | REQ-027, REQ-028, REQ-029, REQ-030, REQ-031, REQ-032, REQ-033 | dcy-005 | [bat-008](./bat-008_20260606_portfolio-hub-snapshot-read-layer/) |
|
||||
| BAT-009 | batch-lite | release-candidate | REQ-010 (CHG-20260606-01 / AC-5) | dcy-002 | [bat-009](./bat-009_20260606_paper-broker-env-tuning/) |
|
||||
| BAT-010 | standard | release-candidate | REQ-034, REQ-035, REQ-036, REQ-041 | dcy-007 | [bat-010](./bat-010_20260607_safety-sell-foundation/) |
|
||||
| BAT-011 | standard | release-candidate | REQ-037, REQ-038, REQ-039, REQ-040, REQ-042 | dcy-007 | [bat-011](./bat-011_20260607_safety-sell-triggers/) |
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# 0. 문서 상태
|
||||
|
||||
- 상태: draft
|
||||
- 상태: confirmed
|
||||
- 문서 ID: dcy-007_20260606_core-autonomous-safety-sell
|
||||
- 이슈명: core 자율 안전 매도 — stop-loss·take-profit·시간 만기 자동 트리거
|
||||
- 작성 시각(KST): 2026-06-06
|
||||
- 마지막 갱신 시각(KST): 2026-06-06 (review-discovery 반영: M-1~2, m-1~7; OQ-5 Answered; OQ-6 Deferred)
|
||||
- 마지막 갱신 시각(KST): 2026-06-07 (draft-req: REQ-034~042 생성)
|
||||
- 대체됨: 없음
|
||||
- 후속 Discovery 참조: 없음
|
||||
- 생성된 REQ 참조: 없음
|
||||
- 생성된 REQ 참조: REQ-034, REQ-035, REQ-036, REQ-037, REQ-038, REQ-039, REQ-040, REQ-041, REQ-042
|
||||
|
||||
# 1. 계획 상태 요약
|
||||
|
||||
@@ -252,8 +252,8 @@
|
||||
|
||||
## Freeze 플래그
|
||||
|
||||
- Handoff Decision: REQ Drafting 진행 가능
|
||||
- Handoff Decision: 승인
|
||||
- Handoff Rationale: OQ-5 Answered, OQ-6 Deferred. 모든 Open 항목 해소. FR-7 범위 확정(forced_close_at 포함).
|
||||
- Ready for REQ Drafting: true
|
||||
- Confirmed By: Justin
|
||||
- Confirmed At (KST): TBD
|
||||
- Confirmed By: Justin Ko
|
||||
- Confirmed At (KST): 2026-06-07
|
||||
|
||||
@@ -12,4 +12,4 @@
|
||||
| dcy-004_20260604_portfolio-hub-ledger-integration | 20260604 | portfolio-hub ledger 연동 — 체결을 외부 원장에 기록 | [Discovery](./dcy-004_20260604_portfolio-hub-ledger-integration.md) | confirmed |
|
||||
| dcy-005_20260604_portfolio-hub-asset-snapshot-api | 20260604 | portfolio-hub 종목 스냅샷 read API — 전략 모듈에 전략 실행 대상 자산 리스트 제공 | [Discovery](./dcy-005_20260604_portfolio-hub-asset-snapshot-api.md) | confirmed |
|
||||
| dcy-006_20260605_strategy-module-rabbitmq-transport | 20260605 | trex-core ↔ 전략 모듈 통신 transport — RabbitMQ 도입 (스냅샷 read + intent 제출 + 이벤트) | [Discovery](./dcy-006_20260605_strategy-module-rabbitmq-transport.md) | confirmed |
|
||||
| dcy-007_20260606_core-autonomous-safety-sell | 20260606 | core 자율 안전 매도 — stop-loss·시간 만기 자동 트리거 | [Discovery](./dcy-007_20260606_core-autonomous-safety-sell.md) | draft |
|
||||
| dcy-007_20260606_core-autonomous-safety-sell | 20260606 | core 자율 안전 매도 — stop-loss·시간 만기 자동 트리거 | [Discovery](./dcy-007_20260606_core-autonomous-safety-sell.md) | confirmed |
|
||||
@@ -4,7 +4,7 @@
|
||||
- Baseline Seed: .stage-pilot/bootstrap/baseline.yaml
|
||||
- Status: draft
|
||||
- Owner: project maintainers
|
||||
- Last Updated (KST): 2026-06-06 (BAT-008)
|
||||
- Last Updated (KST): 2026-06-07 (BAT-011)
|
||||
- Source Discovery / Batch: bootstrap-baseline (pre-Discovery); HTTP API = dcy-002 / BAT-002
|
||||
- Primary Runtime: daemon (HTTP API) + library (slot 도메인)
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
|---|---|---|---|
|
||||
| 스냅샷 RPC | `trex.snapshot.req` | `correlation_id`, `reply_to=amq.rabbitmq.reply-to` | reply-to 큐로 `{view, strategy_id, trading_date, slots[], capacity{}}` |
|
||||
| intent RPC | `trex.intent.req` | `correlation_id` = intent 멱등키, `reply_to=amq.rabbitmq.reply-to`, `delivery_mode=2` | reply-to 큐로 `{intent_id, status:admitted|rejected, slot_id?, reason?}`. 체결/종료는 응답 아닌 이벤트로 |
|
||||
| update_thresholds | `trex.intent.req` | `action="update_thresholds"`, `reply_to` | reply-to 큐로 `{status:updated|ignored, slot_id, reason?}` (REQ-040) |
|
||||
| 이벤트 pub/sub | `trex.events` (topic) | routing key `{event_type}.{strategy_id}.{symbol}` | 전략 큐가 자기 `strategy_id` 패턴으로 바인딩 |
|
||||
|
||||
### Message contracts
|
||||
@@ -82,10 +83,25 @@
|
||||
"trading_date": "YYYY-MM-DD",
|
||||
"symbol": str, "qty": float?, "allocated_capital": float?,
|
||||
"entry_price": float?, "strategy_id": str?, "sell_strategy_id": str?,
|
||||
"portfolio_id": str?, "forced_close_at": "ISO-8601"? }
|
||||
"portfolio_id": str?, "forced_close_at": "ISO-8601"?,
|
||||
"stop_loss_price": number?, "take_profit_price": number? }
|
||||
```
|
||||
- `stop_loss_price`/`take_profit_price`(선택, REQ-035/038): 매수 시 설정하면 워치독 만료 후 safety-sell 가격 트리거의 기준이 된다. 미포함 시 NULL(트리거 비활성).
|
||||
sell payload 는 `{"intent_type":"sell", "intent_id":..., "slot_id":..., "reason":..., "sell_strategy_id":...}`.
|
||||
|
||||
#### `trex.intent.req` `update_thresholds` 액션 (BAT-011 / REQ-040)
|
||||
```
|
||||
{ "action": "update_thresholds",
|
||||
"slot_id": str,
|
||||
"stop_loss_price": number | null?, # 생략=유지, null=해제
|
||||
"take_profit_price": number | null?, # 생략=유지, null=해제
|
||||
"forced_close_at": "ISO-8601" | null?, # 생략=유지, null=해제
|
||||
"actor": str? }
|
||||
```
|
||||
- 매수 이후 손절·익절·강제 만기 시각을 갱신한다. 세 필드는 독립적이며 payload 에 **키가 없으면 기존 값을 유지**, 명시적 `null` 이면 해제한다(REQ-040 AC-4/AC-5).
|
||||
- `holding` 슬롯에서만 적용된다. 처리 성공 시 해당 슬롯의 워치독 `last_seen_at` 을 리셋한다(전략 모듈 생존 신호, REQ-034).
|
||||
- 응답: `{status: "updated", slot_id}` / 비-holding 은 `{status: "ignored", slot_id, reason: "not_holding"}` / 미존재는 `{status: "ignored", slot_id, reason: "slot_not_found"}`.
|
||||
|
||||
#### `trex.events` payload (BAT-007)
|
||||
```
|
||||
{ "event_type": "slot_filled_buy"|"slot_filled_sell"|...,
|
||||
@@ -108,7 +124,7 @@ sell payload 는 `{"intent_type":"sell", "intent_id":..., "slot_id":..., "reason
|
||||
|
||||
- 어댑터 `TransportHealth` ∈ {connected, disconnected, reconnecting}. 어플리케이션 레이어는 disconnected 상태에서 `TransportUnavailableError` 로 빠르게 실패한다(버퍼링 0, 보수적 중단).
|
||||
- 재연결은 백오프 1s→2s→4s→8s→16s→30s cap, 어댑터가 자체 구현.
|
||||
- core 자율 안전 매도는 transport 와 무관하게 동작해야 한다(architecture contract; 자동 트리거 구현은 별도 Discovery).
|
||||
- core 자율 안전 매도는 transport 와 무관하게 `trex-safety-sell` worker 가 동작한다(forced_close / stop-loss·take-profit / 장 마감, dcy-007 REQ-037~041, BAT-010/011 구현 완료).
|
||||
|
||||
### `trex.snapshot.req` envelope (BAT-008 / REQ-028)
|
||||
|
||||
@@ -141,6 +157,8 @@ sell payload 는 `{"intent_type":"sell", "intent_id":..., "slot_id":..., "reason
|
||||
```
|
||||
**No `price` field on items** (REQ-028 AC-3 / REQ-027 AC-1).
|
||||
|
||||
- 워치독 신호 (REQ-034): `trex.snapshot.req` 처리는 read-only 응답 산출에 더해, 응답에 포함된 모든 `holding` 슬롯의 워치독 `last_seen_at` 을 갱신한다. 즉 전략 모듈이 스냅샷을 폴링하는 것 자체가 해당 슬롯의 생존 신호이며, 폴링이 끊긴(워치독 만료) 슬롯만 safety-sell 트리거 대상이 된다.
|
||||
|
||||
### `trex.pause.req` control RPC (BAT-008 / REQ-032)
|
||||
|
||||
요청:
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
- Baseline Seed: .stage-pilot/bootstrap/baseline.yaml
|
||||
- Status: draft
|
||||
- Owner: project maintainers
|
||||
- Last Updated (KST): 2026-06-06 (BAT-008)
|
||||
- Last Updated (KST): 2026-06-07 (BAT-011)
|
||||
- Source Discovery / Batch: bootstrap-baseline (pre-Discovery, 3-phase lifecycle 밖의 bootstrap 산출물)
|
||||
|
||||
## Project Summary
|
||||
@@ -23,7 +23,7 @@
|
||||
## Primary Runtime
|
||||
- Type: daemon (sync + thread, no HTTP) + library (slot 도메인)
|
||||
- Entry Points:
|
||||
- `trex-core-daemon` (console_script → `trex_core.runtime.daemon:main`) :: 슬롯 코어 데몬 — migrate+preallocate+reconcile 후 6 백그라운드 스레드(intent RPC consumer / snapshot RPC consumer / pause RPC consumer / snapshot scheduler / execution / ledger) 기동, RabbitMQ messaging 으로 전략 모듈과 통신 (BAT-002 + BAT-007 + BAT-008)
|
||||
- `trex-core-daemon` (console_script → `trex_core.runtime.daemon:main`) :: 슬롯 코어 데몬 — migrate+preallocate+reconcile 후 7 백그라운드 스레드(intent RPC consumer / snapshot RPC consumer / pause RPC consumer / snapshot scheduler / execution / ledger / safety-sell) 기동, RabbitMQ messaging 으로 전략 모듈과 통신 (BAT-002 + BAT-007 + BAT-008 + BAT-011)
|
||||
- `trex-core` (console_script → `trex_core.runtime.cli:main`) :: 운영 CLI (`pause` / `resume` / `paused`) (BAT-008)
|
||||
- RabbitMQ `trex.snapshot.req` / `trex.intent.req` / `trex.pause.req` / `trex.events` :: 매수·매도 전략 데몬과의 transport (docs/interface-contract.md)
|
||||
- SlotService / SlotRepository (Protocol) :: in-process 도메인 서비스 + 저장소 경계(라이브러리 사용)
|
||||
@@ -73,19 +73,21 @@
|
||||
│ │ ├── ledger_port.py # LedgerPort + LedgerTrade/LedgerWriteResult/예외 (BAT-006)
|
||||
│ │ ├── ledger_outbox.py # LedgerOutbox Protocol + LedgerWriter + InMemoryLedgerOutbox (BAT-006)
|
||||
│ │ ├── messaging.py # MessagingTransport Protocol + DTO + TransportHealth (BAT-007)
|
||||
│ │ ├── messaging_handlers.py # intent/snapshot/pause RPC handlers + slot event payload builder (BAT-007/BAT-008)
|
||||
│ │ ├── messaging_handlers.py # intent/snapshot/pause RPC handlers (+update_thresholds REQ-040) + slot event payload builder (BAT-007/BAT-008/BAT-011)
|
||||
│ │ ├── portfolio.py # PortfolioHubReadPort + SnapshotStore + PauseStore + envelope builder (BAT-008)
|
||||
│ │ ├── reconciliation.py # reconcile_positions (hub ↔ broker symbol-aggregate) (BAT-008)
|
||||
│ │ └── snapshot_scheduler.py # SnapshotScheduler — daily fetch + swap + reconcile (BAT-008)
|
||||
│ │ ├── snapshot_scheduler.py # SnapshotScheduler — daily fetch + swap + reconcile (BAT-008)
|
||||
│ │ ├── watchdog.py # WatchdogStore — per-slot last_seen_at + expiry/grace 판정 (BAT-010 / REQ-034)
|
||||
│ │ └── safety_sell.py # SafetySellService — forced_close / stop-loss·take-profit / 장 마감 트리거 (BAT-011 / REQ-037~039)
|
||||
│ ├── adapters/ # 인프라 어댑터 (BAT-002, BAT-004, BAT-006, BAT-007, BAT-008)
|
||||
│ │ ├── broker/ # paper.py(결정적 async 더블) · kis.py(KIS Open API + 잔고조회) (BAT-004/BAT-008)
|
||||
│ │ ├── broker/ # paper.py(결정적 async 더블 + 현재가 stub) · kis.py(KIS Open API + 잔고조회 + 현재가 polling) (BAT-004/BAT-008/BAT-011)
|
||||
│ │ ├── ledger/ # in_memory.py(stub) · portfolio_hub.py(HTTP 어댑터) (BAT-006)
|
||||
│ │ ├── messaging/ # topology.py · in_memory.py(stub) · rabbitmq.py(pika 동기) (BAT-007)
|
||||
│ │ ├── portfolio/ # hub_read.py(HTTP) · stub_read.py(stub) · pause_store.py(Postgres) (BAT-008)
|
||||
│ │ └── persistence/ # postgresql.py(+PostgreSQLLedgerOutbox) · in_memory_intent_store.py · migrate.py · sql/(001_…, 002_create_ledger_outbox.sql, 003_create_snapshot_pause.sql)
|
||||
│ │ └── persistence/ # postgresql.py(+PostgreSQLLedgerOutbox) · in_memory_intent_store.py · migrate.py · sql/(001_…, 002_create_ledger_outbox.sql, 003_create_snapshot_pause.sql, 004_add_price_thresholds.sql)
|
||||
│ └── runtime/ # 데몬 (BAT-002, BAT-004, BAT-006, BAT-007, BAT-008)
|
||||
│ ├── config.py # DaemonConfig + KISConfig + PortfolioHubConfig + RabbitMQConfig + SnapshotScheduleConfig (env)
|
||||
│ ├── daemon.py # Daemon class: setup(migrate/preallocate/reconcile/declare-topology) + 6 worker threads (execution·ledger·intent-rpc·snapshot-rpc·pause-rpc·snapshot-scheduler) + graceful shutdown
|
||||
│ ├── config.py # DaemonConfig + KISConfig + PortfolioHubConfig + RabbitMQConfig + SnapshotScheduleConfig + SafetySellConfig(TREX_WATCHDOG_*/FORCED_CLOSE/STOP_LOSS_POLL/MARKET_CLOSE) (env)
|
||||
│ ├── daemon.py # Daemon class: setup(migrate/preallocate/reconcile/declare-topology) + 7 worker threads (execution·ledger·intent-rpc·snapshot-rpc·pause-rpc·snapshot-scheduler·safety-sell) + WatchdogStore + graceful shutdown
|
||||
│ └── cli.py # trex-core CLI: pause/resume/paused subcommands (BAT-008 / REQ-032)
|
||||
└── tests/
|
||||
├── domain/slot/ # test_state / test_service / test_contract
|
||||
@@ -143,3 +145,5 @@
|
||||
- 2026-06-04 :: BAT-006 — ledger 레이어 추가(application LedgerPort/LedgerOutbox, adapters/ledger in_memory·portfolio_hub, persistence PostgreSQLLedgerOutbox + 002 마이그레이션, runtime ledger writer 루프, FillStatus 확장, NFR-1 import 스윕) (REQ-017~020).
|
||||
- 2026-06-05 :: BAT-007 — transport 전환(`adapters/api/` 제거, `application/messaging.py` + `application/messaging_handlers.py` 신설, `adapters/messaging/` 신설(topology/in_memory/rabbitmq), runtime/daemon 을 sync+thread 4 스레드 구조로 재구성, `RabbitMQConfig` + `TREX_RABBITMQ_URL` env, `TREX_API_HOST`/`TREX_API_PORT` 폐기, `fastapi`/`uvicorn` 의존 제거 + `pika` 도입, NFR-1 import 스윕에 pika/messaging 추가, HTTP 표면 0 회귀 가드) (REQ-021~026).
|
||||
- 2026-06-06 :: BAT-008 — portfolio-hub 스냅샷 read 레이어 추가(`application/portfolio.py` + `application/reconciliation.py` + `application/snapshot_scheduler.py` 신설, `adapters/portfolio/` 신설(hub_read/stub_read/pause_store), `BrokerPort.get_balance()` 확장 + paper/KIS 구현, `messaging_handlers` 에 snapshot envelope + pause RPC 추가, runtime/daemon 을 6 스레드로 확장(pause-rpc + snapshot-scheduler), `SnapshotScheduleConfig` + `TREX_SNAPSHOT_*` env, `runtime/cli.py` + `trex-core` 콘솔 스크립트, `003_create_snapshot_pause.sql` 마이그레이션, NFR-1 import 스윕 유지) (REQ-027~033; REQ-030 옵션 D 는 portfolio-hub#8 배포 후 별도 구현).
|
||||
- 2026-06-07 :: BAT-010 — safety-sell 기반 인프라(`application/watchdog.py` WatchdogStore 신설, `Slot` 에 `stop_loss_price`/`take_profit_price` 필드 + `004_add_price_thresholds.sql` additive 마이그레이션, `SlotService.sell()` 자율 매도 내부 경로, `make_snapshot_rpc_handler` 워치독 갱신, `SafetySellConfig` env 5종 파싱) (REQ-034~036, 041).
|
||||
- 2026-06-07 :: BAT-011 — safety-sell 트리거 레이어(`application/safety_sell.py` SafetySellService 신설 — forced_close/stop-loss·take-profit/장 마감, `BrokerPort.get_current_price()` + KIS `inquire-price`(`TTTC8397R`/`VTTC8397R`)·paper stub, `SlotService.update_thresholds()` + intent RPC `update_thresholds` 액션, runtime/daemon 을 7 스레드로 확장(`trex-safety-sell`) + WatchdogStore 배선) (REQ-037~040, 042).
|
||||
|
||||
+42
-5
@@ -4,7 +4,7 @@
|
||||
- Baseline Seed: .stage-pilot/bootstrap/baseline.yaml
|
||||
- Status: draft
|
||||
- Owner: project maintainers
|
||||
- Last Updated (KST): 2026-06-06 (BAT-008)
|
||||
- Last Updated (KST): 2026-06-07 (BAT-011)
|
||||
- Source Discovery / Batch: bootstrap-baseline (pre-Discovery); 데몬/HTTP 흐름 = dcy-002 / BAT-002
|
||||
- Primary Runtime: daemon (HTTP API) + library (slot 도메인)
|
||||
|
||||
@@ -33,11 +33,14 @@
|
||||
|
||||
1. `trex-core-daemon` 기동 → `DaemonConfig.from_env()`(TREX_POSTGRESQL_DSN, `TREX_RABBITMQ_URL` 등).
|
||||
2. setup: `apply_migrations(dsn)`(멱등) → 거래일 슬롯 `preallocate_daily_slots`(멱등) → `execution.reconcile()`(REQ-014) → 이벤트 publisher 의 `declare_topology()`.
|
||||
3. start: 4개 백그라운드 스레드 동시 기동
|
||||
3. start: 7개 백그라운드 스레드 동시 기동
|
||||
- `trex-execution` :: 브로커 dispatch/poll 루프 (REQ-013/014)
|
||||
- `trex-ledger` :: ledger outbox 드레인 (REQ-019)
|
||||
- `trex-intent-rpc` :: `trex.intent.req` 소비자 (prefetch=1, FIFO, dedup via intent_log)
|
||||
- `trex-snapshot-rpc` :: `trex.snapshot.req` 소비자
|
||||
- `trex-intent-rpc` :: `trex.intent.req` 소비자 (prefetch=1, FIFO, dedup via intent_log; `update_thresholds` 액션 포함 REQ-040)
|
||||
- `trex-snapshot-rpc` :: `trex.snapshot.req` 소비자 (응답 holding 슬롯마다 워치독 `last_seen_at` 갱신 REQ-034)
|
||||
- `trex-pause-rpc` :: `trex.pause.req` 소비자 (REQ-032)
|
||||
- `trex-snapshot-scheduler` :: 일일 portfolio-hub 스냅샷 refresh (REQ-030)
|
||||
- `trex-safety-sell` :: 자율 안전 매도 트리거 루프 (forced_close / stop-loss·take-profit / 장 마감, REQ-037/038/039)
|
||||
4. shutdown: `SIGINT`/`SIGTERM` → `stop.set()` → 모든 스레드 join + 모든 transport `close()`. HTTP 표면 0, 별도 포트 바인딩 없음.
|
||||
|
||||
## Flow: `스냅샷 RPC` (RabbitMQ `trex.snapshot.req`, BAT-007)
|
||||
@@ -107,11 +110,45 @@
|
||||
- degraded 상태가 있으면 `degraded=true + reason + sanitized message` 동봉.
|
||||
4. envelope: `{assets[], view, available_slots, degraded, reason, message}` — `assets` 는 항상 배열, null 금지. 항목 필드는 `symbol/asset_id/cash_balance/asset_quantity/active_strategy_id/strategy_params`, **price 필드는 없음**.
|
||||
|
||||
## Flow: `per-slot 워치독 갱신` (BAT-010/011 / REQ-034, REQ-040)
|
||||
|
||||
전략 모듈의 생존 신호를 추적해 자율 안전 매도가 정상 운용 슬롯에 개입하지 않게 한다.
|
||||
|
||||
1. `trex-snapshot-rpc` 가 `trex.snapshot.req` 를 처리할 때 응답에 포함된 모든 `holding` 슬롯에 대해 `WatchdogStore.update(slot_id)`(`last_seen_at = now`)를 호출한다. 전략 모듈이 스냅샷을 폴링하는 한 그 슬롯은 "활성"으로 판정된다.
|
||||
2. `update_thresholds` intent(REQ-040)도 수신 시 해당 슬롯 `last_seen_at` 을 리셋한다(임계값 갱신 = 생존 신호).
|
||||
3. `WatchdogStore.is_expired(slot_id, TREX_WATCHDOG_TIMEOUT)` 가 True 이고 grace period(`TREX_WATCHDOG_GRACE_PERIOD`, 데몬 부팅 직후) 밖이면 그 슬롯은 워치독 만료 → forced_close/가격 트리거 대상이 된다.
|
||||
4. in-memory store 라 데몬 재시작 시 초기화되며 grace period 가 자동 재시작된다(부팅 직후 오탐 방지).
|
||||
|
||||
## Flow: `forced_close_at 만기 자동 매도` (BAT-011 / REQ-037)
|
||||
|
||||
`trex-safety-sell` worker 가 `TREX_FORCED_CLOSE_CHECK_INTERVAL` 주기로 수행.
|
||||
|
||||
1. 오늘 `holding` 슬롯 중 워치독 만료(grace 밖)이고 `forced_close_at <= now(UTC)` 인 슬롯을 선별.
|
||||
2. 대상 슬롯에 `SlotService.sell(actor="trex-safety-sell", reason="forced_close_at")` 호출 → `holding→pending_sell`(REQ-036). 이후 체결은 집행 루프가 처리.
|
||||
3. 워치독 활성 슬롯·`forced_close_at=None` 슬롯은 제외. 이미 `pending_sell` 이면 idempotent(중복 매도 없음). KIS 호출 없음(DB + in-memory 비교만).
|
||||
|
||||
## Flow: `stop-loss·take-profit 가격 체크` (BAT-011 / REQ-038)
|
||||
|
||||
`trex-safety-sell` worker 가 `TREX_STOP_LOSS_POLL_INTERVAL` 주기로 수행.
|
||||
|
||||
1. `holding` + 워치독 만료 + (`stop_loss_price` 또는 `take_profit_price` 설정) 슬롯만 대상(워치독 활성 슬롯은 KIS 호출조차 안 함).
|
||||
2. `BrokerPort.get_current_price(symbol)` → KIS `inquire-price`(`TTTC8397R` 실전 / `VTTC8397R` 모의)의 `stck_prpr`. paper 는 프로그램된 가격(미설정 시 None).
|
||||
3. `현재가 <= stop_loss_price` → 손절(`reason="stop_loss"`), `현재가 >= take_profit_price` → 익절(`reason="take_profit"`)으로 `SlotService.sell()`. 가격 미수신(None) 시 skip(오탐 방지, 다음 tick 재시도).
|
||||
4. 쿼터 가드: 시간당 호출 상한 `N × (3600 / interval)` 추정이 임계 초과 시 경고 로그(REQ-038 AC-8).
|
||||
|
||||
## Flow: `장 마감 강제 청산` (BAT-011 / REQ-039)
|
||||
|
||||
`TREX_MARKET_CLOSE_LIQUIDATION_TIME`(HH:MM KST, optional) 설정 시에만 동작.
|
||||
|
||||
1. `trex-safety-sell` worker 가 매 tick 현재 KST 시각을 평가 → 설정 시각 도달 + 같은 거래일 미발동(`last_liquidation_date` 가드)이면 발동.
|
||||
2. 발동 시 **워치독·grace 무관** 모든 `holding` 슬롯에 `SlotService.sell(reason="market_close_liquidation")`. 이미 `pending_sell` 은 idempotent.
|
||||
3. 미설정이면 동작하지 않음. in-memory 가드라 재시작 시 같은 날 재발동 가능(설계상 허용 — REQ-039 Notes).
|
||||
|
||||
## Flow: `broker degrade 분기` (BAT-007 / REQ-024)
|
||||
|
||||
1. transport connection drop → 어댑터가 `_mark_disconnected()` + 백오프(1s→2s→4s→8s→16s→30s cap) 재연결.
|
||||
2. application 레이어는 `transport.health()` 가 `disconnected/reconnecting` 인 동안 신규 발행/RPC 가 `TransportUnavailableError` 로 빠르게 실패한다 → 전략은 매매 보류.
|
||||
3. core 자율 안전 매도(stop-loss / forced_close)는 transport 와 무관하게 execution 루프가 수행한다(설계 contract; 자동 트리거 실제 구현은 별도 Discovery — A-5 미구현).
|
||||
3. core 자율 안전 매도(stop-loss·take-profit / forced_close / 장 마감 청산)는 transport 와 무관하게 `trex-safety-sell` worker 가 수행한다(아래 safety-sell 흐름; dcy-007 REQ-037~041 구현, BAT-010/011). 이전 "A-5 미구현" 항목은 dcy-007 로 해소됨.
|
||||
4. 로컬 버퍼링 없음 — broker 복구 후 옛 intent 가 자동 재생되지 않는다(전략이 신선 상태로 재판단).
|
||||
|
||||
## Flow: `주문 집행 루프` (백그라운드, BAT-004)
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
# req-041: safety-sell 환경 설정 — 5종 env var
|
||||
|
||||
- Status: Implemented
|
||||
- Type: Configuration
|
||||
- Priority: High
|
||||
- Owner: project maintainers
|
||||
|
||||
## Intent
|
||||
|
||||
자율 안전 매도 기능(REQ-034~039)에서 사용하는 5종의 환경 변수를 `trex_core/runtime/config.py`의 `DaemonConfig.from_env()`에 추가한다. 모든 값은 env에서 파싱되며 기본값을 제공해야 한다(dcy-007 NFR-1·3, A-4).
|
||||
|
||||
## Requirement
|
||||
|
||||
- 시스템은 아래 5종 env var를 `DaemonConfig.from_env()`에서 파싱해야 한다.
|
||||
|
||||
| 환경 변수 | 타입 | 기본값 | 설명 |
|
||||
|---|---|---|---|
|
||||
| `TREX_WATCHDOG_TIMEOUT` | int (초) | 120 | 워치독 만료 임계값 (REQ-034) |
|
||||
| `TREX_WATCHDOG_GRACE_PERIOD` | int (초) | 60 | 데몬 재시작 후 만료 판정 유예 시간 (REQ-034) |
|
||||
| `TREX_FORCED_CLOSE_CHECK_INTERVAL` | int (초) | 60 | forced_close_at 만기 체크 주기 (REQ-037) |
|
||||
| `TREX_STOP_LOSS_POLL_INTERVAL` | int (초) | 30 | stop-loss·take-profit 가격 polling 주기 (REQ-038) |
|
||||
| `TREX_MARKET_CLOSE_LIQUIDATION_TIME` | str (HH:MM KST) | None (미설정) | 장 마감 강제 청산 시각 (REQ-039). 미설정 시 동작 없음. |
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. Given 환경 변수가 설정되지 않았을 때, When `DaemonConfig.from_env()`를 호출하면, Then `watchdog_timeout=120`, `watchdog_grace_period=60`, `forced_close_check_interval=60`, `stop_loss_poll_interval=30`, `market_close_liquidation_time=None`이 반환된다.
|
||||
2. Given `TREX_WATCHDOG_TIMEOUT=180`이 설정됐을 때, When `from_env()`를 호출하면, Then `watchdog_timeout=180`이 반환된다.
|
||||
3. Given `TREX_MARKET_CLOSE_LIQUIDATION_TIME=15:10`이 설정됐을 때, When `from_env()`를 호출하면, Then `market_close_liquidation_time`이 파싱되어 `datetime.time(15, 10)`(또는 동등한 형식)으로 반환된다.
|
||||
4. Given `TREX_MARKET_CLOSE_LIQUIDATION_TIME`이 설정되지 않았을 때, When `from_env()`를 호출하면, Then `market_close_liquidation_time=None`이 반환된다.
|
||||
5. Given 잘못된 형식의 `TREX_MARKET_CLOSE_LIQUIDATION_TIME`(예: `"25:99"`)이 설정됐을 때, When `from_env()`를 호출하면, Then 명확한 오류(ValueError 또는 ConfigError)가 발생한다.
|
||||
|
||||
## Impacted Area
|
||||
|
||||
- Docs:
|
||||
- `docs/project-structure.md` — Primary Runtime env var 목록에 5종 추가 (REQ-042 참조)
|
||||
- Modules:
|
||||
- `trex_core/runtime/config.py` — `DaemonConfig`에 5종 env var 파싱 추가
|
||||
- Tests:
|
||||
- `DaemonConfig.from_env()` 단위 테스트 — 기본값 확인, 개별 설정 확인, 잘못된 형식 오류 확인
|
||||
|
||||
## Notes
|
||||
|
||||
- `TREX_MARKET_CLOSE_LIQUIDATION_TIME`은 HH:MM KST 형식으로 파싱한다. `datetime.time` 또는 `(hour, minute)` tuple로 저장한다. 배치에서 결정.
|
||||
- `TREX_WATCHDOG_TIMEOUT`과 `TREX_WATCHDOG_GRACE_PERIOD`의 단위는 초(int)다.
|
||||
- 기존 `DaemonConfig`에는 `TREX_SNAPSHOT_TICK_SEC`, `TREX_SNAPSHOT_SCHEDULE_KST` 등 유사 env var가 이미 있으므로 동일 파싱 패턴을 따른다.
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260607-01
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- dcy-007 confirmed → REQ-041 초안 생성
|
||||
|
||||
#### Intent
|
||||
|
||||
- safety-sell 기능에 필요한 5종 env var를 Configuration REQ로 정규화
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- Added: AC-1~5 전체
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- Docs: project-structure.md (REQ-042에서 처리)
|
||||
- Modules: config.py
|
||||
- Tests: DaemonConfig 단위 테스트
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: none (미할당)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: suggest-batch-reqs 후 batch 배정
|
||||
|
||||
### CHG-20260607-02
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- Proposed → Approved (confirm-req)
|
||||
|
||||
#### Intent
|
||||
|
||||
- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: none (미할당)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: suggest-batch-reqs 후 batch 배정
|
||||
@@ -0,0 +1,116 @@
|
||||
# req-035: 슬롯 가격 임계값 도메인 필드 + DB 마이그레이션
|
||||
|
||||
- Status: Implemented
|
||||
- Type: Data
|
||||
- Priority: High
|
||||
- Owner: project maintainers
|
||||
|
||||
## Intent
|
||||
|
||||
stop-loss(손절)·take-profit(익절) 가격 트리거를 지원하기 위해 슬롯 도메인 모델에 `stop_loss_price: Optional[Decimal]`과 `take_profit_price: Optional[Decimal]` 필드를 추가하고, PostgreSQL 스키마에 해당 컬럼을 additive 마이그레이션으로 추가한다(dcy-007 코드 영향 — 필드 추가 + DB 마이그레이션).
|
||||
|
||||
## Requirement
|
||||
|
||||
- 슬롯 도메인 모델(`SlotModel` / `Slot`)에 `stop_loss_price: Optional[Decimal]`과 `take_profit_price: Optional[Decimal]` 필드를 추가해야 한다. 기본값은 `None`이다.
|
||||
- PostgreSQL `slots` 테이블에 `stop_loss_price NUMERIC` 및 `take_profit_price NUMERIC` 컬럼을 NULL 허용으로 추가하는 마이그레이션을 제공해야 한다. 기존 슬롯 행은 `NULL`로 유지된다.
|
||||
- 매수 intent(`BuyIntent` / `IntentIn`) payload에 `stop_loss_price`와 `take_profit_price` 필드를 선택 항목으로 추가해야 한다. 미설정 시 `None`으로 처리한다.
|
||||
- persistence 레이어(PostgreSQL 어댑터)는 두 필드를 읽기·쓰기 대상에 포함해야 한다.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. Given `SlotModel` 인스턴스를 생성할 때 `stop_loss_price`와 `take_profit_price`를 지정하지 않으면, When 필드를 조회하면, Then 두 필드 모두 `None`이다.
|
||||
2. Given `stop_loss_price=Decimal("50000")`으로 슬롯을 생성해 Postgres에 저장하면, When 같은 슬롯을 다시 조회하면, Then `stop_loss_price`가 `Decimal("50000")`으로 복원된다.
|
||||
3. Given 기존 슬롯 row가 마이그레이션 전에 존재하면, When 마이그레이션을 실행하면, Then 기존 row의 `stop_loss_price`와 `take_profit_price`가 `NULL`이고 마이그레이션이 멱등하게 완료된다.
|
||||
4. Given 매수 intent payload에 `stop_loss_price`와 `take_profit_price`를 포함하면, When `IntentService.submit_buy`를 호출하면, Then 해당 슬롯의 두 필드가 저장된다.
|
||||
5. Given 매수 intent payload에 두 필드가 없으면, When `submit_buy`를 호출하면, Then 두 필드는 `None`으로 저장되고 오류가 발생하지 않는다.
|
||||
|
||||
## Impacted Area
|
||||
|
||||
- Docs:
|
||||
- `docs/interface-contract.md` — intent RPC payload에 `stop_loss_price`, `take_profit_price` 선택 필드 추가 (REQ-042 참조)
|
||||
- Modules:
|
||||
- `trex_core/domain/slot/models.py` — `stop_loss_price`, `take_profit_price` 필드 추가
|
||||
- `trex_core/adapters/persistence/postgresql.py` — 두 필드 read/write 포함
|
||||
- `trex_core/adapters/persistence/sql/` — 마이그레이션 스크립트 추가
|
||||
- `trex_core/application/intents.py` — `IntentIn`(또는 `BuyIntent`)에 두 필드 추가
|
||||
- `trex_core/application/messaging_handlers.py` — intent RPC payload 파싱에 두 필드 추가
|
||||
- Tests:
|
||||
- 슬롯 도메인 모델 단위 테스트 — 필드 기본값 및 직렬화 확인
|
||||
- persistence 통합 테스트 — 저장·조회 라운드트립 확인
|
||||
- 마이그레이션 멱등성 테스트
|
||||
|
||||
## Notes
|
||||
|
||||
- `Decimal` 타입을 사용해 부동소수점 오차 없이 가격을 표현한다.
|
||||
- `take_profit_price`와 `stop_loss_price` 값의 논리적 유효성(예: take_profit > stop_loss)은 이번 REQ 범위 밖이다. REQ 단계에서 추가 필요 시 change-req로 처리한다.
|
||||
- 기존 `forced_close_at` 필드는 이미 존재하며 이 REQ에서 다루지 않는다.
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260607-01
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- dcy-007 confirmed → REQ-035 초안 생성
|
||||
|
||||
#### Intent
|
||||
|
||||
- 가격 임계값 필드 추가 + DB 마이그레이션을 독립 Data REQ로 정규화
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- Added: AC-1~5 전체
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- Docs: interface-contract.md (REQ-042에서 처리)
|
||||
- Modules: models.py, postgresql.py, sql 마이그레이션, intents.py, messaging_handlers.py
|
||||
- Tests: 도메인 단위 + persistence 통합 + 마이그레이션 멱등성
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: none (미할당)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: suggest-batch-reqs 후 batch 배정
|
||||
|
||||
### CHG-20260607-02
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- Proposed → Approved (confirm-req)
|
||||
|
||||
#### Intent
|
||||
|
||||
- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: none (미할당)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: suggest-batch-reqs 후 batch 배정
|
||||
@@ -0,0 +1,151 @@
|
||||
# req-042: safety-sell baseline 문서 정합
|
||||
|
||||
- Status: Implemented
|
||||
- Type: Documentation
|
||||
- Priority: Medium
|
||||
- Owner: project maintainers
|
||||
|
||||
## Intent
|
||||
|
||||
dcy-007에서 정의된 자율 안전 매도 기능(REQ-034~041)의 구현 완료 후 `docs/interface-contract.md`, `docs/runtime-flows.md`, `docs/project-structure.md` 세 baseline 문서를 최신 아키텍처와 정합시킨다.
|
||||
|
||||
## Requirement
|
||||
|
||||
- `docs/interface-contract.md`에 아래 항목을 반영해야 한다:
|
||||
- intent RPC payload에 `stop_loss_price`, `take_profit_price` 선택 필드 추가
|
||||
- `update_thresholds` action 계약 (payload 구조, 응답 구조, holding-only 제약)
|
||||
- snapshot RPC가 워치독 `last_seen_at` 갱신 신호임을 명시
|
||||
- `docs/runtime-flows.md`에 아래 흐름을 추가해야 한다:
|
||||
- `per-slot 워치독 갱신` 흐름 (snapshot RPC handler → last_seen_at 갱신)
|
||||
- `forced_close_at 만기 자동 매도` 흐름 (safety-sell worker → forced_close_at 체크 → sell())
|
||||
- `stop-loss·take-profit 가격 체크` 흐름 (KIS polling → 조건 평가 → sell())
|
||||
- `장 마감 강제 청산` 흐름 (TREX_MARKET_CLOSE_LIQUIDATION_TIME → 전체 holding 매도)
|
||||
- `runtime-flows.md:114`의 "A-5 미구현" 주석을 dcy-007 REQ 목록 참조로 갱신
|
||||
- `docs/project-structure.md`에 아래 항목을 반영해야 한다:
|
||||
- Primary Runtime 스레드 목록에 `trex-safety-sell` 추가
|
||||
- 신규 5종 env var(`TREX_WATCHDOG_TIMEOUT` 등) 목록 추가
|
||||
- 신규 어댑터/모듈(KIS 현재가 polling, watchdog store) 반영
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. Given `docs/interface-contract.md`를 읽을 때, When `update_thresholds` action을 찾으면, Then payload 구조(`slot_id`, `stop_loss_price?`, `take_profit_price?`, `forced_close_at?`)와 응답(`status: updated|ignored`)이 명시되어 있다.
|
||||
2. Given `docs/interface-contract.md`를 읽을 때, When intent RPC payload 예시를 찾으면, Then `stop_loss_price`와 `take_profit_price`가 선택 필드로 포함되어 있다.
|
||||
3. Given `docs/runtime-flows.md`를 읽을 때, When safety-sell 관련 흐름을 찾으면, Then 워치독 갱신·forced_close_at 체크·가격 체크·장 마감 청산 흐름이 각각 서술되어 있다.
|
||||
4. Given `docs/runtime-flows.md:114`(또는 이에 해당하는 broker degrade 흐름)을 읽을 때, When "A-5 미구현" 주석을 찾으면, Then 해당 주석이 dcy-007 구현 REQ 참조로 갱신되어 있다.
|
||||
5. Given `docs/project-structure.md`를 읽을 때, When Primary Runtime 스레드 목록을 찾으면, Then `trex-safety-sell`이 포함되어 있다.
|
||||
|
||||
## Impacted Area
|
||||
|
||||
- Docs:
|
||||
- `docs/interface-contract.md`
|
||||
- `docs/runtime-flows.md`
|
||||
- `docs/project-structure.md`
|
||||
- Modules: 없음 (문서 전용)
|
||||
- Tests: 없음
|
||||
|
||||
## Notes
|
||||
|
||||
- 이 REQ는 REQ-034~041 구현이 완료된 후 실행해야 한다. batch verification 단계에서 최신 구현 상태를 반영해 문서를 갱신한다.
|
||||
- `runtime-flows.md:114`의 "A-5 미구현" 문구는 dcy-007 confirmed 시점에 이미 근거가 확보됐으므로, 구현 완료 전에도 "dcy-007 REQ-034~041로 트래킹" 형태로 미리 갱신할 수 있다.
|
||||
- NFR-2(outbox retry) 관련 흐름은 기존 runtime-flows.md의 outbox 섹션에 흡수되어 있으므로 별도 추가 불필요.
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260607-01
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- dcy-007 confirmed → REQ-042 초안 생성
|
||||
|
||||
#### Intent
|
||||
|
||||
- safety-sell 구현 후 세 baseline 문서 정합을 Documentation REQ로 정규화
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- Added: AC-1~5 전체
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- Docs: interface-contract.md, runtime-flows.md, project-structure.md
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: none (미할당)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: suggest-batch-reqs 후 batch 배정 (REQ-034~041 구현 batch 이후 연계)
|
||||
|
||||
### CHG-20260607-02
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- Proposed → Approved (confirm-req)
|
||||
|
||||
#### Intent
|
||||
|
||||
- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: none (미할당)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: suggest-batch-reqs 후 batch 배정
|
||||
|
||||
### CHG-20260607-03
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-011 release-candidate).
|
||||
|
||||
#### Intent
|
||||
|
||||
- BAT-011 에서 세 baseline 문서 정합 완료(interface-contract: update_thresholds 계약 + price 선택 필드 + 워치독 신호; runtime-flows: 워치독 갱신/forced_close/가격 체크/장 마감 4종 흐름 + A-5 주석 갱신; project-structure: trex-safety-sell 스레드 + watchdog.py/safety_sell.py 모듈). AC-1~5 충족 → 구현 완료 동기화.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: BAT-011 (release-candidate)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current (Implemented)
|
||||
- Follow-up Action: draft-release 로 BAT-011 배포 편성.
|
||||
@@ -0,0 +1,150 @@
|
||||
# req-037: forced_close_at 만기 자동 매도 루프
|
||||
|
||||
- Status: Implemented
|
||||
- Type: Integration
|
||||
- Priority: High
|
||||
- Owner: project maintainers
|
||||
|
||||
## Intent
|
||||
|
||||
슬롯에 설정된 `forced_close_at` 만기 시각이 도달했을 때 데몬이 자율적으로 매도를 제출하는 백그라운드 루프를 추가한다. 워치독이 만료된 슬롯에 한해 동작하므로 전략 모듈이 정상 활성 중인 슬롯에는 개입하지 않는다(dcy-007 FR-1, NFR-1).
|
||||
|
||||
## Requirement
|
||||
|
||||
- 데몬은 `TREX_FORCED_CLOSE_CHECK_INTERVAL(초)` 주기로 `holding` 상태이며 **워치독이 만료된** 슬롯 중 `forced_close_at <= now(KST)`인 슬롯을 감지해야 한다.
|
||||
- 조건에 해당하는 슬롯에 대해 `SlotService.sell()`(REQ-036)을 통해 내부 매도를 제출해야 한다.
|
||||
- 이 루프는 daemon.py의 safety-sell worker 스레드에서 실행된다. 기존 6개 스레드에 safety-sell worker(`trex-safety-sell`)를 추가한다.
|
||||
- `forced_close_at`이 `None`인 슬롯은 이 루프의 대상이 아니다.
|
||||
- 워치독 활성(만료되지 않은) 슬롯은 이 루프의 대상이 아니다.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. Given `holding` 상태이고 워치독 만료이며 `forced_close_at = now() − 1s`인 슬롯 S가 있을 때, When checker 1회가 수행되면, Then S에 대해 `SlotService.sell()`이 호출된다.
|
||||
2. Given `holding` 상태이고 워치독 만료이며 `forced_close_at = now() + 1hour`인 슬롯 S가 있을 때, When checker 1회가 수행되면, Then S에 대해 `sell()`이 호출되지 않는다.
|
||||
3. Given `holding` 상태이고 워치독 **활성**이며 `forced_close_at = now() − 1s`인 슬롯 S가 있을 때, When checker 1회가 수행되면, Then S에 대해 `sell()`이 호출되지 않는다(워치독 보호).
|
||||
4. Given `forced_close_at = None`인 슬롯 S가 있을 때, When checker 1회가 수행되면, Then S는 만기 체크 대상에서 제외된다.
|
||||
5. Given 대상 슬롯이 이미 `pending_sell`일 때, When checker 1회가 수행되면, Then 중복 `sell()` 호출이 발생하지 않는다(REQ-036 idempotency 적용).
|
||||
6. Given `TREX_FORCED_CLOSE_CHECK_INTERVAL=60`일 때, When daemon이 정상 실행 중이면, Then checker는 60±허용오차 초 주기로 반복 실행된다(NFR-1: 감지 지연 ≤ 60초).
|
||||
|
||||
## Impacted Area
|
||||
|
||||
- Docs:
|
||||
- `docs/runtime-flows.md` — forced_close_at 만기 감지 루프 흐름 추가 (REQ-042 참조)
|
||||
- `docs/project-structure.md` — `trex-safety-sell` 스레드 추가 반영 (REQ-042 참조)
|
||||
- Modules:
|
||||
- `trex_core/runtime/daemon.py` — `trex-safety-sell` worker 스레드 추가, `_safety_sell_loop()` 메서드 구현
|
||||
- `trex_core/domain/slot/service.py` — forced_close_at 조건 평가 메서드(또는 checker 내 인라인)
|
||||
- Tests:
|
||||
- forced_close_at checker 단위 테스트 — 만기 슬롯 감지 + sell() 호출 확인
|
||||
- 워치독 활성 슬롯 통과 확인
|
||||
- idempotency 확인 (pending_sell 슬롯 중복 처리 없음)
|
||||
|
||||
## Notes
|
||||
|
||||
- safety-sell worker(`trex-safety-sell`)는 forced_close_at checker(REQ-037), stop-loss·take-profit checker(REQ-038), 장 마감 청산(REQ-039)을 하나의 통합 루프로 묶어 처리할 수 있다. 구체적인 스레드 분리/통합 방식은 batch planning에서 결정한다.
|
||||
- 체크 로직은 DB 조회(forced_close_at 필드)와 in-memory 워치독 store 비교만 수행하므로 KIS API 호출이 없다. 낮은 연산 비용.
|
||||
- `TREX_FORCED_CLOSE_CHECK_INTERVAL` env var는 REQ-041 참조.
|
||||
- `SlotService.sell()` 구현은 REQ-036 참조.
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260607-01
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- dcy-007 confirmed → REQ-037 초안 생성
|
||||
|
||||
#### Intent
|
||||
|
||||
- forced_close_at 만기 자동 매도 루프(FR-1)를 독립 Integration REQ로 정규화
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- Added: AC-1~6 전체
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- Docs: runtime-flows.md, project-structure.md (REQ-042에서 처리)
|
||||
- Modules: daemon.py (스레드 추가), service.py (forced_close 조건 평가)
|
||||
- Tests: checker 단위 테스트 3종
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: none (미할당)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: suggest-batch-reqs 후 batch 배정
|
||||
|
||||
### CHG-20260607-02
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- Proposed → Approved (confirm-req)
|
||||
|
||||
#### Intent
|
||||
|
||||
- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: none (미할당)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: suggest-batch-reqs 후 batch 배정
|
||||
|
||||
### CHG-20260607-03
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-011 release-candidate).
|
||||
|
||||
#### Intent
|
||||
|
||||
- BAT-011 verification.md 의 AC↔evidence(SafetySellService forced_close checker — 만기/미만기/워치독 보호/None 제외/idempotency/캐던스)로 AC-1~6 충족 → 구현 완료 동기화.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: BAT-011 (release-candidate)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current (Implemented)
|
||||
- Follow-up Action: draft-release 로 BAT-011 배포 편성.
|
||||
@@ -0,0 +1,155 @@
|
||||
# req-038: stop-loss·take-profit KIS 가격 polling + 트리거
|
||||
|
||||
- Status: Implemented
|
||||
- Type: Integration
|
||||
- Priority: High
|
||||
- Owner: project maintainers
|
||||
|
||||
## Intent
|
||||
|
||||
워치독이 만료된 슬롯에 한해 KIS API(`TTTC8397R` 실전 / `VTTC8397R` 모의)로 현재가를 주기적으로 polling하고, `현재가 <= stop_loss_price`(손절) 또는 `현재가 >= take_profit_price`(익절) 조건 충족 시 자동 매도를 제출한다. 전략 모듈이 정상 활성 중인 슬롯에는 개입하지 않는다(dcy-007 FR-2, NFR-3, A-3).
|
||||
|
||||
## Requirement
|
||||
|
||||
- 데몬은 `TREX_STOP_LOSS_POLL_INTERVAL(초)` 주기로, `stop_loss_price` 또는 `take_profit_price`가 설정되고 **워치독이 만료된** `holding` 슬롯들의 현재가를 KIS `TTTC8397R`(실전) / `VTTC8397R`(모의) API로 polling해야 한다.
|
||||
- 현재가 평가 기준값은 `stck_prpr`(직전 체결가) 필드를 사용한다.
|
||||
- `현재가 <= stop_loss_price` 조건 충족 시 손절 매도를 제출해야 한다.
|
||||
- `현재가 >= take_profit_price` 조건 충족 시 익절 매도를 제출해야 한다.
|
||||
- 두 조건은 동일 polling 루프에서 평가한다.
|
||||
- 워치독 활성(만료되지 않은) 슬롯과 임계값 미설정(`None`) 슬롯은 polling 대상이 아니다.
|
||||
- KIS API 호출 상한은 `워치독 만료 대상 슬롯 수 × (3600 / TREX_STOP_LOSS_POLL_INTERVAL)`로 산정한다. 이 상한이 KIS 쿼터를 초과할 경우 polling 간격 조정 AC를 이 REQ의 Notes에 명시하고 REQ 단계에서 상한 경고를 구현한다.
|
||||
- 자동 매도 제출은 `SlotService.sell()`(REQ-036)을 사용한다.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. Given `holding`, 워치독 만료, `stop_loss_price=Decimal("50000")`, `take_profit_price=None`인 슬롯 S가 있고 KIS polling 결과 `stck_prpr=49000`이면, When 가격 체크가 수행되면, Then S에 대해 `SlotService.sell()`이 호출된다(손절 트리거).
|
||||
2. Given `holding`, 워치독 만료, `stop_loss_price=None`, `take_profit_price=Decimal("80000")`인 슬롯 S가 있고 KIS polling 결과 `stck_prpr=81000`이면, When 가격 체크가 수행되면, Then S에 대해 `sell()`이 호출된다(익절 트리거).
|
||||
3. Given `holding`, 워치독 만료, `stop_loss_price=Decimal("50000")`, `take_profit_price=Decimal("80000")`인 슬롯 S가 있고 `stck_prpr=65000`이면, When 가격 체크가 수행되면, Then `sell()`이 호출되지 않는다(두 조건 모두 미충족).
|
||||
4. Given `holding`, 워치독 **활성**인 슬롯 S가 있을 때, When 가격 polling이 수행되면, Then S에 대한 KIS API 호출이 발생하지 않는다(워치독 보호).
|
||||
5. Given `stop_loss_price=None`이고 `take_profit_price=None`인 슬롯 S가 있을 때, When polling이 수행되면, Then S는 polling 대상에서 제외된다.
|
||||
6. Given 이미 `pending_sell` 상태인 슬롯 S에 조건이 충족될 때, When 가격 체크가 수행되면, Then 중복 `sell()` 호출이 발생하지 않는다(REQ-036 idempotency).
|
||||
7. Given 실전 모드(`broker=kis`)일 때, When 가격 polling을 수행하면, Then `TTTC8397R` endpoint를 사용한다. 모의 모드(`broker=paper`)일 때는 `VTTC8397R`을 사용한다.
|
||||
8. Given `TREX_STOP_LOSS_POLL_INTERVAL=30`이고 워치독 만료 대상 슬롯이 N개일 때, When 정상 동작 중이면, Then 1시간당 KIS API 호출 수가 `N × 120`회를 넘지 않는다(NFR-3 쿼터 산정).
|
||||
|
||||
## Impacted Area
|
||||
|
||||
- Docs:
|
||||
- `docs/runtime-flows.md` — stop-loss·take-profit 가격 체크 흐름 추가 (REQ-042 참조)
|
||||
- `docs/interface-contract.md` — 현재가 polling 어댑터 계약 명시 (REQ-042 참조)
|
||||
- Modules:
|
||||
- 신규 `trex_core/adapters/broker/kis_price.py` 또는 `kis.py` 확장 — `TTTC8397R`/`VTTC8397R` 현재가 polling, `stck_prpr` 파싱
|
||||
- `trex_core/runtime/daemon.py` — stop-loss·take-profit 가격 체크를 safety-sell worker에 통합
|
||||
- `trex_core/domain/slot/service.py` — stop-loss·take-profit 조건 평가 메서드(또는 worker 내 인라인)
|
||||
- Tests:
|
||||
- KIS 현재가 polling 어댑터 단위 테스트 — `stck_prpr` 파싱 확인, 실전/모의 분기 확인
|
||||
- 가격 체크 단위 테스트(mock polling) — 손절/익절/미충족/워치독 보호 시나리오
|
||||
|
||||
## Notes
|
||||
|
||||
- KIS 현재가 polling 어댑터는 기존 `trex_core/adapters/broker/kis.py`에 `get_current_price(symbol)` 메서드로 추가하거나, 별도 모듈로 분리한다. batch planning에서 결정한다.
|
||||
- paper broker 모의 환경에서는 `VTTC8397R`를 사용하거나, stub 구현으로 현재가를 반환하는 방식을 batch에서 결정한다.
|
||||
- KIS 쿼터 초과 방지: 워치독 만료 슬롯 수가 임계값을 넘으면 polling 간격 자동 조정 또는 경고 로그를 구현한다. 구체적인 쿼터 수치는 KIS API 문서 기준으로 batch 설계에서 확정한다.
|
||||
- `TREX_STOP_LOSS_POLL_INTERVAL` env var는 REQ-041 참조.
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260607-01
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- dcy-007 confirmed → REQ-038 초안 생성
|
||||
|
||||
#### Intent
|
||||
|
||||
- stop-loss·take-profit KIS 가격 polling + 트리거(FR-2)를 독립 Integration REQ로 정규화
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- Added: AC-1~8 전체
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- Docs: runtime-flows.md, interface-contract.md (REQ-042에서 처리)
|
||||
- Modules: kis 현재가 polling 어댑터(신규/확장), daemon.py, service.py
|
||||
- Tests: polling 어댑터 단위 + 가격 체크 시나리오 단위 (mock)
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: none (미할당)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: suggest-batch-reqs 후 batch 배정
|
||||
|
||||
### CHG-20260607-02
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- Proposed → Approved (confirm-req)
|
||||
|
||||
#### Intent
|
||||
|
||||
- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: none (미할당)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: suggest-batch-reqs 후 batch 배정
|
||||
|
||||
### CHG-20260607-03
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-011 release-candidate).
|
||||
|
||||
#### Intent
|
||||
|
||||
- BAT-011 verification.md 의 AC↔evidence(KIS `get_current_price` TTTC8397R/VTTC8397R·`stck_prpr` 파싱 + 손절/익절/미충족/워치독 보호/쿼터 산정/가격 미수신 skip)로 AC-1~8 충족 → 구현 완료 동기화.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: BAT-011 (release-candidate)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current (Implemented)
|
||||
- Follow-up Action: draft-release 로 BAT-011 배포 편성.
|
||||
@@ -0,0 +1,147 @@
|
||||
# req-039: 장 마감 강제 청산 (TREX_MARKET_CLOSE_LIQUIDATION_TIME)
|
||||
|
||||
- Status: Implemented
|
||||
- Type: Integration
|
||||
- Priority: High
|
||||
- Owner: project maintainers
|
||||
|
||||
## Intent
|
||||
|
||||
`TREX_MARKET_CLOSE_LIQUIDATION_TIME`(HH:MM KST)이 설정된 경우, 해당 시각에 전략 모듈 활성 여부와 워치독 만료 여부에 관계없이 모든 `holding` 슬롯에 매도를 제출해 장 마감 전 자동 청산을 보장한다. 미설정 시에는 동작하지 않는다(dcy-007 FR-6).
|
||||
|
||||
## Requirement
|
||||
|
||||
- `TREX_MARKET_CLOSE_LIQUIDATION_TIME`(HH:MM KST 형식, optional)이 설정된 경우, 데몬은 해당 시각이 도래하면 워치독 만료 여부와 무관하게 모든 `holding` 슬롯에 `SlotService.sell()`(REQ-036)을 통해 매도를 제출해야 한다.
|
||||
- `TREX_MARKET_CLOSE_LIQUIDATION_TIME`이 설정되지 않은 경우 이 기능은 동작하지 않는다.
|
||||
- 장 마감 청산은 같은 거래일에 한 번만 발동해야 한다(중복 발동 방지).
|
||||
- 청산 대상은 발동 시각의 `holding` 슬롯 전체이며 워치독 상태를 조건으로 사용하지 않는다.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. Given `TREX_MARKET_CLOSE_LIQUIDATION_TIME=15:10`이고 `holding` 슬롯이 N개 있을 때, When KST 15:10이 도래하면, Then N개 슬롯 모두에 `SlotService.sell()`이 호출된다.
|
||||
2. Given `TREX_MARKET_CLOSE_LIQUIDATION_TIME=15:10`이고 워치독 **활성** 슬롯이 포함되어 있을 때, When KST 15:10이 도래하면, Then 워치독 활성 슬롯도 청산 대상에 포함된다(워치독 무관).
|
||||
3. Given `TREX_MARKET_CLOSE_LIQUIDATION_TIME`이 설정되지 않았을 때, When 어느 시각이든, Then 장 마감 청산 루프가 매도를 제출하지 않는다.
|
||||
4. Given 장 마감 청산이 같은 거래일 내에 이미 발동된 적이 있을 때, When 같은 거래일 내 청산 로직이 다시 평가되면, Then 중복 발동이 발생하지 않는다.
|
||||
5. Given 이미 `pending_sell` 상태인 슬롯 S가 청산 대상에 포함될 때, When 장 마감 청산이 발동되면, Then S에 대한 중복 `sell()` 호출이 발생하지 않는다(REQ-036 idempotency).
|
||||
6. Given grace period(REQ-034) 중에 `TREX_MARKET_CLOSE_LIQUIDATION_TIME`이 도래할 때, When 장 마감 청산이 발동되면, Then grace period 여부와 무관하게 청산이 실행된다(FR-6: 워치독 무관).
|
||||
|
||||
## Impacted Area
|
||||
|
||||
- Docs:
|
||||
- `docs/runtime-flows.md` — 장 마감 강제 청산 흐름 추가 (REQ-042 참조)
|
||||
- Modules:
|
||||
- `trex_core/runtime/daemon.py` — safety-sell worker 내 장 마감 청산 로직 추가
|
||||
- `trex_core/runtime/config.py` — `TREX_MARKET_CLOSE_LIQUIDATION_TIME` 파싱 (REQ-041 참조)
|
||||
- Tests:
|
||||
- 장 마감 청산 단위 테스트(시각 mock) — 설정/미설정 분기, 전체 holding 청산 확인
|
||||
- 중복 발동 방지 테스트
|
||||
- grace period 무관 동작 테스트
|
||||
|
||||
## Notes
|
||||
|
||||
- 장 마감 청산 발동은 safety-sell worker 루프(`trex-safety-sell`)에서 KST 시각을 주기적으로 체크하는 방식으로 구현한다. 발동 여부는 in-memory `last_liquidation_date`로 추적한다.
|
||||
- 발동 시각 정밀도는 worker 루프 주기에 의존한다. `TREX_FORCED_CLOSE_CHECK_INTERVAL` 또는 별도 설정으로 루프 주기를 결정한다. batch에서 확정한다.
|
||||
- `TREX_MARKET_CLOSE_LIQUIDATION_TIME` env var는 REQ-041 참조.
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260607-01
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- dcy-007 confirmed → REQ-039 초안 생성
|
||||
|
||||
#### Intent
|
||||
|
||||
- 장 마감 강제 청산(FR-6)을 독립 Integration REQ로 정규화
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- Added: AC-1~6 전체
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- Docs: runtime-flows.md (REQ-042에서 처리)
|
||||
- Modules: daemon.py (safety-sell worker 확장), config.py
|
||||
- Tests: 단위 테스트 3종
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: none (미할당)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: suggest-batch-reqs 후 batch 배정
|
||||
|
||||
### CHG-20260607-02
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- Proposed → Approved (confirm-req)
|
||||
|
||||
#### Intent
|
||||
|
||||
- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: none (미할당)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: suggest-batch-reqs 후 batch 배정
|
||||
|
||||
### CHG-20260607-03
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-011 release-candidate).
|
||||
|
||||
#### Intent
|
||||
|
||||
- BAT-011 verification.md 의 AC↔evidence(시각 도래 전체 holding 청산·워치독 무관·미설정 무동작·거래일 1회 가드·grace 무관·발동 전 무동작)로 AC-1~6 충족 → 구현 완료 동기화.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: BAT-011 (release-candidate)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current (Implemented)
|
||||
- Follow-up Action: draft-release 로 BAT-011 배포 편성.
|
||||
@@ -0,0 +1,115 @@
|
||||
# req-034: per-slot 워치독 — snapshot RPC 기반 전략 모듈 활성 감지
|
||||
|
||||
- Status: Implemented
|
||||
- Type: Interface
|
||||
- Priority: High
|
||||
- Owner: project maintainers
|
||||
|
||||
## Intent
|
||||
|
||||
전략 모듈이 슬롯을 활성 관리 중인지 데몬이 감지하기 위해, snapshot RPC 응답 시 해당 슬롯들의 `last_seen_at` 타임스탬프를 in-memory 워치독 store에 기록한다. `TREX_WATCHDOG_TIMEOUT` 이상 갱신되지 않은 슬롯은 "전략 모듈 미감시" 상태로 판정해 자율 안전 매도 체크 대상이 된다(dcy-007 FR-5).
|
||||
|
||||
## Requirement
|
||||
|
||||
- 시스템은 `trex.snapshot.req` 요청을 처리할 때, 응답 envelope에 포함되는 `holding` 슬롯들의 `last_seen_at`을 in-memory 워치독 store(`dict[slot_id, datetime]`)에 현재 시각(UTC 또는 KST 일관 적용)으로 갱신해야 한다.
|
||||
- 시스템은 `now() − last_seen_at > TREX_WATCHDOG_TIMEOUT(초)` 조건이 성립하는 슬롯을 "워치독 만료(watchdog-expired)"로 판정해야 한다.
|
||||
- 데몬 기동 후 `TREX_WATCHDOG_GRACE_PERIOD(초)` 동안은 워치독 만료 판정을 유예해야 한다(grace period). grace period 중에는 어떤 슬롯도 만료로 판정하지 않는다.
|
||||
- 워치독 store는 Postgres에 영속하지 않으며 데몬 재시작 시 초기화된다.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. Given `trex.snapshot.req` handler가 응답을 산출할 때, When 응답 슬롯 목록에 `slot_id = S`가 포함되면, Then `watchdog_store[S]`가 현재 시각으로 갱신된다.
|
||||
2. Given `TREX_WATCHDOG_TIMEOUT=120`이고 slot S의 `watchdog_store[S]`가 121초 전일 때, When 워치독 만료 여부를 평가하면, Then S는 만료로 판정된다.
|
||||
3. Given `TREX_WATCHDOG_TIMEOUT=120`이고 slot S의 `watchdog_store[S]`가 60초 전일 때, When 워치독 만료 여부를 평가하면, Then S는 만료로 판정되지 않는다.
|
||||
4. Given 데몬이 기동된 지 `TREX_WATCHDOG_GRACE_PERIOD=60` 초 이내일 때, When 워치독 만료 평가를 수행하면, Then 모든 슬롯의 만료 판정이 유예된다(store 비어 있는 슬롯 포함).
|
||||
5. Given 데몬 재시작이 발생하면, When 기동 직후 워치독 store를 조회하면, Then store는 비어 있다.
|
||||
6. Given 전략 모듈이 `TREX_WATCHDOG_TIMEOUT / 2` 미만 주기로 snapshot RPC를 보낼 때, When 정상 동작 중이면, Then 해당 슬롯의 워치독이 만료되지 않는다(NFR-4 관계식 검증).
|
||||
|
||||
## Impacted Area
|
||||
|
||||
- Docs:
|
||||
- `docs/runtime-flows.md` — 워치독 갱신 흐름 추가 (REQ-042 참조)
|
||||
- `docs/interface-contract.md` — snapshot RPC가 watchdog 갱신 신호임을 명시 (REQ-042 참조)
|
||||
- Modules:
|
||||
- `trex_core/application/messaging_handlers.py` — `make_snapshot_rpc_handler` 확장: 응답 슬롯의 `last_seen_at` 갱신
|
||||
- `trex_core/application/portfolio.py` 또는 신규 `trex_core/application/watchdog.py` — WatchdogStore (`dict[slot_id, datetime]`), grace period 시작 타임스탬프 관리
|
||||
- Tests:
|
||||
- `make_snapshot_rpc_handler` 단위 테스트 — `last_seen_at` 갱신 확인
|
||||
- 워치독 만료 판정 단위 테스트 — 만료/비만료/grace period 시나리오
|
||||
|
||||
## Notes
|
||||
|
||||
- 워치독 store 갱신은 snapshot RPC 응답 슬롯 기준이다. intent RPC(매수/매도)는 갱신 신호가 아니다(`update_thresholds` 액션은 제외 — REQ-040 참조).
|
||||
- NFR-4 권장 관계식: `snapshot RPC 호출 주기 < TREX_WATCHDOG_TIMEOUT / 2`. 기본값 기준 전략 모듈은 60초 이내 주기로 snapshot RPC를 호출해야 한다. 관계식 위반 시 정상 동작 슬롯에서도 워치독 오탐 발생.
|
||||
- 워치독 store 구현 위치(portfolio.py 확장 vs 신규 watchdog.py)는 batch planning에서 결정한다.
|
||||
- `TREX_WATCHDOG_TIMEOUT`, `TREX_WATCHDOG_GRACE_PERIOD` env var 파싱은 REQ-041 참조.
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260607-01
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- dcy-007 confirmed → REQ-034 초안 생성
|
||||
|
||||
#### Intent
|
||||
|
||||
- per-slot 워치독 메커니즘(FR-5)을 독립 구현·검증 단위로 정규화
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- Added: AC-1~6 전체
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- Docs: runtime-flows.md, interface-contract.md (REQ-042에서 처리)
|
||||
- Modules: messaging_handlers.py, watchdog store 신규/확장
|
||||
- Tests: snapshot handler + 워치독 판정 단위 테스트
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: none (미할당)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: suggest-batch-reqs 후 batch 배정
|
||||
|
||||
### CHG-20260607-02
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- Proposed → Approved (confirm-req)
|
||||
|
||||
#### Intent
|
||||
|
||||
- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: none (미할당)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: suggest-batch-reqs 후 batch 배정
|
||||
@@ -0,0 +1,112 @@
|
||||
# req-036: 자동 매도 내부 경로 — SlotService.sell + idempotency
|
||||
|
||||
- Status: Implemented
|
||||
- Type: Interface
|
||||
- Priority: High
|
||||
- Owner: project maintainers
|
||||
|
||||
## Intent
|
||||
|
||||
자율 안전 매도 트리거가 RabbitMQ intent RPC를 거치지 않고 직접 슬롯 매도를 제출할 수 있도록 `SlotService.sell()` 내부 메서드를 추가한다. RabbitMQ 장애와 독립적으로 동작해야 하는 transport-independent 매도 경로다. 이미 `pending_sell` 상태인 슬롯에 대한 중복 트리거를 방지하는 idempotency도 함께 보장한다(dcy-007 FR-3, FR-4, A-1).
|
||||
|
||||
## Requirement
|
||||
|
||||
- `SlotService`는 `sell(slot_id, actor, reason)` 메서드를 제공해야 한다. 이 메서드는 `holding` 슬롯을 `pending_sell`로 전이시키고 기존 outbox 내구성·execution 루프 경로를 따른다.
|
||||
- `sell()`은 내부 함수 직접 호출이며 RabbitMQ intent RPC를 거치지 않는다.
|
||||
- 대상 슬롯이 이미 `pending_sell` 상태이면 `sell()`은 추가 전이 없이 조용히 반환하거나 멱등 응답을 반환해야 한다(중복 트리거 방지).
|
||||
- 대상 슬롯이 `holding` 이 아니고 `pending_sell`도 아닌 상태이면 적절한 예외 또는 오류 응답을 반환한다.
|
||||
- `sell()`을 통해 제출된 매도는 기존 `execution` worker의 `transit_slot(holding→pending_sell)` + broker dispatch + ledger 기록 경로를 재사용해야 한다.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. Given `holding` 상태의 슬롯 S가 있을 때, When `SlotService.sell(S, actor="safety-sell", reason="stop_loss")` 를 호출하면, Then S의 상태가 `pending_sell`로 전이된다.
|
||||
2. Given `pending_sell` 상태의 슬롯 S가 있을 때, When `sell(S, ...)` 를 다시 호출하면, Then 슬롯 상태가 변경되지 않고 중복 매도 intent가 제출되지 않는다.
|
||||
3. Given `idle` 상태의 슬롯 S가 있을 때, When `sell(S, ...)` 를 호출하면, Then 오류(예외 또는 rejected 응답)가 반환되고 슬롯 상태는 변경되지 않는다.
|
||||
4. Given `sell()` 호출 성공 시, When execution worker가 이후 처리하면, Then 매도 주문이 broker에 제출되고 outbox 및 ledger 경로가 기존 intent RPC 경로와 동일하게 작동한다.
|
||||
5. Given RabbitMQ transport가 불가용 상태일 때, When `sell()` 을 호출하면, Then transport 상태와 무관하게 `holding→pending_sell` 전이가 성공한다.
|
||||
|
||||
## Impacted Area
|
||||
|
||||
- Docs: 없음 (런타임 흐름 변경은 REQ-037~039에서 발생, baseline 문서 갱신은 REQ-042 참조)
|
||||
- Modules:
|
||||
- `trex_core/domain/slot/service.py` — `SlotService.sell(slot_id, actor, reason)` 메서드 추가
|
||||
- Tests:
|
||||
- `SlotService.sell()` 단위 테스트 — `holding→pending_sell` 전이 확인
|
||||
- idempotency 테스트 — `pending_sell` 슬롯 중복 호출 시 무전이 확인
|
||||
- 잘못된 상태(`idle`) 슬롯 호출 시 오류 반환 확인
|
||||
|
||||
## Notes
|
||||
|
||||
- `sell()`은 `transit_slot(slot_id, from_state=HOLDING, to_state=PENDING_SELL, ...)` 패턴 위에 구현한다. 기존 advisory lock + Postgres commit 보장이 그대로 적용된다.
|
||||
- NFR-2(자동 매도 실패 시 outbox retry): broker 오류 발생 시 execution worker의 기존 retry 경로를 따른다. 별도 retry 로직 불필요.
|
||||
- `actor` 값은 트리거 구분을 위해 `"safety-sell"`, `"forced-close"`, `"market-close"` 등으로 구체화하는 것을 권장한다. 정확한 값은 batch 설계에서 결정한다.
|
||||
- 이 메서드는 dcy-007 FR-3의 "내부 자동 매도 trigger 경로"를 표준화한다. 이후 kill-switch Discovery(trex-core#3)에서도 재사용 가능하도록 격리한다.
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260607-01
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- dcy-007 confirmed → REQ-036 초안 생성
|
||||
|
||||
#### Intent
|
||||
|
||||
- 자동 매도 내부 경로(FR-3, FR-4)를 독립 Interface REQ로 정규화
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- Added: AC-1~5 전체
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- Modules: service.py (`sell()` 메서드 추가)
|
||||
- Tests: 단위 테스트 3종
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: none (미할당)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: suggest-batch-reqs 후 batch 배정
|
||||
|
||||
### CHG-20260607-02
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- Proposed → Approved (confirm-req)
|
||||
|
||||
#### Intent
|
||||
|
||||
- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: none (미할당)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: suggest-batch-reqs 후 batch 배정
|
||||
@@ -0,0 +1,151 @@
|
||||
# req-040: update_thresholds intent RPC 액션
|
||||
|
||||
- Status: Implemented
|
||||
- Type: Interface
|
||||
- Priority: High
|
||||
- Owner: project maintainers
|
||||
|
||||
## Intent
|
||||
|
||||
전략 모듈이 매수 이후에도 손절·익절 임계값과 강제 만기 시각을 갱신할 수 있도록 `trex.intent.req` 채널에 `action: "update_thresholds"` 메시지 액션을 추가한다. 수신 시 해당 슬롯의 `stop_loss_price`·`take_profit_price`·`forced_close_at`를 DB에 갱신하고, 해당 슬롯의 워치독 `last_seen_at`을 현재 시각으로 리셋해 전략 모듈 활성 신호로 활용한다(dcy-007 FR-7).
|
||||
|
||||
## Requirement
|
||||
|
||||
- 시스템은 `trex.intent.req` 채널의 `action: "update_thresholds"` 메시지를 처리해야 한다.
|
||||
- 메시지 payload는 `{action: "update_thresholds", slot_id, stop_loss_price?, take_profit_price?, forced_close_at?}` 형식이며, 세 필드는 각각 독립적으로 포함하거나 생략할 수 있다. 생략된 필드는 현재 값을 유지한다.
|
||||
- 처리 성공 시 요청한 필드를 DB에 갱신하고 해당 슬롯의 워치독 `last_seen_at`을 현재 시각으로 리셋해야 한다.
|
||||
- 대상 슬롯이 `holding` 상태인 경우에만 갱신을 수행한다.
|
||||
- 대상 슬롯이 `holding`이 아닌 경우의 처리(무시 또는 에러 응답)는 구현 시 결정한다(OQ-6 Deferred — 선택 옵션: `{status: ignored, reason: "not_holding"}` 응답 또는 silent discard).
|
||||
- `reply_to`가 있으면 갱신 결과(`{status: updated|ignored, slot_id}`)를 응답으로 반환한다.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. Given `holding` 상태의 슬롯 S가 있을 때, When `{action: "update_thresholds", slot_id: S, stop_loss_price: 50000}`을 `trex.intent.req`에 발행하면, Then S의 `stop_loss_price`가 `50000`으로 DB에 갱신된다.
|
||||
2. Given `holding` 상태의 슬롯 S가 있을 때, When `{action: "update_thresholds", slot_id: S, take_profit_price: 80000, forced_close_at: "2026-06-07T15:10:00+09:00"}`를 발행하면, Then S의 `take_profit_price`와 `forced_close_at`가 DB에 갱신된다.
|
||||
3. Given `holding` 상태의 슬롯 S가 있을 때, When `update_thresholds`를 수신하면, Then S의 워치독 `last_seen_at`이 현재 시각으로 리셋된다.
|
||||
4. Given `update_thresholds` 메시지에 `stop_loss_price`만 포함되고 `take_profit_price`와 `forced_close_at`는 생략될 때, When 처리되면, Then `stop_loss_price`만 갱신되고 나머지 두 필드는 기존 값을 유지한다.
|
||||
5. Given `null` 값으로 `stop_loss_price: null`을 명시할 때, When 처리되면, Then `stop_loss_price`가 `NULL`로 갱신된다(임계값 해제).
|
||||
6. Given 대상 슬롯이 `holding`이 아닌 상태일 때, When `update_thresholds`를 수신하면, Then 슬롯 필드가 변경되지 않는다. `reply_to`가 있으면 `{status: ignored, reason: "not_holding"}` 응답을 반환한다(OQ-6 구현 결정).
|
||||
7. Given `reply_to`가 있는 요청에서 갱신이 성공하면, When 처리가 완료되면, Then `{status: updated, slot_id}`가 `reply_to`로 반환된다.
|
||||
|
||||
## Impacted Area
|
||||
|
||||
- Docs:
|
||||
- `docs/interface-contract.md` — `update_thresholds` action 계약 추가 (REQ-042 참조)
|
||||
- Modules:
|
||||
- `trex_core/application/messaging_handlers.py` — `make_intent_rpc_handler` 확장: `update_thresholds` action 라우팅 + DB 갱신 + `last_seen_at` 리셋
|
||||
- `trex_core/domain/slot/service.py` — `SlotService.update_thresholds(slot_id, stop_loss_price, take_profit_price, forced_close_at)` 메서드 추가
|
||||
- Tests:
|
||||
- `update_thresholds` handler 단위 테스트 — holding 슬롯 갱신 확인, last_seen_at 리셋 확인
|
||||
- 필드 부분 갱신 테스트 (일부만 포함된 payload)
|
||||
- null 값 처리 테스트
|
||||
- non-holding 슬롯 무시 테스트
|
||||
|
||||
## Notes
|
||||
|
||||
- OQ-6(Deferred → REQ AC): `holding`이 아닌 슬롯 요청 처리 방식. 구현 시 `{status: ignored, reason: "not_holding"}` 응답 반환을 권장한다. silent discard도 허용되며 batch에서 결정한다.
|
||||
- `update_thresholds` 수신은 워치독 `last_seen_at` 리셋을 겸한다. 전략 모듈이 임계값을 갱신하면 활성 신호로 처리돼 그 시점부터 `TREX_WATCHDOG_TIMEOUT` 타이머가 재시작된다(REQ-034).
|
||||
- 기존 intent RPC dedup(`intent_log.try_insert`) 적용 여부는 batch 설계에서 결정한다. `update_thresholds`는 상태 read-modify가 아닌 idempotent SET이므로 dedup 생략이 적절할 수 있다.
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260607-01
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- dcy-007 confirmed → REQ-040 초안 생성
|
||||
|
||||
#### Intent
|
||||
|
||||
- update_thresholds intent RPC 액션(FR-7)을 독립 Interface REQ로 정규화. OQ-6 Deferred 처리 방식을 AC-6에 명시.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- Added: AC-1~7 전체
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- Docs: interface-contract.md (REQ-042에서 처리)
|
||||
- Modules: messaging_handlers.py (handler 확장), service.py (`update_thresholds()` 추가)
|
||||
- Tests: handler 단위 테스트 4종
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: none (미할당)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: suggest-batch-reqs 후 batch 배정
|
||||
|
||||
### CHG-20260607-02
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- Proposed → Approved (confirm-req)
|
||||
|
||||
#### Intent
|
||||
|
||||
- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: none (미할당)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: suggest-batch-reqs 후 batch 배정
|
||||
|
||||
### CHG-20260607-03
|
||||
|
||||
- Date: 2026-06-07
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-011 release-candidate).
|
||||
|
||||
#### Intent
|
||||
|
||||
- BAT-011 verification.md 의 AC↔evidence(`update_thresholds` 부분 갱신·null 해제·last_seen_at 리셋·non-holding ignored·미존재 slot_not_found·updated 응답)로 AC-1~7 충족 → 구현 완료 동기화. OQ-6 은 AC-6 의 `{ignored, not_holding}` 응답으로 확정 구현.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- (변경 없음)
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: BAT-011 (release-candidate)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current (Implemented)
|
||||
- Follow-up Action: draft-release 로 BAT-011 배포 편성.
|
||||
+14
-5
@@ -74,7 +74,7 @@
|
||||
|
||||
## Next Requirement ID
|
||||
|
||||
- Current: `REQ-034`
|
||||
- Current: `REQ-043`
|
||||
- Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다.
|
||||
|
||||
## Requirement Register
|
||||
@@ -114,11 +114,20 @@
|
||||
| REQ-031 | 포지션 정합 검증 + warning 마스킹 | Exception | Implemented | High | project maintainers | [req-031](./Exception/req-031_position-reconciliation-and-warning.md) |
|
||||
| REQ-032 | 런타임 pause/resume 제어 | Interface | Implemented | Medium | project maintainers | [req-032](./Interface/req-032_runtime-pause-resume-control.md) |
|
||||
| REQ-033 | 스냅샷 read 레이어 baseline 문서 정합 | Documentation | Implemented | Medium | project maintainers | [req-033](./Documentation/req-033_snapshot-read-baseline-alignment.md) |
|
||||
| REQ-034 | per-slot 워치독 — snapshot RPC 기반 전략 모듈 활성 감지 | Interface | Implemented | High | project maintainers | [req-034](./Interface/req-034_per-slot-watchdog.md) |
|
||||
| REQ-035 | 슬롯 가격 임계값 도메인 필드 + DB 마이그레이션 | Data | Implemented | High | project maintainers | [req-035](./Data/req-035_slot-price-threshold-fields-and-migration.md) |
|
||||
| REQ-036 | 자동 매도 내부 경로 — SlotService.sell + idempotency | Interface | Implemented | High | project maintainers | [req-036](./Interface/req-036_auto-sell-internal-path.md) |
|
||||
| REQ-037 | forced_close_at 만기 자동 매도 루프 | Integration | Implemented | High | project maintainers | [req-037](./Integration/req-037_forced-close-at-safety-sell-loop.md) |
|
||||
| REQ-038 | stop-loss·take-profit KIS 가격 polling + 트리거 | Integration | Implemented | High | project maintainers | [req-038](./Integration/req-038_stop-loss-take-profit-kis-polling.md) |
|
||||
| REQ-039 | 장 마감 강제 청산 (TREX_MARKET_CLOSE_LIQUIDATION_TIME) | Integration | Implemented | High | project maintainers | [req-039](./Integration/req-039_market-close-liquidation.md) |
|
||||
| REQ-040 | update_thresholds intent RPC 액션 | Interface | Implemented | High | project maintainers | [req-040](./Interface/req-040_update-thresholds-intent-rpc.md) |
|
||||
| REQ-041 | safety-sell 환경 설정 — 5종 env var | Configuration | Implemented | High | project maintainers | [req-041](./Configuration/req-041_safety-sell-env-config.md) |
|
||||
| REQ-042 | safety-sell baseline 문서 정합 | Documentation | Implemented | Medium | project maintainers | [req-042](./Documentation/req-042_safety-sell-baseline-alignment.md) |
|
||||
|
||||
## Recent Change Log Summary
|
||||
|
||||
- 2026-06-07 :: REQ-037, REQ-038, REQ-039, REQ-040, REQ-042 Approved → Implemented (confirm-req-implemented): BAT-011 verify 완료(신규 38 테스트 green, 단위 236 passed, 인프라 게이트 259 passed, ruff clean). SafetySellService(forced_close/SL·TP KIS polling/장 마감), `BrokerPort.get_current_price`(TTTC8397R/VTTC8397R), `SlotService.update_thresholds` + intent RPC 액션, `trex-safety-sell` 7번째 스레드, baseline 3문서 정합 확인.
|
||||
- 2026-06-07 :: REQ-034, REQ-035, REQ-036, REQ-041 Approved → Implemented (confirm-req-implemented): BAT-010 verify 완료(32 신규 테스트 green, 95 회귀 passed). WatchdogStore, stop_loss/take_profit 도메인 필드, SQL 마이그레이션, SlotService.sell(), SafetySellConfig env vars 5종 구현 확인.
|
||||
- 2026-06-07 :: REQ-034~042 Proposed → Approved (confirm-req): 9개 전원 승인 게이트 통과. dcy-007(confirmed) 범위·결정과 모순 없음, 필수 필드·AC·Impacted Area·Change Log 완비 확인. (confirm-req)
|
||||
- 2026-06-07 :: REQ-034~042 생성 — dcy-007(core 자율 안전 매도: stop-loss·take-profit·시간 만기 자동 트리거) confirmed → 9개 REQ 초안. 워치독(034), 가격 임계값 필드(035), 자동 매도 경로(036), forced_close_at 루프(037), KIS 가격 polling(038), 장 마감 청산(039), update_thresholds RPC(040), env 설정(041), baseline 문서 정합(042). (draft-req)
|
||||
- 2026-06-06 :: REQ-010 CHG-20260606-02 — Status Approved → Implemented (confirm-req-implemented, 근거 BAT-009 release-candidate). AC-1~4 BAT-003 회귀 + AC-5 단위 2개·e2e(거부/지연/부분) evidence 충족. (confirm-req-implemented)
|
||||
- 2026-06-06 :: REQ-010 CHG-20260606-01 — paper broker 튜닝 knob(REQ-011 AC-2 지연/부분/거부)을 데몬 env(`TREX_PAPER_FILL_AFTER_POLLS`/`QTY_PER_POLL`/`REJECT_SYMBOLS`)로 노출(AC-5 신설). 미설정=기존 동작 byte-for-byte. 신규 AC 한정 Implemented → Approved(BAT-009 batch-lite verification 대기). REL-001 의 AC-1~4 coverage 는 유효. (change-req)
|
||||
- 2026-06-06 :: REQ-017 CHG-20260606-01 — 실 portfolio-hub ledger write 계약 정정(`POST /v1/portfolios/{portfolio_id}/ledger/trades`, body `quantity`/`fee_amount`/`tax_amount`, 성공 201). 구 orbit 가정(`/v1/assets/{asset_id}`, `qty`/`fee`/`tax`) 폐기. 정정 구현 재검증 완료(unit 16 + 실 e2e 201)·Implemented 유지. REL-001 ledger blocker 해소. (change-req)
|
||||
- 2026-06-06 :: REQ-027 CHG-04 — 실 portfolio-hub 계약 정정(whoami `/v1/user_info`, `{portfolios}` envelope, 클라이언트측 daemon 필터, 전략 필드 None/`{}` 이월). 정정 구현 재검증 완료·Implemented 유지. (change-req)
|
||||
- 2026-06-06 :: REQ-029 CHG-04 — orb-v1 소비 참고 사항 추가(strategy_key 없이 view=buyable, 전략 필드 부재 무해). 문서 전용·Implemented 유지. (change-req)
|
||||
|
||||
Reference in New Issue
Block a user