Administrator
2025-12-28 302ebc7896dd6cc7913ac14eda7427dfa496d4ef
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
package com.xcong.excoin.modules.okxNewPrice.indicator;
 
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
 
import java.math.BigDecimal;
import java.util.List;
 
/**
 * MA (Moving Average) 指标实现
 * 支持不同周期的简单移动平均线(SMA)和指数移动平均线(EMA)
 * 
 * 作用:
 * 1. 平滑价格波动,识别趋势方向
 * 2. 短周期MA上穿长周期MA形成金叉,提示买入信号
 * 3. 短周期MA下穿长周期MA形成死叉,提示卖出信号
 * 4. 价格上穿/下穿MA线,也可作为买卖参考
 * 
 * 价格参数类型:
 * - 参数名称:prices
 * - 参数类型:List<BigDecimal>
 * - 参数说明:需要至少1个价格数据点用于计算,根据不同周期需求更多数据点
 * 
 * 推荐时间粒度及优缺点:
 * 1. 1分钟(1m):
 *    - 优点:反应迅速,适合超短线交易
 *    - 缺点:噪音多,容易产生虚假信号
 * 2. 5分钟(5m):
 *    - 优点:平衡了反应速度和噪音过滤
 *    - 缺点:仍有一定噪音
 * 3. 15分钟(15m):
 *    - 优点:适合日内交易,信号相对可靠
 *    - 缺点:反应速度较慢
 * 4. 1小时(1h)及以上:
 *    - 优点:趋势信号明确,虚假信号少
 *    - 缺点:反应滞后,不适合短线交易
 */
@Slf4j
@Getter
@Setter
public class MA extends IndicatorBase {
 
    // 默认周期
    public static final int DEFAULT_MA5 = 5;
    public static final int DEFAULT_MA10 = 10;
    public static final int DEFAULT_MA20 = 20;
    public static final int DEFAULT_MA30 = 30;
    public static final int DEFAULT_MA60 = 60;
 
    // 动态周期参数
    private int ma5Period;
    private int ma10Period;
    private int ma20Period;
    private int ma30Period;
    private int ma60Period;
 
    private BigDecimal ma5 = BigDecimal.ZERO;
    private BigDecimal ma10 = BigDecimal.ZERO;
    private BigDecimal ma20 = BigDecimal.ZERO;
    private BigDecimal ma30 = BigDecimal.ZERO;
    private BigDecimal ma60 = BigDecimal.ZERO;
 
    private BigDecimal ema5 = BigDecimal.ZERO;
    private BigDecimal ema10 = BigDecimal.ZERO;
    private BigDecimal ema20 = BigDecimal.ZERO;
    private BigDecimal ema30 = BigDecimal.ZERO;
    private BigDecimal ema60 = BigDecimal.ZERO;
 
    private BigDecimal prevEma5 = null;
    private BigDecimal prevEma10 = null;
    private BigDecimal prevEma20 = null;
    private BigDecimal prevEma30 = null;
    private BigDecimal prevEma60 = null;
 
    // 构造函数使用默认周期
    public MA() {
        this.ma5Period = DEFAULT_MA5;
        this.ma10Period = DEFAULT_MA10;
        this.ma20Period = DEFAULT_MA20;
        this.ma30Period = DEFAULT_MA30;
        this.ma60Period = DEFAULT_MA60;
    }
 
    /**
     * 计算所有周期的MA指标(使用当前周期设置)
     * @param prices 价格列表
     */
    public void calculate(List<BigDecimal> prices) {
        calculate(prices, null);
    }
 
    /**
     * 计算所有周期的MA指标,并支持动态周期调整
     * @param prices 价格列表
     * @param volatility 标准化波动率(ATR百分比),用于动态调整周期
     */
    public void calculate(List<BigDecimal> prices, BigDecimal volatility) {
        if (prices == null || prices.size() < 1) {
            return;
        }
 
        // 如果提供了波动率,则动态调整周期
        if (volatility != null) {
            adjustPeriodsByVolatility(volatility);
        }
 
        // 计算SMA
        if (prices.size() >= ma5Period) {
            ma5 = calculateMA(prices, ma5Period);
        }
        if (prices.size() >= ma10Period) {
            ma10 = calculateMA(prices, ma10Period);
        }
        if (prices.size() >= ma20Period) {
            ma20 = calculateMA(prices, ma20Period);
        }
        if (prices.size() >= ma30Period) {
            ma30 = calculateMA(prices, ma30Period);
        }
        if (prices.size() >= ma60Period) {
            ma60 = calculateMA(prices, ma60Period);
        }
 
        // 计算EMA
        prevEma5 = calculateEMA(prices, ma5Period, prevEma5);
        ema5 = prevEma5;
        
        prevEma10 = calculateEMA(prices, ma10Period, prevEma10);
        ema10 = prevEma10;
        
        prevEma20 = calculateEMA(prices, ma20Period, prevEma20);
        ema20 = prevEma20;
        
        prevEma30 = calculateEMA(prices, ma30Period, prevEma30);
        ema30 = prevEma30;
        
        prevEma60 = calculateEMA(prices, ma60Period, prevEma60);
        ema60 = prevEma60;
 
        log.info("MA计算结果 - MA5({}): {}, MA10({}): {}, MA20({}): {}, MA30({}): {}, MA60({}): {}",
                ma5Period, ma5, ma10Period, ma10, ma20Period, ma20, ma30Period, ma30, ma60Period, ma60);
        log.info("EMA计算结果 - EMA5({}): {}, EMA10({}): {}, EMA20({}): {}, EMA30({}): {}, EMA60({}): {}",
                ma5Period, ema5, ma10Period, ema10, ma20Period, ema20, ma30Period, ema30, ma60Period, ema60);
    }
 
    /**
     * 根据波动率调整MA周期
     * @param volatility 标准化波动率(ATR百分比)
     */
    private void adjustPeriodsByVolatility(BigDecimal volatility) {
        // 根据波动率缩放均线周期
        // 3%、5%、8%作为ATR阈值
        BigDecimal lowVolatility = new BigDecimal(3);
        BigDecimal midVolatility = new BigDecimal(5);
        BigDecimal highVolatility = new BigDecimal(8);
 
        // 快速MA周期 (ma5)
        ma5Period = volatility.compareTo(lowVolatility) < 0 ? 10 : 6;
        
        // 中期MA周期 (ma10, ma20)
        ma10Period = volatility.compareTo(midVolatility) < 0 ? 10 : 8;
        ma20Period = volatility.compareTo(midVolatility) < 0 ? 21 : 13;
        
        // 长期MA周期 (ma30, ma60)
        ma30Period = volatility.compareTo(highVolatility) < 0 ? 30 : 24;
        ma60Period = volatility.compareTo(highVolatility) < 0 ? 50 : 34;
 
        log.info("根据波动率{}调整MA周期: ma5={}, ma10={}, ma20={}, ma30={}, ma60={}",
                volatility, ma5Period, ma10Period, ma20Period, ma30Period, ma60Period);
    }
 
    /**
     * 判断短期均线是否上穿长期均线(金叉)
     * @param shortMA 短期均线
     * @param longMA 长期均线
     * @param prevShortMA 前一期短期均线
     * @param prevLongMA 前一期长期均线
     * @return 是否形成金叉
     */
    public boolean isGoldenCross(BigDecimal shortMA, BigDecimal longMA, 
                                BigDecimal prevShortMA, BigDecimal prevLongMA) {
        return prevShortMA != null && prevLongMA != null &&
                prevShortMA.compareTo(prevLongMA) < 0 && 
                shortMA.compareTo(longMA) > 0;
    }
 
    /**
     * 判断短期均线是否下穿长期均线(死叉)
     * @param shortMA 短期均线
     * @param longMA 长期均线
     * @param prevShortMA 前一期短期均线
     * @param prevLongMA 前一期长期均线
     * @return 是否形成死叉
     */
    public boolean isDeathCross(BigDecimal shortMA, BigDecimal longMA, 
                               BigDecimal prevShortMA, BigDecimal prevLongMA) {
        return prevShortMA != null && prevLongMA != null &&
                prevShortMA.compareTo(prevLongMA) > 0 && 
                shortMA.compareTo(longMA) < 0;
    }
 
    /**
     * 判断价格是否上穿均线
     * @param price 当前价格
     * @param ma 均线值
     * @param prevPrice 前一期价格
     * @param prevMA 前一期均线值
     * @return 是否上穿
     */
    public boolean isPriceCrossUp(BigDecimal price, BigDecimal ma, 
                                 BigDecimal prevPrice, BigDecimal prevMA) {
        return prevPrice != null && prevMA != null &&
                prevPrice.compareTo(prevMA) < 0 && 
                price.compareTo(ma) > 0;
    }
 
    /**
     * 判断价格是否下穿均线
     * @param price 当前价格
     * @param ma 均线值
     * @param prevPrice 前一期价格
     * @param prevMA 前一期均线值
     * @return 是否下穿
     */
    public boolean isPriceCrossDown(BigDecimal price, BigDecimal ma, 
                                   BigDecimal prevPrice, BigDecimal prevMA) {
        return prevPrice != null && prevMA != null &&
                prevPrice.compareTo(prevMA) > 0 && 
                price.compareTo(ma) < 0;
    }
}