Administrator
2025-12-23 5f436f56456240c99cc849d0a49a007386251492
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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
 */
@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<BigDecimal> prices) {
        if (prices == null || prices.size() < period) {
            return;
        }
 
        // 获取最近N天的价格
        List<BigDecimal> 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.debug("KDJ计算结果 - K: {}, D: {}, J: {}", k, d, j);
    }
 
    /**
     * 判断超买(K > 80)
     * @return 是否超买
     */
    public boolean isOverbought() {
        return k.compareTo(new BigDecimal(80)) > 0;
    }
 
    /**
     * 判断超卖(K < 20)
     * @return 是否超卖
     */
    public boolean isOversold() {
        return k.compareTo(new BigDecimal(20)) < 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;
    }
}