Files
goldenChart/backend/src/main/java/com/goldenchart/service/StrategyService.java
T
2026-05-28 14:44:19 +09:00

163 lines
7.4 KiB
Java

package com.goldenchart.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.goldenchart.dto.StrategyDto;
import com.goldenchart.entity.GcStrategy;
import com.goldenchart.repository.GcStrategyRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
@Service
@RequiredArgsConstructor
@Slf4j
public class StrategyService {
private final GcStrategyRepository repository;
private final ObjectMapper objectMapper;
private final StrategyConditionTimeframeService conditionTimeframes;
private final LiveStrategyEvaluator liveStrategyEvaluator;
private final StrategyDslTimeframeNormalizer dslTimeframeNormalizer;
// ── 조회 ──────────────────────────────────────────────────────────────────
public List<StrategyDto> list(Long userId, String deviceId) {
List<GcStrategy> entities = userId != null
? repository.findByUserIdOrderByUpdatedAtDesc(userId)
: repository.findByDeviceIdOrderByUpdatedAtDesc(deviceId != null ? deviceId : "");
return entities.stream().map(this::toDto).toList();
}
public Optional<StrategyDto> findById(Long id) {
return repository.findById(id).map(this::toDto);
}
// ── 저장 / 수정 ───────────────────────────────────────────────────────────
@Transactional
public StrategyDto save(StrategyDto dto, Long userId, String deviceId) {
GcStrategy entity;
if (dto.getId() != null) {
entity = repository.findById(dto.getId()).orElseGet(GcStrategy::new);
} else {
entity = new GcStrategy();
entity.setUserId(userId);
entity.setDeviceId(deviceId);
}
entity.setName(dto.getName() != null ? dto.getName() : "전략");
entity.setDescription(dto.getDescription());
if (dto.getEnabled() != null) entity.setEnabled(dto.getEnabled());
else if (entity.getEnabled() == null) entity.setEnabled(true);
try {
String strategyName = entity.getName() != null ? entity.getName() : dto.getName();
String buyJson = dto.getBuyCondition() != null
? objectMapper.writeValueAsString(dto.getBuyCondition()) : null;
String sellJson = dto.getSellCondition() != null
? objectMapper.writeValueAsString(dto.getSellCondition()) : null;
if (buyJson != null && sellJson != null) {
sellJson = dslTimeframeNormalizer.alignSellStartTimeframeToBuy(buyJson, sellJson);
}
entity.setBuyConditionJson(
buyJson != null
? dslTimeframeNormalizer.normalizeJson(buyJson, strategyName)
: null);
entity.setSellConditionJson(
sellJson != null
? dslTimeframeNormalizer.normalizeJson(sellJson, strategyName)
: null);
if (dto.getFlowLayout() != null && !dto.getFlowLayout().isNull()) {
entity.setFlowLayoutJson(objectMapper.writeValueAsString(dto.getFlowLayout()));
}
} catch (Exception e) {
log.warn("전략 DSL 직렬화 실패: {}", e.getMessage());
}
GcStrategy saved = repository.save(entity);
conditionTimeframes.invalidate(saved.getId());
liveStrategyEvaluator.invalidateStrategy(saved.getId());
return toDto(saved);
}
/**
* DB에 저장된 매수/매도 DSL에 TIMEFRAME 래핑이 없으면 보정 후 저장.
* (편집기 재저장 없이 기존 3분봉 전략 등을 즉시 수정할 때)
*/
@Transactional
public Optional<StrategyDto> repairTimeframeWrappers(Long id) {
return repository.findById(id).map(entity -> {
boolean changed = false;
if (entity.getBuyConditionJson() != null && !entity.getBuyConditionJson().isBlank()) {
String fixed = dslTimeframeNormalizer.normalizeJson(
entity.getBuyConditionJson(), entity.getName());
if (!fixed.equals(entity.getBuyConditionJson())) {
entity.setBuyConditionJson(fixed);
changed = true;
}
}
String sellJson = entity.getSellConditionJson();
if (entity.getBuyConditionJson() != null && !entity.getBuyConditionJson().isBlank()
&& sellJson != null && !sellJson.isBlank()) {
String aligned = dslTimeframeNormalizer.alignSellStartTimeframeToBuy(
entity.getBuyConditionJson(), sellJson);
if (!aligned.equals(sellJson)) {
entity.setSellConditionJson(aligned);
sellJson = aligned;
changed = true;
}
}
if (sellJson != null && !sellJson.isBlank()) {
String fixed = dslTimeframeNormalizer.normalizeJson(sellJson, entity.getName());
if (!fixed.equals(entity.getSellConditionJson())) {
entity.setSellConditionJson(fixed);
changed = true;
}
}
if (!changed) return toDto(entity);
GcStrategy saved = repository.save(entity);
conditionTimeframes.invalidate(saved.getId());
liveStrategyEvaluator.invalidateStrategy(saved.getId());
log.info("[Strategy] TIMEFRAME 래핑 보정 strategyId={}", id);
return toDto(saved);
});
}
// ── 삭제 ──────────────────────────────────────────────────────────────────
@Transactional
public void delete(Long id) {
conditionTimeframes.invalidate(id);
liveStrategyEvaluator.invalidateStrategy(id);
repository.deleteById(id);
}
// ── 변환 ──────────────────────────────────────────────────────────────────
private StrategyDto toDto(GcStrategy e) {
StrategyDto dto = new StrategyDto();
dto.setId(e.getId());
dto.setName(e.getName());
dto.setDescription(e.getDescription());
dto.setEnabled(e.getEnabled());
dto.setCreatedAt(e.getCreatedAt());
dto.setUpdatedAt(e.getUpdatedAt());
try {
if (e.getBuyConditionJson() != null)
dto.setBuyCondition(objectMapper.readTree(e.getBuyConditionJson()));
if (e.getSellConditionJson() != null)
dto.setSellCondition(objectMapper.readTree(e.getSellConditionJson()));
if (e.getFlowLayoutJson() != null && !e.getFlowLayoutJson().isBlank())
dto.setFlowLayout(objectMapper.readTree(e.getFlowLayoutJson()));
} catch (Exception ex) {
log.warn("전략 DSL 역직렬화 실패: {}", ex.getMessage());
}
return dto;
}
}