지표탭 추가 수정
This commit is contained in:
+176
@@ -0,0 +1,176 @@
|
||||
package com.goldenchart.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.goldenchart.storage.Ta4jStorage;
|
||||
import com.goldenchart.trading.pipeline.TradingPipelineProperties;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.ta4j.core.BarSeries;
|
||||
import org.ta4j.core.BaseBarSeriesBuilder;
|
||||
import org.ta4j.core.Rule;
|
||||
import org.ta4j.core.bars.TimeBarBuilderFactory;
|
||||
import org.ta4j.core.num.DoubleNumFactory;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* START 분기별 트리거 분봉 평가 — OR/AND 분기 격리.
|
||||
*/
|
||||
class StrategyTriggerBranchEvaluatorTest {
|
||||
|
||||
private static final String MARKET = "KRW-BTC";
|
||||
private static final long STRATEGY_ID = 42L;
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
private StrategyTriggerBranchEvaluator evaluator;
|
||||
private StrategyBranchStateCache branchCache;
|
||||
private Ta4jStorage storage;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
branchCache = new StrategyBranchStateCache();
|
||||
evaluator = new StrategyTriggerBranchEvaluator(
|
||||
new StrategyDslToTa4jAdapter(), MAPPER, branchCache);
|
||||
TradingPipelineProperties props = new TradingPipelineProperties();
|
||||
props.setEnabled(false);
|
||||
storage = new Ta4jStorage(props);
|
||||
seedStorage(MARKET, "1m", buildSyntheticSeries("1m", 260));
|
||||
seedStorage(MARKET, "3m", buildSyntheticSeries("3m", 120));
|
||||
seedStorage(MARKET, "5m", buildSyntheticSeries("5m", 80));
|
||||
}
|
||||
|
||||
@Test
|
||||
void orScope_1mTrigger_doesNotRequire5mBranch() throws Exception {
|
||||
String json = """
|
||||
{
|
||||
"type": "OR",
|
||||
"children": [
|
||||
{ "type": "TIMEFRAME", "candleType": "1m", "children": [{
|
||||
"type": "CONDITION", "condition": {
|
||||
"indicatorType": "RSI", "conditionType": "GT",
|
||||
"leftField": "RSI_VALUE", "rightField": "K_0", "period": 14
|
||||
}
|
||||
}]},
|
||||
{ "type": "TIMEFRAME", "candleType": "5m", "children": [{
|
||||
"type": "CONDITION", "condition": {
|
||||
"indicatorType": "RSI", "conditionType": "LT",
|
||||
"leftField": "RSI_VALUE", "rightField": "K_0", "period": 14
|
||||
}
|
||||
}]}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
Rule rule1m = evaluator.buildTriggerRule(json, MARKET, STRATEGY_ID, "buy",
|
||||
"1m", Map.of(), storage);
|
||||
BarSeries s1m = storage.getOrCreate(MARKET, "1m");
|
||||
int idx = s1m.getEndIndex();
|
||||
assertDoesNotThrow(() -> rule1m.isSatisfied(idx, null));
|
||||
assertNotNull(branchCache.get(MARKET, STRATEGY_ID, "buy", "1m"));
|
||||
assertNull(branchCache.get(MARKET, STRATEGY_ID, "buy", "5m"),
|
||||
"1m 트리거 시 5m 분기는 평가·캐시되지 않아야 함");
|
||||
}
|
||||
|
||||
@Test
|
||||
void andScope_requiresAllBranchCaches() throws Exception {
|
||||
String json = """
|
||||
{
|
||||
"type": "AND",
|
||||
"children": [
|
||||
{ "type": "TIMEFRAME", "candleType": "1m", "children": [{
|
||||
"type": "CONDITION", "condition": {
|
||||
"indicatorType": "RSI", "conditionType": "GT",
|
||||
"leftField": "RSI_VALUE", "rightField": "K_0", "period": 14
|
||||
}
|
||||
}]},
|
||||
{ "type": "TIMEFRAME", "candleType": "3m", "children": [{
|
||||
"type": "CONDITION", "condition": {
|
||||
"indicatorType": "RSI", "conditionType": "GT",
|
||||
"leftField": "RSI_VALUE", "rightField": "K_0", "period": 14
|
||||
}
|
||||
}]}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
Rule rule1m = evaluator.buildTriggerRule(json, MARKET, STRATEGY_ID, "sell",
|
||||
"1m", Map.of(), storage);
|
||||
BarSeries s1m = storage.getOrCreate(MARKET, "1m");
|
||||
int idx1m = s1m.getEndIndex();
|
||||
|
||||
rule1m.isSatisfied(idx1m, null);
|
||||
assertNotNull(branchCache.get(MARKET, STRATEGY_ID, "sell", "1m"));
|
||||
assertNull(branchCache.get(MARKET, STRATEGY_ID, "sell", "3m"));
|
||||
|
||||
branchCache.put(MARKET, STRATEGY_ID, "sell", "3m", true, 10);
|
||||
assertTrue(rule1m.isSatisfied(idx1m, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void single3mScope_onlyTriggersOn3mBranch() throws Exception {
|
||||
String json = """
|
||||
{
|
||||
"type": "TIMEFRAME",
|
||||
"candleType": "3m",
|
||||
"children": [{
|
||||
"type": "CONDITION", "condition": {
|
||||
"indicatorType": "RSI", "conditionType": "GT",
|
||||
"leftField": "RSI_VALUE", "rightField": "K_0", "period": 14
|
||||
}
|
||||
}]
|
||||
}
|
||||
""";
|
||||
|
||||
var scope = evaluator.parseScope(json);
|
||||
assertEquals("SINGLE", scope.combineOp());
|
||||
assertEquals(1, scope.branches().size());
|
||||
assertEquals("3m", scope.branches().get(0).candleType());
|
||||
|
||||
Rule on3m = evaluator.buildTriggerRule(json, MARKET, STRATEGY_ID, "buy",
|
||||
"3m", Map.of(), storage);
|
||||
BarSeries s3m = storage.getOrCreate(MARKET, "3m");
|
||||
assertDoesNotThrow(() -> on3m.isSatisfied(s3m.getEndIndex(), null));
|
||||
|
||||
Rule on1m = evaluator.buildTriggerRule(json, MARKET, STRATEGY_ID, "buy",
|
||||
"1m", Map.of(), storage);
|
||||
assertFalse(on1m.isSatisfied(storage.getOrCreate(MARKET, "1m").getEndIndex(), null));
|
||||
}
|
||||
|
||||
private static BarSeries buildSyntheticSeries(String timeframe, int barCount) {
|
||||
Duration period = switch (timeframe) {
|
||||
case "3m" -> Duration.ofMinutes(3);
|
||||
case "5m" -> Duration.ofMinutes(5);
|
||||
default -> Duration.ofMinutes(1);
|
||||
};
|
||||
BarSeries series = new BaseBarSeriesBuilder()
|
||||
.withName("test-" + timeframe)
|
||||
.withNumFactory(DoubleNumFactory.getInstance())
|
||||
.withBarBuilderFactory(new TimeBarBuilderFactory())
|
||||
.build();
|
||||
TimeBarBuilderFactory factory = new TimeBarBuilderFactory();
|
||||
Instant base = Instant.parse("2024-06-01T00:00:00Z");
|
||||
for (int i = 0; i < barCount; i++) {
|
||||
double wave = Math.sin(i * 0.07) * 8 + Math.cos(i * 0.02) * 4;
|
||||
double close = 50_000_000 + i * 120 + wave * 1000;
|
||||
Instant end = base.plus(period.multipliedBy(i + 1L));
|
||||
factory.createBarBuilder(series)
|
||||
.timePeriod(period)
|
||||
.endTime(end)
|
||||
.openPrice(close - 500).highPrice(close + 800)
|
||||
.lowPrice(close - 800).closePrice(close)
|
||||
.volume(12.5 + i * 0.01)
|
||||
.add();
|
||||
}
|
||||
return series;
|
||||
}
|
||||
|
||||
private void seedStorage(String market, String candleType, BarSeries source) {
|
||||
for (int i = source.getBeginIndex(); i <= source.getEndIndex(); i++) {
|
||||
storage.addBar(market, candleType, source.getBar(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user