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.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;
|
}
|
}
|