n봉 존재 조건 수정

This commit is contained in:
Macbook
2026-06-12 23:40:20 +09:00
parent b55349062f
commit 4a6be82c15
8 changed files with 365 additions and 32 deletions
@@ -0,0 +1,157 @@
package com.goldenchart.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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.TradingRecord;
import org.ta4j.core.bars.TimeBarBuilderFactory;
import org.ta4j.core.num.DoubleNumFactory;
import java.time.Duration;
import java.time.Instant;
import static org.junit.jupiter.api.Assertions.*;
/**
* candleRange / candleRangeMode — 최근 N봉 윈도우 존재·미존재 평가.
*/
class CandleRangeWindowRuleTest {
private static final ObjectMapper MAPPER = new ObjectMapper();
private StrategyDslToTa4jAdapter adapter;
private BarSeries series;
@BeforeEach
void setUp() {
adapter = new StrategyDslToTa4jAdapter();
series = buildSyntheticSeries(40);
}
@Test
void existsIn_trueWhenInnerTrueInWindow() throws Exception {
Rule rule = toGtRule("""
"candleRangeMode": "EXISTS_IN",
"candleRange": 4
""");
int idx = series.getEndIndex();
assertTrue(rule.isSatisfied(idx, null),
"close > 0 은 모든 봉에서 true → EXISTS_IN(4) 도 true");
}
@Test
void notExistsIn_falseWhenInnerTrueInWindow() throws Exception {
Rule rule = toGtRule("""
"candleRangeMode": "NOT_EXISTS_IN",
"candleRange": 4
""");
int idx = series.getEndIndex();
assertFalse(rule.isSatisfied(idx, null),
"close > 0 이 윈도우 내 항상 true → NOT_EXISTS_IN 은 false");
}
@Test
void legacyCandleRangeFour_treatedAsExistsIn() throws Exception {
Rule rule = toGtRule("""
"candleRange": 4
""");
int idx = series.getEndIndex();
assertTrue(rule.isSatisfied(idx, null),
"candleRangeMode 미설정 + candleRange=4 → EXISTS_IN 호환");
}
@Test
void currentMode_sameAsInnerWithoutWindow() throws Exception {
Rule current = toGtRule("""
"candleRangeMode": "CURRENT",
"candleRange": 1
""");
Rule plain = toGtRule("");
int idx = series.getEndIndex();
assertEquals(plain.isSatisfied(idx, null), current.isSatisfied(idx, null));
}
@Test
void notExistsIn_trueWhenInnerNeverTrueInWindow() throws Exception {
Rule rule = toGtRule("""
"candleRangeMode": "NOT_EXISTS_IN",
"candleRange": 3,
"rightField": "K_999999999999"
""");
int idx = series.getEndIndex();
assertTrue(rule.isSatisfied(idx, null),
"close > 매우 큰 값 은 윈도우 내 항상 false → NOT_EXISTS_IN true");
}
@Test
void obvCrossUp_existsInWindow_compilesAndEvaluates() throws Exception {
JsonNode cond = MAPPER.readTree("""
{
"id": "c-obv-exists",
"type": "CONDITION",
"condition": {
"indicatorType": "OBV",
"conditionType": "CROSS_UP",
"leftField": "OBV_LINE",
"rightField": "OBV_SIGNAL",
"candleRangeMode": "EXISTS_IN",
"candleRange": 4
}
}
""");
Rule rule = adapter.toRule(cond, series, java.util.Map.of());
assertNotNull(rule);
assertDoesNotThrow(() -> rule.isSatisfied(series.getEndIndex(), null));
}
private Rule toGtRule(String extraFields) throws Exception {
String extra = extraFields == null || extraFields.isBlank()
? ""
: ",\n " + extraFields.strip().replaceAll(",\\s*$", "");
String json = """
{
"id": "c-range",
"type": "CONDITION",
"condition": {
"indicatorType": "MA",
"conditionType": "GT",
"leftField": "CLOSE_PRICE",
"rightField": "K_0"%s
}
}
""".formatted(extra);
JsonNode node = MAPPER.readTree(json);
Rule rule = adapter.toRule(node, series, java.util.Map.of());
assertNotNull(rule);
return rule;
}
private static BarSeries buildSyntheticSeries(int barCount) {
BarSeries s = new BaseBarSeriesBuilder()
.withName("test-1m")
.withNumFactory(DoubleNumFactory.getInstance())
.withBarBuilderFactory(new TimeBarBuilderFactory())
.build();
TimeBarBuilderFactory factory = new TimeBarBuilderFactory();
Instant base = Instant.parse("2024-06-01T00:00:00Z");
Duration period = Duration.ofMinutes(1);
for (int i = 0; i < barCount; i++) {
double close = 50_000_000 + i * 120;
Instant end = base.plus(period.multipliedBy(i + 1L));
factory.createBarBuilder(s)
.timePeriod(period)
.endTime(end)
.openPrice(close - 500)
.highPrice(close + 800)
.lowPrice(close - 800)
.closePrice(close)
.volume(10 + i)
.add();
}
return s;
}
}