fix(be): DCA 납입 횟수 정합성 및 첫날 미매수 자산 수정 - #52
Merged
Conversation
수익률의 분모가 되는 총 투자금을 "월 = 30일" 근사로 계산했는데, 실제 매수는 Nth-weekday 달력을 따르므로 두 값이 어긋났다. 집행되지 않은 납입금이 손실로 보고돼, 가격이 고정이고 수수료가 0인 DCA에서도 음수 수익률이 나왔다 (2024년 전체 기준 월간 -7.69%, 분기 -20%). 시뮬레이션과 동일한 규칙으로 실행 예정일을 생성하는 generate_periodic_schedule을 추가하고, 납입 횟수를 "초회 1회 + 예정일 수"로 계산한다. 초회 매수도 executed_count에 계상해 정기 매수가 남은 횟수만 집행하도록 했다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
execute_initial_purchases는 가격이 없는 종목을 건너뛰었고 is_first_day는 한 번만 참이라, 재시도가 이뤄지지 않았다. 혼합 시장 포트폴리오에서 한쪽 시장이 휴장인 날 시작하면(예: 2024-07-04) 해당 종목의 포지션이 끝까지 열리지 않고 투자금만 분모에 남아 수익률이 과소보고됐다 (2종목 중 1종목 누락 시 -50%). 초기 매수 대상을 PortfolioState.pending_initial_keys로 추적해, 각 종목이 처음 가격을 갖는 날 매수하고 성공한 종목만 집합에서 제거한다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P1-07/P1-08 완료 처리하고 측정값을 기록. CLAUDE.md 기준선을 BE 180 unit + 10 integration, FE 112로 갱신. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
백테스트 BE의 포트폴리오(buy&hold) 경로에서 DCA 총투자금(수익률 분모) 계산을 실제 매수 스케줄(Nth-weekday)과 일치시키고, 시작일에 가격이 없는 자산(혼합 시장 휴장 등)의 초기 매수가 영구 누락되는 문제를 수정하는 PR입니다. 플랫폼의 “잘못된 수익률 표시”를 직접적으로 교정하는 성격의 변경입니다.
Changes:
- DCA 납입 횟수를 30일 근사치가 아닌, 시뮬레이션과 동일 규칙으로 생성한 예정일 기반(
generate_periodic_schedule)으로 계산하도록 변경 - 시작일에 가격이 없는 종목도 “첫 가격 등장일”에 초기 매수가 실행되도록
pending_initial_keys로 초기 매수 대상을 추적 - 위 두 이슈를 고정하는 단위 테스트 추가 및 관련 문서(TODO/CLAUDE baseline) 갱신
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| TODO.md | P1-07/P1-08 완료 체크 및 수정 내용 요약 반영 |
| CLAUDE.md | 테스트 베이스라인(통과 개수) 최신화 |
| backtest_be_fast/tests/unit/test_dca_schedule_alignment.py | DCA 분모-스케줄 정합성 및 초기 매수 재시도 회귀 테스트 추가 |
| backtest_be_fast/app/services/rebalance_helper.py | 시뮬레이션과 동일 규칙으로 정기 예정일을 생성하는 generate_periodic_schedule 추가 |
| backtest_be_fast/app/services/portfolio/portfolio_simulation_engine.py | pending_initial_keys 기반으로 초기 매수 재시도 로직 적용 |
| backtest_be_fast/app/services/portfolio/portfolio_dca_manager.py | 초기 매수에 pending_keys 지원 및 DCA 초회 매수를 executed_count에 반영 |
| backtest_be_fast/app/services/portfolio_manager_service.py | DCA 납입 횟수/총투자금 계산을 generate_periodic_schedule 기반으로 변경 |
| backtest_be_fast/app/domain/portfolio_domain.py | PortfolioState.pending_initial_keys 상태 필드 추가 |
Comment on lines
+84
to
87
| # 초회 매수도 납입 1회로 계상한다. dca_periods가 "총 납입 횟수" | ||
| # (초회 포함)이므로, 이렇게 해야 정기 매수가 남은 횟수만 집행한다. | ||
| info.executed_count = 1 | ||
| logger.info(f"{current_date.date()}: {unique_key} DCA 첫 투자 (금액: ${monthly_amount:,.2f}, interval_weeks: {info.dca_frequency})") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
배치 2에서 남겨 둔 마지막 P1 두 건입니다. 둘 다 사용자에게 잘못된 수익률이 표시되던 문제입니다.
DCA 납입 횟수와 실제 매수 일정의 불일치
수익률의 분모가 되는 총 투자금을
월 = 30일근사로 계산했습니다.그런데 실제 매수는 Nth-weekday 달력을 따르므로 두 값이 어긋났고, 집행되지 않은 납입금이 그대로 손실로 보고됐습니다. 가격이 전혀 움직이지 않고 수수료도 0인데 마이너스 수익률이 나오는 상태였습니다.
monthly_1)monthly_3)weekly_1)시뮬레이션과 동일한 규칙으로 실행 예정일을 생성하는
generate_periodic_schedule을 추가하고, 납입 횟수를 "초회 1회 + 예정일 수"로 계산하도록 바꿨습니다. 초회 매수도executed_count에 계상해 정기 매수가 남은 횟수만 집행합니다.첫날 가격이 없는 종목의 초기 매수 누락
execute_initial_purchases가 가격이 없는 종목을continue로 건너뛰는데is_first_day는 한 번만 참이라, 재시도가 이뤄지지 않았습니다. 한국·미국 혼합 포트폴리오에서 한쪽 시장이 휴장인 날 시작하면(예: 2024-07-04 — KRX 개장, NYSE 휴장) 그 종목의 포지션이 끝까지 열리지 않고 투자금만 분모에 남습니다. 2종목 중 1종목이 누락되면 −50% 로 보고됩니다.초기 매수 대상을
PortfolioState.pending_initial_keys로 추적해, 각 종목이 처음 가격을 갖는 날 매수하고 성공한 종목만 집합에서 제거하도록 했습니다.검증
TDD로 진행했으며, 각 케이스에서 예상한 틀린 값(−7.69% / −20% / −50%)이 먼저 재현되는 것을 확인한 뒤 수정했습니다.
docker build --target test재현 통과작업 중 정기 매수 로직에도 두 가지 변경(예정일 경과 시 재시도, 일정 드리프트 방지)을 시도했으나, 어떤 실패 테스트도 이를 요구하지 않는 것을 확인하고 되돌렸습니다.
_pre_calculate_prices가 forward fill을 하기 때문에 첫 관측 이후에는 가격이 항상 존재하고, 데이터 공백 구간은 시뮬레이션 날짜 범위에서 아예 빠지므로 회차 유실이 발생하지 않습니다. 검증되지 않은 동작 변경은 남기지 않았습니다. 대신 그 시나리오(예정일이 데이터 공백에 걸리는 경우)를 회귀 테스트로 고정했습니다.🤖 Generated with Claude Code