Administrator
2025-12-29 ef00aeb456a54c7a98b71617fb7e46e56d02aa78
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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
/**
 * MACD和MA组合交易策略实现类
 * 基于15分钟K线数据生成交易信号并确定持仓方向
 *
 * 该策略综合考虑了EMA指标、MACD指标、价格突破信号和波动率因素,
 * 形成了一套完整的开仓、平仓和持仓管理机制。
 */
package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy;
 
import lombok.extern.slf4j.Slf4j;
 
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
 
/**
 * MACD和MA组合交易策略实现
 * <p>
 * 该策略利用EMA交叉、MACD指标、价格突破信号和波动率过滤,
 * 为15分钟K线级别交易提供综合决策支持。
 */
@Slf4j
public class MacdMaStrategy {
 
    /** 持仓状态枚举 */
    public enum PositionType {
        /** 多头持仓 */
        LONG,
        /** 空头持仓 */
        SHORT,
        /** 空仓 */
        NONE
    }
 
    // 策略参数
    private int shortPeriod;      // 短期EMA周期
    private int longPeriod;       // 长期EMA周期
    private int signalPeriod;     // MACD信号线周期
    private int volatilityPeriod; // 波动率计算周期
    private BigDecimal stopLossRatio;   // 止损比例
    private BigDecimal takeProfitRatio; // 止盈比例
 
    // 持仓信息
    private BigDecimal entryPrice;       // 开仓价格
    private long entryTime;              // 开仓时间戳
 
    /**
     * 默认构造函数,使用标准MACD参数
     * 短期周期=12, 长期周期=26, 信号线周期=9, 波动率周期=20
     * 止损比例=1%, 止盈比例=2%
     */
    public MacdMaStrategy() {
        this(12, 26, 9, 20, new BigDecimal("0.01"), new BigDecimal("0.02"));
    }
 
    /**
     * 自定义参数构造函数
     *
     * @param shortPeriod 短期EMA周期
     * @param longPeriod 长期EMA周期
     * @param signalPeriod MACD信号线周期
     * @param volatilityPeriod 波动率计算周期
     * @param stopLossRatio 止损比例
     * @param takeProfitRatio 止盈比例
     */
    public MacdMaStrategy(int shortPeriod, int longPeriod, int signalPeriod, int volatilityPeriod,
                          BigDecimal stopLossRatio, BigDecimal takeProfitRatio) {
        this.shortPeriod = shortPeriod;
        this.longPeriod = longPeriod;
        this.signalPeriod = signalPeriod;
        this.volatilityPeriod = volatilityPeriod;
        this.stopLossRatio = stopLossRatio;
        this.takeProfitRatio = takeProfitRatio;
 
        // 初始化持仓状态为空仓
        this.entryPrice = BigDecimal.ZERO;
        this.entryTime = 0;
    }
 
    /**
     * 分析最新价格数据并生成交易信号
     *
     * @param closePrices 收盘价序列
     * @return 生成的交易信号(LONG、SHORT或NONE)
     */
    public PositionType analyze(List<BigDecimal> closePrices) {
        // 数据检查:确保有足够的数据点进行计算
        if (closePrices == null || closePrices.size() < 34) {
            return PositionType.NONE; // 数据不足,无法生成信号
        }
 
        // 1. 计算MACD指标
        MACDResult macdResult = MACDCalculator.calculateMACD(
                closePrices, shortPeriod, longPeriod, signalPeriod);
 
        // 2. 计算波动率
        Volatility volatility = new Volatility(volatilityPeriod);
        for (int i = Math.max(0, closePrices.size() - volatilityPeriod);
             i < closePrices.size(); i++) {
            volatility.addPrice(closePrices.get(i));
        }
        volatility.calculate();
 
        // 最新收盘价
        BigDecimal latestPrice = closePrices.get(closePrices.size() - 1);
 
        // 3. 检查开平仓条件
        // 多头开仓条件检查
        if (isLongEntryCondition(macdResult, closePrices, volatility.getValue())) {
            // 执行开多
            this.entryPrice = latestPrice;
            this.entryTime = System.currentTimeMillis();
            return PositionType.LONG;
        }
 
        // 空头开仓条件检查
        if (isShortEntryCondition(macdResult, closePrices, volatility.getValue())) {
            // 执行开空
            this.entryPrice = latestPrice;
            this.entryTime = System.currentTimeMillis();
            return PositionType.SHORT;
        }
 
        // 无信号
        return PositionType.NONE;
 
    }
 
    /**
     * 交易指令类,封装side和posSide的组合
     */
    public static class TradingOrder {
        private String side;    // buy或sell
        private String posSide; // long或short
 
        public TradingOrder(String side, String posSide) {
            this.side = side;
            this.posSide = posSide;
        }
 
        public String getSide() {
            return side;
        }
 
        public String getPosSide() {
            return posSide;
        }
 
        @Override
        public String toString() {
            return String.format("TradingOrder{side='%s', posSide='%s'}", side, posSide);
        }
    }
 
    /**
     * 分析历史价格数据并生成交易指令
     *
     * @param historicalPrices 历史价格序列
     * @return 交易指令(包含side和posSide),如果没有交易信号则返回null
     */
    public TradingOrder generateTradingOrder(List<BigDecimal> historicalPrices) {
        PositionType signal = analyze(historicalPrices);
 
        // 根据信号和当前持仓状态生成交易指令
        if (signal == PositionType.LONG) {
            // 开多:买入开多(side 填写 buy; posSide 填写 long )
            return new TradingOrder("buy", "long");
        } else if (signal == PositionType.SHORT) {
            // 开空:卖出开空(side 填写 sell; posSide 填写 short )
            return new TradingOrder("sell", "short");
        }
        // 没有交易信号
        return null;
    }
 
    /**
     * 多头开仓条件检查
     *
     * @param macdResult MACD计算结果
     * @param closePrices 收盘价序列
     * @param volatility 当前波动率
     * @return 是否满足多头开仓条件
     */
    private boolean isLongEntryCondition(MACDResult macdResult, List<BigDecimal> closePrices, BigDecimal volatility) {
        // 1. EMA金叉检查(简化为只检查当前短期EMA > 当前长期EMA)
        List<PriceData> macdData = macdResult.getMacdData();
        if (macdData.size() < 1) {
            return false;
        }
        boolean emaGoldenCross = isMacdGoldenCrossAndExpanding(macdResult);
 
        PriceData latest = macdData.get(macdData.size() - 1);
        // 2. MACD柱状线为正
        boolean macdPositive = latest.getMacdHist().compareTo(BigDecimal.ZERO) > 0;
 
        // 3. 简化的波动率检查
        boolean volatilityFilter = volatility.compareTo(BigDecimal.ZERO) > 0;
 
        if ( emaGoldenCross  && macdPositive && volatilityFilter){
            log.info( "EMA金叉: {}", emaGoldenCross);
            log.info( "MACD柱状线为正: {}" ,macdPositive);
            log.info( "波动率过滤: {}" ,volatilityFilter);
        }
        // 只需要EMA短期在长期上方、MACD柱状线为正且波动率大于0
        return emaGoldenCross && macdPositive && volatilityFilter;
    }
 
    /**
     * 空头开仓条件检查
     *
     * @param macdResult MACD计算结果
     * @param closePrices 收盘价序列
     * @param volatility 当前波动率
     * @return 是否满足空头开仓条件
     */
    private boolean isShortEntryCondition(MACDResult macdResult, List<BigDecimal> closePrices,
                                          BigDecimal volatility) {
        List<PriceData> macdData = macdResult.getMacdData();
        if (macdData.size() < 1) {
            return false;
        }
        // 2. MACD柱状线收缩+死叉检查
        boolean macdDeathCross = isMacdDeathCrossAndContracting(macdResult);
 
        // 2. MACD柱状线为负
        PriceData latest = macdData.get(macdData.size() - 1);
        boolean macdPositive = latest.getMacdHist().compareTo(BigDecimal.ZERO) < 0;
 
 
        // 4. 波动率过滤检查(0.5% ~ 5%)
        boolean volatilityFilter = isVolatilityInRange(volatility);
        if ( macdDeathCross&& volatilityFilter){
            log.info( "MACD柱状线收缩+死叉: {}", macdDeathCross);
            log.info( "MACD柱状线为负: {}" ,macdPositive);
            log.info( "波动率过滤: {}", volatilityFilter);
        }
 
        // 所有条件必须同时满足
        return macdDeathCross && volatilityFilter && macdPositive;
    }
 
    /**
     * EMA金叉检查
     *
     * @param macdResult MACD计算结果
     * @return 是否形成EMA金叉
     */
    private boolean isEmaGoldenCross(MACDResult macdResult) {
        List<PriceData> macdData = macdResult.getMacdData();
        if (macdData.size() < 2) {
            return false;
        }
 
        PriceData latest = macdData.get(macdData.size() - 1);
        PriceData previous = macdData.get(macdData.size() - 2);
 
        // 当前短期EMA > 当前长期EMA,并且前一期短期EMA <= 前一期长期EMA
        return latest.getEmaShort().compareTo(latest.getEmaLong()) > 0 &&
                previous.getEmaShort().compareTo(previous.getEmaLong()) <= 0;
    }
 
    /**
     * EMA死叉检查
     *
     * @param macdResult MACD计算结果
     * @return 是否形成EMA死叉
     */
    private boolean isEmaDeathCross(MACDResult macdResult) {
        List<PriceData> macdData = macdResult.getMacdData();
        if (macdData.size() < 2) {
            return false;
        }
 
        PriceData latest = macdData.get(macdData.size() - 1);
        PriceData previous = macdData.get(macdData.size() - 2);
 
        // 当前短期EMA < 当前长期EMA,并且前一期短期EMA >= 前一期长期EMA
        return latest.getEmaShort().compareTo(latest.getEmaLong()) < 0 &&
                previous.getEmaShort().compareTo(previous.getEmaLong()) >= 0;
    }
 
    /**
     * MACD金叉且柱状线扩张检查
     *
     * @param macdResult MACD计算结果
     * @return 是否形成MACD金叉且柱状线扩张
     */
    private boolean isMacdGoldenCrossAndExpanding(MACDResult macdResult) {
        List<PriceData> macdData = macdResult.getMacdData();
        if (macdData.size() < 3) {
            return false;
        }
 
        PriceData latest = macdData.get(macdData.size() - 1);
        PriceData previous = macdData.get(macdData.size() - 2);
 
        // 1. MACD金叉检查(DIF上穿DEA)
        boolean goldenCross = previous.getDif().compareTo(previous.getDea()) <= 0 &&
                latest.getDif().compareTo(latest.getDea()) > 0;
 
        // 2. MACD柱状线扩张检查
        boolean histogramExpanding =
                previous.getMacdHist().compareTo(latest.getMacdHist()) < 0 &&
                latest.getMacdHist().compareTo(BigDecimal.ZERO) > 0;
 
        return goldenCross && histogramExpanding;
    }
 
    /**
     * MACD死叉且柱状线收缩检查
     *
     * @param macdResult MACD计算结果
     * @return 是否形成MACD死叉且柱状线收缩
     */
    private boolean isMacdDeathCrossAndContracting(MACDResult macdResult) {
        List<PriceData> macdData = macdResult.getMacdData();
        if (macdData.size() < 2) {
            return false;
        }
 
        PriceData latest = macdData.get(macdData.size() - 1);
        PriceData previous = macdData.get(macdData.size() - 2);
 
        // 1. MACD死叉检查(DIF下穿DEA)
        boolean deathCross = previous.getDif().compareTo(previous.getDea()) >= 0 &&
                latest.getDif().compareTo(latest.getDea()) < 0;
 
        // 2. MACD柱状线收缩检查(绝对值减小)
        boolean histogramContracting =
                previous.getMacdHist().abs().compareTo(
                        latest.getMacdHist().abs()) < 0 &&
                latest.getMacdHist().compareTo(BigDecimal.ZERO) < 0;
 
        return deathCross && histogramContracting;
    }
 
    /**
     * 波动率过滤检查
     *
     * @param volatility 当前波动率
     * @return 波动率是否在0.5%~5%范围内
     */
    private boolean isVolatilityInRange(BigDecimal volatility) {
        BigDecimal minVolatility = new BigDecimal("0.1"); // 降低最小波动率阈值
        BigDecimal maxVolatility = new BigDecimal("5.0");
 
        return volatility.compareTo(minVolatility) >= 0 &&
                volatility.compareTo(maxVolatility) <= 0;
    }
 
 
    /**
     * 获取开仓价格
     *
     * @return 开仓价格
     */
    public BigDecimal getEntryPrice() {
        return entryPrice;
    }
 
    /**
     * 获取开仓时间戳
     *
     * @return 开仓时间戳
     */
    public long getEntryTime() {
        return entryTime;
    }
}