package com.xcong.excoin.modules.okxNewPrice.indicator; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.List; /** * KDJ (Stochastic Oscillator) 指标实现 * 计算逻辑: * 1. RSV = (收盘价 - N日内最低价) / (N日内最高价 - N日内最低价) * 100 * 2. K = 2/3 * 前一日K值 + 1/3 * 当日RSV * 3. D = 2/3 * 前一日D值 + 1/3 * 当日K值 * 4. J = 3*K - 2*D * * 作用: * 1. 衡量价格的超买超卖状态(K值>80超买,K值<20超卖) * 2. K线上穿D线形成金叉,提示买入信号 * 3. K线下穿D线形成死叉,提示卖出信号 * 4. J值反映市场的极端状态,J值>100或J值<0为极端行情 * * 价格参数类型: * - 参数名称:prices * - 参数类型:List * - 参数说明:需要至少9个(默认周期)价格数据点用于计算 * * 推荐时间粒度及优缺点: * 1. 1分钟(1m): * - 优点:反应迅速,适合超短线交易 * - 缺点:K值波动剧烈,信号频繁且可靠性低 * 2. 5分钟(5m): * - 优点:K值波动相对稳定,适合短线交易 * - 缺点:仍有一定虚假信号 * 3. 15分钟(15m): * - 优点:信号较为可靠,适合日内交易 * - 缺点:反应速度较慢 * 4. 1小时(1h)及以上: * - 优点:超买超卖信号明确,适合中期交易 * - 缺点:反应滞后,不适合短线交易 */ @Slf4j @Getter @Setter public class KDJ extends IndicatorBase { private static final int DEFAULT_PERIOD = 9; private static final int K_PERIOD = 3; private static final int D_PERIOD = 3; private int period = DEFAULT_PERIOD; private BigDecimal k = new BigDecimal(50); private BigDecimal d = new BigDecimal(50); private BigDecimal j = new BigDecimal(50); private BigDecimal prevK = new BigDecimal(50); private BigDecimal prevD = new BigDecimal(50); public KDJ() {} public KDJ(int period) { this.period = period; } /** * 计算KDJ指标 * @param prices 价格列表 */ public void calculate(List prices) { if (prices == null || prices.size() < period) { return; } // 获取最近N天的价格 List recentPrices = getRecentPrices(prices, period); // 计算最高价和最低价 BigDecimal high = recentPrices.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO); BigDecimal low = recentPrices.stream().min(BigDecimal::compareTo).orElse(BigDecimal.ZERO); BigDecimal close = recentPrices.get(recentPrices.size() - 1); // 计算RSV BigDecimal rsv; if (high.compareTo(low) == 0) { rsv = new BigDecimal(50); } else { rsv = close.subtract(low) .divide(high.subtract(low), 8, RoundingMode.HALF_UP) .multiply(new BigDecimal(100)) .setScale(8, RoundingMode.HALF_UP); } // 计算K值 prevK = k; k = new BigDecimal(2).multiply(prevK) .add(rsv) .divide(new BigDecimal(3), 8, RoundingMode.HALF_UP); // 计算D值 prevD = d; d = new BigDecimal(2).multiply(prevD) .add(k) .divide(new BigDecimal(3), 8, RoundingMode.HALF_UP); // 计算J值 j = k.multiply(new BigDecimal(3)) .subtract(d.multiply(new BigDecimal(2))) .setScale(8, RoundingMode.HALF_UP); log.info("KDJ计算结果 - K: {}, D: {}, J: {}", k, d, j); } /** * 判断超买(J > 85) * @return 是否超买 */ public boolean isOverbought() { return j.compareTo(new BigDecimal(85)) > 0; } /** * 判断超卖(J < 15) * @return 是否超卖 */ public boolean isOversold() { return j.compareTo(new BigDecimal(15)) < 0; } /** * 判断金叉信号(K线上穿D线) * @return 是否形成金叉 */ public boolean isGoldenCross() { return prevK.compareTo(prevD) < 0 && k.compareTo(d) > 0; } /** * 判断死叉信号(K线下穿D线) * @return 是否形成死叉 */ public boolean isDeathCross() { return prevK.compareTo(prevD) > 0 && k.compareTo(d) < 0; } }