Administrator
6 days ago 97b7f914d060b2a052299416384fc80e061f4b1d
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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<BigDecimal>
 * - 参数说明:需要至少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<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.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;
    }
}