Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0fe61e1
[BE] SISC1-56 [FEAT] 백테스트 실행 로직 구현
discipline24 Oct 29, 2025
7df6f56
[BE] SISC1-56 [FEAT] 외부 DB의 주식 정보 이용을 위한 멀티 데이터소스 구현
discipline24 Oct 29, 2025
2652c9b
[BE] SISC1-56 [FIX] @AuthenticationPrincipal 작동 안되는 문제 해결
discipline24 Oct 29, 2025
368f2cc
[BE] SISC1-56 [FEAT] 주식 데이터 관련 간단한 테스트용 컨트롤러 추가
discipline24 Nov 1, 2025
45fa9d3
[BE] SISC1-56 [FEAT] 주식 데이터 관련 멀티 데이터소스 설정 추가
discipline24 Nov 1, 2025
ec45228
[BE] SISC1-56 [DOCS] 백테스트, 템플릿, 포인트 관련 스웨거 요청 JSON 예시, 컨트롤러 설명 등 추가
discipline24 Nov 1, 2025
b623024
[BE] SISC1-56 [REFACTOR] 템플릿 상세 조회 시, 해당 템플릿에 속한 백테스트 리스트 반환 추가
discipline24 Nov 1, 2025
5a7a15c
Merge branch 'main' into SISC1-56-BE-백테스팅-실행-API-구현
discipline24 Nov 1, 2025
70c2b70
[BE] SISC1-56 [FIX] 테스트 전용 컨트롤러 삭제 및 스웨거 관련 주석 수정
discipline24 Nov 2, 2025
49d5ff1
Update .gitignore
discipline24 Nov 2, 2025
42634fe
[BE] SISC1-56 [FIX] 리포지토리 @Param 추가
discipline24 Nov 2, 2025
8bf8c52
[BE] SISC1-56 [FIX] ByteBuddy 직렬화 오류 수정
discipline24 Nov 5, 2025
0b13e1e
[BE] SISC1-56 [FIX] ByteBuddy 직렬화 오류 수정
discipline24 Nov 6, 2025
20476eb
Merge branch 'SISC1-56-BE-백테스팅-실행-API-구현' of https://github.com/SISC-…
discipline24 Nov 6, 2025
b7641f2
conflict 해결
discipline24 Nov 6, 2025
043c54c
Merge branch 'main' into SISC1-56-BE-백테스팅-실행-API-구현
discipline24 Nov 11, 2025
744cdb2
[BE] SISC1-56 [FEAT] 백테스팅 실행 API 구현
discipline24 Nov 13, 2025
3afe57f
Merge branch 'main' into SISC1-56-BE-백테스팅-실행-API-구현
discipline24 Nov 13, 2025
6bb3d3b
[BE] SISC1-56 [FEAT] 토끼 오류 반영
discipline24 Nov 13, 2025
5211c44
[BE] SISC1-56 [FEAT] 토끼 오류 반영
discipline24 Nov 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public class BacktestRequest {
@Schema(description = "백테스트 실행 요청 JSON")
private BacktestRunRequest strategy;

@Schema(description = "기본 청산 기간")
private int defaultExitDays;


// 백테스트 리스트 삭제
@Schema(description = "삭제할 백테스트 실행 리스트")
private List<Long> backtestRunIds;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
package org.sejongisc.backend.backtest.dto;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
import org.sejongisc.backend.backtest.entity.BacktestRun;
import org.sejongisc.backend.backtest.entity.BacktestRunMetrics;
import org.sejongisc.backend.backtest.entity.BacktestStatus;
import org.sejongisc.backend.template.entity.Template;
import org.sejongisc.backend.user.entity.User;

import java.time.LocalDate;

Expand All @@ -21,13 +16,6 @@
@AllArgsConstructor
@NoArgsConstructor
public class BacktestResponse {
private Long id;
private Template template;
private String title;
private BacktestStatus status;
private String paramsJson;
private LocalDate startDate;
private LocalDate endDate;

private BacktestRunMetrics backtestRunMetrics;
private BacktestRun backtestRun;
private BacktestRunMetricsResponse backtestRunMetricsResponse;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.sejongisc.backend.backtest.dto;


import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.sejongisc.backend.backtest.entity.BacktestRunMetrics;

import java.math.BigDecimal;


@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class BacktestRunMetricsResponse {
private Long id;
private BigDecimal totalReturn; // 총 수익률
private BigDecimal maxDrawdown; // 최대 낙폭
private BigDecimal sharpeRatio; // 샤프 지수
private BigDecimal avgHoldDays; // 평균 보유 기간
private int tradesCount; // 총 거래 횟수

public static BacktestRunMetricsResponse fromEntity(BacktestRunMetrics backtestRunMetrics) {
return BacktestRunMetricsResponse.builder()
.id(backtestRunMetrics.getId())
.totalReturn(backtestRunMetrics.getTotalReturn())
.maxDrawdown(backtestRunMetrics.getMaxDrawdown())
.sharpeRatio(backtestRunMetrics.getSharpeRatio())
.avgHoldDays(backtestRunMetrics.getAvgHoldDays())
.tradesCount(backtestRunMetrics.getTradesCount())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.math.BigDecimal;
import java.util.Map;
Expand All @@ -14,7 +15,7 @@
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Setter
public class StrategyOperand {

@Schema(description = "항의 타입: \"indicator\", \"price\", \"const\"")
Expand All @@ -34,4 +35,6 @@ public class StrategyOperand {

@Schema(description = "지표의 파라미터 맵 (예: {\"length\": 20})")
private Map<String, Object> params;

//private String transform; // 거래량 관련 필드, 추후 적용 고려
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.sejongisc.backend.backtest.dto;

import java.math.BigDecimal;
import java.time.LocalDateTime;

public class TradeLog {
public enum Type { BUY, SELL }
public final Type type;
public final LocalDateTime time;
public final BigDecimal price;
public final BigDecimal shares;

public TradeLog(Type type, LocalDateTime time, BigDecimal price, BigDecimal shares) {
this.type = type;
this.time = time;
this.price = price;
this.shares = shares;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class BacktestRunMetrics {
private BigDecimal totalReturn; // 총 수익률

@Column(nullable = false, precision = 12, scale = 6)
private BigDecimal maxDrawdown; // 최대 낙폭
private BigDecimal maxDrawdown; // 최대 낙폭, 퍼센티지로 계산됨

@Column(nullable = false, precision = 12, scale = 6)
private BigDecimal sharpeRatio; // 샤프 지수
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

@Repository
Expand All @@ -16,4 +17,10 @@ public interface BacktestRunRepository extends JpaRepository<BacktestRun, Long>
"WHERE t.templateId = :templateTemplateId " +
"ORDER BY br.startedAt DESC")
List<BacktestRun> findByTemplate_TemplateIdWithTemplate(@Param("templateTemplateId") UUID templateTemplateId);

@Query("SELECT br FROM BacktestRun br " +
"LEFT JOIN FETCH br.template t " + // template은 없을 수 있기에 left join
"JOIN FETCH br.user u " +
"WHERE br.id = :backtestRunId ")
Optional<BacktestRun> findByIdWithMember(@Param("backtestRunId") Long backtestRunId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import lombok.extern.slf4j.Slf4j;
import org.sejongisc.backend.backtest.dto.BacktestRequest;
import org.sejongisc.backend.backtest.dto.BacktestResponse;
import org.sejongisc.backend.backtest.dto.BacktestRunMetricsResponse;
import org.sejongisc.backend.backtest.entity.BacktestRun;
import org.sejongisc.backend.backtest.entity.BacktestRunMetrics;
import org.sejongisc.backend.backtest.entity.BacktestStatus;
Expand All @@ -15,12 +16,11 @@
import org.sejongisc.backend.common.exception.ErrorCode;
import org.sejongisc.backend.template.entity.Template;
import org.sejongisc.backend.template.repository.TemplateRepository;
import org.sejongisc.backend.user.dao.UserRepository;
import org.sejongisc.backend.user.entity.User;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;

Expand All @@ -33,36 +33,31 @@ public class BacktestService {
private final TemplateRepository templateRepository;
private final BacktestingEngine backtestingEngine;
private final ObjectMapper objectMapper;
private final EntityManager em;
private final UserRepository userRepository;

public BacktestResponse getBacktestStatus(Long backtestRunId, UUID userId) {
// TODO : 백테스트 상태 조회 로직 구현 (진행 중, 완료, 실패 등)
log.info("백테스팅 실행 상태 조회를 시작합니다.");
BacktestRun backtestRun = findBacktestRunByIdAndVerifyUser(backtestRunId, userId);
return BacktestResponse.builder()
.id(backtestRun.getId())
.paramsJson(backtestRun.getParamsJson())
.title(backtestRun.getTitle())
.status(backtestRun.getStatus())
.startDate(backtestRun.getStartDate())
.endDate(backtestRun.getEndDate())
.template(backtestRun.getTemplate())
.backtestRun(backtestRun)
.build();
}
@Transactional
public BacktestResponse getBackTestDetails(Long backtestRunId, UUID userId) {
BacktestRunMetrics backtestRunMetrics = backtestRunMetricsRepository.findByBacktestRunId(backtestRunId)
.orElse(null);
BacktestRun backtestRun = findBacktestRunByIdAndVerifyUser(backtestRunId, userId);

if (backtestRun.getStatus() != BacktestStatus.COMPLETED) {
return BacktestResponse.builder()
.backtestRun(backtestRun)
.build();
}

BacktestRunMetrics backtestRunMetrics = backtestRunMetricsRepository.findByBacktestRunId(backtestRunId)
.orElseThrow(() -> new CustomException(ErrorCode.BACKTEST_METRICS_NOT_FOUND));

return BacktestResponse.builder()
.id(backtestRun.getId())
.paramsJson(backtestRun.getParamsJson())
.title(backtestRun.getTitle())
.status(backtestRun.getStatus())
.startDate(backtestRun.getStartDate())
.endDate(backtestRun.getEndDate())
.template(backtestRun.getTemplate())
.backtestRunMetrics(backtestRunMetrics)
.backtestRun(backtestRun)
.backtestRunMetricsResponse(BacktestRunMetricsResponse.fromEntity(backtestRunMetrics))
.build();
}

Expand All @@ -81,11 +76,11 @@ public void addBacktestTemplate(BacktestRequest request) {
}

public BacktestResponse runBacktest(BacktestRequest request) {
User userRef = em.getReference(User.class, request.getUserId());

Template templateRef = null;
User user = userRepository.findById(request.getUserId())
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
Template template = null;
if (request.getTemplateId() != null)
templateRef = em.getReference(Template.class, request.getTemplateId());
template = findTemplateByIdAndVerifyUser(request.getTemplateId(), request.getUserId());

String paramsJson;
try {
Expand All @@ -97,8 +92,8 @@ public BacktestResponse runBacktest(BacktestRequest request) {

// BacktestRun 엔티티를 "PENDING" 상태로 생성
BacktestRun backtestRun = BacktestRun.builder()
.user(userRef)
.template(templateRef)
.user(user)
.template(template)
.title(request.getTitle())
.paramsJson(paramsJson)
.startDate(request.getStartDate())
Expand All @@ -114,13 +109,7 @@ public BacktestResponse runBacktest(BacktestRequest request) {

// 사용자에게 실행 중 응답 반환
return BacktestResponse.builder()
.id(savedRun.getId())
.paramsJson(savedRun.getParamsJson())
.title(savedRun.getTitle())
.status(savedRun.getStatus())
.startDate(savedRun.getStartDate())
.endDate(savedRun.getEndDate())
.template(templateRef)
.backtestRun(savedRun)
.build();
}

Expand Down Expand Up @@ -159,7 +148,7 @@ private Template findTemplateByIdAndVerifyUser(UUID templateId, UUID userId) {
}

private BacktestRun findBacktestRunByIdAndVerifyUser(Long backtestRunId, UUID userId) {
BacktestRun backtestRun = backtestRunRepository.findById(backtestRunId)
BacktestRun backtestRun = backtestRunRepository.findByIdWithMember(backtestRunId)
.orElseThrow(() -> new CustomException(ErrorCode.BACKTEST_NOT_FOUND));

if (!backtestRun.getUser().getUserId().equals(userId)) {
Expand Down
Loading