일목균형표 수정
This commit is contained in:
+22
@@ -55,6 +55,28 @@ class StrategyConditionTimeframeServiceTest {
|
||||
assertFalse(service.usesTimeframe(1L, "1m"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void collectForStrategy_readsMultipleCandleTypesFromTimeframeNode() {
|
||||
GcStrategy multi = new GcStrategy();
|
||||
multi.setId(3L);
|
||||
multi.setBuyConditionJson("""
|
||||
{
|
||||
"type": "TIMEFRAME",
|
||||
"candleType": "1m",
|
||||
"candleTypes": ["1m", "3m", "5m"],
|
||||
"children": [{
|
||||
"type": "CONDITION",
|
||||
"condition": { "indicatorType": "RSI", "period": 14 }
|
||||
}]
|
||||
}
|
||||
""");
|
||||
when(strategyRepo.findById(3L)).thenReturn(Optional.of(multi));
|
||||
|
||||
Set<String> tfs = service.collectForStrategy(3L);
|
||||
assertEquals(Set.of("1m", "3m", "5m"), tfs);
|
||||
assertTrue(service.usesTimeframe(3L, "3m"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void collectForStrategy_legacyTreeDefaultsTo1m() {
|
||||
GcStrategy legacy = new GcStrategy();
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.goldenchart.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class StrategyDslTimeframeNormalizerTest {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final StrategyDslTimeframeNormalizer normalizer = new StrategyDslTimeframeNormalizer(objectMapper);
|
||||
|
||||
@Test
|
||||
void normalize_preservesMultiCandleTypesOnSave() throws Exception {
|
||||
String json = """
|
||||
{
|
||||
"type": "TIMEFRAME",
|
||||
"candleType": "1m",
|
||||
"candleTypes": ["1m", "3m", "5m"],
|
||||
"children": [{
|
||||
"type": "CONDITION",
|
||||
"condition": { "indicatorType": "RSI", "period": 14 }
|
||||
}]
|
||||
}
|
||||
""";
|
||||
String normalized = normalizer.normalizeJson(json, "5분봉 테스트 전략");
|
||||
JsonNode root = objectMapper.readTree(normalized);
|
||||
assertTrue(root.path("candleTypes").isArray());
|
||||
assertEquals(3, root.path("candleTypes").size());
|
||||
assertEquals("1m", root.path("candleTypes").get(0).asText());
|
||||
assertEquals("3m", root.path("candleTypes").get(1).asText());
|
||||
assertEquals("5m", root.path("candleTypes").get(2).asText());
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package com.goldenchart.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.goldenchart.entity.GcStrategy;
|
||||
import com.goldenchart.repository.GcStrategyRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* START candleTypes → BarBuilder 게이트(usesTimeframe) → TriggerBranch 평가 연결 검증.
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class StrategyEvaluationTimeframeIntegrationTest {
|
||||
|
||||
@Mock
|
||||
private GcStrategyRepository strategyRepo;
|
||||
|
||||
@Spy
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Mock
|
||||
private StrategyDslTimeframeNormalizer dslNormalizer;
|
||||
|
||||
@Mock
|
||||
private LiveStrategyEvaluator liveStrategyEvaluator;
|
||||
|
||||
private StrategyConditionTimeframeService timeframeService;
|
||||
|
||||
private StrategyTriggerBranchEvaluator triggerEvaluator;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
timeframeService = new StrategyConditionTimeframeService(
|
||||
strategyRepo, objectMapper, dslNormalizer, liveStrategyEvaluator);
|
||||
triggerEvaluator = new StrategyTriggerBranchEvaluator(
|
||||
new StrategyDslToTa4jAdapter(), objectMapper, new StrategyBranchStateCache());
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkedTimeframes_gateBarCloseAndTriggerScope() throws Exception {
|
||||
String buyJson = """
|
||||
{
|
||||
"type": "TIMEFRAME",
|
||||
"candleType": "1m",
|
||||
"candleTypes": ["1m", "3m", "5m"],
|
||||
"children": [{
|
||||
"type": "CONDITION",
|
||||
"condition": {
|
||||
"indicatorType": "RSI", "conditionType": "GT",
|
||||
"leftField": "RSI_VALUE", "rightField": "K_0", "period": 14
|
||||
}
|
||||
}]
|
||||
}
|
||||
""";
|
||||
GcStrategy strategy = new GcStrategy();
|
||||
strategy.setId(100L);
|
||||
strategy.setName("multi tf test");
|
||||
strategy.setBuyConditionJson(buyJson);
|
||||
when(strategyRepo.findById(100L)).thenReturn(Optional.of(strategy));
|
||||
when(dslNormalizer.normalizeJson(buyJson, strategy.getName())).thenReturn(buyJson);
|
||||
|
||||
Set<String> collected = timeframeService.collectForStrategy(100L);
|
||||
assertEquals(Set.of("1m", "3m", "5m"), collected);
|
||||
|
||||
assertTrue(timeframeService.usesTimeframe(100L, "1m"));
|
||||
assertTrue(timeframeService.usesTimeframe(100L, "3m"));
|
||||
assertTrue(timeframeService.usesTimeframe(100L, "5m"));
|
||||
assertFalse(timeframeService.usesTimeframe(100L, "15m"));
|
||||
|
||||
var scope = triggerEvaluator.parseScope(buyJson);
|
||||
assertEquals("OR", scope.combineOp());
|
||||
assertEquals(3, scope.branches().size());
|
||||
assertTrue(scope.branches().stream().anyMatch(b -> "3m".equals(b.candleType())));
|
||||
}
|
||||
}
|
||||
@@ -110,6 +110,52 @@ class StrategyTriggerBranchEvaluatorTest {
|
||||
assertTrue(rule1m.isSatisfied(idx1m, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiCandleTypes_sameSubtree_onlyEvaluatesTriggerBranch() throws Exception {
|
||||
String json = """
|
||||
{
|
||||
"type": "TIMEFRAME",
|
||||
"candleType": "1m",
|
||||
"candleTypes": ["1m", "3m", "5m"],
|
||||
"children": [{
|
||||
"type": "CONDITION", "condition": {
|
||||
"indicatorType": "RSI", "conditionType": "GT",
|
||||
"leftField": "RSI_VALUE", "rightField": "K_0", "period": 14
|
||||
}
|
||||
}]
|
||||
}
|
||||
""";
|
||||
|
||||
var scope = evaluator.parseScope(json);
|
||||
assertEquals("OR", scope.combineOp());
|
||||
assertEquals(3, scope.branches().size());
|
||||
assertEquals("1m", scope.branches().get(0).candleType());
|
||||
assertEquals("3m", scope.branches().get(1).candleType());
|
||||
assertEquals("5m", scope.branches().get(2).candleType());
|
||||
|
||||
Rule on1m = evaluator.buildTriggerRule(json, MARKET, STRATEGY_ID, "buy",
|
||||
"1m", Map.of(), storage);
|
||||
BarSeries s1m = storage.getOrCreate(MARKET, "1m");
|
||||
on1m.isSatisfied(s1m.getEndIndex(), null);
|
||||
assertNotNull(branchCache.get(MARKET, STRATEGY_ID, "buy", "1m"));
|
||||
assertNull(branchCache.get(MARKET, STRATEGY_ID, "buy", "3m"),
|
||||
"1m 마감 트리거 시 3m 분기는 평가·캐시되지 않아야 함");
|
||||
assertNull(branchCache.get(MARKET, STRATEGY_ID, "buy", "5m"));
|
||||
|
||||
branchCache.invalidateStrategy(STRATEGY_ID);
|
||||
Rule on3m = evaluator.buildTriggerRule(json, MARKET, STRATEGY_ID, "buy",
|
||||
"3m", Map.of(), storage);
|
||||
BarSeries s3m = storage.getOrCreate(MARKET, "3m");
|
||||
on3m.isSatisfied(s3m.getEndIndex(), null);
|
||||
assertNotNull(branchCache.get(MARKET, STRATEGY_ID, "buy", "3m"));
|
||||
assertNull(branchCache.get(MARKET, STRATEGY_ID, "buy", "1m"));
|
||||
|
||||
Rule on5mWrongTrigger = evaluator.buildTriggerRule(json, MARKET, STRATEGY_ID, "buy",
|
||||
"1m", Map.of(), storage);
|
||||
assertFalse(on5mWrongTrigger.isSatisfied(s3m.getEndIndex(), null),
|
||||
"3m 인덱스로 1m 트리거 Rule 평가 시 false");
|
||||
}
|
||||
|
||||
@Test
|
||||
void single3mScope_onlyTriggersOn3mBranch() throws Exception {
|
||||
String json = """
|
||||
|
||||
Reference in New Issue
Block a user