package com.goldenchart.service; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.filter.Filter; import ch.qos.logback.core.read.ListAppender; import ch.qos.logback.core.spi.FilterReply; import org.slf4j.LoggerFactory; import org.ta4j.core.Rule; import org.ta4j.core.TradingRecord; /** * Ta4j Rule 평가 시 TRACE 로그를 메모리에 수집 (AI 검증용). */ final class Ta4jRuleTraceCapture implements AutoCloseable { private static final int MAX_LINES = 100; private static final int MAX_LINE_LEN = 600; private final ListAppender appender; private final Logger rulesLogger; private final Level previousLevel; private Ta4jRuleTraceCapture(ListAppender appender, Logger rulesLogger, Level previousLevel) { this.appender = appender; this.rulesLogger = rulesLogger; this.previousLevel = previousLevel; } static Ta4jRuleTraceCapture open() { LoggerContext ctx = (LoggerContext) LoggerFactory.getILoggerFactory(); Logger rulesLogger = ctx.getLogger("org.ta4j.core.rules"); Level prev = rulesLogger.getLevel(); ListAppender appender = new ListAppender<>(); appender.addFilter(new Filter<>() { @Override public FilterReply decide(ILoggingEvent event) { if (event == null || event.getLoggerName() == null) return FilterReply.DENY; return event.getLoggerName().startsWith("org.ta4j.core.rules") ? FilterReply.ACCEPT : FilterReply.DENY; } }); appender.setContext(ctx); appender.start(); rulesLogger.setLevel(Level.TRACE); rulesLogger.setAdditive(true); rulesLogger.addAppender(appender); return new Ta4jRuleTraceCapture(appender, rulesLogger, prev); } boolean evaluateWithTrace(Rule rule, int index, TradingRecord tradingRecord) { return rule.isSatisfiedWithTraceMode(index, tradingRecord, Rule.TraceMode.VERBOSE); } java.util.List lines() { return appender.list.stream() .filter(e -> e.getLevel().isGreaterOrEqual(Level.TRACE)) .map(ILoggingEvent::getFormattedMessage) .map(Ta4jRuleTraceCapture::truncate) .limit(MAX_LINES) .toList(); } @Override public void close() { rulesLogger.detachAppender(appender); appender.stop(); rulesLogger.setLevel(previousLevel); } private static String truncate(String s) { if (s == null) return ""; if (s.length() <= MAX_LINE_LEN) return s; return s.substring(0, MAX_LINE_LEN) + "…"; } }