Skip to content

fix(be): DCA 납입 횟수 정합성 및 첫날 미매수 자산 수정 - #52

Merged
kyj0503 merged 3 commits into
mainfrom
fix/audit-dca-schedule
Aug 2, 2026
Merged

fix(be): DCA 납입 횟수 정합성 및 첫날 미매수 자산 수정#52
kyj0503 merged 3 commits into
mainfrom
fix/audit-dca-schedule

Conversation

@kyj0503

@kyj0503 kyj0503 commented Aug 2, 2026

Copy link
Copy Markdown
Owner

배치 2에서 남겨 둔 마지막 P1 두 건입니다. 둘 다 사용자에게 잘못된 수익률이 표시되던 문제입니다.

DCA 납입 횟수와 실제 매수 일정의 불일치

수익률의 분모가 되는 총 투자금을 월 = 30일 근사로 계산했습니다.

approx_days_per_period = interval * 30   # 월 평균 30일
dca_periods = max(1, (backtest_days // approx_days_per_period) + 1)
total_investment = per_period_amount * dca_periods

그런데 실제 매수는 Nth-weekday 달력을 따르므로 두 값이 어긋났고, 집행되지 않은 납입금이 그대로 손실로 보고됐습니다. 가격이 전혀 움직이지 않고 수수료도 0인데 마이너스 수익률이 나오는 상태였습니다.

조건 (2024년 전체, 고정가 $100, 수수료 0) 계획 실제 보고된 수익률
월간 (monthly_1) 13회 12회 −7.69%
분기 (monthly_3) 5회 4회 −20%
주간 (weekly_1) 53회 53회 0% (근사가 우연히 일치)

시뮬레이션과 동일한 규칙으로 실행 예정일을 생성하는 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%)이 먼저 재현되는 것을 확인한 뒤 수정했습니다.

항목 결과
BE 단위 테스트 166 → 180 통과 (신규 14)
BE 통합 테스트 10 통과
CI 품질 게이트 docker build --target test 재현 통과

작업 중 정기 매수 로직에도 두 가지 변경(예정일 경과 시 재시도, 일정 드리프트 방지)을 시도했으나, 어떤 실패 테스트도 이를 요구하지 않는 것을 확인하고 되돌렸습니다. _pre_calculate_prices가 forward fill을 하기 때문에 첫 관측 이후에는 가격이 항상 존재하고, 데이터 공백 구간은 시뮬레이션 날짜 범위에서 아예 빠지므로 회차 유실이 발생하지 않습니다. 검증되지 않은 동작 변경은 남기지 않았습니다. 대신 그 시나리오(예정일이 데이터 공백에 걸리는 경우)를 회귀 테스트로 고정했습니다.

🤖 Generated with Claude Code

kyj0503 and others added 3 commits August 2, 2026 22:41
수익률의 분모가 되는 총 투자금을 "월 = 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>
Copilot AI review requested due to automatic review settings August 2, 2026 13:42
@kyj0503
kyj0503 merged commit c2af684 into main Aug 2, 2026
1 check passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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})")
@kyj0503
kyj0503 deleted the fix/audit-dca-schedule branch August 2, 2026 13:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants