Merge pull request 'fix: KR session-aware exchange routing (#409)' (#411) from feature/issue-409-kr-session-exchange-routing into main
All checks were successful
Gitea CI / test (push) Successful in 36s
All checks were successful
Gitea CI / test (push) Successful in 36s
Reviewed-on: #411 Reviewed-by: jihoson <kiparang7th@gmail.com>
This commit was merged in pull request #411.
This commit is contained in:
281
docs/plans/2026-03-03-398-400-401-implementation.md
Normal file
281
docs/plans/2026-03-03-398-400-401-implementation.md
Normal file
@@ -0,0 +1,281 @@
|
||||
# 398/400/401 Integration Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Implement #398, #400, #401 as three isolated PRs targeting `feature/398-400-401`, merge only when CI passes and self-review has zero minor issues, then run and monitor overnight script without stopping the process.
|
||||
|
||||
**Architecture:** Create one integration base branch from `origin/main`, branch per issue, and ship in strict sequence (`398 -> 400 -> 401`) to keep diffs isolated. Use TDD per issue (fail-first tests, minimal fix, regression checks), then perform PR self-review and CI gate before merge. After all merges, run overnight in background and monitor logs/process health while leaving runtime active.
|
||||
|
||||
**Tech Stack:** Python 3, pytest, asyncio runtime loop, Git/Gitea (`tea`), shell scripts (`scripts/run_overnight.sh`).
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Prepare Integration Branch Topology
|
||||
|
||||
**Files:**
|
||||
- Modify: `.git` refs only (branch operations)
|
||||
|
||||
**Step 1: Sync base branch**
|
||||
|
||||
Run: `git fetch origin && git checkout main && git pull --ff-only origin main`
|
||||
Expected: local `main` equals `origin/main`
|
||||
|
||||
**Step 2: Create integration branch**
|
||||
|
||||
Run: `git checkout -b feature/398-400-401`
|
||||
Expected: current branch is `feature/398-400-401`
|
||||
|
||||
**Step 3: Create issue branches from integration branch**
|
||||
|
||||
Run: `git checkout -b fix/398 && git checkout feature/398-400-401 && git checkout -b fix/400 && git checkout feature/398-400-401 && git checkout -b fix/401 && git checkout feature/398-400-401`
|
||||
Expected: three issue branches exist and point to same base commit
|
||||
|
||||
**Step 4: Push all branches**
|
||||
|
||||
Run: `git push -u origin feature/398-400-401 fix/398 fix/400 fix/401`
|
||||
Expected: remote tracking set for all four branches
|
||||
|
||||
**Step 5: Commit checkpoint**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
git status --short
|
||||
```
|
||||
Expected: clean workspace before issue implementation
|
||||
|
||||
### Task 2: Implement #398 with TDD (KR rt_cd failure handling)
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/main.py`
|
||||
- Test: `tests/test_main.py`
|
||||
|
||||
**Step 1: Write failing test**
|
||||
|
||||
Add test in `tests/test_main.py` verifying KR order returns `rt_cd != '0'` does not trigger success side effects (no BUY notify, no trade log success path).
|
||||
|
||||
**Step 2: Run test to verify failure**
|
||||
|
||||
Run: `pytest tests/test_main.py -k "kr and rt_cd" -v`
|
||||
Expected: FAIL showing current code incorrectly treats KR order as success
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
In KR order branch of `src/main.py`, immediately after `send_order`, add `rt_cd` acceptance check identical to overseas branch behavior; set `order_succeeded = False` and warning log when rejected.
|
||||
|
||||
**Step 4: Run targeted tests**
|
||||
|
||||
Run: `pytest tests/test_main.py -k "kr and rt_cd" -v`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Run safety regression**
|
||||
|
||||
Run: `pytest tests/test_main.py tests/test_order_policy.py -q`
|
||||
Expected: PASS
|
||||
|
||||
**Step 6: Commit**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
git add tests/test_main.py src/main.py
|
||||
git commit -m "fix: handle KR order rejection via rt_cd check (#398)"
|
||||
```
|
||||
|
||||
### Task 3: Open PR for #398, Self-review, CI gate, Merge
|
||||
|
||||
**Files:**
|
||||
- Modify: remote PR metadata/comments only
|
||||
|
||||
**Step 1: Push branch**
|
||||
|
||||
Run: `git checkout fix/398 && git push -u origin fix/398`
|
||||
|
||||
**Step 2: Create PR targeting integration branch**
|
||||
|
||||
Run: `tea pr create --base feature/398-400-401 --head fix/398 --title "fix: #398 KR rt_cd rejection handling" --description "Implements issue #398 with tests."`
|
||||
Expected: PR URL returned
|
||||
|
||||
**Step 3: Add self-review comment (severity rubric)**
|
||||
|
||||
Run: `tea pr comment <PR_398> --message "Self-review: Critical 0 / Major 0 / Minor 0. Merge allowed when CI passes."`
|
||||
|
||||
**Step 4: Wait for CI success**
|
||||
|
||||
Run: `tea pr checks <PR_398>` (poll until all success)
|
||||
Expected: all checks success
|
||||
|
||||
**Step 5: Merge only when gate passes**
|
||||
|
||||
Run: `tea pr merge <PR_398> --delete-branch=false`
|
||||
Expected: merged into `feature/398-400-401`
|
||||
|
||||
### Task 4: Implement #400 with TDD (US session transition correctness)
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/main.py`, `src/core/order_policy.py`, `src/markets/schedule.py`
|
||||
- Test: `tests/test_main.py`, `tests/test_market_schedule.py`, `tests/test_order_policy.py`
|
||||
|
||||
**Step 1: Write failing tests**
|
||||
|
||||
Add tests for:
|
||||
- session transition event handling (`US_DAY -> US_REG`) emits open event and forces rescan
|
||||
- `US_DAY` treated non-tradable for playbook/trading actions
|
||||
|
||||
**Step 2: Run failing tests**
|
||||
|
||||
Run: `pytest tests/test_main.py tests/test_market_schedule.py tests/test_order_policy.py -k "US_DAY or US_REG or session" -v`
|
||||
Expected: FAIL at current behavior
|
||||
|
||||
**Step 3: Minimal implementation**
|
||||
|
||||
- Track market state by session identifier (not bool only)
|
||||
- Force rescan/playbook refresh on US_REG entry
|
||||
- Exclude/suppress US_DAY for trading/playbook generation path
|
||||
|
||||
**Step 4: Re-run targeted tests**
|
||||
|
||||
Run: same command as Step 2
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Regression pass**
|
||||
|
||||
Run: `pytest tests/test_main.py tests/test_market_schedule.py tests/test_order_policy.py tests/test_pre_market_planner.py -q`
|
||||
Expected: PASS
|
||||
|
||||
**Step 6: Commit**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
git add src/main.py src/core/order_policy.py src/markets/schedule.py tests/test_main.py tests/test_market_schedule.py tests/test_order_policy.py
|
||||
git commit -m "fix: handle US session transitions and suppress US_DAY trading (#400)"
|
||||
```
|
||||
|
||||
### Task 5: Open PR for #400, Self-review, CI gate, Merge
|
||||
|
||||
**Files:**
|
||||
- Modify: remote PR metadata/comments only
|
||||
|
||||
**Step 1: Push branch**
|
||||
|
||||
Run: `git checkout fix/400 && git push -u origin fix/400`
|
||||
|
||||
**Step 2: Create PR**
|
||||
|
||||
Run: `tea pr create --base feature/398-400-401 --head fix/400 --title "fix: #400 US session transition handling" --description "Implements issue #400 with tests."`
|
||||
|
||||
**Step 3: Add self-review comment**
|
||||
|
||||
Run: `tea pr comment <PR_400> --message "Self-review: Critical 0 / Major 0 / Minor 0. Merge allowed when CI passes."`
|
||||
|
||||
**Step 4: Wait for CI success**
|
||||
|
||||
Run: `tea pr checks <PR_400>`
|
||||
Expected: all checks success
|
||||
|
||||
**Step 5: Merge**
|
||||
|
||||
Run: `tea pr merge <PR_400> --delete-branch=false`
|
||||
|
||||
### Task 6: Implement #401 with TDD (multi-market parallel processing)
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/main.py`
|
||||
- Test: `tests/test_main.py`
|
||||
|
||||
**Step 1: Write failing tests**
|
||||
|
||||
Add tests verifying:
|
||||
- open markets are processed via parallel task dispatch
|
||||
- circuit breaker behavior still triggers global shutdown semantics
|
||||
- shared state updates remain deterministic under parallel market execution
|
||||
|
||||
**Step 2: Run failing tests**
|
||||
|
||||
Run: `pytest tests/test_main.py -k "parallel or market" -v`
|
||||
Expected: FAIL before implementation
|
||||
|
||||
**Step 3: Minimal implementation**
|
||||
|
||||
Refactor sequential market loop into market-level async tasks (`asyncio.gather`/task group) while preserving stock-level processing order per market and existing failure semantics.
|
||||
|
||||
**Step 4: Re-run targeted tests**
|
||||
|
||||
Run: same command as Step 2
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Regression pass**
|
||||
|
||||
Run: `pytest tests/test_main.py tests/test_runtime_overnight_scripts.py -q`
|
||||
Expected: PASS
|
||||
|
||||
**Step 6: Commit**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
git add src/main.py tests/test_main.py
|
||||
git commit -m "feat: process active markets in parallel with preserved shutdown semantics (#401)"
|
||||
```
|
||||
|
||||
### Task 7: Open PR for #401, Self-review, CI gate, Merge
|
||||
|
||||
**Files:**
|
||||
- Modify: remote PR metadata/comments only
|
||||
|
||||
**Step 1: Push branch**
|
||||
|
||||
Run: `git checkout fix/401 && git push -u origin fix/401`
|
||||
|
||||
**Step 2: Create PR**
|
||||
|
||||
Run: `tea pr create --base feature/398-400-401 --head fix/401 --title "feat: #401 parallel multi-market processing" --description "Implements issue #401 with tests."`
|
||||
|
||||
**Step 3: Add self-review comment**
|
||||
|
||||
Run: `tea pr comment <PR_401> --message "Self-review: Critical 0 / Major 0 / Minor 0. Merge allowed when CI passes."`
|
||||
|
||||
**Step 4: Wait for CI success**
|
||||
|
||||
Run: `tea pr checks <PR_401>`
|
||||
Expected: all checks success
|
||||
|
||||
**Step 5: Merge**
|
||||
|
||||
Run: `tea pr merge <PR_401> --delete-branch=false`
|
||||
|
||||
### Task 8: Final Branch Validation + Overnight Runtime Monitoring
|
||||
|
||||
**Files:**
|
||||
- Execute: `scripts/run_overnight.sh`
|
||||
- Observe: runtime log file (e.g., `logs/overnight.log`)
|
||||
|
||||
**Step 1: Checkout integrated branch and sync**
|
||||
|
||||
Run: `git checkout feature/398-400-401 && git pull --ff-only origin feature/398-400-401`
|
||||
Expected: branch contains merged PRs
|
||||
|
||||
**Step 2: Start overnight in background (non-blocking)**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
nohup ./scripts/run_overnight.sh > /tmp/ouroboros_overnight.log 2>&1 &
|
||||
echo $! > /tmp/ouroboros_overnight.pid
|
||||
```
|
||||
Expected: PID written and process running
|
||||
|
||||
**Step 3: Verify process alive**
|
||||
|
||||
Run: `ps -p $(cat /tmp/ouroboros_overnight.pid) -o pid,ppid,stat,etime,cmd`
|
||||
Expected: process present
|
||||
|
||||
**Step 4: Monitor startup logs**
|
||||
|
||||
Run: `tail -n 120 /tmp/ouroboros_overnight.log`
|
||||
Expected: startup complete and runtime loop active without fatal errors
|
||||
|
||||
**Step 5: Ongoing monitor without shutdown**
|
||||
|
||||
Run: `tail -f /tmp/ouroboros_overnight.log` (sample monitoring window, then detach)
|
||||
Expected: continued activity; do not kill process
|
||||
|
||||
**Step 6: Final status note**
|
||||
|
||||
Record PID, log path, and “process left running” status.
|
||||
62
docs/plans/2026-03-03-398-400-401-integration-design.md
Normal file
62
docs/plans/2026-03-03-398-400-401-integration-design.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# 398/400/401 통합 처리 설계
|
||||
|
||||
## 개요
|
||||
이 문서는 이슈 #398, #400, #401을 `origin/main` 기반 통합 브랜치에서 순차적으로 처리하고,
|
||||
각 PR을 셀프 리뷰 및 CI 게이트로 검증한 뒤 머지하는 운영 설계를 정의한다.
|
||||
최종 머지된 통합 브랜치에서 overnight 스크립트를 실행하고, 모니터링 이후에도 프로그램은 계속 실행 상태를 유지한다.
|
||||
|
||||
## 목표
|
||||
- 통합 브랜치: `feature/398-400-401`
|
||||
- 작업 브랜치: `fix/398`, `fix/400`, `fix/401`
|
||||
- PR base: 모두 `feature/398-400-401`
|
||||
- 머지 조건: `CI 전체 통과` + `셀프 리뷰에서 minor 포함 이슈 0건`
|
||||
- 최종 확인: 통합 브랜치에서 overnight 실행 및 모니터링, 프로세스 지속 실행
|
||||
|
||||
## 아키텍처
|
||||
- `origin/main`에서 `feature/398-400-401` 생성
|
||||
- 각 이슈는 독립 브랜치(`fix/398`, `fix/400`, `fix/401`)에서 구현
|
||||
- PR은 순차적으로 생성/검증/머지 (`398 -> 400 -> 401`)
|
||||
- 각 PR은 셀프 리뷰 코멘트를 남기고, minor 이상 발견 시 수정 후 재검증
|
||||
- 3개 PR 머지 완료 후 통합 브랜치에서 overnight 백그라운드 실행 및 로그 모니터링
|
||||
- 모니터링 완료 후에도 프로세스는 종료하지 않음
|
||||
|
||||
## 컴포넌트
|
||||
- Git/브랜치 컴포넌트: 브랜치 생성, 리베이스, 충돌 해결
|
||||
- 이슈 구현 컴포넌트:
|
||||
- #398: KR 주문 `rt_cd` 실패 처리, 오알림/오기록 차단
|
||||
- #400: US 세션 전환 감지, US_DAY 억제, US_REG 진입 이벤트/강제 재스캔
|
||||
- #401: 시장 단위 병렬 처리 및 공유 상태 동시성 보호
|
||||
- PR 운영 컴포넌트: PR 생성, 셀프 리뷰 코멘트 작성, 승인 기준 확인
|
||||
- CI 게이트 컴포넌트: 체크 상태 폴링 및 pass 확인
|
||||
- 머지 컴포넌트: 게이트 통과 PR만 머지
|
||||
- 런타임 검증 컴포넌트: overnight 실행, 로그 추적, 프로세스 생존 확인
|
||||
|
||||
## 데이터/제어 흐름
|
||||
1. `feature/398-400-401` 생성
|
||||
2. `fix/398` 구현 -> 테스트 -> 커밋 -> PR 생성
|
||||
3. 셀프 리뷰 코멘트 작성(결함 레벨 포함)
|
||||
4. CI 완료 대기 후 `CI pass && minor 0`이면 머지
|
||||
5. `fix/400`, `fix/401`에 대해 동일 절차 반복
|
||||
6. 통합 브랜치에서 overnight 백그라운드 실행
|
||||
7. 로그/상태 모니터링으로 실제 동작 확인
|
||||
8. 결과 보고 후에도 프로세스는 계속 실행
|
||||
|
||||
## 에러 처리/복구
|
||||
- PR 생성/충돌 실패: 해당 브랜치만 중단 후 해결, 다른 브랜치와 격리 유지
|
||||
- 셀프 리뷰 실패(minor 포함): 머지 금지, 수정 커밋 후 리뷰 갱신
|
||||
- CI 실패: 실패 원인 수정 후 재푸시, 재검증
|
||||
- 머지 실패: base 최신화 및 재시도
|
||||
- overnight 시작 실패: 로그 분석 후 재기동
|
||||
- 모니터링 중 오류: 오류 보고는 하되 자동 종료하지 않고 실행 유지
|
||||
|
||||
## 테스트/검증
|
||||
- PR별 관련 단위/통합 테스트 실행
|
||||
- 필요 시 `tests/test_main.py`, `tests/test_runtime_overnight_scripts.py` 포함 회귀 실행
|
||||
- 셀프 리뷰는 `Critical/Major/Minor` 기준으로 작성
|
||||
- minor 0건 명시된 경우에만 머지 진행
|
||||
- 최종 통합 브랜치에서 overnight 기동/루프 진입/에러 로그 확인
|
||||
- PID/프로세스 생존 확인 후 실행 지속 상태 보고
|
||||
|
||||
## 비목표
|
||||
- 본 문서는 구현 상세 코드 변경 자체를 다루지 않는다.
|
||||
- 본 문서는 외부 리뷰어 승인 프로세스를 다루지 않는다(셀프 리뷰만 대상).
|
||||
@@ -0,0 +1,103 @@
|
||||
# Issue #409 Design - KR Session-Aware Exchange Routing
|
||||
|
||||
## Context
|
||||
- Issue: #409 (bug: KR 세션별 거래소 미분리 - 스크리닝/주문/이중상장 우선순위 미처리)
|
||||
- Related runtime observation targets: #318, #325
|
||||
- Date: 2026-03-04
|
||||
- Confirmed approach: Option 2 (routing module introduction)
|
||||
|
||||
## Goals
|
||||
1. Ensure domestic screening uses session-specific exchange market code.
|
||||
2. Ensure domestic order submission explicitly sets exchange routing code.
|
||||
3. Add dual-listing routing priority logic (spread/liquidity aware) with safe fallback.
|
||||
4. Keep existing behavior stable for non-KR flows and existing risk/order policy guards.
|
||||
5. Enable runtime observability for #409 while monitoring #318/#325 in parallel.
|
||||
|
||||
## Non-Goals
|
||||
- Replacing current session classification model.
|
||||
- Introducing new market sessions or changing session boundaries.
|
||||
- Refactoring overseas order flow.
|
||||
|
||||
## Architecture
|
||||
### New Component
|
||||
- Add `KRExchangeRouter` (new module, e.g. `src/broker/kr_exchange_router.py`).
|
||||
- Responsibility split:
|
||||
- `classify_session_id`: session classification only.
|
||||
- `KRExchangeRouter`: final domestic exchange selection (`KRX`/`NXT`) for ranking and order.
|
||||
- `KISBroker`: inject resolved routing values into request params/body.
|
||||
|
||||
### Integration Points
|
||||
- `KISBroker.fetch_market_rankings`
|
||||
- Session-aware market division code:
|
||||
- `KRX_REG` -> `J`
|
||||
- `NXT_PRE`, `NXT_AFTER` -> `NX`
|
||||
- `KISBroker.send_order`
|
||||
- Explicit `EXCG_ID_DVSN_CD` is always set.
|
||||
- `SmartVolatilityScanner._scan_domestic`
|
||||
- Ensure domestic ranking API path resolves exchange consistently with current session.
|
||||
|
||||
## Data Flow
|
||||
1. Scanner path:
|
||||
- Determine `session_id`.
|
||||
- `resolve_for_ranking(session_id)`.
|
||||
- Inject `J` or `NX` into ranking API params.
|
||||
2. Order path:
|
||||
- Pass `session_id` into order path.
|
||||
- `resolve_for_order(stock_code, session_id)`.
|
||||
- Single listing: session default exchange.
|
||||
- Dual listing: select by spread/liquidity heuristic when data is available.
|
||||
- Data unavailable/error: fallback to session default.
|
||||
- Send order with explicit `EXCG_ID_DVSN_CD`.
|
||||
3. Observability:
|
||||
- Log `session_id`, `resolved_exchange`, `routing_reason`.
|
||||
|
||||
## Dual-Listing Routing Priority
|
||||
- Preferred decision source: spread/liquidity comparison.
|
||||
- Deterministic fallback: session-default exchange.
|
||||
- Proposed reasons in logs:
|
||||
- `session_default`
|
||||
- `dual_listing_spread`
|
||||
- `dual_listing_liquidity`
|
||||
- `fallback_data_unavailable`
|
||||
|
||||
## Error Handling
|
||||
- Router does not block order path when auxiliary data is unavailable.
|
||||
- Fail-open strategy for routing selection (fallback to session default) while preserving existing API/network error semantics.
|
||||
- `send_order` exchange field omission is forbidden by design after this change.
|
||||
|
||||
## Testing Strategy
|
||||
### Unit
|
||||
- Router mapping by session (`KRX_REG`, `NXT_PRE`, `NXT_AFTER`).
|
||||
- Dual-listing routing priority and fallback.
|
||||
- Broker order body includes `EXCG_ID_DVSN_CD`.
|
||||
- Ranking params use session-aware market code.
|
||||
|
||||
### Integration/Regression
|
||||
- `smart_scanner` domestic calls align with session exchange.
|
||||
- Existing order policy tests remain green.
|
||||
- Re-run regression sets covering #318/#325 related paths.
|
||||
|
||||
### Runtime Observation (24h)
|
||||
- Restart program from working branch build.
|
||||
- Run runtime monitor for up to 24h.
|
||||
- Verify and track:
|
||||
- #409: session-aware routing evidence in logs.
|
||||
- #318: ATR dynamic stop evidence.
|
||||
- #325: ATR/pred_down_prob injection evidence.
|
||||
- If anomalies are detected during monitoring, create separate issue tickets with evidence and links.
|
||||
|
||||
## Acceptance Criteria
|
||||
1. No domestic ranking call uses hardcoded KRX-only behavior across NXT sessions.
|
||||
2. No domestic order is sent without `EXCG_ID_DVSN_CD`.
|
||||
3. Dual-listing path has explicit priority logic and deterministic fallback.
|
||||
4. Tests pass for new and affected paths.
|
||||
5. Runtime monitor evidence is collected for #409, #318, #325; anomalies are ticketed.
|
||||
|
||||
## Risks and Mitigations
|
||||
- Risk: Increased routing complexity introduces regressions.
|
||||
- Mitigation: isolate router, high-coverage unit tests, preserve existing interfaces where possible.
|
||||
- Risk: Runtime events for #318/#325 may not naturally occur in 24h.
|
||||
- Mitigation: mark as `NOT_OBSERVED` and keep issue state based on evidence policy; do not force-close without proof.
|
||||
|
||||
## Planned Next Step
|
||||
- Invoke `writing-plans` workflow and produce implementation plan before code changes.
|
||||
@@ -0,0 +1,352 @@
|
||||
# Issue #409 KR Session Exchange Routing Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Fix #409 by making KR screening/order routing session-aware and adding dual-listing exchange priority with deterministic fallback, then run 24h runtime observation for #409/#318/#325.
|
||||
|
||||
**Architecture:** Introduce a dedicated `KRExchangeRouter` module that resolves exchange by session and dual-listing metadata. Keep session classification in `order_policy`, and inject router outputs into `KISBroker` ranking/order requests. Add explicit routing logs for runtime evidence and keep non-KR behavior unchanged.
|
||||
|
||||
**Tech Stack:** Python 3.12, aiohttp client layer, pytest/pytest-asyncio, Gitea CLI (`tea`), bash runtime monitor scripts.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Preflight and Branch Runtime Gate
|
||||
|
||||
**Files:**
|
||||
- Modify: `workflow/session-handover.md`
|
||||
|
||||
**Step 1: Add handover entry for this ticket branch**
|
||||
|
||||
```md
|
||||
### 2026-03-04 | session=codex-issue409-start
|
||||
- branch: feature/issue-409-kr-session-exchange-routing
|
||||
- docs_checked: docs/workflow.md, docs/commands.md, docs/agent-constraints.md
|
||||
- open_issues_reviewed: #409, #318, #325
|
||||
- next_ticket: #409
|
||||
- process_gate_checked: process_ticket=#306,#308 merged_to_feature_branch=yes
|
||||
- risks_or_notes: #409 code fix + 24h monitor, runtime anomaly creates separate issue ticket
|
||||
```
|
||||
|
||||
**Step 2: Run strict handover check**
|
||||
|
||||
Run: `python3 scripts/session_handover_check.py --strict`
|
||||
Expected: PASS
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add workflow/session-handover.md
|
||||
git commit -m "chore: add handover entry for issue #409"
|
||||
```
|
||||
|
||||
### Task 2: Add Router Unit Tests First (TDD)
|
||||
|
||||
**Files:**
|
||||
- Create: `tests/test_kr_exchange_router.py`
|
||||
|
||||
**Step 1: Write failing tests for session mapping**
|
||||
|
||||
```python
|
||||
from src.broker.kr_exchange_router import KRExchangeRouter
|
||||
|
||||
|
||||
def test_ranking_market_code_by_session() -> None:
|
||||
router = KRExchangeRouter()
|
||||
assert router.resolve_for_ranking("KRX_REG") == "J"
|
||||
assert router.resolve_for_ranking("NXT_PRE") == "NX"
|
||||
assert router.resolve_for_ranking("NXT_AFTER") == "NX"
|
||||
```
|
||||
|
||||
**Step 2: Write failing tests for dual-listing fallback behavior**
|
||||
|
||||
```python
|
||||
def test_order_exchange_falls_back_to_session_default_on_missing_data() -> None:
|
||||
router = KRExchangeRouter()
|
||||
resolved = router.resolve_for_order(
|
||||
stock_code="0001A0",
|
||||
session_id="NXT_PRE",
|
||||
is_dual_listed=True,
|
||||
spread_krx=None,
|
||||
spread_nxt=None,
|
||||
liquidity_krx=None,
|
||||
liquidity_nxt=None,
|
||||
)
|
||||
assert resolved.exchange_code == "NXT"
|
||||
assert resolved.reason == "fallback_data_unavailable"
|
||||
```
|
||||
|
||||
**Step 3: Run tests to verify fail**
|
||||
|
||||
Run: `pytest tests/test_kr_exchange_router.py -v`
|
||||
Expected: FAIL (`ModuleNotFoundError` or missing class)
|
||||
|
||||
**Step 4: Commit tests-only checkpoint**
|
||||
|
||||
```bash
|
||||
git add tests/test_kr_exchange_router.py
|
||||
git commit -m "test: add failing tests for KR exchange router"
|
||||
```
|
||||
|
||||
### Task 3: Implement Router Minimal Code
|
||||
|
||||
**Files:**
|
||||
- Create: `src/broker/kr_exchange_router.py`
|
||||
- Modify: `src/broker/__init__.py`
|
||||
|
||||
**Step 1: Add routing dataclass + session default mapping**
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class ExchangeResolution:
|
||||
exchange_code: str
|
||||
reason: str
|
||||
|
||||
|
||||
class KRExchangeRouter:
|
||||
def resolve_for_ranking(self, session_id: str) -> str:
|
||||
return "NX" if session_id in {"NXT_PRE", "NXT_AFTER"} else "J"
|
||||
```
|
||||
|
||||
**Step 2: Add dual-listing decision path + fallback**
|
||||
|
||||
```python
|
||||
if is_dual_listed and spread_krx is not None and spread_nxt is not None:
|
||||
if spread_nxt < spread_krx:
|
||||
return ExchangeResolution("NXT", "dual_listing_spread")
|
||||
return ExchangeResolution("KRX", "dual_listing_spread")
|
||||
|
||||
return ExchangeResolution(default_exchange, "fallback_data_unavailable")
|
||||
```
|
||||
|
||||
**Step 3: Run router tests**
|
||||
|
||||
Run: `pytest tests/test_kr_exchange_router.py -v`
|
||||
Expected: PASS
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add src/broker/kr_exchange_router.py src/broker/__init__.py
|
||||
git commit -m "feat: add KR session-aware exchange router"
|
||||
```
|
||||
|
||||
### Task 4: Broker Request Wiring (Ranking + Order)
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/broker/kis_api.py`
|
||||
- Modify: `tests/test_broker.py`
|
||||
|
||||
**Step 1: Add failing tests for ranking param and order body exchange field**
|
||||
|
||||
```python
|
||||
assert called_params["FID_COND_MRKT_DIV_CODE"] == "NX"
|
||||
assert called_json["EXCG_ID_DVSN_CD"] == "NXT"
|
||||
```
|
||||
|
||||
**Step 2: Run targeted test subset (fail first)**
|
||||
|
||||
Run: `pytest tests/test_broker.py -k "market_rankings or EXCG_ID_DVSN_CD" -v`
|
||||
Expected: FAIL on missing field/value
|
||||
|
||||
**Step 3: Implement minimal wiring**
|
||||
|
||||
```python
|
||||
session_id = runtime_session_id or classify_session_id(MARKETS["KR"])
|
||||
market_div_code = self._kr_router.resolve_for_ranking(session_id)
|
||||
params["FID_COND_MRKT_DIV_CODE"] = market_div_code
|
||||
|
||||
resolution = self._kr_router.resolve_for_order(...)
|
||||
body["EXCG_ID_DVSN_CD"] = resolution.exchange_code
|
||||
```
|
||||
|
||||
**Step 4: Add routing evidence logs**
|
||||
|
||||
```python
|
||||
logger.info(
|
||||
"KR routing resolved",
|
||||
extra={"session_id": session_id, "exchange": resolution.exchange_code, "reason": resolution.reason},
|
||||
)
|
||||
```
|
||||
|
||||
**Step 5: Re-run broker tests**
|
||||
|
||||
Run: `pytest tests/test_broker.py -k "market_rankings or EXCG_ID_DVSN_CD" -v`
|
||||
Expected: PASS
|
||||
|
||||
**Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add src/broker/kis_api.py tests/test_broker.py
|
||||
git commit -m "fix: apply KR exchange routing to rankings and orders"
|
||||
```
|
||||
|
||||
### Task 5: Scanner Session Alignment
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/analysis/smart_scanner.py`
|
||||
- Modify: `tests/test_smart_scanner.py`
|
||||
|
||||
**Step 1: Add failing test for domestic session-aware ranking path**
|
||||
|
||||
```python
|
||||
assert mock_broker.fetch_market_rankings.call_args_list[0].kwargs["session_id"] == "NXT_PRE"
|
||||
```
|
||||
|
||||
**Step 2: Run scanner tests (fail first)**
|
||||
|
||||
Run: `pytest tests/test_smart_scanner.py -k "session" -v`
|
||||
Expected: FAIL on missing session argument
|
||||
|
||||
**Step 3: Implement scanner call wiring**
|
||||
|
||||
```python
|
||||
fluct_rows = await self.broker.fetch_market_rankings(
|
||||
ranking_type="fluctuation",
|
||||
limit=50,
|
||||
session_id=session_id,
|
||||
)
|
||||
```
|
||||
|
||||
**Step 4: Re-run scanner tests**
|
||||
|
||||
Run: `pytest tests/test_smart_scanner.py -v`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add src/analysis/smart_scanner.py tests/test_smart_scanner.py
|
||||
git commit -m "fix: align domestic scanner rankings with KR session routing"
|
||||
```
|
||||
|
||||
### Task 6: Full Verification and Regression
|
||||
|
||||
**Files:**
|
||||
- No new files
|
||||
|
||||
**Step 1: Run focused regressions for #409**
|
||||
|
||||
Run:
|
||||
- `pytest tests/test_kr_exchange_router.py tests/test_broker.py tests/test_smart_scanner.py -v`
|
||||
Expected: PASS
|
||||
|
||||
**Step 2: Run related runtime-path regressions for #318/#325**
|
||||
|
||||
Run:
|
||||
- `pytest tests/test_main.py -k "atr or staged_exit or pred_down_prob" -v`
|
||||
Expected: PASS
|
||||
|
||||
**Step 3: Run lint/type checks for touched modules**
|
||||
|
||||
Run:
|
||||
- `ruff check src/broker/kis_api.py src/broker/kr_exchange_router.py src/analysis/smart_scanner.py tests/test_kr_exchange_router.py tests/test_broker.py tests/test_smart_scanner.py`
|
||||
- `mypy src/broker/kis_api.py src/broker/kr_exchange_router.py src/analysis/smart_scanner.py --strict`
|
||||
Expected: PASS
|
||||
|
||||
**Step 4: Commit final fixup if needed**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "chore: finalize #409 verification adjustments"
|
||||
```
|
||||
|
||||
### Task 7: PR Creation, Self-Review, and Merge
|
||||
|
||||
**Files:**
|
||||
- Modify: PR metadata only
|
||||
|
||||
**Step 1: Push branch**
|
||||
|
||||
Run: `git push -u origin feature/issue-409-kr-session-exchange-routing`
|
||||
Expected: remote branch created
|
||||
|
||||
**Step 2: Create PR to `main` with issue links**
|
||||
|
||||
```bash
|
||||
PR_BODY=$(cat <<'MD'
|
||||
## Summary
|
||||
- fix KR session-aware exchange routing for rankings and orders (#409)
|
||||
- add dual-listing exchange priority with deterministic fallback
|
||||
- add logs and tests for routing evidence
|
||||
|
||||
## Validation
|
||||
- pytest tests/test_kr_exchange_router.py tests/test_broker.py tests/test_smart_scanner.py -v
|
||||
- pytest tests/test_main.py -k "atr or staged_exit or pred_down_prob" -v
|
||||
- ruff check ...
|
||||
- mypy ...
|
||||
MD
|
||||
)
|
||||
|
||||
tea pr create --base main --head feature/issue-409-kr-session-exchange-routing --title "fix: KR session-aware exchange routing (#409)" --description "$PR_BODY"
|
||||
```
|
||||
|
||||
**Step 3: Validate PR body integrity**
|
||||
|
||||
Run: `python3 scripts/validate_pr_body.py --pr <PR_NUMBER>`
|
||||
Expected: PASS
|
||||
|
||||
**Step 4: Self-review checklist (blocking)**
|
||||
- Re-check diff for missing `EXCG_ID_DVSN_CD`
|
||||
- Confirm session mapping (`KRX_REG=J`, `NXT_PRE/NXT_AFTER=NX`)
|
||||
- Confirm fallback reason logging exists
|
||||
- Confirm tests cover dual-listing fallback
|
||||
|
||||
**Step 5: Merge only if no minor issues remain**
|
||||
|
||||
Run: `tea pr merge <PR_NUMBER> --merge`
|
||||
Expected: merged
|
||||
|
||||
### Task 8: Restart Program and 24h Runtime Monitoring
|
||||
|
||||
**Files:**
|
||||
- Runtime artifacts: `data/overnight/*.log`
|
||||
|
||||
**Step 1: Restart runtime from merged state**
|
||||
|
||||
Run:
|
||||
- `bash scripts/stop_overnight.sh`
|
||||
- `bash scripts/run_overnight.sh`
|
||||
Expected: live process and watchdog healthy
|
||||
|
||||
**Step 2: Start 24h monitor**
|
||||
|
||||
Run:
|
||||
- `INTERVAL_SEC=60 MAX_HOURS=24 POLICY_TZ=Asia/Seoul bash scripts/runtime_verify_monitor.sh`
|
||||
Expected: monitor loop runs and writes `data/overnight/runtime_verify_*.log`
|
||||
|
||||
**Step 3: Track #409/#318/#325 evidence in loop**
|
||||
|
||||
Run examples:
|
||||
- `rg -n "KR routing resolved|EXCG_ID_DVSN_CD|session=NXT_|session=KRX_REG" data/overnight/run_*.log`
|
||||
- `rg -n "atr_value|dynamic hard stop|staged exit|pred_down_prob" data/overnight/run_*.log`
|
||||
|
||||
Expected:
|
||||
- #409 routing evidence present when KR flows trigger
|
||||
- #318/#325 evidence captured if runtime conditions occur
|
||||
|
||||
**Step 4: If anomaly found, create separate issue ticket immediately**
|
||||
|
||||
```bash
|
||||
ISSUE_BODY=$(cat <<'MD'
|
||||
## Summary
|
||||
- runtime anomaly detected during #409 monitor
|
||||
|
||||
## Evidence
|
||||
- log: data/overnight/run_xxx.log
|
||||
- timestamp: <UTC/KST>
|
||||
- observed: <symptom>
|
||||
|
||||
## Suspected Scope
|
||||
- related to #409/#318/#325 monitoring path
|
||||
|
||||
## Next Action
|
||||
- triage + reproducible test
|
||||
MD
|
||||
)
|
||||
|
||||
tea issues create -t "bug: runtime anomaly during #409 monitor" -d "$ISSUE_BODY"
|
||||
```
|
||||
|
||||
**Step 5: Post monitoring summary to #409/#318/#325**
|
||||
- Include PASS/FAIL/NOT_OBSERVED matrix and exact timestamps.
|
||||
- Do not close #318/#325 without concrete acceptance evidence.
|
||||
Reference in New Issue
Block a user