Skip to content

Sisc1 56 be 백테스팅 실행 api 구현#125

Merged
discipline24 merged 27 commits intomainfrom
SISC1-56-BE-백테스팅-실행-API-구현
Nov 20, 2025

Hidden character warning

The head ref may contain hidden characters: "SISC1-56-BE-\ubc31\ud14c\uc2a4\ud305-\uc2e4\ud589-API-\uad6c\ud604"
Merged

Sisc1 56 be 백테스팅 실행 api 구현#125
discipline24 merged 27 commits intomainfrom
SISC1-56-BE-백테스팅-실행-API-구현

Conversation

@discipline24
Copy link
Contributor

@discipline24 discipline24 commented Nov 20, 2025

Summary by CodeRabbit

  • New Features

    • 백테스트 가능 주식 정보 조회용 API 추가
    • 백테스트 응답에 사용 가능한 주식 목록 포함
    • 기본 청산 기간(defaultExitDays)을 활용한 자동 포지션 정리 및 강제 청산 로직 추가
  • Refactor

    • 백테스트 엔진 성과 지표 계산 개선 (샤프지수, 평균 보유일 등)
    • 강제 청산 거래 기록 추가 및 거래 처리 로직 정교화
    • 시계열 처리 시 시간대 일관성 개선 및 데이터 로딩/처리 흐름 정리

✏️ Tip: You can customize this high-level summary in your review settings.

customUserDetails에 user 추가 후, 반환시 해당 객체 넣음으로써 해결
userRepository 이용하여 DB 접근으로 해결
# Conflicts:
#	.gitignore
#	backend/src/main/java/org/sejongisc/backend/backtest/repository/BacktestRunRepository.java
#	backend/src/main/java/org/sejongisc/backend/template/service/TemplateService.java
#	backend/src/main/java/org/sejongisc/backend/user/entity/User.java
# Conflicts:
#	backend/src/main/java/org/sejongisc/backend/backtest/service/BacktestingEngine.java
#	backend/src/main/java/org/sejongisc/backend/backtest/service/Ta4jHelperService.java
@coderabbitai
Copy link

coderabbitai bot commented Nov 20, 2025

Walkthrough

백테스트 API와 엔진에 기능 추가 및 리팩토링이 이루어졌습니다: 티커 목록 조회 엔드포인트 추가, DTO 필드 재배치(기본 청산 기간 이동), 강제 청산 로직 및 비율 기반 매매·메트릭(Sharpe, 평균 보유일) 계산이 엔진에 도입되었습니다.

Changes

Cohort / File(s) 변경 요약
컨트롤러
backend/src/main/java/org/sejongisc/backend/backtest/controller/BacktestController.java
/api/backtest/stocks/info GET 엔드포인트 추가하여 BacktestResponse 반환
서비스 (Backtest)
backend/src/main/java/org/sejongisc/backend/backtest/service/BacktestService.java
PriceDataRepository 주입 및 getBacktestStockInfo() 추가; 백테스트 실행 시 execute(savedRun)로 BacktestingEngine 호출 변경
백테스팅 엔진
backend/src/main/java/org/sejongisc/backend/backtest/service/BacktestingEngine.java
execute(Long)execute(BacktestRun) 시그니처 변경; RUNNING 상태/startedAt 설정; 기본 청산 기간(defaultExitDays) 기반 강제 매도, 비율 기반 매수·매도 로직 도입; 일별 포트폴리오·낙폭 추적; Sharpe·평균 보유일 계산 추가; 보조 헬퍼(convertPercentToRatio) 추가
DTOs 및 열거형
backend/src/main/java/org/sejongisc/backend/backtest/dto/BacktestRequest.java,
backend/src/main/java/org/sejongisc/backend/backtest/dto/BacktestRunRequest.java,
backend/src/main/java/org/sejongisc/backend/backtest/dto/BacktestResponse.java,
backend/src/main/java/org/sejongisc/backend/backtest/dto/TradeLog.java
BacktestRequest에서 defaultExitDays 제거; BacktestRunRequestdefaultExitDays 추가(스키마 설명 포함); BacktestResponseavailableTickers 추가; TradeLog.TypeSELL_FORCED 열거값 추가
메트릭 엔티티
backend/src/main/java/org/sejongisc/backend/backtest/entity/BacktestRunMetrics.java
fromDto(...) 정적 팩토리 메서드 추가 (빌더 사용)
Ta4j 헬퍼
backend/src/main/java/org/sejongisc/backend/backtest/service/Ta4jHelperService.java
BarSeries 생성 시 getFirst() 사용, Asia/Seoul 타임존 적용; 빈 시리즈에 대한 예외 가드 제거(빈 입력 처리 흐름 변경)
저장소
backend/src/main/java/org/sejongisc/backend/stock/repository/PriceDataRepository.java
List<String> findDistinctTickers() JPQL 쿼리 메서드 추가 (고유 티커 조회)

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller as BacktestController
    participant Service as BacktestService
    participant Repo as PriceDataRepository
    participant Engine as BacktestingEngine
    participant RunRepo as BacktestRunRepository

    Client->>Controller: GET /api/backtest/stocks/info
    Controller->>Service: getBacktestStockInfo()
    Service->>Repo: findDistinctTickers()
    Repo-->>Service: List<String> (티커)
    Service-->>Controller: BacktestResponse(availableTickers)
    Controller-->>Client: 200 OK

    Note over Client,RunRepo: 백테스트 실행 흐름 (요약)
    Client->>Controller: POST /api/backtest/run (BacktestRunRequest)
    Controller->>Service: runBacktest(request)
    Service->>RunRepo: save(BacktestRun)
    RunRepo-->>Service: saved BacktestRun
    Service->>Engine: execute(backtestRun)

    rect rgb(220,245,230)
        Engine->>Engine: set RUNNING, startedAt
        Engine->>Engine: params <- backtestRun.getParamsJson()
        Engine->>Engine: load price data
        Engine->>Engine: create BarSeries (Ta4j)
        loop for each bar
            Engine->>Engine: evaluate buyRule / sellRule
            Engine->>Engine: if held > defaultExitDays -> SELL_FORCED
            Engine->>Engine: apply buyRatio / sellRatio trades, update cash/shares
            Engine->>Engine: compute daily portfolio value, update drawdown
        end
        Engine->>Engine: calculate Sharpe, avgHoldDays, metrics
    end

    Engine->>RunRepo: save(metrics, set COMPLETED/FAILED, finishedAt)
    Engine-->>Service: execution finished
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40-70 minutes

  • 집중 검토 필요 항목:
    • BacktestingEngine의 대규모 로직 변경 (거래 비율, 강제 청산, 상태 전이)
    • Sharpe 비율·평균 보유일 계산의 수학적 정확성 및 경계 사례
    • execute 시그니처 변경에 따른 호출 일관성(다른 호출 지점 존재 여부)
    • Ta4jHelperService에서 빈 시리즈 처리 변경으로 인한 NPE/엣지 케이스

Possibly related PRs

Suggested reviewers

  • ochanhyeok
  • Kosw6

Poem

🐰 토끼가 쓴 축하 시
기본 청산 날 수를 새로 두고,
티커 목록 꺼내 보여주네 🥕
강제 매도도 알고, Sharpe로 성과 재고,
평균 보유일도 쓱쓱 계산했어—뛰어라, 작은 발! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 변경 사항의 주요 내용을 명확하게 설명하고 있습니다. 백테스팅 실행 API 구현이라는 구체적인 목표를 직접적으로 반영하고 있습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch SISC1-56-BE-백테스팅-실행-API-구현

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/src/main/java/org/sejongisc/backend/backtest/service/BacktestingEngine.java (1)

222-242: SELL_FORCED 타입도 평균 보유 기간 계산에 포함해야 합니다.

Line 230에서 TradeLog.Type.SELL만 처리하고 있는데, SELL_FORCED도 매도 이벤트이므로 평균 보유 기간 계산에 포함되어야 합니다.

   for (TradeLog log : tradeLogs) {
     if (log.type == TradeLog.Type.BUY) {
       currentBuyTime = log.time;
-    } else if (log.type == TradeLog.Type.SELL && currentBuyTime != null) {
+    } else if ((log.type == TradeLog.Type.SELL || log.type == TradeLog.Type.SELL_FORCED) && currentBuyTime != null) {
       long days = java.time.temporal.ChronoUnit.DAYS.between(currentBuyTime.toLocalDate(), log.time.toLocalDate());
       holdDurations.add(days);
       currentBuyTime = null;
     }
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd56304 and 46dbcae.

📒 Files selected for processing (10)
  • backend/src/main/java/org/sejongisc/backend/backtest/controller/BacktestController.java (2 hunks)
  • backend/src/main/java/org/sejongisc/backend/backtest/dto/BacktestRequest.java (0 hunks)
  • backend/src/main/java/org/sejongisc/backend/backtest/dto/BacktestResponse.java (2 hunks)
  • backend/src/main/java/org/sejongisc/backend/backtest/dto/BacktestRunRequest.java (2 hunks)
  • backend/src/main/java/org/sejongisc/backend/backtest/dto/TradeLog.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/backtest/entity/BacktestRunMetrics.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/backtest/service/BacktestService.java (3 hunks)
  • backend/src/main/java/org/sejongisc/backend/backtest/service/BacktestingEngine.java (5 hunks)
  • backend/src/main/java/org/sejongisc/backend/backtest/service/Ta4jHelperService.java (3 hunks)
  • backend/src/main/java/org/sejongisc/backend/stock/repository/PriceDataRepository.java (2 hunks)
💤 Files with no reviewable changes (1)
  • backend/src/main/java/org/sejongisc/backend/backtest/dto/BacktestRequest.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-19T05:02:44.318Z
Learnt from: daye200
Repo: SISC-IT/sisc-web PR: 120
File: backend/src/main/java/org/sejongisc/backend/stock/repository/ExecutionRepository.java:11-12
Timestamp: 2025-11-19T05:02:44.318Z
Learning: Spring Data JPA supports Java record-based DTO projections in derived query methods (e.g., findAllByOrderByFillDateDesc() returning List<TradeLogDto>) without requiring explicit Query annotations, as long as the record's constructor parameters match the entity's field names.

Applied to files:

  • backend/src/main/java/org/sejongisc/backend/stock/repository/PriceDataRepository.java
🧬 Code graph analysis (1)
backend/src/main/java/org/sejongisc/backend/backtest/service/BacktestingEngine.java (1)
backend/src/main/java/org/sejongisc/backend/backtest/dto/TradeLog.java (1)
  • TradeLog (6-23)
🔇 Additional comments (14)
backend/src/main/java/org/sejongisc/backend/backtest/service/Ta4jHelperService.java (2)

39-39: 타임존 명시화가 잘 되었습니다.

시스템 기본 타임존 대신 Asia/Seoul을 명시적으로 사용하여 서버 환경에 관계없이 일관된 동작을 보장합니다. 한국 주식 시장 데이터를 다루는 경우 적절한 변경입니다.


34-36: 원본 리뷰 의견은 부정확합니다.

createBarSeries 메서드를 호출하는 BacktestingEngine.java 라인 67-69에서 이미 빈 리스트 검증이 이루어지고 있습니다. 호출 전에 priceDataList.isEmpty()를 확인하여 비어있으면 CustomException(ErrorCode.PRICE_DATA_NOT_FOUND)을 발생시키므로, 빈 리스트가 createBarSeries에 도달하지 않습니다. 따라서 getFirst()에서 NoSuchElementException이 발생할 위험은 없습니다.

Likely an incorrect or invalid review comment.

backend/src/main/java/org/sejongisc/backend/backtest/dto/TradeLog.java (1)

7-11: LGTM! 강제 청산 타입이 명확하게 정의되었습니다.

SELL_FORCED 열거형 값 추가로 기본 청산 기간에 의한 강제 매도를 일반 매도와 구분할 수 있게 되었습니다. BacktestingEngine의 강제 청산 로직과 잘 통합됩니다.

backend/src/main/java/org/sejongisc/backend/backtest/dto/BacktestRunRequest.java (1)

39-42: 주석 처리된 buyRatio, sellRatio 필드의 처리 계획을 확인하세요.

BacktestingEngine(line 90-91)에서 이 값들이 하드코딩(10, 100)되어 있습니다. 향후 DTO에서 받을 계획이라면 TODO를 명확히 하고, 그렇지 않다면 주석을 제거하는 것이 좋습니다.

backend/src/main/java/org/sejongisc/backend/backtest/service/BacktestingEngine.java (2)

46-47: LGTM! 메서드 시그니처 개선이 합리적입니다.

BacktestRun 객체를 직접 받도록 변경하여 불필요한 데이터베이스 조회를 제거했습니다. 이는 성능과 코드 명확성 측면에서 개선입니다.


247-256: 유효성 검증 로직이 잘 구현되었습니다.

퍼센티지를 비율로 변환하는 헬퍼 함수가 null 체크와 범위 검증을 포함하여 안전하게 구현되었습니다.

backend/src/main/java/org/sejongisc/backend/backtest/dto/BacktestResponse.java (1)

12-12: LGTM! 백테스트 주식 정보 제공을 위한 필드가 추가되었습니다.

availableTickers 필드 추가로 백테스트에 사용 가능한 종목 목록을 클라이언트에 제공할 수 있게 되었습니다.

Also applies to: 22-22

backend/src/main/java/org/sejongisc/backend/stock/repository/PriceDataRepository.java (1)

18-22: LGTM! JPQL 쿼리가 올바르게 구현되었습니다.

DISTINCT를 사용하여 중복 없이 모든 티커를 조회하는 쿼리가 명확하게 구현되었습니다. JavaDoc 문서화도 적절합니다.

Based on learnings

backend/src/main/java/org/sejongisc/backend/backtest/controller/BacktestController.java (2)

30-37: 엔드포인트 인증 요구사항을 확인하세요.

getBacktestStockInfo 엔드포인트가 인증 없이 호출 가능하도록 구현되어 있습니다. 다른 엔드포인트들은 @AuthenticationPrincipal을 사용하는데, 이 엔드포인트는 공개 API로 의도된 것인지 확인이 필요합니다.

만약 인증이 필요하다면 다음과 같이 수정하세요:

-public ResponseEntity<BacktestResponse> getBacktestStockInfo() {
+public ResponseEntity<BacktestResponse> getBacktestStockInfo(@AuthenticationPrincipal CustomUserDetails customUserDetails) {
   return ResponseEntity.ok(backtestService.getBacktestStockInfo());
 }

85-85: LGTM! 예제에 defaultExitDays가 추가되었습니다.

Swagger 예제에 새로운 defaultExitDays 필드가 포함되어 API 사용자가 이 파라미터의 사용법을 명확히 이해할 수 있습니다.

backend/src/main/java/org/sejongisc/backend/backtest/entity/BacktestRunMetrics.java (1)

40-54: LGTM! 정적 팩토리 메서드가 잘 구현되었습니다.

fromDto 팩토리 메서드를 사용하여 메트릭 객체 생성 로직을 캡슐화했습니다. 빌더 패턴과 결합하여 가독성과 유지보수성이 향상되었습니다.

backend/src/main/java/org/sejongisc/backend/backtest/service/BacktestService.java (3)

38-38: LGTM! 새로운 의존성이 적절히 추가되었습니다.

PriceDataRepository 의존성을 추가하여 주식 정보 조회 기능을 지원합니다.


40-46: LGTM! 주식 정보 조회 메서드가 간결하게 구현되었습니다.

빌더 패턴을 사용하여 사용 가능한 티커 목록을 담은 응답 객체를 생성합니다. @Transactional 어노테이션도 적절히 적용되었습니다.


119-119: LGTM! BacktestingEngine 호출이 개선되었습니다.

ID 대신 BacktestRun 객체를 직접 전달하도록 변경하여 불필요한 데이터베이스 조회를 제거했습니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/src/main/java/org/sejongisc/backend/backtest/service/BacktestingEngine.java (1)

222-242: 강제 매도(SELL_FORCED)가 평균 보유 기간 계산에서 누락되었습니다.

Line 230에서 TradeLog.Type.SELL만 확인하고 있어, 기본 청산 기간에 의한 강제 매도(SELL_FORCED)가 평균 보유 기간 계산에 포함되지 않습니다. 이는 계산 결과를 부정확하게 만듭니다.

-            } else if (log.type == TradeLog.Type.SELL && currentBuyTime != null) {
+            } else if ((log.type == TradeLog.Type.SELL || log.type == TradeLog.Type.SELL_FORCED) && currentBuyTime != null) {
                 long days = java.time.temporal.ChronoUnit.DAYS.between(currentBuyTime.toLocalDate(), log.time.toLocalDate());
                 holdDurations.add(days);
                 currentBuyTime = null;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46dbcae and 48ee07b.

📒 Files selected for processing (1)
  • backend/src/main/java/org/sejongisc/backend/backtest/service/BacktestingEngine.java (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
backend/src/main/java/org/sejongisc/backend/backtest/service/BacktestingEngine.java (1)
backend/src/main/java/org/sejongisc/backend/backtest/dto/TradeLog.java (1)
  • TradeLog (6-23)
🔇 Additional comments (7)
backend/src/main/java/org/sejongisc/backend/backtest/service/BacktestingEngine.java (7)

46-69: 메서드 시그니처 변경 및 초기 설정이 잘 구현되었습니다.

BacktestRun 객체를 직접 받아 불필요한 repository 조회를 제거한 점이 좋습니다. 상태 전환과 가격 데이터 검증 로직도 적절합니다.


71-93: Ta4j 설정 및 변수 초기화가 적절합니다.

BarSeries 생성, 룰 빌딩, 시뮬레이션 변수 초기화가 잘 구현되었습니다. 기본 청산 기간을 위한 buyBarIndexdefaultExitDays 추가도 합리적입니다.

참고: Lines 90-91의 TODO 주석은 이전 리뷰에서 이미 지적되었습니다.


95-109: 메인 루프 및 기본 청산 로직이 올바르게 구현되었습니다.

기본 청산 기간 조건을 확인하는 로직이 적절하며, buyBarIndex를 사용하여 매수 시점부터의 경과 일수를 추적하는 방식이 합리적입니다.


111-127: 매수 로직이 적절하게 구현되었습니다.

매수 비중 적용, 거래 가능 주식 수 계산, 거래 로그 기록, 자산 업데이트가 올바르게 처리되었습니다. 첫 매수 시에만 buyBarIndex를 설정하는 로직도 적절합니다.

참고: Line 114의 currentClosePrice로 나누기 연산에 대한 0 체크는 이전 리뷰에서 이미 지적되었습니다.


143-174: 일일 지표 계산 및 예외 처리가 적절합니다.

포트폴리오 가치 및 수익률 계산, MDD 업데이트, 최종 지표 저장, 예외 처리가 모두 올바르게 구현되었습니다.


247-256: 퍼센트를 비율로 변환하는 헬퍼 메서드가 잘 구현되었습니다.

입력 검증과 기본값 처리, 변환 로직이 모두 적절하게 구현되었습니다.


180-220: 검토 의견이 부정확합니다.

프로젝트의 backend/build.gradle 파일에서 languageVersion = JavaLanguageVersion.of(21)로 명시적으로 설정되어 있습니다. Java 21은 List.getLast() 메서드를 지원하므로, 라인 184의 코드는 완전히 호환성 있으며 올바릅니다. 수정이 필요 없습니다.

Likely an incorrect or invalid review comment.

@discipline24 discipline24 merged commit 27f1c1a into main Nov 20, 2025
1 check passed
@discipline24 discipline24 deleted the SISC1-56-BE-백테스팅-실행-API-구현 branch November 20, 2025 11:41
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.

1 participant