일목-BB 수정

This commit is contained in:
Macbook
2026-06-23 21:24:37 +09:00
parent 3e26373bfe
commit 5d90d1a74e
13 changed files with 226 additions and 239 deletions
@@ -1143,7 +1143,7 @@ public class StrategyDslToTa4jAdapter {
}
/**
* 일목 후행스팬(현재 종가) vs N봉 전 볼린저밴드 — 상향/하향 돌파·위/아래 유지.
* N봉 전 후행스팬(종가) vs N봉 전 볼린저밴드 — 동일 시점(현재봉 기준 N봉 전) 비교.
* cond.params.chikouDisplacement (기본 26), rightField: UPPER_BAND | MIDDLE_BAND | LOWER_BAND
*/
private Rule buildIchimokuBbRule(
@@ -1175,13 +1175,15 @@ public class StrategyDslToTa4jAdapter {
ClosePriceIndicator close = new ClosePriceIndicator(series);
Indicator<Num> bbLine = resolveField(bandField, "BOLLINGER", bbParams, series, -1, -1, cond, ctx);
// 후행스팬 = 해당 봉 종가. N봉 전 시점의 후행스팬·BB를 같은 인덱스(t-N)에서 비교.
Indicator<Num> priorLagging = new PreviousValueIndicator(close, d);
Indicator<Num> priorBb = new PreviousValueIndicator(bbLine, d);
Rule core = switch (condType) {
case "CROSS_UP" -> buildCrossUpRule(close, priorBb);
case "CROSS_DOWN" -> buildCrossDownRule(close, priorBb);
case "GT", "GTE" -> new OverIndicatorRule(close, priorBb);
case "LT", "LTE" -> new UnderIndicatorRule(close, priorBb);
case "CROSS_UP" -> buildCrossUpRule(priorLagging, priorBb);
case "CROSS_DOWN" -> buildCrossDownRule(priorLagging, priorBb);
case "GT", "GTE" -> new OverIndicatorRule(priorLagging, priorBb);
case "LT", "LTE" -> new UnderIndicatorRule(priorLagging, priorBb);
default -> {
log.warn("[Adapter] ICHIMOKU_BB 미지원 conditionType: {}", condType);
yield new BooleanRule(false);
@@ -0,0 +1,100 @@
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.bars.TimeBarBuilderFactory;
import org.ta4j.core.indicators.averages.SMAIndicator;
import org.ta4j.core.indicators.bollinger.BollingerBandsUpperIndicator;
import org.ta4j.core.indicators.helpers.ClosePriceIndicator;
import org.ta4j.core.indicators.helpers.PreviousValueIndicator;
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.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
/**
* ICHIMOKU_BB — N봉 전 후행스팬(종가) vs N봉 전 BB 동일 시점 비교.
*/
class IchimokuBbRuleTest {
private static final ObjectMapper MAPPER = new ObjectMapper();
private StrategyDslToTa4jAdapter adapter;
private BarSeries series;
@BeforeEach
void setUp() {
adapter = new StrategyDslToTa4jAdapter();
series = buildSeries(80);
}
@Test
void ichimokuBb_gt_usesSameOffsetForLaggingAndBb() throws Exception {
JsonNode cond = MAPPER.readTree("""
{
"type": "CONDITION",
"condition": {
"indicatorType": "ICHIMOKU_BB",
"conditionType": "GT",
"leftField": "LAGGING_SPAN",
"rightField": "UPPER_BAND",
"candleRange": 1,
"params": { "chikouDisplacement": 5 }
}
}
""");
int evalIndex = 40;
int d = 5;
Rule rule = adapter.toRule(cond, series, Map.of());
ClosePriceIndicator close = new ClosePriceIndicator(series);
SMAIndicator sma = new SMAIndicator(close, 20);
BollingerBandsUpperIndicator upper = new BollingerBandsUpperIndicator(
sma, series.numFactory().numOf(2));
PreviousValueIndicator priorClose = new PreviousValueIndicator(close, d);
PreviousValueIndicator priorUpper = new PreviousValueIndicator(upper, d);
boolean expectedSameOffset = priorClose.getValue(evalIndex)
.isGreaterThan(priorUpper.getValue(evalIndex));
boolean wrongCurrentClose = close.getValue(evalIndex)
.isGreaterThan(priorUpper.getValue(evalIndex));
assertNotEquals(expectedSameOffset, wrongCurrentClose,
"테스트 시계열에서 현재 종가 vs N봉 전 BB와 N봉 전 종가 vs N봉 전 BB가 달라야 함");
assertEquals(expectedSameOffset, rule.isSatisfied(evalIndex, null));
}
private static BarSeries buildSeries(int bars) {
BarSeries s = new BaseBarSeriesBuilder()
.withName("ichimoku-bb-test")
.withNumFactory(DoubleNumFactory.getInstance())
.withBarBuilderFactory(new TimeBarBuilderFactory())
.build();
TimeBarBuilderFactory factory = new TimeBarBuilderFactory();
Instant base = Instant.parse("2024-01-01T00:00:00Z");
Duration period = Duration.ofMinutes(3);
double price = 100.0;
for (int i = 0; i < bars; i++) {
double drift = Math.sin(i * 0.35) * 3.0 + (i > 35 ? (i - 35) * 0.4 : 0);
double c = price + drift;
factory.createBarBuilder(s)
.timePeriod(period)
.endTime(base.plus(period.multipliedBy(i + 1L)))
.openPrice(c).highPrice(c + 1.0).lowPrice(c - 1.0).closePrice(c)
.volume(1000)
.add();
price = c;
}
return s;
}
}