추세검색 설정 수정
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
package com.goldenchart.service;
|
||||
|
||||
import com.goldenchart.dto.TrendSearchRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* 추세검색 상승추세 배점·합격 점수 로직 검증.
|
||||
*/
|
||||
class TrendSearchBullishScoringTest {
|
||||
|
||||
private static TrendSearchBullishScoring.BullishFlags flags(
|
||||
boolean align, boolean slope, boolean adx, boolean macd, boolean price) {
|
||||
return new TrendSearchBullishScoring.BullishFlags() {
|
||||
@Override public boolean bullishEmaAlignment() { return align; }
|
||||
@Override public boolean bullishEmaSlope() { return slope; }
|
||||
@Override public boolean bullishAdxTrend() { return adx; }
|
||||
@Override public boolean bullishMacdMomentum() { return macd; }
|
||||
@Override public boolean bullishPricePosition() { return price; }
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
void allPass_defaultWeights_scores100() {
|
||||
TrendSearchRequest r = new TrendSearchRequest();
|
||||
int score = TrendSearchBullishScoring.computeScore(
|
||||
flags(true, true, true, true, true), r);
|
||||
assertEquals(100, score);
|
||||
}
|
||||
|
||||
@Test
|
||||
void partialPass_sumsOnlyMatchedWeights() {
|
||||
TrendSearchRequest r = new TrendSearchRequest();
|
||||
r.setWeightMaAlignment(40);
|
||||
r.setWeightMaSlope(10);
|
||||
r.setWeightAdxTrend(20);
|
||||
r.setWeightMacdMomentum(15);
|
||||
r.setWeightPricePosition(15);
|
||||
int score = TrendSearchBullishScoring.computeScore(
|
||||
flags(true, false, true, false, true), r);
|
||||
assertEquals(75, score);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customWeights_onlyAlignment_countsAlignmentOnly() {
|
||||
TrendSearchRequest r = new TrendSearchRequest();
|
||||
r.setWeightMaAlignment(50);
|
||||
r.setWeightMaSlope(0);
|
||||
r.setWeightAdxTrend(0);
|
||||
r.setWeightMacdMomentum(0);
|
||||
r.setWeightPricePosition(0);
|
||||
assertEquals(50, TrendSearchBullishScoring.computeScore(
|
||||
flags(true, true, true, true, true), r));
|
||||
assertEquals(0, TrendSearchBullishScoring.computeScore(
|
||||
flags(false, true, true, true, true), r));
|
||||
}
|
||||
|
||||
@Test
|
||||
void scoreCappedAt100_whenWeightsOverflow() {
|
||||
TrendSearchRequest r = new TrendSearchRequest();
|
||||
r.setWeightMaAlignment(60);
|
||||
r.setWeightMaSlope(60);
|
||||
r.setWeightAdxTrend(60);
|
||||
r.setWeightMacdMomentum(60);
|
||||
r.setWeightPricePosition(60);
|
||||
assertEquals(100, TrendSearchBullishScoring.computeScore(
|
||||
flags(true, true, true, true, true), r));
|
||||
}
|
||||
|
||||
@Test
|
||||
void minTrendScore_filter() {
|
||||
TrendSearchRequest r = new TrendSearchRequest();
|
||||
r.setMinTrendScore(70);
|
||||
assertTrue(TrendSearchBullishScoring.passesMinTrendScore(70, r));
|
||||
assertTrue(TrendSearchBullishScoring.passesMinTrendScore(85, r));
|
||||
assertFalse(TrendSearchBullishScoring.passesMinTrendScore(69, r));
|
||||
r.setMinTrendScore(0);
|
||||
assertTrue(TrendSearchBullishScoring.passesMinTrendScore(0, r));
|
||||
}
|
||||
|
||||
@Test
|
||||
void negativeWeights_treatedAsZero() {
|
||||
TrendSearchRequest r = new TrendSearchRequest();
|
||||
r.setWeightMaAlignment(-10);
|
||||
r.setWeightMaSlope(20);
|
||||
assertEquals(20, TrendSearchBullishScoring.computeScore(flags(true, true, false, false, false), r));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.goldenchart.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.goldenchart.dto.CandleBarDto;
|
||||
import com.goldenchart.dto.TrendSearchRequest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.ta4j.core.BarSeries;
|
||||
import org.ta4j.core.BaseBarSeriesBuilder;
|
||||
import org.ta4j.core.bars.TimeBarBuilderFactory;
|
||||
import org.ta4j.core.num.DoubleNumFactory;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* 합성 상승 시계열에서 지표·배점 점수가 일관되는지 검증.
|
||||
*/
|
||||
class TrendSearchServiceMetricsTest {
|
||||
|
||||
private TrendSearchService service;
|
||||
private Method computeMetrics;
|
||||
private Method computeBullishTrendScore;
|
||||
private Class<?> tickerSnapClass;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
service = new TrendSearchService(
|
||||
mock(HistoricalDataService.class),
|
||||
WebClient.builder(),
|
||||
new ObjectMapper());
|
||||
for (Class<?> c : TrendSearchService.class.getDeclaredClasses()) {
|
||||
if ("TickerSnap".equals(c.getSimpleName())) tickerSnapClass = c;
|
||||
}
|
||||
for (Method m : TrendSearchService.class.getDeclaredMethods()) {
|
||||
if ("computeMetrics".equals(m.getName())) computeMetrics = m;
|
||||
if ("computeBullishTrendScore".equals(m.getName())) computeBullishTrendScore = m;
|
||||
}
|
||||
assertNotNull(computeMetrics);
|
||||
assertNotNull(computeBullishTrendScore);
|
||||
computeMetrics.setAccessible(true);
|
||||
computeBullishTrendScore.setAccessible(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void strongUptrend_series_earnsHighBullishScore() throws Exception {
|
||||
BarSeries series = buildUptrendSeries(150);
|
||||
List<CandleBarDto> candles = toCandles(series);
|
||||
Object tick = newTickerSnap();
|
||||
TrendSearchRequest req = new TrendSearchRequest();
|
||||
|
||||
Object metrics = computeMetrics.invoke(service, series, candles, tick, req);
|
||||
int score = (int) computeBullishTrendScore.invoke(service, metrics, req);
|
||||
|
||||
assertTrue(score >= 70,
|
||||
"상승 추세 합성 데이터는 기본 배점(합 100) 기준 70점 이상이어야 함, actual=" + score);
|
||||
}
|
||||
|
||||
@Test
|
||||
void highMinTrendScore_excludesLowScores_inScanLogic() {
|
||||
TrendSearchRequest r = new TrendSearchRequest();
|
||||
r.setMinTrendScore(95);
|
||||
assertFalse(TrendSearchBullishScoring.passesMinTrendScore(80, r));
|
||||
assertTrue(TrendSearchBullishScoring.passesMinTrendScore(95, r));
|
||||
}
|
||||
|
||||
private Object newTickerSnap() throws Exception {
|
||||
assertNotNull(tickerSnapClass);
|
||||
var ctor = tickerSnapClass.getDeclaredConstructor(
|
||||
String.class, String.class, double.class, double.class, double.class);
|
||||
ctor.setAccessible(true);
|
||||
return ctor.newInstance("KRW-TEST", "테스트", 100.0, 5.0, 1_000_000_000.0);
|
||||
}
|
||||
|
||||
private static BarSeries buildUptrendSeries(int bars) {
|
||||
Duration period = Duration.ofDays(1);
|
||||
BarSeries series = new BaseBarSeriesBuilder()
|
||||
.withName("uptrend")
|
||||
.withNumFactory(DoubleNumFactory.getInstance())
|
||||
.withBarBuilderFactory(new TimeBarBuilderFactory())
|
||||
.build();
|
||||
TimeBarBuilderFactory factory = new TimeBarBuilderFactory();
|
||||
Instant base = Instant.parse("2020-01-01T00:00:00Z");
|
||||
double price = 100;
|
||||
for (int i = 0; i < bars; i++) {
|
||||
double close = price * 1.008;
|
||||
double high = Math.max(price, close) * 1.005;
|
||||
double low = Math.min(price, close) * 0.995;
|
||||
factory.createBarBuilder(series)
|
||||
.timePeriod(period)
|
||||
.endTime(base.plus(period.multipliedBy(i + 1L)))
|
||||
.openPrice(price).highPrice(high).lowPrice(low).closePrice(close)
|
||||
.volume(1_000_000 + i * 1000L)
|
||||
.add();
|
||||
price = close;
|
||||
}
|
||||
return series;
|
||||
}
|
||||
|
||||
private static List<CandleBarDto> toCandles(BarSeries series) {
|
||||
List<CandleBarDto> out = new ArrayList<>();
|
||||
for (int i = 0; i < series.getBarCount(); i++) {
|
||||
var bar = series.getBar(i);
|
||||
out.add(CandleBarDto.builder()
|
||||
.time(bar.getBeginTime().getEpochSecond())
|
||||
.open(bar.getOpenPrice().doubleValue())
|
||||
.high(bar.getHighPrice().doubleValue())
|
||||
.low(bar.getLowPrice().doubleValue())
|
||||
.close(bar.getClosePrice().doubleValue())
|
||||
.volume(bar.getVolume().doubleValue())
|
||||
.build());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.goldenchart.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.goldenchart.dto.TrendSearchRequest;
|
||||
import com.goldenchart.entity.GcAppSettings;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* 앱 설정 JSON → 스캔 요청 배점 병합 검증.
|
||||
*/
|
||||
class TrendSearchSettingsHelperTest {
|
||||
|
||||
private TrendSearchSettingsHelper helper;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
helper = new TrendSearchSettingsHelper(new ObjectMapper());
|
||||
}
|
||||
|
||||
@Test
|
||||
void mergeRequestWithSettings_appliesWeightsFromJson() throws Exception {
|
||||
GcAppSettings app = GcAppSettings.builder()
|
||||
.deviceId("test-device")
|
||||
.trendSearchSettingsJson("""
|
||||
{"weightMaAlignment":50,"weightMaSlope":10,"weightAdxTrend":15,
|
||||
"weightMacdMomentum":10,"weightPricePosition":15,"minTrendScore":60,
|
||||
"limit":15,"scanLimit":40}
|
||||
""")
|
||||
.build();
|
||||
|
||||
TrendSearchRequest body = new TrendSearchRequest();
|
||||
body.setWeightMaAlignment(30);
|
||||
body.setMinTrendScore(70);
|
||||
|
||||
TrendSearchRequest merged = helper.mergeRequestWithSettings(body, app);
|
||||
|
||||
assertEquals(50, merged.getWeightMaAlignment());
|
||||
assertEquals(10, merged.getWeightMaSlope());
|
||||
assertEquals(15, merged.getWeightAdxTrend());
|
||||
assertEquals(60, merged.getMinTrendScore());
|
||||
assertEquals(15, merged.getLimit());
|
||||
assertEquals(40, merged.getScanLimit());
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildRequest_usesLastRequestTimeframe_whenPresent() throws Exception {
|
||||
GcAppSettings app = GcAppSettings.builder()
|
||||
.defaultTimeframe("1D")
|
||||
.trendSearchSettingsJson("{\"weightMaAlignment\":25}")
|
||||
.build();
|
||||
|
||||
TrendSearchRequest last = new TrendSearchRequest();
|
||||
last.setTimeframe("4h");
|
||||
last.setWeightMaAlignment(99);
|
||||
|
||||
TrendSearchRequest built = helper.buildRequest(app, last);
|
||||
|
||||
assertEquals("4h", built.getTimeframe());
|
||||
assertEquals(25, built.getWeightMaAlignment());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user