전략 수정

This commit is contained in:
Macbook
2026-06-17 01:33:30 +09:00
parent 7c3f3433e7
commit f56e925509
4 changed files with 227 additions and 29 deletions
@@ -117,6 +117,165 @@ class CciChartScanIntegrationTest {
assertTrue(hits > 0, "candleTypes[0]=3m must win over stale candleType=1m, got " + hits);
}
@Test
void chartScan_5mStrategy_remappedTo3mChart_firesSignals() throws Exception {
String chartTf = "3m";
List<OhlcvBar> bars = buildOscillatingOhlcvBars(150, chartTf);
BarSeries primarySeries = OhlcvBarSeriesSupport.buildSeries(bars, chartTf);
JsonNode buyDslRaw = MAPPER.readTree("""
{
"type": "TIMEFRAME",
"candleTypes": ["5m"],
"candleType": "5m",
"children": [{
"type": "CONDITION",
"condition": {
"indicatorType": "CCI",
"conditionType": "CROSS_UP",
"leftField": "CCI_VALUE_20",
"rightField": "CCI_SIGNAL",
"period": 20,
"valuePeriodOverride": true,
"candleRange": 1
}
}]
}
""");
JsonNode buyDsl = normalizer.remapForChartTimeframe(buyDslRaw, chartTf);
assertEquals("3m", buyDsl.path("candleType").asText());
assertEquals("3m", buyDsl.path("candleTypes").get(0).asText());
Map<String, BarSeries> seriesOverrides = OhlcvBarSeriesSupport.buildSeriesOverrides(
primarySeries, chartTf, buyDsl, null);
Map<String, Map<String, Object>> params = Map.of(
"CCI", Map.of("length", 20, "maLength", 10, "maType", "SMA", "src", "hlc3"));
StrategyDslToTa4jAdapter.RuleBuildContext ctx =
new StrategyDslToTa4jAdapter.RuleBuildContext(
primarySeries, params, Map.of(), "KRW-BTC", null, false, seriesOverrides);
Rule rule = adapter.toRule(buyDsl, ctx);
int hits = 0;
for (int i = primarySeries.getBeginIndex(); i <= primarySeries.getEndIndex(); i++) {
if (rule.isSatisfied(i, null)) hits++;
}
assertTrue(hits > 0, "5m strategy on 3m chart should detect CCI crosses after remap, got " + hits);
}
@Test
void chartScan_5mNativeOn3mChart_aggregatedSeries_firesSignals() throws Exception {
String chartTf = "3m";
List<OhlcvBar> bars = buildOscillatingOhlcvBars(150, chartTf);
BarSeries primarySeries = OhlcvBarSeriesSupport.buildSeries(bars, chartTf);
// remap 없음 — 5m TIMEFRAME + 3m 차트 (CrossSeriesRule 집계 경로)
JsonNode buyDsl = MAPPER.readTree("""
{
"type": "TIMEFRAME",
"candleTypes": ["5m"],
"candleType": "5m",
"children": [{
"type": "CONDITION",
"condition": {
"indicatorType": "CCI",
"conditionType": "CROSS_UP",
"leftField": "CCI_VALUE_20",
"rightField": "CCI_SIGNAL",
"period": 20,
"valuePeriodOverride": true
}
}]
}
""");
Map<String, BarSeries> seriesOverrides = OhlcvBarSeriesSupport.buildSeriesOverrides(
primarySeries, chartTf, buyDsl, null);
StrategyDslToTa4jAdapter.RuleBuildContext ctx =
new StrategyDslToTa4jAdapter.RuleBuildContext(
primarySeries, Map.of("CCI", Map.of("length", 20, "maLength", 10, "maType", "SMA")),
Map.of(), "KRW-BTC", null, false, seriesOverrides);
Rule rule = adapter.toRule(buyDsl, ctx);
int hits = 0;
for (int i = primarySeries.getBeginIndex(); i <= primarySeries.getEndIndex(); i++) {
if (rule.isSatisfied(i, null)) hits++;
}
assertTrue(hits > 0, "5m native on 3m chart via aggregation should fire, got " + hits);
}
@Test
void cciValueField_period20_matchesValue20Suffix() throws Exception {
BarSeries series = buildOscillatingOhlcvBars(150, "3m").stream()
.collect(java.util.stream.Collectors.collectingAndThen(
java.util.stream.Collectors.toList(),
list -> OhlcvBarSeriesSupport.buildSeries(list, "3m")));
Map<String, Object> p = Map.of("length", 13, "maLength", 10, "maType", "SMA", "src", "hlc3");
JsonNode condPlain = MAPPER.readTree("""
{"period":20,"leftField":"CCI_VALUE","rightField":"CCI_SIGNAL","valuePeriodOverride":true}
""");
JsonNode condSuffix = MAPPER.readTree("""
{"period":20,"leftField":"CCI_VALUE_20","rightField":"CCI_SIGNAL","valuePeriodOverride":true}
""");
var plainValue = adapter.resolveIndicatorFieldForTest(
"CCI_VALUE", "CCI", p, series, 20, -1, condPlain);
var suffixValue = adapter.resolveIndicatorFieldForTest(
"CCI_VALUE_20", "CCI", p, series, 20, -1, condSuffix);
var plainSignal = adapter.resolveIndicatorFieldForTest(
"CCI_SIGNAL", "CCI", p, series, 20, -1, condPlain);
var suffixSignal = adapter.resolveIndicatorFieldForTest(
"CCI_SIGNAL", "CCI", p, series, 20, -1, condSuffix);
int idx = 80;
assertEquals(suffixValue.getValue(idx).doubleValue(), plainValue.getValue(idx).doubleValue(), 1e-6,
"CCI_VALUE+period must match CCI_VALUE_20");
assertEquals(suffixSignal.getValue(idx).doubleValue(), plainSignal.getValue(idx).doubleValue(), 1e-6,
"CCI_SIGNAL must match for both left field forms");
assertNotEquals(suffixValue.getValue(idx).doubleValue(), suffixSignal.getValue(idx).doubleValue(), 1e-6,
"CCI_SIGNAL must differ from raw CCI at index " + idx);
}
@Test
void chartScan_cciValueFieldWithPeriod20_firesSignals() throws Exception {
String chartTf = "3m";
List<OhlcvBar> bars = buildOscillatingOhlcvBars(150, chartTf);
BarSeries primarySeries = OhlcvBarSeriesSupport.buildSeries(bars, chartTf);
JsonNode buyDsl = MAPPER.readTree("""
{
"type": "TIMEFRAME",
"candleTypes": ["3m"],
"candleType": "3m",
"children": [{
"type": "CONDITION",
"condition": {
"indicatorType": "CCI",
"conditionType": "CROSS_UP",
"leftField": "CCI_VALUE",
"rightField": "CCI_SIGNAL",
"period": 20,
"valuePeriodOverride": true
}
}]
}
""");
Map<String, BarSeries> seriesOverrides = OhlcvBarSeriesSupport.buildSeriesOverrides(
primarySeries, chartTf, buyDsl, null);
StrategyDslToTa4jAdapter.RuleBuildContext ctx =
new StrategyDslToTa4jAdapter.RuleBuildContext(
primarySeries, Map.of("CCI", Map.of("length", 13, "maLength", 10, "maType", "SMA")),
Map.of(), null, null, false, seriesOverrides);
Rule rule = adapter.toRule(buyDsl, ctx);
int hits = 0;
for (int i = primarySeries.getBeginIndex(); i <= primarySeries.getEndIndex(); i++) {
if (rule.isSatisfied(i, null)) hits++;
}
assertTrue(hits > 0, "CCI_VALUE + period=20 must use period not global length=13, got " + hits);
}
private static List<OhlcvBar> buildOscillatingOhlcvBars(int count, String tf) {
long tfSec = switch (OhlcvBarSeriesSupport.normalizeTf(tf)) {
case "3m" -> 180;