Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
5821bdb
FEAT(#129): 모의 투자 현황, 종목 매수 API 개발
alkwen0996 Jul 27, 2025
0966ebd
Merge branch 'main' into experiment-feat
alkwen0996 Jul 27, 2025
d381084
REFACTOR(#129): 모의 투자 현황, 종목 매수 API 유저 정보 조회 로직 수정
alkwen0996 Jul 27, 2025
3c056bf
REFACTOR(#129): 모의 투자 자동 매도 기능 구현
alkwen0996 Jul 27, 2025
c3b0681
REFACTOR(#129): 모의 투자 자동 매도 기능 roi 관련 로직 추가
alkwen0996 Jul 27, 2025
c4f9c1c
REFACTOR(#129): 주식 종목 정보 조회 주석 작성
alkwen0996 Jul 27, 2025
bcc88b4
REFACTOR(#129): 모의 투자 현황 실험 정보 관련 로직 수정
alkwen0996 Jul 27, 2025
85e0178
REFACTOR(#129): 관심 종목 검색, 관심 종목 검색어 자동완성 API 구현
alkwen0996 Jul 27, 2025
2bcdf6d
REFACTOR(#129): 모의 투자 모의 매수 API 종가 결정 및 동일 일자 종목 매수 예외처리 로직 추가
alkwen0996 Jul 29, 2025
5d93e47
Merge remote-tracking branch 'origin/experiment-feat' into notificati…
MuuiGong Sep 7, 2025
d36b263
FIX(#129): 비동기 요청 처리 시 SecurityContext 유지되도록 변경
MuuiGong Sep 7, 2025
4334be4
FEAT(#129): 실험실 관련 API 개발 (리포트, 자동매매, 모의투자현황 조회, 모의투자종목 상세조회)
alkwen0996 Sep 17, 2025
5c46318
Merge remote-tracking branch 'refs/remotes/origin/experiment-fix-secu…
MuuiGong Oct 8, 2025
493ac12
Merge remote-tracking branch 'refs/remotes/origin/experiment-feat' in…
MuuiGong Oct 8, 2025
f70bad4
Fix scheduler configuration: Remove duplicate @EnableScheduling annot…
MuuiGong Oct 10, 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
@@ -0,0 +1,50 @@
package com.fund.stockProject.experiment.controller;

import com.fund.stockProject.experiment.dto.ExperimentReportResponse;
import com.fund.stockProject.experiment.dto.ExperimentSimpleResponse;
import com.fund.stockProject.experiment.dto.ExperimentStatusDetailResponse;
import com.fund.stockProject.experiment.dto.ExperimentStatusResponse;
import com.fund.stockProject.experiment.service.ExperimentService;
import com.fund.stockProject.security.principle.CustomUserDetails;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RestController
@RequiredArgsConstructor
@RequestMapping("/experiment")
public class ExperimentController {

private final ExperimentService experimentService;

@GetMapping("/status")
@Operation(summary = "실험(모의 매수) 현황 API", description = "실험(모의 매수) 현황 조회")
public ResponseEntity<Mono<ExperimentStatusResponse>> getExperimentStatus(@AuthenticationPrincipal CustomUserDetails customUserDetails) {
return ResponseEntity.ok().body(experimentService.getExperimentStatus(customUserDetails));
}

@GetMapping("/status/{experimentId}/detail")
@Operation(summary = "실험(모의 매수) 현황 상세 보기 API", description = "실험(모의 매수) 현황 상세 보기")
public ResponseEntity<Mono<ExperimentStatusDetailResponse>> getExperimentStatusDetail(@PathVariable("experimentId") Integer experimentId) {
return ResponseEntity.ok().body(experimentService.getExperimentStatusDetail(experimentId));
}

@PostMapping("/{stockId}/buy/{country}")
@Operation(summary = "실험(모의 매수) 종목 매수 API", description = "실험(모의 매수) 종목 매수")
public ResponseEntity<Mono<ExperimentSimpleResponse>> buyExperiment(@AuthenticationPrincipal CustomUserDetails customUserDetails, final @PathVariable("stockId") Integer stockId, final @PathVariable("country") String country) {
return ResponseEntity.ok().body(experimentService.buyExperiment(customUserDetails, stockId, country));
}

@GetMapping("/report")
@Operation(summary = "실험 결과 API", description = "실험 결과 조회")
public ResponseEntity<Mono<ExperimentReportResponse>> getReport(@AuthenticationPrincipal CustomUserDetails customUserDetails) {
return ResponseEntity.ok().body(experimentService.getReport(customUserDetails));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.fund.stockProject.experiment.domain;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;

public enum SCORERANGE {
RANGE_0_59("60점 미만"),
RANGE_60_69("60-69점"),
RANGE_70_79("70-79점"),
RANGE_80_89("80-89점"),
RANGE_90_100("90점 이상");

private final String range;

SCORERANGE(String range) {
this.range = range;
}

@JsonValue
public String getRange() {
return range;
}

@JsonCreator
public static SCORERANGE fromRange(String range) {
for (SCORERANGE scorerange : SCORERANGE.values()) {
if (scorerange.range.equals(range)) {
return scorerange;
}
}
throw new IllegalArgumentException("Unknown exchange range: " + range);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.fund.stockProject.experiment.dto;

import com.fund.stockProject.stock.domain.COUNTRY;
import java.time.LocalDateTime;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class ExperimentInfoResponse {

private Integer experimentId;

private String symbolName;

private LocalDateTime buyAt;

private Integer buyPrice;

private Double roi;

private String status;

private COUNTRY country;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.fund.stockProject.experiment.dto;

import java.util.List;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class ExperimentReportResponse {
// 1번 결과 데이터
private long weeklyExperimentCount; // 이번주 진행한 실험 횟수
private List<ReportStatisticDto> reportStatisticDtos; // 점수 구간별 평균 수익률

// 2번 결과 데이터
private long totalUserExperiments; // 유저가 진행한 전체 실험 횟수
private long successUserExperiments; // 유저 실험 중 수익에 성공한 실험 횟수
private long sameGradeUserRate; // 동일 등급의 전체 유저 비율

private List<ReportPatternDto> reportPatternDtos; // 인간지표 점수 별 투자 유형 패턴 데이터
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.fund.stockProject.experiment.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class ExperimentSimpleResponse {
private boolean success;
private String message;
private Double price;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.fund.stockProject.experiment.dto;

import java.time.LocalDateTime;
import java.util.List;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class ExperimentStatusDetailResponse {
private String symbolName; // 종목명
private double roi; // 최종 수익률
private String status; // 거래 상태
private List<TradeInfo> tradeInfos; // 거래 내역

@Getter
public static class TradeInfo {
private double price; // 가격
private int score; // 점수
private LocalDateTime tradeAt; // 거래일
private double roi; // 수익률

@Builder
public TradeInfo(Double price, Integer score, LocalDateTime tradeAt, double roi) {
this.price = price;
this.score = score;
this.tradeAt = tradeAt;
this.roi = roi;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.fund.stockProject.experiment.dto;

import java.util.List;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class ExperimentStatusResponse {

private List<ExperimentInfoResponse> progressExperiments; // 진행중인 실험 데이터

private List<ExperimentInfoResponse> completeExperiments; // 완료된 실험 데이터

private double avgRoi; // 평균수익률

private int totalTradeCount; // 총 실험 수

private int progressTradeCount; // 진행중인 실험 수

private double successRate; // 성공률
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.fund.stockProject.experiment.dto;

import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Getter
@AllArgsConstructor
@Builder
public class ReportPatternDto {
private double roi; // 수익률
private int score; // 인간지표 점수
private LocalDateTime buyAt; // 매수날짜
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.fund.stockProject.experiment.dto;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class ReportStatisticDto {
private String scoreRange;
private double totalAvgRoi;
private double userAvgRoi;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.fund.stockProject.experiment.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fund.stockProject.user.entity.User;
import com.fund.stockProject.stock.entity.Stock;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import java.time.LocalDateTime;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Experiment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "stock_id", nullable = false)
private Stock stock;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;

@JsonIgnore
private LocalDateTime buyAt;

@JsonIgnore
private LocalDateTime sellAt;

@Column(nullable = false)
private Double buyPrice;

@Column
private Double sellPrice;

@Column(nullable = false)
private Double roi;

@Column(nullable = false)
private String status;

@Column(nullable = false)
private int score;

public void updateExperiment(Double sellPrice, String status, LocalDateTime sellAt, Double roi) {
this.sellPrice = sellPrice;
this.status = status;
this.sellAt = sellAt;
this.roi = roi;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.fund.stockProject.experiment.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import java.time.LocalDateTime;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ExperimentTradeItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "experiment_id", nullable = false)
private Experiment experiment;

@JsonIgnore
private LocalDateTime tradeAt; // 거래일

@Column(nullable = false)
private Double price; // 가격

@Column(nullable = false)
private Integer score; // 점수

@Column(nullable = false)
private Double roi; // 수익률
}
Loading