Compare commits
1 Commits
fix/test-f
...
feature/is
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a8936ab34 |
@@ -64,3 +64,25 @@
|
|||||||
**참고:** Realtime 모드 전용. Daily 모드는 배치 효율성을 위해 정적 watchlist 사용.
|
**참고:** Realtime 모드 전용. Daily 모드는 배치 효율성을 위해 정적 watchlist 사용.
|
||||||
|
|
||||||
**이슈/PR:** #76, #77
|
**이슈/PR:** #76, #77
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-02-10
|
||||||
|
|
||||||
|
### 코드 리뷰 시 플랜-구현 일치 검증 규칙
|
||||||
|
|
||||||
|
**배경:**
|
||||||
|
- 코드 리뷰 시 플랜(EnterPlanMode에서 승인된 계획)과 실제 구현이 일치하는지 확인하는 절차가 없었음
|
||||||
|
- 플랜과 다른 구현이 리뷰 없이 통과될 위험
|
||||||
|
|
||||||
|
**요구사항:**
|
||||||
|
1. 모든 PR 리뷰에서 플랜-구현 일치 여부를 필수 체크
|
||||||
|
2. 플랜에 없는 변경은 정당한 사유 필요
|
||||||
|
3. 플랜 항목이 누락되면 PR 설명에 사유 기록
|
||||||
|
4. 스코프가 플랜과 일치하는지 확인
|
||||||
|
|
||||||
|
**구현 결과:**
|
||||||
|
- `docs/workflow.md`에 Code Review Checklist 섹션 추가
|
||||||
|
- Plan Consistency (필수), Safety & Constraints, Quality, Workflow 4개 카테고리
|
||||||
|
|
||||||
|
**이슈/PR:** #114
|
||||||
|
|||||||
@@ -74,3 +74,37 @@ task_tool(
|
|||||||
```
|
```
|
||||||
|
|
||||||
Use `run_in_background=True` for independent tasks that don't block subsequent work.
|
Use `run_in_background=True` for independent tasks that don't block subsequent work.
|
||||||
|
|
||||||
|
## Code Review Checklist
|
||||||
|
|
||||||
|
**CRITICAL: Every PR review MUST verify plan-implementation consistency.**
|
||||||
|
|
||||||
|
Before approving any PR, the reviewer (human or agent) must check ALL of the following:
|
||||||
|
|
||||||
|
### 1. Plan Consistency (MANDATORY)
|
||||||
|
|
||||||
|
- [ ] **Implementation matches the approved plan** — Compare the actual code changes against the plan created during `EnterPlanMode`. Every item in the plan must be addressed.
|
||||||
|
- [ ] **No unplanned changes** — If the implementation includes changes not in the plan, they must be explicitly justified.
|
||||||
|
- [ ] **No plan items omitted** — If any planned item was skipped, the reason must be documented in the PR description.
|
||||||
|
- [ ] **Scope matches** — The PR does not exceed or fall short of the planned scope.
|
||||||
|
|
||||||
|
### 2. Safety & Constraints
|
||||||
|
|
||||||
|
- [ ] `src/core/risk_manager.py` is unchanged (READ-ONLY)
|
||||||
|
- [ ] Circuit breaker threshold not weakened (only stricter allowed)
|
||||||
|
- [ ] Fat-finger protection (30% max order) still enforced
|
||||||
|
- [ ] Confidence < 80 still forces HOLD
|
||||||
|
- [ ] No hardcoded API keys or secrets
|
||||||
|
|
||||||
|
### 3. Quality
|
||||||
|
|
||||||
|
- [ ] All new/modified code has corresponding tests
|
||||||
|
- [ ] Test coverage >= 80%
|
||||||
|
- [ ] `ruff check src/ tests/` passes (no lint errors)
|
||||||
|
- [ ] No `assert` statements removed from tests
|
||||||
|
|
||||||
|
### 4. Workflow
|
||||||
|
|
||||||
|
- [ ] PR references the Gitea issue number
|
||||||
|
- [ ] Feature branch follows naming convention (`feature/issue-N-description`)
|
||||||
|
- [ ] Commit messages are clear and descriptive
|
||||||
|
|||||||
@@ -230,44 +230,21 @@ class ContextAggregator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def run_all_aggregations(self) -> None:
|
def run_all_aggregations(self) -> None:
|
||||||
"""Run all aggregations from L7 to L1 (bottom-up).
|
"""Run all aggregations from L7 to L1 (bottom-up)."""
|
||||||
|
|
||||||
All timeframes are derived from the latest trade timestamp so that
|
|
||||||
past data re-aggregation produces consistent results across layers.
|
|
||||||
"""
|
|
||||||
cursor = self.conn.execute("SELECT MAX(timestamp) FROM trades")
|
|
||||||
row = cursor.fetchone()
|
|
||||||
if not row or row[0] is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
ts_raw = row[0]
|
|
||||||
if ts_raw.endswith("Z"):
|
|
||||||
ts_raw = ts_raw.replace("Z", "+00:00")
|
|
||||||
latest_ts = datetime.fromisoformat(ts_raw)
|
|
||||||
trade_date = latest_ts.date()
|
|
||||||
date_str = trade_date.isoformat()
|
|
||||||
|
|
||||||
iso_year, iso_week, _ = trade_date.isocalendar()
|
|
||||||
week_str = f"{iso_year}-W{iso_week:02d}"
|
|
||||||
month_str = f"{trade_date.year}-{trade_date.month:02d}"
|
|
||||||
quarter = (trade_date.month - 1) // 3 + 1
|
|
||||||
quarter_str = f"{trade_date.year}-Q{quarter}"
|
|
||||||
year_str = str(trade_date.year)
|
|
||||||
|
|
||||||
# L7 (trades) → L6 (daily)
|
# L7 (trades) → L6 (daily)
|
||||||
self.aggregate_daily_from_trades(date_str)
|
self.aggregate_daily_from_trades()
|
||||||
|
|
||||||
# L6 (daily) → L5 (weekly)
|
# L6 (daily) → L5 (weekly)
|
||||||
self.aggregate_weekly_from_daily(week_str)
|
self.aggregate_weekly_from_daily()
|
||||||
|
|
||||||
# L5 (weekly) → L4 (monthly)
|
# L5 (weekly) → L4 (monthly)
|
||||||
self.aggregate_monthly_from_weekly(month_str)
|
self.aggregate_monthly_from_weekly()
|
||||||
|
|
||||||
# L4 (monthly) → L3 (quarterly)
|
# L4 (monthly) → L3 (quarterly)
|
||||||
self.aggregate_quarterly_from_monthly(quarter_str)
|
self.aggregate_quarterly_from_monthly()
|
||||||
|
|
||||||
# L3 (quarterly) → L2 (annual)
|
# L3 (quarterly) → L2 (annual)
|
||||||
self.aggregate_annual_from_quarterly(year_str)
|
self.aggregate_annual_from_quarterly()
|
||||||
|
|
||||||
# L2 (annual) → L1 (legacy)
|
# L2 (annual) → L1 (legacy)
|
||||||
self.aggregate_legacy_from_annual()
|
self.aggregate_legacy_from_annual()
|
||||||
|
|||||||
@@ -300,17 +300,9 @@ class TestContextAggregator:
|
|||||||
# Verify data exists in each layer
|
# Verify data exists in each layer
|
||||||
store = aggregator.store
|
store = aggregator.store
|
||||||
assert store.get_context(ContextLayer.L6_DAILY, date, "total_pnl") == 1000.0
|
assert store.get_context(ContextLayer.L6_DAILY, date, "total_pnl") == 1000.0
|
||||||
from datetime import date as date_cls
|
current_week = datetime.now(UTC).strftime("%Y-W%V")
|
||||||
trade_date = date_cls.fromisoformat(date)
|
assert store.get_context(ContextLayer.L5_WEEKLY, current_week, "weekly_pnl") is not None
|
||||||
iso_year, iso_week, _ = trade_date.isocalendar()
|
# Further layers depend on time alignment, just verify no crashes
|
||||||
trade_week = f"{iso_year}-W{iso_week:02d}"
|
|
||||||
assert store.get_context(ContextLayer.L5_WEEKLY, trade_week, "weekly_pnl") is not None
|
|
||||||
trade_month = f"{trade_date.year}-{trade_date.month:02d}"
|
|
||||||
trade_quarter = f"{trade_date.year}-Q{(trade_date.month - 1) // 3 + 1}"
|
|
||||||
trade_year = str(trade_date.year)
|
|
||||||
assert store.get_context(ContextLayer.L4_MONTHLY, trade_month, "monthly_pnl") == 1000.0
|
|
||||||
assert store.get_context(ContextLayer.L3_QUARTERLY, trade_quarter, "quarterly_pnl") == 1000.0
|
|
||||||
assert store.get_context(ContextLayer.L2_ANNUAL, trade_year, "annual_pnl") == 1000.0
|
|
||||||
|
|
||||||
|
|
||||||
class TestLayerMetadata:
|
class TestLayerMetadata:
|
||||||
|
|||||||
Reference in New Issue
Block a user