가상매매 수정

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
@@ -10,6 +10,7 @@ import org.springframework.stereotype.Component;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Pattern;
/**
* 전략 DSL에 TIMEFRAME 래핑이 없으면 평가 분봉을 추론해 래핑한다.
@@ -19,13 +20,36 @@ import java.util.UUID;
@RequiredArgsConstructor
public class StrategyDslTimeframeNormalizer {
private static final Pattern NAME_3M = Pattern.compile("3\\s*분|3m", Pattern.CASE_INSENSITIVE);
private static final Pattern NAME_5M = Pattern.compile("5\\s*분|5m", Pattern.CASE_INSENSITIVE);
private static final Pattern NAME_10M = Pattern.compile("10\\s*분|10m", Pattern.CASE_INSENSITIVE);
private static final Pattern NAME_15M = Pattern.compile("15\\s*분|15m", Pattern.CASE_INSENSITIVE);
private static final Pattern NAME_30M = Pattern.compile("30\\s*분|30m", Pattern.CASE_INSENSITIVE);
private static final Pattern NAME_1H = Pattern.compile("1\\s*시간|1h", Pattern.CASE_INSENSITIVE);
private static final Pattern NAME_4H = Pattern.compile("4\\s*시간|4h", Pattern.CASE_INSENSITIVE);
private static final Pattern NAME_1D = Pattern.compile("일봉|1\\s*일|1d", Pattern.CASE_INSENSITIVE);
private final ObjectMapper objectMapper;
public JsonNode normalize(JsonNode root) {
public JsonNode normalize(JsonNode root) {
return normalize(root, null);
}
public JsonNode normalize(JsonNode root, String strategyName) {
if (root == null || root.isNull()) return root;
if ("TIMEFRAME".equals(root.path("type").asText(""))) {
String expected = inferPrimaryCandleType(root, strategyName);
String current = LiveStrategyTimeframeService.normalize(root.path("candleType").asText("1m"));
if (!expected.equals(current)) {
return copyTimeframeWithCandleType(root, expected);
}
return root;
}
if (hasTimeframeScope(root)) return root;
String primary = inferPrimaryCandleType(root);
String primary = inferPrimaryCandleType(root, strategyName);
ObjectNode wrapped = objectMapper.createObjectNode();
wrapped.put("id", UUID.randomUUID().toString());
wrapped.put("type", "TIMEFRAME");
@@ -37,16 +61,34 @@ public class StrategyDslTimeframeNormalizer {
}
public String normalizeJson(String json) {
return normalizeJson(json, null);
}
public String normalizeJson(String json, String strategyName) {
if (json == null || json.isBlank()) return json;
try {
JsonNode root = objectMapper.readTree(json);
JsonNode normalized = normalize(root);
JsonNode normalized = normalize(root, strategyName);
return objectMapper.writeValueAsString(normalized);
} catch (Exception e) {
return json;
}
}
/** 전략 이름에서 평가 분봉 추론 (예: cci_3분봉 테스트 → 3m) */
public static String inferFromStrategyName(String name) {
if (name == null || name.isBlank()) return null;
if (NAME_3M.matcher(name).find()) return "3m";
if (NAME_5M.matcher(name).find()) return "5m";
if (NAME_10M.matcher(name).find()) return "10m";
if (NAME_15M.matcher(name).find()) return "15m";
if (NAME_30M.matcher(name).find()) return "30m";
if (NAME_4H.matcher(name).find()) return "4h";
if (NAME_1H.matcher(name).find()) return "1h";
if (NAME_1D.matcher(name).find()) return "1d";
return null;
}
/** 루트 TIMEFRAME 또는 AND|OR + 전부 TIMEFRAME 자식 */
public static boolean hasTimeframeScope(JsonNode node) {
if (node == null || node.isNull()) return false;
@@ -88,6 +130,16 @@ public class StrategyDslTimeframeNormalizer {
return out;
}
/** 조건 기본값 1m 은 제외하고 의미 있는 분봉만 */
public static Set<String> collectMeaningfulCandleTypes(JsonNode node) {
Set<String> all = collectExplicitCandleTypes(node);
Set<String> meaningful = new LinkedHashSet<>();
for (String ct : all) {
if (!"1m".equals(ct)) meaningful.add(ct);
}
return meaningful;
}
private static void collectExplicitCandleTypes(JsonNode node, Set<String> out) {
if (node == null || node.isNull()) return;
@@ -117,21 +169,33 @@ public class StrategyDslTimeframeNormalizer {
}
public static String inferPrimaryCandleType(JsonNode node) {
return inferPrimaryCandleType(node, null);
}
public static String inferPrimaryCandleType(JsonNode node, String strategyName) {
JsonNode nested = findNestedTimeframe(node);
if (nested != null) {
return LiveStrategyTimeframeService.normalize(nested.path("candleType").asText("1m"));
String ct = LiveStrategyTimeframeService.normalize(nested.path("candleType").asText("1m"));
if (!"1m".equals(ct)) return ct;
String fromName = inferFromStrategyName(strategyName);
if (fromName != null) return fromName;
return ct;
}
Set<String> meaningful = collectMeaningfulCandleTypes(node);
if (!meaningful.isEmpty()) return meaningful.iterator().next();
String fromName = inferFromStrategyName(strategyName);
if (fromName != null) return fromName;
Set<String> explicit = collectExplicitCandleTypes(node);
if (explicit.isEmpty()) return "1m";
Set<String> non1m = new LinkedHashSet<>();
for (String ct : explicit) {
if (!"1m".equals(ct)) non1m.add(ct);
}
if (non1m.size() == 1) return non1m.iterator().next();
if (non1m.size() > 1) return non1m.iterator().next();
if (explicit.size() == 1) return explicit.iterator().next();
return explicit.iterator().next();
return "1m";
}
private ObjectNode copyTimeframeWithCandleType(JsonNode tf, String candleType) {
ObjectNode copy = tf.deepCopy();
copy.put("candleType", LiveStrategyTimeframeService.normalize(candleType));
return copy;
}
}