일목-BB 수정
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user