163 lines
7.4 KiB
Java
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;
|
|
}
|
|
}
|