feat(runtime): dcy-014 trading_date KST 타임존 분리 + 일일 슬롯 preallocation (BAT-018)
데몬이 시스템 UTC 날짜로 슬롯을 1회만 preallocate 해 trading_date 가 KST 캘린더와 어긋나고 재시작 없이는 다음날 슬롯이 안 생기던 문제(issue #14) 정정. 거버넌스: 전부 change-req (신규 REQ 없음, dcy-014 OQ-3; 스케줄러는 REQ-030 패턴 준용). - REQ-005: trading_date 를 설정 타임존(기본 Asia/Seoul) 기준 계산(timestamp 는 UTC 유지). DailyPreallocationScheduler 신규 — TZ 일자당 1회 멱등 fire, 재시작 없이 다음날 슬롯 자동 생성. setup() 배선 + trex-preallocation 스레드 - REQ-048: TOML trading_timezone/daily_preallocation_time 키 + from_config 전파 - REQ-009: runtime-flows/data-model 정합 + instance.example.toml OQ-1 기본 00:00(TZ 자정, TOML 설정 가능) / OQ-2 매 캘린더일 생성(거래일 캘린더 후속). 멱등 preallocate 로 중복 tick·다중 인스턴스 중복 생성 0. 검증: 단위 302 passed/32 skipped, 인프라 게이트(PG+RabbitMQ) 334 passed, ruff clean. 신규 test_preallocation_scheduler(고정 clock) + test_instance_config 확장. BAT-018 release-candidate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
# BAT-018 Design
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-018
|
||||
|
||||
## 설계 목표
|
||||
- `trading_date` 를 설정 타임존(기본 KST) 기준으로 분리해 KST 캘린더 일자와 DB 일자를 일치시키고, 재시작 없이 일자 경계마다 다음날 슬롯을 자동 생성한다(issue #14). timestamp 는 UTC 유지(NFR-1).
|
||||
|
||||
## 1. DailyPreallocationScheduler (REQ-005, REQ-030 패턴)
|
||||
- `application/preallocation_scheduler.py`(신규). 의존: `slot_service`(preallocate_daily_slots), `total_slots`, `tz`(tzinfo), `schedule_time`(time, 기본 00:00), `clock`(주입식, 기본 utcnow).
|
||||
- `run_once()`: `trading_date = clock().astimezone(tz).date()` → `preallocate_daily_slots(trading_date, total_slots)`(멱등) → `_last_fired_date = trading_date`. 반환 trading_date.
|
||||
- `_maybe_tick()`: `now_tz = clock().astimezone(tz)`; `target = combine(now_tz.date(), schedule_time, tz)`; `now_tz < target` 면 skip; `_last_fired_date == now_tz.date()` 면 skip(오늘 이미 생성); 아니면 run_once.
|
||||
- `run_forever(stop, tick_sec=60)`: boot run_once(당일 즉시 확보) 후 `stop.wait(tick_sec)` 루프 + try/except(루프 생존).
|
||||
- 경계 의미: boot 는 항상 현재 TZ 일자 슬롯 확보(시각 무관). schedule_time 은 **다음날** 경계 fire 시점 게이트. 기본 00:00 → 자정 직후 다음날 생성.
|
||||
|
||||
## 2. 도메인 무침범 (A-3)
|
||||
- `SlotService.preallocate_daily_slots(trading_date: date, count)` 시그니처/멱등 그대로 재사용. TZ 계산은 스케줄러(호출부)에서만 수행 → REQ-002 도메인 본문 변경 없음.
|
||||
|
||||
## 3. 설정 (REQ-048, TOML 일원화)
|
||||
- `DaemonInstanceConfig`(TOML, frozen): `trading_timezone: str = "Asia/Seoul"`, `daily_preallocation_time: str = "00:00"`. `from_toml` 이 optional 키 파싱(부재 시 기본).
|
||||
- `DaemonConfig`: 동일 두 필드(기본 KST/00:00). `from_env` 는 기본값 사용(TOML 일원화 — env var 미도입). `from_config` 가 TOML 값으로 `replace`.
|
||||
- 운영자는 `--config` TOML 로 명시 지정(FR-4). 신규 설정 표면 아님.
|
||||
|
||||
## 4. daemon 배선 (REQ-005)
|
||||
- `__init__`: `DailyPreallocationScheduler(slot_service, total_slots, tz=ZoneInfo(config.trading_timezone), schedule_time=_parse_kst_time(config.daily_preallocation_time))`.
|
||||
- `setup()`: UTC 날짜 `preallocate_daily_slots` 직접 호출 제거 → `preallocation_scheduler.run_once()`(boot, TZ 당일 확보).
|
||||
- `_preallocation_loop`: `run_forever(stop, tick_sec=60)`. workers 에 `trex-preallocation` 추가(8 스레드).
|
||||
|
||||
## 5. timestamp UTC 유지 (NFR-1)
|
||||
- 슬롯 `created_at`/`updated_at`/전이 ts 는 기존대로 `datetime.now(tz=timezone.utc)`. 본 변경은 trading_date(date) 계산만 TZ 화.
|
||||
|
||||
## 6. baseline 문서 (REQ-009)
|
||||
- runtime-flows: 데몬 lifecycle(TOML 키 추가·8 스레드·setup preallocation_scheduler) + `일일 슬롯 preallocation` Flow 신규.
|
||||
- data-model: trading_date=설정 TZ 기준 / timestamp=UTC 구분.
|
||||
- config/instance.example.toml: 두 키 주석 문서화.
|
||||
|
||||
## 멱등성/안정성 (NFR-2/3)
|
||||
- 일자당 1회 fire + preallocate date 멱등 → 중복 tick·다중 인스턴스 중복 생성 0. 재시작 시 boot run_once 가 당일 확보. 무재시작 일자 경계는 _maybe_tick 가 다음날 생성.
|
||||
|
||||
## 테스트 설계
|
||||
- `tests/application/test_preallocation_scheduler.py`(고정 clock 주입): KST/UTC 자정 경계 trading_date(S-1), timestamp UTC 유지(S-2), 일자 경계 자동 생성+멱등 재tick(S-3), 설정 TZ 변경(S-4), 트리거 시각 게이팅, idle 슬롯.
|
||||
- `tests/runtime/test_instance_config.py` 확장: TOML 기본값(KST/00:00) + TOML 값 from_config 전파.
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# BAT-018 Implementation
|
||||
|
||||
- Status: completed
|
||||
- Batch: BAT-018
|
||||
|
||||
## 구현 요약
|
||||
- dcy-014 trading_date 설정 타임존(KST) 분리 + 일일 슬롯 preallocation(issue #14)을 전부 change-req 로 구현(신규 REQ 없음; 스케줄러는 REQ-030 패턴 준용).
|
||||
|
||||
## 변경 내역
|
||||
|
||||
### REQ-005 — 스케줄러 + 부트스트랩
|
||||
- `trex_core/application/preallocation_scheduler.py`(신규): `DailyPreallocationScheduler` — `run_once()`(TZ trading_date 멱등 preallocate), `_maybe_tick()`(TZ 일자당 1회 + 트리거 시각 게이트), `run_forever(stop, tick_sec)`. clock 주입식(테스트 결정론).
|
||||
- `trex_core/runtime/daemon.py`: `DailyPreallocationScheduler` 인스턴스화(`tz=ZoneInfo(config.trading_timezone)`, `schedule_time=_parse_kst_time(config.daily_preallocation_time)`); `setup()` 의 UTC 날짜 직접 preallocation → `preallocation_scheduler.run_once()`; `_preallocation_loop` + `trex-preallocation` 워커(8 스레드); `zoneinfo.ZoneInfo` import. 미사용 `datetime, timezone` import 제거.
|
||||
|
||||
### REQ-048 — TOML 설정
|
||||
- `trex_core/runtime/config.py`: `DaemonInstanceConfig` 에 `trading_timezone="Asia/Seoul"`/`daily_preallocation_time="00:00"` + `from_toml` optional 파싱; `DaemonConfig` 동일 필드(기본값); `from_config` 가 TOML 값 전파(`replace`). `from_env` 는 기본값.
|
||||
- `config/instance.example.toml`: 두 키 주석 문서화.
|
||||
|
||||
### REQ-009 — baseline 문서
|
||||
- `docs/runtime-flows.md`: 데몬 lifecycle TOML 키·8 스레드·setup preallocation_scheduler + `일일 슬롯 preallocation` Flow 신규. Last Updated → BAT-018.
|
||||
- `docs/data-model.md`: trading_date=설정 TZ 기준 / timestamp=UTC 구분. Last Updated → BAT-018.
|
||||
|
||||
## 테스트
|
||||
- 신규 `tests/application/test_preallocation_scheduler.py`(고정 clock): 6건 — KST/UTC 자정 경계 trading_date(S-1), timestamp UTC 유지(S-2), 일자 경계 자동 생성+멱등 재tick(S-3), 설정 TZ 변경(S-4), 트리거 시각 게이팅, idle 슬롯.
|
||||
- `tests/runtime/test_instance_config.py` 확장: TOML 기본값(KST/00:00) + TOML 값 from_config 전파 2건.
|
||||
|
||||
## 검증 실행
|
||||
- 단위: `TREX_BROKER=paper .venv/bin/python -m pytest -q` → `302 passed / 32 skipped`(`::test_sandbox_token_and_submit` deselect — LIVE `.env` 환경 의존, 본 batch 무관).
|
||||
- 인프라 게이트: `TREX_POSTGRESQL_TEST_DSN=...(trex_core_test) TREX_RABBITMQ_TEST_URL=... pytest -q` → `334 passed`.
|
||||
- `ruff check trex_core tests` → All checks passed.
|
||||
@@ -0,0 +1,34 @@
|
||||
# BAT-018: trading_date 설정 타임존(KST) 분리 + 일일 슬롯 preallocation (issue #14)
|
||||
|
||||
- Status: release-candidate
|
||||
- Profile: standard
|
||||
- Source Discovery: dcy-014
|
||||
- Included REQ: REQ-005 (CHG-20260608-01), REQ-048 (CHG-20260608-01), REQ-009 (CHG-20260608-01)
|
||||
|
||||
## Summary
|
||||
- dcy-014(당일 슬롯 생성 기준 시간을 KST(설정 가능)로 분리 — trading_date 타임존 분리 + 일자 경계 preallocation, issue #14)을 단일 standard batch 로 delivery 한다. 거버넌스는 dcy-014 OQ-3 결정대로 **전부 change-req, 신규 REQ 없음**(스케줄러는 REQ-030 SnapshotScheduler 패턴 준용).
|
||||
- 핵심: `trading_date` 를 시스템 UTC 가 아니라 설정 타임존(기본 `Asia/Seoul`) 기준으로 계산하고, timestamp 는 UTC 유지. 데몬 시작 1회 UTC preallocation 을 `DailyPreallocationScheduler`(tick 루프 + TZ 일자당 1회 멱등 fire)로 대체해 재시작 없이 다음날 슬롯이 자동 생성된다.
|
||||
|
||||
## Scope
|
||||
- In Scope:
|
||||
- `trading_date` 의 설정 가능한 타임존 분리(기본 KST) + timestamp UTC 유지 (REQ-005)
|
||||
- TZ 일자 경계 기준 일일 멱등 preallocation 스케줄러 + daemon 배선 (REQ-005)
|
||||
- 타임존·트리거 시각 TOML 설정(`trading_timezone`/`daily_preallocation_time`) (REQ-048)
|
||||
- runtime-flows/data-model 문서 갱신 (REQ-009)
|
||||
- Out of Scope:
|
||||
- timestamp 저장 형식 변경(UTC 유지)
|
||||
- 슬롯 용량/total_slots 정책 변경
|
||||
- 거래일 캘린더(주말·공휴일 비거래일 제외) — 매 캘린더일 생성으로 확정(OQ-2), 캘린더 도입은 후속 분리
|
||||
- REQ-002(slot 도메인) 본문 변경 — `trading_date: date` 시그니처 유지, TZ 계산은 호출부(스케줄러)에서 수행(A-3)
|
||||
|
||||
## 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-014). OQ-1(기본 00:00 TZ 자정·TOML 설정)·OQ-2(매 캘린더일 생성)·OQ-3(전부 change-req) Answered.
|
||||
- 스케줄러는 `SnapshotScheduler`(REQ-030)의 KST 일자당 1회 fire + tick 루프 패턴을 준용(A-1).
|
||||
- 멱등성(A-2): `preallocate_daily_slots` 가 date 멱등 → 일일 재실행·다중 인스턴스·중복 tick 안전(NFR-2).
|
||||
- 운영 설정은 기존 `--config` TOML(REQ-048)에 키 추가로 편입(신규 설정 표면 아님). from_env 는 KST/00:00 기본.
|
||||
@@ -0,0 +1,47 @@
|
||||
# BAT-018 Planning
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-018
|
||||
- Profile: standard
|
||||
|
||||
## Included REQ
|
||||
- REQ-005 (Installation, change-req): setup() UTC→TZ trading_date + `DailyPreallocationScheduler` 신규 + daemon 스레드 배선.
|
||||
- REQ-048 (Configuration, change-req): TOML `trading_timezone`/`daily_preallocation_time` 키 + from_config 전파.
|
||||
- REQ-009 (Documentation, change-req): runtime-flows/data-model 정합.
|
||||
|
||||
## Delivery Plan
|
||||
1. REQ-005: `application/preallocation_scheduler.py`(SnapshotScheduler 패턴, TZ date + 일자당 1회 멱등 fire + boot run_once).
|
||||
2. REQ-048: `config.py` DaemonInstanceConfig TOML 키 2종 + DaemonConfig 필드 + from_config 전파(+ from_env KST/00:00 기본).
|
||||
3. REQ-005 배선: `daemon.py` setup() → `preallocation_scheduler.run_once()`, `_preallocation_loop` 스레드 + workers 등록, ZoneInfo(tz).
|
||||
4. REQ-009: runtime-flows(데몬 lifecycle + 일일 preallocation Flow)/data-model(trading_date TZ vs UTC) + instance.example.toml.
|
||||
5. 신규 테스트(test_preallocation_scheduler, test_instance_config 확장) + 전체 회귀 + ruff + 인프라 게이트.
|
||||
|
||||
## Dependencies
|
||||
- Internal:
|
||||
- dcy-002/BAT-002 daemon 부트스트랩(REQ-005) — preallocation 지점.
|
||||
- dcy-005/BAT-008 SnapshotScheduler(REQ-030) — 스케줄러 패턴 준용.
|
||||
- dcy-009/BAT-013 DaemonInstanceConfig TOML(REQ-048) — 설정 키 추가.
|
||||
- REQ-002 preallocate_daily_slots — date 멱등(재사용, 본문 변경 없음).
|
||||
- External:
|
||||
- stdlib `zoneinfo`(py3.12) — IANA 타임존.
|
||||
|
||||
## Design Gate
|
||||
- Design Required Before Implementation: yes
|
||||
- Reason: (a) trading_date TZ 분리는 daemon 부트스트랩·스케줄러·설정·도메인 호출부 경계에 걸쳐, "TZ 는 호출부에서, 도메인 시그니처는 date 유지"(A-3) 경계를 설계에서 못박지 않으면 도메인 침범/회귀 위험. (b) 일자 경계 fire/멱등/트리거 시각 게이팅 규칙(SnapshotScheduler 준용)을 설계에서 고정해야 중복 생성·누락(NFR-2/3)을 막는다. (c) 설정 표면(TOML 일원화 + from_env 기본)·timestamp UTC 유지(NFR-1) 경계 명시. → design.md 동일 턴 scaffold.
|
||||
|
||||
## Milestones
|
||||
1. M1 — REQ-005 DailyPreallocationScheduler, test_preallocation_scheduler green
|
||||
2. M2 — REQ-048 TOML 키 + from_config 전파, test_instance_config 확장 green
|
||||
3. M3 — REQ-005 daemon 배선(setup + 스레드)
|
||||
4. M4 — REQ-009 문서 정합 + instance.example.toml
|
||||
5. M5 — 전체 회귀 + ruff + 인프라 게이트
|
||||
|
||||
## Risks
|
||||
- R-1 (Low, OQ-1): 트리거 시점 운영 기대 불일치. 대응: 기본 00:00 + TOML 설정, SnapshotScheduler 패턴 재사용.
|
||||
- R-2 (Medium): TZ 전환 경계에서 기존 UTC 날짜 슬롯과 새 TZ trading_date 불일치. 대응: 멱등 생성으로 중복 방지 + 경계 케이스 테스트(KST/UTC 경계).
|
||||
- R-3 (Low, OQ-2 수용): 비거래일 idle 슬롯 누적. 대응: 매 캘린더일 생성 수용, idle 은 buy admit 시에만 활성화로 무해. 거래일 캘린더 후속.
|
||||
|
||||
## Notes
|
||||
- 1차 게이트: 전체 회귀 green(부트스트랩 변경 무회귀).
|
||||
- 경계 검증은 고정 clock 주입 단위로 결정론 확보(스레드 불필요).
|
||||
- 멱등 preallocation 은 PG 게이트 전이/마이그레이션 테스트 회귀로도 간접 검증.
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
# BAT-018 Verification
|
||||
|
||||
- Status: verified
|
||||
- Batch: BAT-018
|
||||
|
||||
## Acceptance Mapping
|
||||
|
||||
### REQ-005 (스케줄러 + 부트스트랩, change-req)
|
||||
| AC | Evidence |
|
||||
|----|----------|
|
||||
| trading_date = 설정 TZ 기준(UTC 아님) | tests/application/test_preallocation_scheduler.py::test_trading_date_uses_kst_not_utc_at_midnight_boundary |
|
||||
| 일자 경계 자동 생성(무재시작) + 멱등 | ::test_cross_day_boundary_auto_creates_next_day_idempotently |
|
||||
| 트리거 시각 게이팅 | ::test_trigger_time_gates_next_day_creation |
|
||||
| idle 슬롯 생성 | ::test_idle_slots_created_for_trading_date |
|
||||
| 부트스트랩/스레드 배선 회귀 | 전체 회귀(daemon import/setup) |
|
||||
|
||||
### REQ-048 (TOML 설정, change-req)
|
||||
| AC | Evidence |
|
||||
|----|----------|
|
||||
| TOML 기본값(KST/00:00) | tests/runtime/test_instance_config.py::test_trading_timezone_defaults_to_kst |
|
||||
| TOML 값 from_config 전파 | ::test_trading_timezone_from_toml_propagates |
|
||||
| 기존 TOML 키 회귀 | 기존 test_instance_config 전체 |
|
||||
|
||||
### REQ-009 (baseline 문서, change-req)
|
||||
| AC | Evidence |
|
||||
|----|----------|
|
||||
| runtime-flows/data-model 정합 | 문서 diff(BAT-018) + instance.example.toml |
|
||||
|
||||
## Evidence
|
||||
- Test:
|
||||
- 단위 전체 회귀: `302 passed / 32 skipped`(신규 스케줄러 6 + config 2 포함; `::test_sandbox_token_and_submit` deselect).
|
||||
- 인프라 게이트(PG trex_core_test + RabbitMQ): `334 passed`.
|
||||
- `ruff check trex_core tests` → All checks passed.
|
||||
- Logs: `302 passed, 32 skipped, 1 deselected` / `334 passed, 1 deselected ~16.1s`.
|
||||
|
||||
## 성공 기준(dcy-014) 대응
|
||||
- S-1(설정 TZ "오늘" = DB trading_date, UTC 자정 경계 포함): test_trading_date_uses_kst_not_utc_at_midnight_boundary(15:30Z → KST 6/9).
|
||||
- S-2(timestamp UTC 유지): test_timestamps_stay_utc(updated_at utcoffset 0).
|
||||
- S-3(무재시작 일자 경계 자동 생성 + 중복 0): test_cross_day_boundary_auto_creates_next_day_idempotently.
|
||||
- S-4(TOML 타임존 설정 반영): test_configurable_timezone_changes_trading_date(UTC TZ → 6/8) + test_trading_timezone_from_toml_propagates.
|
||||
|
||||
## Carried-Over / 비차단
|
||||
- 기본 타임존 `Asia/Seoul` 확정·TOML 키 명명(DATA): 구현에서 `trading_timezone`/`daily_preallocation_time` 로 확정. 운영 배포 안내는 instance.example.toml 에 문서화.
|
||||
- 실 다중 인스턴스 동시 일일 preallocation 무중복(NFR-2): 멱등 preallocate 로 보장, 실 환경 동시 기동 e2e 는 운영 검증 비차단.
|
||||
- 거래일 캘린더(주말/공휴일): OQ-2 결정으로 매 캘린더일 생성, 후속 분리.
|
||||
|
||||
## Result
|
||||
- Ready for Release: true
|
||||
- Blocking Issues:
|
||||
- 없음. 부트스트랩 TZ 전환·일일 preallocation·TOML 설정 모두 단위 green(334 passed 회귀). LIVE sandbox 토큰 deselect 는 `.env` 환경 의존(본 batch 무관).
|
||||
@@ -25,3 +25,4 @@
|
||||
| BAT-015 | standard | release-candidate | REQ-007 (CHG-20260608-01), REQ-018 (CHG-20260608-01), REQ-021 (CHG-20260608-01), REQ-055 | dcy-011 | [bat-015](./bat-015_20260608_buy-intent-portfolio-id-contract/) |
|
||||
| BAT-016 | standard | release-candidate | REQ-002 (CHG-20260608-01), REQ-003 (CHG-20260608-01), REQ-006 (CHG-20260608-01), REQ-007 (CHG-20260608-02), REQ-013 (CHG-20260608-01), REQ-015 (CHG-20260608-01), REQ-036 (CHG-20260608-01) | dcy-012 | [bat-016](./bat-016_20260608_sell-pricing-limit-price-rename/) |
|
||||
| BAT-017 | standard | release-candidate | REQ-012 (CHG-20260608-01), REQ-013 (CHG-20260608-02), REQ-018 (CHG-20260608-02), REQ-031 (CHG-20260608-01), REQ-014 (CHG-20260608-01), REQ-015 (CHG-20260608-02) | dcy-013 | [bat-017](./bat-017_20260608_external-cancel-detection-stale-watchdog/) |
|
||||
| BAT-018 | standard | release-candidate | REQ-005 (CHG-20260608-01), REQ-048 (CHG-20260608-01), REQ-009 (CHG-20260608-01) | dcy-014 | [bat-018](./bat-018_20260608_trading-date-timezone-daily-preallocation/) |
|
||||
|
||||
Reference in New Issue
Block a user