Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;

import org.sejongisc.backend.backtest.dto.BacktestRequest;
import org.sejongisc.backend.backtest.dto.BacktestResponse;
import org.sejongisc.backend.backtest.dto.BacktestRunRequest;
import org.sejongisc.backend.backtest.service.BacktestService;
import org.sejongisc.backend.common.auth.springsecurity.CustomUserDetails;
import org.springframework.http.ResponseEntity;
Expand Down Expand Up @@ -61,132 +63,193 @@ public ResponseEntity<BacktestResponse> getBackTestResultDetails(@PathVariable L
// 백테스트 실행
@PostMapping("/runs")
@Operation(
summary = "백테스트 실행",
description = "사용자가 요청한 전략을 기반으로 백테스트를 실행합니다."
)
@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "백테스트 실행을 위한 기본 정보 및 전략",
required = true,
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = BacktestRequest.class),
examples = {
@ExampleObject(
summary = "SMA 골든크로스 및 RSI 필터 전략 예시",
value = """
{
"title": "골든크로스 + RSI 필터 (AAPL)",
"startDate": "2023-01-01",
"endDate": "2024-12-31",
"templateId": null,
"strategy": {
"initialCapital": 100000.00,
"ticker": "AAPL",
"defaultExitDays" : 30,
"buyConditions": [
{
"leftOperand": {
"type": "indicator",
"indicatorCode": "RSI",
"output": "value",
"params": {
"length": 14
}
},
"operator": "LT",
"rightOperand": {
"type": "const",
"constantValue": 30
},
"isAbsolute": true
},
{
"leftOperand": {
"type": "indicator",
"indicatorCode": "SMA",
"output": "value",
"params": {
"length": 50
}
},
"operator": "CROSSES_ABOVE",
"rightOperand": {
"type": "indicator",
"indicatorCode": "SMA",
"output": "value",
"params": {
"length": 200
}
},
"isAbsolute": false
},
{
"leftOperand": {
"type": "price",
"priceField": "Close"
},
"operator": "GTE",
"rightOperand": {
"type": "indicator",
"indicatorCode": "SMA",
"output": "value",
"params": {
"length": 200
summary = "백테스팅 실행",
description = "다양한 보조지표를 조합하여 비동기 백테스팅을 실행합니다. 아래 Examples에서 원하는 전략을 선택하여 테스트하세요.",
requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
content = @Content(
schema = @Schema(implementation = BacktestRequest.class),
examples = {
@ExampleObject(
name = "1. [추세] 이동평균 골든크로스 (SMA)",
description = "단기 이평선이 장기 이평선을 돌파할 때 매수합니다.",
value = """
{
"title": "SMA 5일/20일 골든크로스",
"startDate": "2023-01-01",
"endDate": "2023-12-31",
"strategy": {
"ticker": "AAPL",
"initialCapital": 10000000,
"defaultExitDays": 0,
"buyConditions": [
{
"leftOperand": { "type": "indicator", "indicatorCode": "SMA", "params": { "length": 5 } },
"operator": "CROSSES_ABOVE",
"rightOperand": { "type": "indicator", "indicatorCode": "SMA", "params": { "length": 20 } },
"isAbsolute": true
}
],
"sellConditions": [
{
"leftOperand": { "type": "indicator", "indicatorCode": "SMA", "params": { "length": 5 } },
"operator": "CROSSES_BELOW",
"rightOperand": { "type": "indicator", "indicatorCode": "SMA", "params": { "length": 20 } },
"isAbsolute": true
}
]
}
}
"""
),
@ExampleObject(
name = "2. [모멘텀] RSI & MACD 조합",
description = "RSI 과매도 + MACD 상승 반전 시 매수",
value = """
{
"title": "RSI + MACD 복합 전략",
"startDate": "2023-01-01",
"endDate": "2023-12-31",
"strategy": {
"ticker": "TSLA",
"initialCapital": 10000000,
"defaultExitDays": 0,
"buyConditions": [
{
"leftOperand": { "type": "indicator", "indicatorCode": "RSI", "params": { "length": 14 } },
"operator": "LT",
"rightOperand": { "type": "const", "constantValue": 40 },
"isAbsolute": false
},
{
"leftOperand": { "type": "indicator", "indicatorCode": "MACD", "output": "macd", "params": { "fast": 12, "slow": 26, "signal": 9 } },
"operator": "GT",
"rightOperand": { "type": "indicator", "indicatorCode": "MACD", "output": "signal", "params": { "fast": 12, "slow": 26, "signal": 9 } },
"isAbsolute": false
}
],
"sellConditions": [
{
"leftOperand": { "type": "indicator", "indicatorCode": "RSI", "params": { "length": 14 } },
"operator": "GT",
"rightOperand": { "type": "const", "constantValue": 70 },
"isAbsolute": true
}
]
}
}
"""
),
@ExampleObject(
name = "3. [변동성] 볼린저 밴드 (Bollinger Bands)",
description = "하단 밴드 터치 시 매수, 상단 밴드 터치 시 매도",
value = """
{
"title": "볼린저 밴드 역추세",
"startDate": "2023-01-01",
"endDate": "2023-12-31",
"strategy": {
"ticker": "MSFT",
"initialCapital": 5000000,
"defaultExitDays": 0,
"buyConditions": [
{
"leftOperand": { "type": "price", "priceField": "Close" },
"operator": "LT",
"rightOperand": { "type": "indicator", "indicatorCode": "BB", "output": "lower", "params": { "length": 20, "k": 2.0 } },
"isAbsolute": true
}
],
"sellConditions": [
{
"leftOperand": { "type": "price", "priceField": "Close" },
"operator": "GT",
"rightOperand": { "type": "indicator", "indicatorCode": "BB", "output": "upper", "params": { "length": 20, "k": 2.0 } },
"isAbsolute": true
}
]
}
}
"""
),
@ExampleObject(
name = "4. [오실레이터] 스토캐스틱 & CCI",
description = "스토캐스틱 골든크로스 & CCI 과매수 청산",
value = """
{
"title": "Stochastic & CCI 전략",
"startDate": "2023-01-01",
"endDate": "2023-12-31",
"strategy": {
"ticker": "NVDA",
"initialCapital": 10000000,
"defaultExitDays": 0,
"buyConditions": [
{
"leftOperand": { "type": "indicator", "indicatorCode": "STOCH", "output": "k", "params": { "kLength": 14, "dLength": 3 } },
"operator": "CROSSES_ABOVE",
"rightOperand": { "type": "const", "constantValue": 20 },
"isAbsolute": true
}
],
"sellConditions": [
{
"leftOperand": { "type": "indicator", "indicatorCode": "CCI", "params": { "length": 14 } },
"operator": "GT",
"rightOperand": { "type": "const", "constantValue": 100 },
"isAbsolute": true
}
]
}
}
"""
),
@ExampleObject(
name = "5. [추세강도] ADX & ATR (고급)",
description = "ADX로 추세 확인 후 EMA 돌파 매매",
value = """
{
"title": "ADX 추세 추종 & ATR 활용",
"startDate": "2023-01-01",
"endDate": "2023-12-31",
"strategy": {
"ticker": "AMD",
"initialCapital": 10000000,
"defaultExitDays": 30,
"buyConditions": [
{
"leftOperand": { "type": "indicator", "indicatorCode": "ADX", "params": { "length": 14 } },
"operator": "GT",
"rightOperand": { "type": "const", "constantValue": 25 },
"isAbsolute": false
},
{
"leftOperand": { "type": "price", "priceField": "Close" },
"operator": "GT",
"rightOperand": { "type": "indicator", "indicatorCode": "EMA", "params": { "length": 20 } },
"isAbsolute": false
}
],
"sellConditions": [
{
"leftOperand": { "type": "price", "priceField": "Close" },
"operator": "LT",
"rightOperand": { "type": "indicator", "indicatorCode": "EMA", "params": { "length": 20 } },
"isAbsolute": true
}
]
}
}
"""
)
}
},
"isAbsolute": false
}
],
"sellConditions": [
{
"leftOperand": {
"type": "indicator",
"indicatorCode": "RSI",
"output": "value",
"params": {
"length": 14
}
},
"operator": "GT",
"rightOperand": {
"type": "const",
"constantValue": 70
},
"isAbsolute": true
},
{
"leftOperand": {
"type": "indicator",
"indicatorCode": "SMA",
"output": "value",
"params": {
"length": 50
}
},
"operator": "CROSSES_BELOW",
"rightOperand": {
"type": "indicator",
"indicatorCode": "SMA",
"output": "value",
"params": {
"length": 200
}
},
"isAbsolute": false
}
],
"note": "간단한 골든크로스 전략 테스트. RSI 과매수/과매도 시 우선 청산/진입."
}
}
"""
)
}
)
)
)
)
public ResponseEntity<BacktestResponse> runBacktest(@RequestBody BacktestRequest request,
public ResponseEntity<BacktestResponse> runBacktest(@org.springframework.web.bind.annotation.RequestBody BacktestRequest request, //
@AuthenticationPrincipal CustomUserDetails customUserDetails) {
request.setUserId(customUserDetails.getUserId());
return ResponseEntity.ok(backtestService.runBacktest(request));
request.setUserId(customUserDetails.getUserId()); // 사용자 ID 주입
BacktestResponse response = backtestService.runBacktest(request);
return ResponseEntity.ok(response);
}

// 백테스트 실행 정보 삭제
Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,41 @@
package org.sejongisc.backend.backtest.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import java.util.Map;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.math.BigDecimal;
import java.util.Map;

/**
* 전략 조건의 개별 항 (Operand)
* 예: SMA(20), 종가(Close), 30(상수)
*/
@Getter
@NoArgsConstructor
@Setter
public class StrategyOperand {

@Schema(description = "항의 타입: \"indicator\", \"price\", \"const\"")
private String type;
@Schema(description = "유형 (price: 가격데이터, const: 상수, indicator: 보조지표)", example = "indicator")
private String type; // 'price', 'const', 'indicator'

@Schema(description = "type == \"indicator\" 일 때의 지표 코드 (예: \"SMA\", \"RSI\", \"MACD\")")
@Schema(description = "지표 코드 (SMA, EMA, RSI, MACD, BB, STOCH, CCI, ATR, ADX)", example = "BB")
private String indicatorCode;

@Schema(description = "type == \"price\" 일 때의 가격 필드 (예: \"Close\", \"Open\", \"High\", \"Low\", \"Volume\")")
@Schema(description = "가격 기준 (Close, Open, High, Low, Volume)", example = "Close")
private String priceField;

@Schema(description = "type == \"const\" 일 때의 상수 값 (예: 30, 0.02)")
private BigDecimal constantValue;
@Schema(description = "상수 값 (type이 const일 때 사용)", example = "30")
private Double constantValue;

@Schema(description = "지표의 출력값 (예: \"value\", \"macd\", \"signal\", \"hist\")")
@Schema(description = """
지표별 결과값 선택 (다중 출력 지표용):
- BB (볼린저밴드): upper, middle, lower
- MACD: macd, signal, hist
- STOCH (스토캐스틱): k, d
- 나머지 단일 지표는 null 혹은 생략 가능
""", example = "lower")
private String output;

@Schema(description = "지표의 파라미터 맵 (예: {\"length\": 20})")
@Schema(description = """
지표별 필수 파라미터 (Map 형식):
- SMA, EMA, RSI, CCI, ATR, ADX: { "length": 14 }
- MACD: { "fast": 12, "slow": 26, "signal": 9 }
- BB (볼린저밴드): { "length": 20, "k": 2.0 }
- STOCH (스토캐스틱): { "kLength": 14, "dLength": 3 }
""", example = "{\"length\": 20, \"k\": 2.0}")
private Map<String, Object> params;

//private String transform; // 거래량 관련 필드, 추후 적용 고려
}
Loading