Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -10,13 +10,13 @@
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@RequiredArgsConstructor
@EnableRedisRepositories
public class RedisConfiguration {

private final RedisProperties redisProperties;

@Bean
Expand All @@ -37,7 +37,8 @@ public RedisTemplate<String, Object> redisTemplate() {
public RedisTemplate<String, HistoryDao> historyRedisTemplate() {
RedisTemplate<String, HistoryDao> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(HistoryDao.class));
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
21 changes: 21 additions & 0 deletions src/main/java/com/tradin/module/history/controller/HistoryApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.tradin.module.history.controller;

import com.tradin.common.response.TradinResponse;
import com.tradin.module.history.controller.dto.request.BackTestRequestDto;
import com.tradin.module.history.controller.dto.response.BackTestResponseDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.data.domain.Pageable;

@Tag(name = "히스토리", description = "히스토리 관련 API")
public interface HistoryApi {

@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "성공")
})
@Operation(summary = "백테스트 실행")
TradinResponse<BackTestResponseDto> backTest(BackTestRequestDto request, Pageable pageable);

}
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
package com.tradin.module.history.controller;

import com.tradin.common.annotation.DisableAuthInSwagger;
import com.tradin.common.response.TradinResponse;
import com.tradin.module.history.controller.dto.request.BackTestRequestDto;
import com.tradin.module.history.controller.dto.response.BackTestResponseDto;
import com.tradin.module.history.service.HistoryService;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import jakarta.validation.Valid;

@RestController
@RequiredArgsConstructor
@RequestMapping("/v1/histories")
public class HistoryController {
public class HistoryController implements HistoryApi {

private final HistoryService historyService;

@Operation(summary = "백테스트")
@DisableAuthInSwagger
@GetMapping("")
public BackTestResponseDto backTest(@Valid @ModelAttribute BackTestRequestDto request) {
return historyService.backTest(request.toServiceDto());
public TradinResponse<BackTestResponseDto> backTest(
//TODO - TIMEZONE UTC로 통일하기
@Valid @ModelAttribute BackTestRequestDto request, Pageable pageable) {
return TradinResponse.success(historyService.backTest(request.toServiceDto(), pageable));
}
}
Original file line number Diff line number Diff line change
@@ -1,46 +1,31 @@
package com.tradin.module.history.controller.dto.request;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.tradin.module.history.service.dto.BackTestDto;
import com.tradin.module.strategy.domain.TradingType;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.format.annotation.DateTimeFormat;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import org.springframework.format.annotation.DateTimeFormat;

@AllArgsConstructor
@Getter
public class BackTestRequestDto {
@NotNull(message = "StrategyId must not be null")
private long id;
@Schema(description = "백테스트 실행 DTO")
public record BackTestRequestDto(
@NotNull(message = "StrategyId must not be null") long id,

@NotBlank(message = "StrategyName must not be blank")
private String name;
@NotBlank(message = "StrategyName must not be blank") String name,

@NotNull(message = "StartDate must not be null")
@JsonSerialize(using = LocalDateSerializer.class)
@JsonDeserialize(using = LocalDateDeserializer.class)
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Schema(description = "시작 연,월,일", example = "2021-01-01")
private LocalDate startDate;
@Schema(description = "시작 연,월,일", example = "2021-01-01") LocalDate startDate,

@NotNull(message = "EndDate must not be null")
@JsonSerialize(using = LocalDateSerializer.class)
@JsonDeserialize(using = LocalDateDeserializer.class)
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Schema(description = "종료 연,월,일", example = "2021-01-01")
private LocalDate endDate;

@Schema(description = "매매 타입", example = "LONG")
@Schema(description = "종료 연,월,일", example = "2021-01-01") LocalDate endDate,
@NotNull(message = "TradingType must not be null")
private TradingType tradingType;

@Schema(description = "매매 타입", example = "LONG") TradingType tradingType
) {

public BackTestDto toServiceDto() {
return BackTestDto.of(id, name, startDate, endDate, tradingType);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
package com.tradin.module.history.controller.dto.response;

import com.tradin.module.history.domain.repository.dao.HistoryDao;
import com.tradin.module.history.service.dto.StrategyInfoDto;
import lombok.AllArgsConstructor;
import lombok.Getter;

import com.tradin.module.strategy.domain.repository.dao.StrategyInfoDao;
import java.util.List;

@AllArgsConstructor
@Getter
public class BackTestResponseDto {
private final StrategyInfoDto strategyInfoDto;
private final List<HistoryDao> historyDaos;
public record BackTestResponseDto(StrategyInfoDao strategyInfoDao, List<HistoryDao> historyDaos) {

public static BackTestResponseDto of(StrategyInfoDto strategyInfoDto, List<HistoryDao> historyDaos) {
return new BackTestResponseDto(strategyInfoDto, historyDaos);
public static BackTestResponseDto of(StrategyInfoDao strategyInfoDao, List<HistoryDao> historyDaos) {
return new BackTestResponseDto(strategyInfoDao, historyDaos);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

import com.tradin.module.history.domain.History;
import com.tradin.module.history.domain.repository.dao.HistoryDao;

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

public interface HistoryQueryRepository {

Optional<History> findLastHistoryByStrategyId(Long id);

List<HistoryDao> findHistoryDaoByStrategyId(Long id);
List<HistoryDao> findHistoryByStrategyId(Long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,22 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.querydsl.core.annotations.QueryProjection;
import com.tradin.module.strategy.domain.Position;
import lombok.Getter;
import lombok.Setter;
import io.swagger.v3.oas.annotations.media.Schema;

@Getter
@Setter
public class HistoryDao {
private final Long id;
private final Position entryPosition;
private final Position exitPosition;
private final double profitRate;
private double compoundProfitRate;
@Schema(description = "히스토리 정보")
public record HistoryDao(
@Schema(description = "히스토리 ID", example = "1") Long id,
@Schema(description = "진입 포지션") Position entryPosition,
@Schema(description = "종료 포지션") Position exitPosition,
@Schema(description = "수익률") double profitRate,
@Schema(description = "복리 수익률") double compoundProfitRate) {

@JsonCreator
@QueryProjection
public HistoryDao(@JsonProperty("id") Long id,
@JsonProperty("entryPosition") Position entryPosition,
@JsonProperty("exitPosition") Position exitPosition,
@JsonProperty("profitRate") double profitRate) {
this.id = id;
this.entryPosition = entryPosition;
this.exitPosition = exitPosition;
this.profitRate = profitRate;
this.compoundProfitRate = 0;
@JsonProperty("entryPosition") Position entryPosition,
@JsonProperty("exitPosition") Position exitPosition,
@JsonProperty("profitRate") double profitRate) {
this(id, entryPosition, exitPosition, profitRate, 0);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
package com.tradin.module.history.domain.repository.impl;

import static com.tradin.module.history.domain.QHistory.history;

import com.querydsl.jpa.impl.JPAQueryFactory;
import com.tradin.module.history.domain.History;
import com.tradin.module.history.domain.repository.HistoryQueryRepository;
import com.tradin.module.history.domain.repository.dao.HistoryDao;
import com.tradin.module.history.domain.repository.dao.QHistoryDao;
import lombok.RequiredArgsConstructor;

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

import static com.tradin.module.history.domain.QHistory.history;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class HistoryQueryRepositoryImpl implements HistoryQueryRepository {

private final JPAQueryFactory jpaQueryFactory;

@Override
public Optional<History> findLastHistoryByStrategyId(Long id) {
return Optional.ofNullable(
jpaQueryFactory
.selectFrom(history)
.where(history.strategy.id.eq(id))
.orderBy(history.entryPosition.time.desc())
.fetchFirst()
jpaQueryFactory
.selectFrom(history)
.where(history.strategy.id.eq(id))
.orderBy(history.entryPosition.time.desc())
.fetchFirst()
);
}

@Override
public List<HistoryDao> findHistoryDaoByStrategyId(Long id) {
public List<HistoryDao> findHistoryByStrategyId(Long id) {
return jpaQueryFactory.select(new QHistoryDao(history.id, history.entryPosition, history.exitPosition,
history.profitRate))
.from(history)
.where(history.strategy.id.eq(id))
.orderBy(history.id.asc())
.fetch();
history.profitRate
))
.from(history)
.where(history.strategy.id.eq(id))
.fetch();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.tradin.module.history.implement;

import com.tradin.module.history.domain.repository.dao.HistoryDao;
import java.time.ZoneOffset;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class HistoryCacheProcessor {

private final RedisTemplate<String, HistoryDao> historyRedisTemplate;

@Async
public void addHistoryCache(String cacheKey, List<HistoryDao> histories) {
historyRedisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public <K, V> Object execute(RedisOperations<K, V> operations) {
ZSetOperations<K, V> ops = operations.opsForZSet();
for (HistoryDao history : histories) {
ops.add((K) cacheKey, (V) history, history.entryPosition().getTime().toEpochSecond(ZoneOffset.UTC));
}
return null;
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.tradin.module.history.implement;

import com.tradin.module.history.domain.repository.dao.HistoryDao;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class HistoryCacheReader {

private final RedisTemplate<String, HistoryDao> historyRedisTemplate;

public List<HistoryDao> readHistoryByIdAndPeriod(String cacheKey, LocalDate startDate, LocalDate endDate, Pageable pageable) {

ZSetOperations<String, HistoryDao> historyCaches = historyRedisTemplate.opsForZSet();

Set<HistoryDao> historySet = historyCaches.rangeByScore(
cacheKey,
convertLocalDateToEpochSecond(startDate),
convertLocalDateToEpochSecond(endDate),
pageable.getOffset(),
pageable.getPageSize()
);

return new ArrayList<>(historySet);
}

private long convertLocalDateToEpochSecond(LocalDate date) {
return date.atStartOfDay().toEpochSecond(ZoneOffset.UTC);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.tradin.module.history.implement;

import com.tradin.module.history.domain.repository.HistoryRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class HistoryProcessor {

private final HistoryRepository historyRepository;

}
Loading
Loading