Commit Graph
18 Commits
Author SHA1 Message Date
genichinandClaude Opus 4.8 6e8e79660c feat(slot): slot entry_count + closed-slot reallocation (dcy-019, BAT-025) + REL-005
dcy-019/issue #18: slot 자원을 진입 횟수 기준으로 고르게 재사용하고 idle 소진 시
closed 슬롯을 직접 재할당한다. 거버넌스는 전부 change-req(신규 REQ 없음).

- 도메인(REQ-002): Slot.entry_count(기본 0) + EXPECTED_SLOT_FIELDS, 상태 머신
  closed→pending_buy 신규 전이(base 8→9, ALLOWED_TRANSITIONS 14→15),
  _apply_transition_fields closed→pending_buy clean-slate + →pending_buy 시 +1
- 영속화(REQ-006): 멱등 마이그레이션 008(entry_count), flat 매핑,
  find_allocatable_slot(idle∪closed, entry_count→updated_at→slot_id) + in-memory 동등
- admission(REQ-007): _admit_buy find_idle_slot→find_allocatable_slot + from_state=slot.status
- 테스트(REQ-003): 전이 카운트/계약 가드 정합 + test_slot_entry_count.py / test_buy_admission_reallocation.py
- 문서(REQ-009): data-model.md / runtime-flows.md 정합
- 기존 daily_symbol_entries(symbol-scoped) 분리·미변경

BAT-025(release-candidate) delivery + change-req REQ-002/003/006/007/009(CHG-20260609-01).
REL-005(confirmed, app-service): BAT-024+BAT-025 묶음. operator-facing run-the-app
스모크로 enter --forced-close-at(BAT-024)·closed 재할당(BAT-025) 실 데몬 검증
(실 PG 마이그레이션 008 round-trip 포함).

검증: 단위 366 passed / 35 skipped(KIS sandbox 1건 deselect — live .env 환경 의존), ruff clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 14:49:22 +09:00
genichinandClaude Opus 4.8 2bfecd59a8 feat(ledger): dcy-018 option D cash/position write-back + 추정 현금 (BAT-023)
ledger write 응답의 portfolio-hub 권위 cash/quantity 를 스냅샷에 write-back 해
매수/매도 직후 "수량 live·현금 stale" 비대칭 제거(option D 활성화). portfolio-hub#8
(*_after 응답) 동작 중 확인으로 그간 deferred 됐던 REQ-030 을 닫음.

거버넌스: 전부 change-req (신규 REQ 없음, dcy-018 OQ-3 a).
- REQ-017: record_trade → LedgerWriteOutcome{result, cash_balance_after,
  asset_quantity_after}. 어댑터가 응답 root flat *_after 파싱(new+duplicate, 누락 None)
- REQ-019: LedgerWriter 에 SnapshotStore 주입 → new+duplicate write-back
  (best-effort, *_after 절대값이라 멱등). LedgerOutbox.pending_cash_delta()
- REQ-029: 추정 현금 오버레이(_apply_cash_estimate, cash_estimated 라벨, OQ-4 i)
  + 슬롯 오버레이 유지(OQ-1 B). 미정산 outbox delta 기반, write-back 수렴
- REQ-030: option D 활성화 → Approved→Implemented

best-effort 격리(NFR-1): write-back/추정 실패가 ledger 멱등·청산 무차단.
배치 번호: 동시 커밋된 BAT-022(slot-state, dcy-016)와 충돌해 BAT-023 으로 배정.

검증: 단위 350 passed/35 skipped, 인프라 게이트(PG+RabbitMQ) 385 passed, ruff clean.
신규 test_ledger_option_d + adapter/PG 테스트. BAT-023 release-candidate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 21:50:56 +09:00
genichinandClaude Opus 4.8 b851b927ee fix(ledger): dcy-015 outbox 영속 오류 분류 + 백오프/dead-letter (BAT-019)
portfolio-hub 404(미등록 포트폴리오)가 retryable 로 분류되어 백오프·상한 없이
초당 영구 재시도되던 버그(issue #13, 실측 930회) 정정.

거버넌스: 전부 change-req (신규 REQ 없음, dcy-015 OQ-3; 백오프 env 는 REQ-019 흡수).
- REQ-017: 404/400/422 → LedgerPermanentError 분류 (409 conflict / 408·429·5xx·
  network retryable 불변). LedgerPort 에 permanent 타입 추가
- REQ-019: 영속 → 터미널 delivery_failed 즉시(재시도 0). retryable 지수 백오프
  +jitter+next_retry_at, max_attempts 초과 시 dead-letter delivery_failed.
  list_pending 이 백오프 행 스킵(head-of-line 보호). 마이그레이션 007(status enum
  +next_retry_at). 백오프 env(base/cap/max-attempts)
- REQ-018: 터미널/dead-letter WARN + WriterStats.delivery_failed 카운터 가시화
- REQ-020: runtime-flows/data-model/interface-contract 정합

단일 터미널 delivery_failed(permanent/dead-letter, last_outcome 로 사유 구분) —
conflict_blocked 패턴과 동형. dead-letter 는 운영자 재처리 가능 상태.

검증: 단위 311 passed/34 skipped, 인프라 게이트(PG+RabbitMQ) 345 passed, ruff clean.
신규 test_ledger_outbox_backoff + adapter/PG 테스트, 기존 retryable 백오프 정합.
BAT-019 release-candidate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 16:12:17 +09:00
genichinandClaude Opus 4.8 4746f74aae fix(broker): dcy-013 KIS 외부 취소 감지 + 부분 체결 회계 + stale 워치독 (BAT-017)
KIS 계좌에서 직접 매도 주문을 취소하면 poll_fill 이 cncl_yn 을 파싱하지 않아
슬롯이 pending_sell 에 영구히 갇히고 죽은 주문을 무한 폴링하던 버그(issue #9)
정정.

거버넌스: 전부 change-req (신규 REQ 없음, dcy-013 OQ-3).
- REQ-012: KIS poll_fill cncl_yn 파싱 → 외부 취소 감지. FillStatus.cancelled
  /is_partial_cancel 추가. 체결 0 취소→cancelled, 부분 체결 후 취소→partial+cancelled
- REQ-013: 집행 _on_partial_cancel — 체결분 원장 1회 + qty 차감(매도 잔여/매수 체결분)
  + holding 복귀 (OQ-1 A안). 전량 취소는 기존 롤백 경로 재사용
- REQ-018: 부분 체결분 원장 1회 기록 (_record_ledger 재사용)
- REQ-031: 잔여 포지션 정합 연계 (slot.qty ↔ 실 잔고, 과매도 방지)
- REQ-014: pending stale 워치독 Alert-only (TREX_PENDING_STALE_THRESHOLD,
  WARN+카운터, 상태 미변경) — 취소 감지 누락 backstop
- REQ-015: interface-contract/runtime-flows 정합

전량 취소는 holding 자동 복구(limit_price clear→재매도 시장가, dcy-012 연계).
KIS cncl_yn 실 규약은 운영 검증(미지 값 보수 처리 + 워치독 backstop).

검증: 단위 294 passed/32 skipped, 인프라 게이트(PG+RabbitMQ) 326 passed, ruff clean.
신규 test_kis_cancel(httpx stub) + test_execution_cancel(programmable broker).
BAT-017 release-candidate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 14:11:49 +09:00
genichinandClaude Opus 4.8 cc28e1fba5 fix(execution): dcy-012 매도 가격 버그 정정 — slot entry_price→limit_price rename (BAT-016)
매도(sell) 주문이 현재가가 아닌 매수가(entry_price) 지정가로 나가던 버그(issue #10)
정정. 슬롯의 매수 전용 entry_price 를 side 무관 "현재 pending 주문 지정가"
limit_price 로 내부 rename 하고, pending 이탈(→holding/→closed) 시 clear 해
매수 지정가가 매도로 누수되는 경로를 구조적으로 제거.

거버넌스: 전부 change-req (신규 REQ 없음, dcy-012 OQ-3).
- REQ-002: Slot entry_price→limit_price rename + clear-on-exit 생애주기
- REQ-006: daily_slot 컬럼 rename 마이그레이션 006 (idempotent) + persistence 매핑
- REQ-013: _dispatch 가 slot.limit_price 단일 필드로 가격/주문유형 결정
- REQ-007: SellIntent/sell payload optional limit_price + _admit_sell 운반
- REQ-036: SlotService.sell optional 가격 → 자율 매도(safety-sell/kill-switch) 시장가
- REQ-003: 컬럼 계약/드리프트 가드 limit_price 반영
- REQ-015: runtime-flows/interface-contract/data-model 정합

매도 정책(OQ-1 하이브리드): 가격 지정→지정가("00"), 미지정→시장가("01").
자율 매도는 가격 미전달 → 항상 시장가 (손절 미체결 pending_sell 갇힘 방지, issue #9 연계).

검증: 단위 286 passed/32 skipped, 인프라 게이트(PG+RabbitMQ) 318 passed, ruff clean.
신규 test_sell_pricing(recording broker)로 지정가 매수 슬롯의 매도가 매수가
재사용 안 함을 직접 검증. BAT-016 release-candidate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 13:41:46 +09:00
genichinandClaude Opus 4.8 aee3512383 feat(intents): dcy-011 buy intent portfolio_id 필수 계약 + ledger skip 가시화 (BAT-015)
dcy-011 전 주기 delivery. issue #6(buy 식별자 정정) + issue #11(ledger silent skip) 흡수.
거버넌스: 기존 Implemented REQ는 change-req, 신규 기능만 신규 REQ(OQ-3).

- REQ-007 (change-req): buy admission portfolio_id 필수화 + SnapshotStore symbol
  resolve + unknown_portfolio 거부 (hard cutover, OQ-1/OQ-2)
- REQ-021 (change-req): buy 메시지 payload symbol 제거
- REQ-018 (change-req): _record_ledger skip 가시화 (WARN + ledger_skipped 카운터)
- REQ-055 (신규): closed↔ledger_outbox anti-join 운영 점검 find_ledger_gap_slots

코드: intents.py(BuyIntent portfolio_id 필수 + resolve_symbol 주입 + _admit_buy
reject 분기), messaging_handlers.py, runtime/daemon.py(_resolve_symbol 배선),
execution.py(skip WARN 로그), adapters/persistence/ops_checks.py(신규 anti-join).

검증: 단위 282 passed/32 skipped, 인프라 게이트(PG+RabbitMQ) 314 passed, ruff clean.
BAT-015(standard) release-candidate. srs/index Next ID → REQ-056.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 12:57:51 +09:00
genichinandClaude Opus 4.8 9f83c64a40 feat(multi-instance): dcy-009 BAT-013 — RabbitMQ 다중 인스턴스 토폴로지 (REQ-048~051)
dcy-009(다중 인스턴스 RabbitMQ 토폴로지) 전체 SDLC: draft-req → confirm-req →
draft-batch(BAT-013) → run-batch-delivery → confirm-req-implemented.

- REQ-048 (Configuration): DaemonInstanceConfig.from_toml + DaemonConfig.from_config
  (env 병합, portfolio_hub_token 주입) + 데몬 --config 필수 argparse + .gitignore 인스턴스
  TOML + config/instance.example.toml
- REQ-049 (Interface): topology 동적 큐명 빌더 intent/snapshot/control_queue(instance_key)
  — default=리터럴 큐명(하위호환), named=`{base}.{instance_key}` + daemon per-instance 구독
- REQ-050 (Integration): trex.direct(topic)/trex.broadcast(fanout) exchange +
  declare_topology(instance_key) per-instance binding + control dual binding + direct_routing_key
- REQ-051 (Documentation): interface-contract/runtime-flows/project-structure 정합

하위호환: instance_key="default" → 기존 리터럴 큐명·RPC 경로 무회귀(FR-5).
split-brain 방지(NFR-3): 인스턴스 전용 큐. 민감값(NFR-4): TOML + gitignore.

검증: 신규 9 단위 + RabbitMQ 라우팅 2, 단위 271 passed,
인프라 게이트 301 passed(directed 격리 S-2 + broadcast dual S-3 + default 무회귀 S-4),
ruff clean. REQ-048~051 Implemented, BAT-013 release-candidate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 15:10:36 +09:00
genichin 7e23648489 feat: add kill-switch control flow 2026-06-07 14:00:20 +09:00
genichinandClaude Sonnet 4.6 0082d3b5da fix(kis): REQ-038 현재가 tr_id 정정 + REL-003 확정 + dcy-008 kill-switch Discovery
## REQ-038 CHG-04/05: KIS 시세 tr_id 정정 (FHKST01010100)
- kis.py _tr_price(): TTTC8397R/VTTC8397R → FHKST01010100 (실전/모의 공용)
  배포 전 실 KIS LIVE 진단에서 구 tr_id가 rt_cd=1 "없는 서비스 코드"로 거부됨을
  확인 — stop-loss/take-profit이 LIVE에서 조용히 미발동하는 결함이었음.
  FHKST01010100으로 정정 후 get_current_price('005930')=329000(rt_cd=0) 확인.
- test_kis_price.py: AC-7 테스트 정정 (FHKST01010100 공용 검증)
- REQ-038: CHG-04/05 Change Log 추가, Requirement/AC-7/Notes 정정
- BAT-011 implementation.md: LIVE 검증 및 tr_id 정정 이력 추가
- BAT-011 verification.md: AC-7 evidence를 CHG-04 정정 버전으로 갱신
- docs/srs/index.md: CHG-04/05 Change Log 항목 추가

## REL-003: core 자율 안전 매도 release 문서 생성
- docs/releases/rel-003_20260607_core-autonomous-safety-sell.md 신규 생성
  (BAT-010 + BAT-011, app-service profile, Status: confirmed)
- docs/releases/index.md: REL-003 행 추가

## dcy-008: 비상 정지/강제 청산(kill-switch) Discovery 초안
- docs/discovery/dcy-008_20260607_emergency-stop-kill-switch.md 신규 생성
  GitHub #3 기반; SlotService.sell() 재사용, 당일 전략 제외 day-scoped
  FR 6개, NFR 3개, OQ 4개 (트리거 표면/영속 방식/수동 해제 결정 필요)
- docs/discovery/index.md: dcy-008 행 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 10:17:36 +09:00
genichinandClaude Opus 4.8 a30efa8f3b 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>
2026-06-07 08:32:30 +09:00
genichinandClaude Opus 4.7 9e64a24441 fix: align portfolio-hub ledger write adapter to live API contract
REQ-017 CHG-20260606-01. Live portfolio-hub returned 404 for the
trex-orbit-assumed POST /v1/assets/{asset_id}/ledger/trades. Correct to
POST /v1/portfolios/{portfolio_id}/ledger/trades with body keys
quantity/fee_amount/tax_amount (was qty/fee/tax), success 201. Reverified
with live e2e (201 Created, outbox COMPLETED) + durability drain of the
prior 404'd item. Resolves the REL-001 ledger-write release blocker.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 11:27:30 +09:00
genichinandClaude Opus 4.7 43bad2424e fix: align portfolio-hub read adapter to live API contract
whoami now hits GET /v1/user_info (the live endpoint; /v1/auth/whoami
does not exist), get_portfolios parses the {portfolios:[...],total} envelope
and filters by managed_by_daemon_id client-side (the list endpoint is
token-scoped, no such query param), and a missing URL scheme now defaults
to http:// so a bare host can't silently degrade the daemon.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 10:34:35 +09:00
genichin ca757009f5 feat: add portfolio snapshot read layer 2026-06-06 09:33:00 +09:00
genichin 6c7a1cf6f6 feat: add messaging transport rollout 2026-06-06 00:02:52 +09:00
genichinandClaude Opus 4.8 13837acc60 Implement BAT-006: portfolio-hub ledger integration (REQ-017~019)
- REQ-017: LedgerPort contract (application/ledger_port.py) + portfolio-hub
  adapter + in-memory stub (adapters/ledger/), TREX_PORTFOLIO_HUB_* config
- REQ-018: fill→ledger recording in execution loop; FillStatus carries
  execution_id (deterministic synth) + fee; paper/kis adapters updated
- REQ-019: ledger outbox + retry worker (application/ledger_outbox.py),
  correlation_id idempotency, outcome vocabulary; 002_create_ledger_outbox.sql
- Tests: in-memory/portfolio-hub ledger, execution ledger hook, outbox,
  domain import sweep

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 22:57:31 +09:00
genichinandClaude Opus 4.8 ac6061f510 Deliver BAT-005: KIS token Redis cache (REQ-016, minor-change)
Port the trex-orbit Redis token-cache pattern so the KIS access token
survives daemon restarts and is shared across processes, avoiding KIS
token-issuance rate limits. KISBroker._ensure_token now resolves L1
in-memory -> L2 cache -> issue, storing issued tokens back to the cache.

- adapters/broker/token_cache.py: TokenCache protocol + RedisTokenCache
  (key trex:broker:kis:{account_ref}:token, token+expiry+app_key suffix
  JSON, ex=ttl, best-effort with graceful fallback) + InMemoryTokenCache.
- kis.py: optional token_cache injection.
- runtime config/daemon: TREX_KIS_REDIS_URL wires a RedisTokenCache.
- pyproject: redis>=5.

Verification: 96 passed, ruff clean — cache hit avoids issuance, cache is
shared across instances (restart/multi-process survival), backend errors
fall back to issuance (no regression), and app_key-suffix mismatch
invalidates a stale cache. Tested deterministically with an in-memory
double (no Redis server required). Processed via the minor-change fast
path: REQ-016 -> Implemented, BAT-005 (batch-lite) -> release-candidate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 18:44:09 +09:00
genichinandClaude Opus 4.8 97d58318db Deliver BAT-004: broker execution + KIS adapter + durable handoff
Implement REQ-011~015 (dcy-003): the order-execution layer on top of the
BAT-002 slot-core daemon, connecting intents to real orders.
- application: BrokerPort (submit/poll_fill/cancel) + ExecutionService
  (claim -> submit -> poll -> transition -> rollback -> reconcile)
- adapters/broker: PaperBroker (deterministic async double) and KISBroker
  (KIS Open API: token, order-cash, inquire-daily-ccld, cancel; VTS sandbox
  tr_ids; live account gated behind allow_live)
- persistence: option-A execution queue (find_pending_for_dispatch FOR
  UPDATE SKIP LOCKED, mark_dispatched idempotency guard, clear_dispatch)
- runtime: background execution loop in the daemon lifespan + startup
  reconcile; config selects paper|kis

Verification (real KIS VTS 모의투자 + PostgreSQL): 92 passed, ruff clean,
domain import sweep shows zero broker/web/db deps, buy->holding /
sell->closed / reject->idle, idempotent dispatch (exactly one order),
reconcile after crash creates no duplicate order, KIS sandbox token+submit
round-trip, and a daemon E2E through the background loop. Align the four
baseline docs. BAT-004 -> release-candidate; REQ-011~015 -> Implemented.

KIS live-account go-live, partial-fill accrual, option-B outbox, connection
pooling, and the safety floor remain deferred to follow-up Discoveries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 17:46:17 +09:00
genichinandClaude Opus 4.8 a4e263c8e7 Deliver BAT-002: slot-core daemon + HTTP intent API + PostgreSQL
Implement REQ-005~009 (dcy-002 slice 1) as an onion stack on top of the
BAT-001 pure domain:
- application: IntentService (admission + single-writer transition +
  intent_id idempotency via durable intent_log)
- adapters/persistence: PostgreSQLSlotRepository + PostgreSQLIntentStore +
  trex-core-owned migrations (daily_slot per Appendix A, daily_symbol_entries,
  slot_transition_log, intent_log) + in-memory intent store
- adapters/api: FastAPI /v1 (healthz, intents/buy|sell|{id}, slots, capacity)
- runtime: trex-core-daemon entrypoint with lifespan migrate+preallocate

Verification (against local PostgreSQL 18): 74 passed, ruff clean, domain
import sweep shows zero web/db deps (NFR-1), 20-thread concurrency admits
exactly the idle count (NFR-2), idempotent resubmit causes no double
transition (NFR-3), EXPECTED_SLOT_FIELDS<->information_schema drift test
(realizes dcy-001 OQ-2's deferred DB-side check), and a daemon E2E smoke.

Align all four baseline docs to the new daemon/API/persistence layers.
BAT-002 -> release-candidate; REQ-005~009 -> Implemented. Full async outbox,
broker execution, safety floor, and strategy daemons remain deferred to a
follow-up Discovery.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 13:46:08 +09:00