Administrator
5 days ago 0f3d23847ceda5fc72bb6a971ffdfaf881631b95
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
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;
 
/**
 * BOLL (Bollinger Bands) 指标实现
 * 计算逻辑:
 * 1. 中轨(MB)= N日移动平均线
 * 2. 上轨(UP)= 中轨 + K倍标准差
 * 3. 下轨(DN)= 中轨 - K倍标准差
 */
@Slf4j
@Getter
@Setter
public class BOLL extends IndicatorBase {
 
    private static final int DEFAULT_PERIOD = 20;
    private static final double DEFAULT_K = 2.0;
 
    private int period = DEFAULT_PERIOD;
    private BigDecimal k = new BigDecimal(DEFAULT_K);
    private BigDecimal mid = BigDecimal.ZERO;
    private BigDecimal upper = BigDecimal.ZERO;
    private BigDecimal lower = BigDecimal.ZERO;
 
    public BOLL() {}
 
    public BOLL(int period, double k) {
        this.period = period;
        this.k = new BigDecimal(k);
    }
 
    /**
     * 计算BOLL指标
     * @param prices 价格列表
     */
    public void calculate(List<BigDecimal> prices) {
        if (prices == null || prices.size() < period) {
            return;
        }
 
        // 计算中轨(MB)= N日移动平均线
        mid = calculateMA(prices, period);
        
        // 计算标准差
        BigDecimal stdDev = calculateStdDev(prices, period);
        
        // 计算上轨(UP)和下轨(DN)
        BigDecimal bandWidth = k.multiply(stdDev);
        upper = mid.add(bandWidth).setScale(8, RoundingMode.HALF_UP);
        lower = mid.subtract(bandWidth).setScale(8, RoundingMode.HALF_UP);
        
        log.debug("BOLL计算结果 - 中轨: {}, 上轨: {}, 下轨: {}", mid, upper, lower);
    }
 
    /**
     * 判断价格是否突破上轨
     * @param price 当前价格
     * @return 是否突破上轨
     */
    public boolean isBreakUpper(BigDecimal price) {
        return price.compareTo(upper) > 0;
    }
 
    /**
     * 判断价格是否跌破下轨
     * @param price 当前价格
     * @return 是否跌破下轨
     */
    public boolean isBreakLower(BigDecimal price) {
        return price.compareTo(lower) < 0;
    }
 
    /**
     * 判断价格是否回归上轨下方
     * @param price 当前价格
     * @param prevPrice 前一期价格
     * @return 是否回归上轨下方
     */
    public boolean isReturnFromUpper(BigDecimal price, BigDecimal prevPrice) {
        return prevPrice.compareTo(upper) > 0 && price.compareTo(upper) <= 0;
    }
 
    /**
     * 判断价格是否回归下轨上方
     * @param price 当前价格
     * @param prevPrice 前一期价格
     * @return 是否回归下轨上方
     */
    public boolean isReturnFromLower(BigDecimal price, BigDecimal prevPrice) {
        return prevPrice.compareTo(lower) < 0 && price.compareTo(lower) >= 0;
    }
 
    /**
     * 判断价格是否在中轨上方
     * @param price 当前价格
     * @return 是否在中轨上方
     */
    public boolean isAboveMid(BigDecimal price) {
        return price.compareTo(mid) > 0;
    }
 
    /**
     * 判断价格是否在中轨下方
     * @param price 当前价格
     * @return 是否在中轨下方
     */
    public boolean isBelowMid(BigDecimal price) {
        return price.compareTo(mid) < 0;
    }
 
    /**
     * 计算布林带宽度
     * @return 布林带宽度
     */
    public BigDecimal calculateBandWidth() {
        if (mid.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        }
        return upper.subtract(lower).divide(mid, 8, RoundingMode.HALF_UP);
    }
 
    /**
     * 计算价格相对于布林带的位置
     * @param price 当前价格
     * @return 价格位置指标 (-1: 下轨外, 0: 轨道内, 1: 上轨外)
     */
    public int getPricePosition(BigDecimal price) {
        if (price.compareTo(upper) > 0) {
            return 1;
        } else if (price.compareTo(lower) < 0) {
            return -1;
        } else {
            return 0;
        }
    }
}