전략평가 로직 오류 수정
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
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.num.DoubleNumFactory;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* CCI 본선(CCI_VALUE_20) ↔ 신호선(CCI_SIGNAL) 교차 — 차트와 동일 Rule 평가.
|
||||
*/
|
||||
class CciCrossSignalScanTest {
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
private StrategyDslToTa4jAdapter adapter;
|
||||
private BarSeries series;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
adapter = new StrategyDslToTa4jAdapter();
|
||||
series = buildOscillatingSeries(150);
|
||||
}
|
||||
|
||||
@Test
|
||||
void cciSignal_usesLeftFieldPeriod_notGlobalLength() throws Exception {
|
||||
JsonNode cond = MAPPER.readTree("""
|
||||
{
|
||||
"indicatorType": "CCI",
|
||||
"conditionType": "CROSS_UP",
|
||||
"leftField": "CCI_VALUE_20",
|
||||
"rightField": "CCI_SIGNAL",
|
||||
"period": 20,
|
||||
"valuePeriodOverride": true
|
||||
}
|
||||
""");
|
||||
Map<String, Object> params = Map.of("length", 13, "maLength", 10, "maType", "SMA", "src", "hlc3");
|
||||
|
||||
var value20 = adapter.resolveIndicatorFieldForTest(
|
||||
"CCI_VALUE_20", "CCI", params, series, 20, -1, cond);
|
||||
var signalLinked = adapter.resolveIndicatorFieldForTest(
|
||||
"CCI_SIGNAL", "CCI", params, series, 20, -1, cond);
|
||||
var signalUnlinked = adapter.resolveIndicatorFieldForTest(
|
||||
"CCI_SIGNAL", "CCI", params, series, -1, -1, null);
|
||||
|
||||
int idx = 80;
|
||||
assertNotEquals(signalLinked.getValue(idx).doubleValue(), signalUnlinked.getValue(idx).doubleValue(), 1e-9,
|
||||
"CCI_SIGNAL must use CCI(20) from leftField, not global length=13");
|
||||
assertNotEquals(value20.getValue(idx).doubleValue(), signalLinked.getValue(idx).doubleValue(), 1e-9,
|
||||
"signal line is SMA(CCI), not raw CCI");
|
||||
}
|
||||
|
||||
@Test
|
||||
void cciCrossUp_firesOnAtLeastOneBar() throws Exception {
|
||||
JsonNode buyDsl = MAPPER.readTree("""
|
||||
{
|
||||
"type": "TIMEFRAME",
|
||||
"candleType": "5m",
|
||||
"children": [{
|
||||
"type": "CONDITION",
|
||||
"condition": {
|
||||
"indicatorType": "CCI",
|
||||
"conditionType": "CROSS_UP",
|
||||
"leftField": "CCI_VALUE_20",
|
||||
"rightField": "CCI_SIGNAL",
|
||||
"period": 20,
|
||||
"valuePeriodOverride": true,
|
||||
"candleRange": 1
|
||||
}
|
||||
}]
|
||||
}
|
||||
""");
|
||||
Map<String, Map<String, Object>> indicatorParams = Map.of(
|
||||
"CCI", Map.of("length", 20, "maLength", 10, "maType", "SMA", "src", "hlc3"));
|
||||
|
||||
StrategyDslToTa4jAdapter.RuleBuildContext ctx =
|
||||
new StrategyDslToTa4jAdapter.RuleBuildContext(
|
||||
series, indicatorParams, Map.of(), null, null, false, Map.of());
|
||||
Rule rule = adapter.toRule(buyDsl, ctx);
|
||||
|
||||
int hits = 0;
|
||||
for (int i = series.getBeginIndex(); i <= series.getEndIndex(); i++) {
|
||||
if (rule.isSatisfied(i, null)) hits++;
|
||||
}
|
||||
assertTrue(hits > 0, "CCI(20) cross signal(10) should fire at least once on oscillating series, got " + hits);
|
||||
}
|
||||
|
||||
private static BarSeries buildOscillatingSeries(int count) {
|
||||
BaseBarSeriesBuilder builder = new BaseBarSeriesBuilder()
|
||||
.withName("cci-cross-test")
|
||||
.withNumFactory(DoubleNumFactory.getInstance())
|
||||
.withBarBuilderFactory(new TimeBarBuilderFactory(Duration.ofMinutes(5), 1));
|
||||
BarSeries s = builder.build();
|
||||
Instant t = Instant.parse("2024-06-01T00:00:00Z");
|
||||
double price = 100_000_000.0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
double wave = Math.sin(i * 0.22) * 800_000 + Math.sin(i * 0.07) * 400_000;
|
||||
double close = price + wave + i * 5000;
|
||||
double open = close - 20_000;
|
||||
double high = close + 80_000;
|
||||
double low = close - 80_000;
|
||||
s.addBar(t, open, high, low, close, 50 + i);
|
||||
t = t.plus(Duration.ofMinutes(5));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user