가상매매 수정

This commit is contained in:
Macbook
2026-05-27 17:21:48 +09:00
parent aca895e9fd
commit 701a9b2881
6 changed files with 201 additions and 45 deletions
@@ -4,8 +4,8 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.goldenchart.entity.GcStrategy;
import com.goldenchart.repository.GcStrategyRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import java.util.LinkedHashSet;
@@ -19,15 +19,27 @@ import java.util.concurrent.ConcurrentHashMap;
* START / TIMEFRAME / AND|OR+TIMEFRAME 분기와 동일 규칙.
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class StrategyConditionTimeframeService {
private final GcStrategyRepository strategyRepo;
private final ObjectMapper objectMapper;
private final StrategyDslTimeframeNormalizer dslNormalizer;
private final LiveStrategyEvaluator liveStrategyEvaluator;
private final ConcurrentHashMap<Long, Set<String>> cache = new ConcurrentHashMap<>();
public StrategyConditionTimeframeService(
GcStrategyRepository strategyRepo,
ObjectMapper objectMapper,
StrategyDslTimeframeNormalizer dslNormalizer,
@Lazy LiveStrategyEvaluator liveStrategyEvaluator) {
this.strategyRepo = strategyRepo;
this.objectMapper = objectMapper;
this.dslNormalizer = dslNormalizer;
this.liveStrategyEvaluator = liveStrategyEvaluator;
}
public Set<String> collectForStrategy(long strategyId) {
return cache.computeIfAbsent(strategyId, this::loadFromDb);
}
@@ -53,33 +65,79 @@ public class StrategyConditionTimeframeService {
Optional<GcStrategy> opt = strategyRepo.findById(strategyId);
if (opt.isEmpty()) return Set.of("1m");
GcStrategy strategy = opt.get();
ensureDslRepaired(strategy);
Set<String> out = new LinkedHashSet<>();
collectFromJson(strategy.getBuyConditionJson(), out);
collectFromJson(strategy.getSellConditionJson(), out);
return out.isEmpty() ? Set.of("1m") : out;
collectFromJson(strategy.getBuyConditionJson(), out, strategy.getName());
collectFromJson(strategy.getSellConditionJson(), out, strategy.getName());
if (out.isEmpty()) {
String fromName = StrategyDslTimeframeNormalizer.inferFromStrategyName(strategy.getName());
return Set.of(fromName != null ? fromName : "1m");
}
return out;
}
private void collectFromJson(String json, Set<String> out) {
if (json == null || json.isBlank()) return;
try {
collectFromNode(objectMapper.readTree(json), out);
} catch (Exception e) {
log.warn("[StrategyTimeframes] JSON 파싱 실패: {}", e.getMessage());
out.add("1m");
/** TIMEFRAME 누락·잘못된 1m 래핑을 전략명·DSL 기준으로 DB 보정 */
private void ensureDslRepaired(GcStrategy strategy) {
boolean changed = false;
String name = strategy.getName();
if (strategy.getBuyConditionJson() != null && !strategy.getBuyConditionJson().isBlank()) {
String fixed = dslNormalizer.normalizeJson(strategy.getBuyConditionJson(), name);
if (!fixed.equals(strategy.getBuyConditionJson())) {
strategy.setBuyConditionJson(fixed);
changed = true;
}
}
if (strategy.getSellConditionJson() != null && !strategy.getSellConditionJson().isBlank()) {
String fixed = dslNormalizer.normalizeJson(strategy.getSellConditionJson(), name);
if (!fixed.equals(strategy.getSellConditionJson())) {
strategy.setSellConditionJson(fixed);
changed = true;
}
}
if (changed) {
strategyRepo.save(strategy);
liveStrategyEvaluator.invalidateStrategy(strategy.getId());
log.info("[StrategyTimeframes] DSL 분봉 보정 strategyId={} name={}",
strategy.getId(), name);
}
}
private void collectFromNode(JsonNode node, Set<String> out) {
private void collectFromJson(String json, Set<String> out, String strategyName) {
if (json == null || json.isBlank()) return;
try {
collectFromNode(objectMapper.readTree(json), out, strategyName);
} catch (Exception e) {
log.warn("[StrategyTimeframes] JSON 파싱 실패: {}", e.getMessage());
addFallbackTimeframe(out, strategyName);
}
}
private void collectFromNode(JsonNode node, Set<String> out, String strategyName) {
if (node == null || node.isNull()) return;
if (collectTimeframesFromNode(node, out)) return;
Set<String> explicit = StrategyDslTimeframeNormalizer.collectExplicitCandleTypes(node);
if (!explicit.isEmpty()) {
out.addAll(explicit);
Set<String> meaningful = StrategyDslTimeframeNormalizer.collectMeaningfulCandleTypes(node);
if (!meaningful.isEmpty()) {
out.addAll(meaningful);
return;
}
// TIMEFRAME·명시 분봉 없음 — 기본 1m
out.add("1m");
String fromName = StrategyDslTimeframeNormalizer.inferFromStrategyName(strategyName);
if (fromName != null) {
out.add(fromName);
return;
}
addFallbackTimeframe(out, strategyName);
}
private void addFallbackTimeframe(Set<String> out, String strategyName) {
String fromName = StrategyDslTimeframeNormalizer.inferFromStrategyName(strategyName);
out.add(fromName != null ? fromName : "1m");
}
/**