Administrator
7 days ago bfb3c88962ab015056d9c0ebe3ca0abaf8af8000
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
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
/**
 * 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.util.List;
 
/**
 * MACD和MA组合交易策略实现
 * <p>
 * 该策略利用EMA交叉、MACD指标、价格突破信号和波动率过滤,
 * 为15分钟K线级别交易提供综合决策支持。
 */
@Slf4j
public class MacdMaStrategy {
 
    /** 操作类型枚举 */
    public enum OperationType {
        /** 开仓 */
        open,
        /** 平仓 */
        close
    }
 
    /** 持仓状态枚举 */
    public enum PositionType {
        /** 多头开仓 */
        LONG_BUY,
        /** 多头平仓 */
        LONG_SELL,
        /** 空头开仓 */
        SHORT_SELL,
        /** 空头平仓 */
        SHORT_BUY,
        /** 空仓 */
        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);
        }
    }
 
    // 策略参数
    private int shortPeriod;      // 短期EMA周期
    private int longPeriod;       // 长期EMA周期
    private int signalPeriod;     // MACD信号线周期
    private int volatilityPeriod; // 波动率计算周期
    private int trendPeriod = 200; // 趋势过滤EMA周期(200日)
    private BigDecimal stopLossRatio;   // 止损比例
    private BigDecimal takeProfitRatio; // 止盈比例
 
    /**
     * 默认构造函数,使用标准MACD参数
     * 短期周期=12, 长期周期=26, 信号线周期=9, 波动率周期=20
     * 止损比例=1%, 止盈比例=2%
     */
    public MacdMaStrategy() {
        this(12, 26, 9, 20, new BigDecimal("0.01"), new BigDecimal("0.02"));
    }
 
    /**
     * 自定义参数构造函数,使用默认趋势周期200
     *
     * @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, longPeriod, signalPeriod, volatilityPeriod, 200, stopLossRatio, takeProfitRatio);
    }
 
    /**
     * 自定义参数构造函数
     *
     * @param shortPeriod 短期EMA周期
     * @param longPeriod 长期EMA周期
     * @param signalPeriod MACD信号线周期
     * @param volatilityPeriod 波动率计算周期
     * @param trendPeriod 趋势过滤EMA周期(200日)
     * @param stopLossRatio 止损比例
     * @param takeProfitRatio 止盈比例
     */
    public MacdMaStrategy(int shortPeriod, int longPeriod, int signalPeriod, int volatilityPeriod, int trendPeriod,
                          BigDecimal stopLossRatio, BigDecimal takeProfitRatio) {
        this.shortPeriod = shortPeriod;
        this.longPeriod = longPeriod;
        this.signalPeriod = signalPeriod;
        this.volatilityPeriod = volatilityPeriod;
        this.trendPeriod = trendPeriod;
        this.stopLossRatio = stopLossRatio;
        this.takeProfitRatio = takeProfitRatio;
    }
 
    // 主流程方法
    
    /**
     * 分析历史价格数据并生成交易指令
     *
     * @param historicalPrices 历史价格序列
     * @param historical1DayPrices 日线历史价格序列
     * @param operation 操作类型(open/close)
     * @return 交易指令(包含side和posSide),如果没有交易信号则返回null
     */
    public TradingOrder generateTradingOrder(List<BigDecimal> historicalPrices, List<BigDecimal> historical1DayPrices, String operation) {
        PositionType signal = null;
 
        if (OperationType.open.name().equals(operation)) {
            signal = analyzeOpen(historicalPrices, historical1DayPrices);
        } else if (OperationType.close.name().equals(operation)) {
            signal = analyzeClose(historicalPrices, historical1DayPrices);
        }
        
        // 根据信号生成交易指令
        return convertSignalToTradingOrder(signal);
    }
 
    /**
     * 分析最新价格数据并生成开仓信号
     *
     * @param closePrices 收盘价序列
     * @param close1DPrices 日线收盘价序列
     * @return 生成的交易信号(LONG、SHORT或NONE)
     */
    public PositionType analyzeOpen(List<BigDecimal> closePrices, List<BigDecimal> close1DPrices) {
        // 数据检查:确保有足够的数据点进行计算(需要足够数据计算200日EMA)
        if (closePrices == null || closePrices.size() < Math.max(34, trendPeriod) || 
            close1DPrices == null || close1DPrices.size() < Math.max(34, trendPeriod)) {
            return PositionType.NONE; // 数据不足,无法生成信号
        }
 
        // 计算MACD指标
        MACDResult macdResult = MACDCalculator.calculateMACD(
                closePrices, shortPeriod, longPeriod, signalPeriod);
         log.info("MACD计算结果:{}", macdResult.getMacdData().get(macdResult.getMacdData().size() - 1));
 
        // 多头开仓条件检查
        if (isLongEntryCondition(macdResult, closePrices, close1DPrices)) {
            log.info("多头开仓信号,价格:{}", closePrices.get(closePrices.size() - 1));
            return PositionType.LONG_BUY;
        }
 
        // 空头开仓条件检查
        if (isShortEntryCondition(macdResult, closePrices, close1DPrices)) {
            log.info("空头开仓信号,价格:{}", closePrices.get(closePrices.size() - 1));
            return PositionType.SHORT_SELL;
        }
        
        // 无信号
        return PositionType.NONE;
    }
 
    /**
     * 分析最新价格数据并生成平仓信号
     *
     * @param closePrices 收盘价序列
     * @param close1DPrices 日线收盘价序列
     * @return 生成的交易信号(LONG_SELL、SHORT_BUY或NONE)
     */
    public PositionType analyzeClose(List<BigDecimal> closePrices, List<BigDecimal> close1DPrices) {
        // 数据检查:确保有足够的数据点进行计算
        if (closePrices == null || closePrices.size() < Math.max(34, trendPeriod) || 
            close1DPrices == null || close1DPrices.size() < Math.max(34, trendPeriod)) {
            return PositionType.NONE; // 数据不足,无法生成信号
        }
 
        // 计算MACD指标
        MACDResult macdResult = MACDCalculator.calculateMACD(
                closePrices, shortPeriod, longPeriod, signalPeriod);
 
        // 最新收盘价
        BigDecimal latestPrice = closePrices.get(closePrices.size() - 1);
 
        if (isLongExitCondition(macdResult, latestPrice)) {
            log.info("多头平仓信号,价格:{}", latestPrice);
            return PositionType.LONG_SELL;
        }
        
        if (isShortExitCondition(macdResult, latestPrice)) {
            log.info("空头平仓信号,价格:{}", latestPrice);
            return PositionType.SHORT_BUY;
        }
 
        // 无信号
        return PositionType.NONE;
    }
 
    // 信号转换方法
    
    /**
     * 将持仓信号转换为交易指令
     *
     * @param signal 持仓信号
     * @return 交易指令,无信号则返回null
     */
    private TradingOrder convertSignalToTradingOrder(PositionType signal) {
        if (signal == null) {
            return null;
        }
        
        switch (signal) {
            case LONG_BUY:
                // 开多:买入开多(side 填写 buy; posSide 填写 long )
                return new TradingOrder("buy", "long");
            case LONG_SELL:
                // 平多:卖出平多(side 填写 sell; posSide 填写 long )
                return new TradingOrder("sell", "long");
            case SHORT_SELL:
                // 开空:卖出开空(side 填写 sell; posSide 填写 short )
                return new TradingOrder("sell", "short");
            case SHORT_BUY:
                // 平空:买入平空(side 填写 buy; posSide 填写 short )
                return new TradingOrder("buy", "short");
            default:
                // 无信号
                return null;
        }
    }
 
    // 开仓条件检查方法
    
    /**
     * 多头开仓条件检查
     *
     * @param macdResult MACD计算结果
     * @param closePrices 收盘价序列
     * @param close1DPrices 日线收盘价序列
     * @return 是否满足多头开仓条件
     */
    private boolean isLongEntryCondition(MACDResult macdResult, List<BigDecimal> closePrices, List<BigDecimal> close1DPrices) {
        // 1. 计算200日EMA(趋势过滤)
        List<BigDecimal> trendEma = EMACalculator.calculateEMA(close1DPrices, trendPeriod, true);
        BigDecimal latestTrendEma = trendEma.get(trendEma.size() - 1);
        BigDecimal latestPrice = closePrices.get(0);
        log.info( "200日EMA:{},{}", latestTrendEma,  latestPrice);
        
        // 2. 价格必须位于200日EMA上方(多头趋势确认)
        boolean isAboveTrend = latestPrice.compareTo(latestTrendEma) > 0;
        
        // 3. MACD金叉检查
        boolean isGoldenCross = isGoldenCross(macdResult);
        
        // 4. MACD柱状线由负转正(动量转变)
        boolean isMacdHistTurningPositive = isMacdHistTurningPositive(macdResult);
        
        // 5. 底背离检查(增强多头信号可靠性)
        boolean isBottomDivergence = MACDCalculator.isBottomDivergence(closePrices, macdResult);
 
        log.info("多头信号检查, 价格位于200日EMA上方: {}, 金叉: {}, MACD柱状线由负转正: {}, 底背离: {}",
                isAboveTrend, isGoldenCross, isMacdHistTurningPositive, isBottomDivergence);
        
        // 多头开仓条件:趋势向上 + 金叉 + (柱状线转强或底背离)
        return isAboveTrend && isGoldenCross && (isMacdHistTurningPositive || isBottomDivergence);
    }
 
    /**
     * 空头开仓条件检查
     * 
     * @param macdResult MACD计算结果
     * @param closePrices 收盘价序列
     * @param close1DPrices 日线收盘价序列
     * @return 是否满足空头开仓条件
     */
    private boolean isShortEntryCondition(MACDResult macdResult, List<BigDecimal> closePrices, List<BigDecimal> close1DPrices) {
        // 1. 计算200日EMA(趋势过滤)
        List<BigDecimal> trendEma = EMACalculator.calculateEMA(close1DPrices, trendPeriod, true);
        BigDecimal latestTrendEma = trendEma.get(trendEma.size() - 1);
        BigDecimal latestPrice = closePrices.get(0);
 
        log.info( "200日EMA:{},{}", latestTrendEma,  latestPrice);
        
        // 2. 价格必须位于200日EMA下方(空头趋势确认)
        boolean isBelowTrend = latestPrice.compareTo(latestTrendEma) < 0;
        
        // 3. MACD死叉检查
        boolean isDeathCross = isDeathCross(macdResult);
        
        // 4. MACD柱状线由正转负(动量转变)
        boolean isMacdHistTurningNegative = isMacdHistTurningNegative(macdResult);
        
        // 5. 顶背离检查(增强空头信号可靠性)
        boolean isTopDivergence = MACDCalculator.isTopDivergence(closePrices, macdResult);
 
        log.info("空头信号检查, 价格位于200日EMA下方: {}, 死叉: {}, MACD柱状线由正转负: {}, 顶背离: {}",
                isBelowTrend, isDeathCross, isMacdHistTurningNegative, isTopDivergence);
        
        // 空头开仓条件:趋势向下 + 死叉 + (柱状线转弱或顶背离)
        return isBelowTrend && isDeathCross && (isMacdHistTurningNegative || isTopDivergence);
    }
 
    // 平仓条件检查方法
    
    /**
     * 多头平仓条件检查
     * 
     * @param macdResult MACD计算结果
     * @param currentPrice 当前价格
     * @return 是否满足多头平仓条件
     */
    private boolean isLongExitCondition(MACDResult macdResult, BigDecimal currentPrice) {
        // 多头平仓条件:MACD柱状线动量减弱(由正转弱)
        List<PriceData> macdData = macdResult.getMacdData();
        if (macdData.size() >= 2) {
            PriceData latest = macdData.get(macdData.size() - 1);
            PriceData previous = macdData.get(macdData.size() - 2);
 
            // 柱状线由正转弱:前一根为正,当前绝对值减小
            boolean momentumWeakening = previous.getMacdHist().compareTo(BigDecimal.ZERO) >= 0 && 
                                      latest.getMacdHist().abs().compareTo(previous.getMacdHist().abs()) < 0;
            
            return momentumWeakening;
        }
        return false;
    }
    
    /**
     * 空头平仓条件检查
     * 
     * @param macdResult MACD计算结果
     * @param currentPrice 当前价格
     * @return 是否满足空头平仓条件
     */
    private boolean isShortExitCondition(MACDResult macdResult, BigDecimal currentPrice) {
        // 空头平仓条件:MACD柱状线动量减弱(由负转弱)
        List<PriceData> macdData = macdResult.getMacdData();
        if (macdData.size() >= 2) {
            PriceData latest = macdData.get(macdData.size() - 1);
            PriceData previous = macdData.get(macdData.size() - 2);
            
            // 柱状线由负转弱:前一根为负,当前绝对值减小
            boolean momentumWeakening = previous.getMacdHist().compareTo(BigDecimal.ZERO) <= 0 &&
                                      latest.getMacdHist().abs().compareTo(previous.getMacdHist().abs()) < 0;
            
            return momentumWeakening;
        }
        
        return false;
    }
 
    // MACD信号辅助方法
    
    /**
     * 简单金叉判断
     * <p>
     * 条件:DIF线从下往上穿过DEA线
     * 
     * @param macdResult MACD计算结果
     * @return 是否形成金叉
     */
    private boolean isGoldenCross(MACDResult macdResult) {
        List<PriceData> macdData = macdResult.getMacdData();
        if (macdData.size() < 2) {
            return false;
        }
 
        PriceData latest = macdData.get(0);
        PriceData previous = macdData.get(1);
        
        // 金叉判断:DIF从下往上穿过DEA
        return previous.getDif().compareTo(previous.getDea()) < 0 && 
               latest.getDif().compareTo(latest.getDea()) > 0;
    }
    
    /**
     * 简单死叉判断
     * <p>
     * 条件:DIF线从上往下穿过DEA线
     * 
     * @param macdResult MACD计算结果
     * @return 是否形成死叉
     */
    private boolean isDeathCross(MACDResult macdResult) {
        List<PriceData> macdData = macdResult.getMacdData();
        if (macdData.size() < 2) {
            return false;
        }
 
        PriceData latest = macdData.get(0);
        PriceData previous = macdData.get(1);
        
        // 死叉判断:DIF从上往下穿过DEA
        return previous.getDif().compareTo(previous.getDea()) > 0 && 
               latest.getDif().compareTo(latest.getDea()) < 0;
    }
    
    /**
     * MACD柱状线由负转正判断
     * <p>
     * 条件:前一根柱状线为负,当前柱状线为正
     * 
     * @param macdResult MACD计算结果
     * @return 是否由负转正
     */
    private boolean isMacdHistTurningPositive(MACDResult macdResult) {
        List<PriceData> macdData = macdResult.getMacdData();
        if (macdData.size() < 2) {
            return false;
        }
 
        PriceData latest = macdData.get(0);
        PriceData previous = macdData.get(1);
        
        // 柱状线由负转正:前一根为负,当前为正
        return previous.getMacdHist().compareTo(BigDecimal.ZERO) <= 0 && 
               latest.getMacdHist().compareTo(BigDecimal.ZERO) > 0;
    }
    
    /**
     * MACD柱状线由正转负判断
     * <p>
     * 条件:前一根柱状线为正,当前柱状线为负
     * 
     * @param macdResult MACD计算结果
     * @return 是否由正转负
     */
    private boolean isMacdHistTurningNegative(MACDResult macdResult) {
        List<PriceData> macdData = macdResult.getMacdData();
        if (macdData.size() < 2) {
            return false;
        }
 
        PriceData latest = macdData.get(0);
        PriceData previous = macdData.get(1);
        
        // 柱状线由正转负:前一根为正,当前为负
        return previous.getMacdHist().compareTo(BigDecimal.ZERO) >= 0 && 
               latest.getMacdHist().compareTo(BigDecimal.ZERO) < 0;
    }
 
}