Administrator
6 days ago fe4d5467b8431ef1461bc93e4eba4f9153125da9
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package com.xcong.excoin.modules.okxNewPrice.indicator;
 
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
 
/**
 * Technical indicators base class, provides common calculation methods
 * 
 * Indicator combination strategy:
 * 1. Trend judgment (MA/AdvancedMA/MACD): Determine the overall trend direction of prices
 * 2. Momentum confirmation (RSI/KDJ): Confirm the strength and sustainability of the current trend
 * 3. Volatility reference (BOLL): Determine reasonable price volatility range and breakthrough timing
 * 
 * Long/Short direction selection logic:
 * - Long signal: MA bullish arrangement + MACD golden cross + RSI(30-70) + BOLL price between upper and middle band
 * - Short signal: MA bearish arrangement + MACD death cross + RSI(30-70) + BOLL price between lower and middle band
 * - Consolidation signal: AdvancedMA three-line convergence + RSI(40-60) + BOLL bandwidth narrowing
 * 
 * Open and close position strategies:
 * - Open long: MA golden cross + MACD golden cross + KDJ golden cross + RSI(30-70) + price breaks through BOLL middle band
 * - Open short: MA death cross + MACD death cross + KDJ death cross + RSI(30-70) + price breaks below BOLL middle band
 * - Close long: MA death cross + MACD death cross + RSI overbought(>70) + price breaks below BOLL middle band
 * - Close short: MA golden cross + MACD golden cross + RSI oversold(<30) + price breaks through BOLL middle band
 */
public abstract class IndicatorBase {
 
    /**
     * Calculate moving average
     * @param prices Price list
     * @param period Period
     * @return Moving average value
     */
    protected BigDecimal calculateMA(List<BigDecimal> prices, int period) {
        if (prices == null || prices.size() < period) {
            return BigDecimal.ZERO;
        }
        BigDecimal sum = BigDecimal.ZERO;
        for (int i = prices.size() - period; i < prices.size(); i++) {
            sum = sum.add(prices.get(i));
        }
        return sum.divide(new BigDecimal(period), 8, RoundingMode.HALF_UP);
    }
 
    /**
     * Calculate exponential moving average
     * @param prices Price list
     * @param period Period
     * @param prevEMA Previous EMA value
     * @return Exponential moving average value
     */
    protected BigDecimal calculateEMA(List<BigDecimal> prices, int period, BigDecimal prevEMA) {
        if (prices == null || prices.size() == 0) {
            return BigDecimal.ZERO;
        }
        if (prevEMA == null || prevEMA.compareTo(BigDecimal.ZERO) == 0) {
            return calculateMA(prices, Math.min(period, prices.size()));
        }
        BigDecimal k = new BigDecimal(2).divide(new BigDecimal(period + 1), 8, RoundingMode.HALF_UP);
        BigDecimal currentPrice = prices.get(prices.size() - 1);
        return currentPrice.multiply(k).add(prevEMA.multiply(BigDecimal.ONE.subtract(k)));
    }
 
    /**
     * Calculate standard deviation
     * @param prices Price list
     * @param period Period
     * @return Standard deviation
     */
    protected BigDecimal calculateStdDev(List<BigDecimal> prices, int period) {
        if (prices == null || prices.size() < period) {
            return BigDecimal.ZERO;
        }
        BigDecimal ma = calculateMA(prices, period);
        BigDecimal sumSquares = BigDecimal.ZERO;
        for (int i = prices.size() - period; i < prices.size(); i++) {
            BigDecimal diff = prices.get(i).subtract(ma);
            sumSquares = sumSquares.add(diff.multiply(diff));
        }
        BigDecimal variance = sumSquares.divide(new BigDecimal(period), 8, RoundingMode.HALF_UP);
        return sqrt(variance);
    }
 
    /**
     * Calculate square root (simplified implementation)
     * @param value Input value
     * @return Square root
     */
    protected BigDecimal sqrt(BigDecimal value) {
        if (value.compareTo(BigDecimal.ZERO) < 0) {
            return BigDecimal.ZERO;
        }
        return new BigDecimal(Math.sqrt(value.doubleValue())).setScale(8, RoundingMode.HALF_UP);
    }
 
    /**
     * Get recent price data
     * @param prices All price data
     * @param period Period
     * @return Recent period price data
     */
    protected List<BigDecimal> getRecentPrices(List<BigDecimal> prices, int period) {
        if (prices == null || prices.size() == 0) {
            return new ArrayList<>();
        }
        int startIndex = Math.max(0, prices.size() - period);
        return prices.subList(startIndex, prices.size());
    }
 
    /**
     * Calculate ATR (Average True Range)
     * @param high High price list
     * @param low Low price list
     * @param close Close price list
     * @param period Period
     * @return ATR value
     */
    protected BigDecimal calculateATR(List<BigDecimal> high, List<BigDecimal> low, List<BigDecimal> close, int period) {
        if (high == null || low == null || close == null || 
            high.size() < period || low.size() < period || close.size() < period) {
            return BigDecimal.ZERO;
        }
 
        List<BigDecimal> trList = new ArrayList<>();
        for (int i = 1; i < high.size(); i++) {
            BigDecimal trueRange = calculateTrueRange(high.get(i), low.get(i), close.get(i - 1));
            trList.add(trueRange);
        }
 
        // Use simple moving average to calculate ATR
        return calculateMA(trList, Math.min(period, trList.size()));
    }
 
    /**
     * Calculate True Range
     * @param high Current high price
     * @param low Current low price
     * @param prevClose Previous close price
     * @return True range
     */
    protected BigDecimal calculateTrueRange(BigDecimal high, BigDecimal low, BigDecimal prevClose) {
        BigDecimal h1 = high.subtract(low);
        BigDecimal h2 = high.subtract(prevClose).abs();
        BigDecimal h3 = low.subtract(prevClose).abs();
        return h1.max(h2).max(h3);
    }
 
    /**
     * Calculate normalized volatility (based on ATR)
     * @param close Close price list
     * @param atr ATR value
     * @return Normalized volatility (percentage)
     */
    protected BigDecimal normalizeVolatility(List<BigDecimal> close, BigDecimal atr) {
        if (close == null || close.size() == 0 || atr.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        }
        return atr.divide(close.get(close.size() - 1), 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
    }
}