/** * MACD和MA组合交易策略实现类 * 基于多时间粒度K线数据生成交易信号并确定持仓方向 * * 该策略综合考虑了EMA指标、MACD指标、价格突破信号和波动率因素, * 形成了一套完整的开仓、平仓和持仓管理机制。 * 支持1分钟(K线)和1分钟(K线)级别的数据输入,数据顺序要求从新到旧排列。 */ package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy; import lombok.extern.slf4j.Slf4j; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * MACD和MA组合交易策略实现 *

* 该策略利用EMA交叉、MACD指标、价格突破信号和波动率过滤, * 为多时间粒度K线级别交易提供综合决策支持。 *

* 数据输入要求: * - historicalPrices1M:1分钟K线收盘价列表,顺序从新到旧 * - historicalPrices1D:日线K线收盘价列表,顺序从新到旧 */ @Slf4j public class MacdEmaStrategy { /** 操作类型枚举 */ 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 MacdEmaStrategy() { 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 MacdEmaStrategy(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 MacdEmaStrategy(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 历史价格序列(1分钟K线收盘价),顺序从新到旧 * @param operation 操作类型(open/close) * @return 交易指令(包含side和posSide),如果没有交易信号则返回null */ public TradingOrder generateTradingOrder(List historicalPrices, String operation) { PositionType signal = null; if (OperationType.open.name().equals(operation)) { signal = analyzeOpen(historicalPrices); } else if (OperationType.close.name().equals(operation)) { signal = analyzeClose(historicalPrices); } // 根据信号生成交易指令 return convertSignalToTradingOrder(signal); } /** * 分析最新价格数据并生成开仓信号 * * @param closePrices 1分钟K线收盘价序列,顺序从新到旧 * @return 生成的交易信号(LONG_BUY、SHORT_SELL或NONE) */ public PositionType analyzeOpen(List closePrices) { // 数据检查:确保有足够的数据点进行计算(需要足够数据计算200日EMA) if (closePrices == null || closePrices.size() < Math.max(34, trendPeriod)) { return PositionType.NONE; // 数据不足,无法生成信号 } // 计算MACD指标 MACDResult macdResult = MACDCalculator.calculateMACD( closePrices, shortPeriod, longPeriod, signalPeriod); log.info("MACD计算结果:{}", macdResult.getMacdData().get(0)); // 多头开仓条件检查 if (isLongEntryCondition(macdResult, closePrices)) { log.info("多头开仓信号,价格:{}", closePrices.get(0)); return PositionType.LONG_BUY; } // 空头开仓条件检查 if (isShortEntryCondition(macdResult, closePrices)) { log.info("空头开仓信号,价格:{}", closePrices.get(0)); return PositionType.SHORT_SELL; } // 无信号 return PositionType.NONE; } /** * 分析最新价格数据并生成平仓信号 * * @param closePrices 1分钟K线收盘价序列,顺序从新到旧 * @return 生成的交易信号(LONG_SELL、SHORT_BUY或NONE) */ public PositionType analyzeClose(List closePrices) { // 数据检查:确保有足够的数据点进行计算 if (closePrices == null || closePrices.size() < Math.max(34, trendPeriod)) { return PositionType.NONE; // 数据不足,无法生成信号 } // 计算MACD指标 MACDResult macdResult = MACDCalculator.calculateMACD( closePrices, shortPeriod, longPeriod, signalPeriod); // 最新收盘价 BigDecimal latestPrice = closePrices.get(0); 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 1分钟K线收盘价序列,顺序从新到旧 * @return 是否满足多头开仓条件 */ private boolean isLongEntryCondition(MACDResult macdResult, List closePrices) { // 1. 计算200日EMA(趋势过滤) // 复制并反转日线数据,确保从旧到新计算EMA List reversed1DPrices = new ArrayList<>(closePrices); Collections.reverse(reversed1DPrices); List trendEma = EMACalculator.calculateEMA(reversed1DPrices, trendPeriod, true); BigDecimal latestTrendEma = trendEma.get(trendEma.size() - 1); BigDecimal latestPrice = closePrices.get(0); // 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柱状线由负转正: {}, 底背离: {}", latestTrendEma,isAboveTrend, isGoldenCross, isMacdHistTurningPositive, isBottomDivergence); // 多头开仓条件:柱状线转强 + 金叉 + (趋势向上或底背离) return isMacdHistTurningPositive && isGoldenCross && ( isAboveTrend|| isBottomDivergence); } /** * 空头开仓条件检查 * * @param macdResult MACD计算结果 * @param closePrices 1分钟K线收盘价序列,顺序从新到旧 * @return 是否满足空头开仓条件 */ private boolean isShortEntryCondition(MACDResult macdResult, List closePrices) { // 1. 计算200日EMA(趋势过滤) // 复制并反转日线数据,确保从旧到新计算EMA List reversed1DPrices = new ArrayList<>(closePrices); Collections.reverse(reversed1DPrices); List trendEma = EMACalculator.calculateEMA(reversed1DPrices, trendPeriod, true); BigDecimal latestTrendEma = trendEma.get(trendEma.size() - 1); BigDecimal latestPrice = closePrices.get(0); // 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 isMacdHistTurningNegative && isDeathCross && ( isBelowTrend || isTopDivergence); } // 平仓条件检查方法 /** * 多头平仓条件检查 * * @param macdResult MACD计算结果 * @param currentPrice 当前价格 * @return 是否满足多头平仓条件 */ private boolean isLongExitCondition(MACDResult macdResult, BigDecimal currentPrice) { // 多头平仓条件:MACD柱状线动量减弱(由正转弱) List macdData = macdResult.getMacdData(); if (macdData.size() >= 2) { PriceData latest = macdData.get(0); // 最新数据 PriceData previous = macdData.get(1); // 前一个数据 // 柱状线由正转弱:前一根为正,当前绝对值减小 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 macdData = macdResult.getMacdData(); if (macdData.size() >= 2) { PriceData latest = macdData.get(0); // 最新数据 PriceData previous = macdData.get(1); // 前一个数据 // 柱状线由负转弱:前一根为负,当前绝对值减小 boolean momentumWeakening = previous.getMacdHist().compareTo(BigDecimal.ZERO) <= 0 && latest.getMacdHist().abs().compareTo(previous.getMacdHist().abs()) < 0; return momentumWeakening; } return false; } // MACD信号辅助方法 /** * 简单金叉判断 *

* 条件:DIF线从下往上穿过DEA线 * * @param macdResult MACD计算结果 * @return 是否形成金叉 */ private boolean isGoldenCross(MACDResult macdResult) { List 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; } /** * 简单死叉判断 *

* 条件:DIF线从上往下穿过DEA线 * * @param macdResult MACD计算结果 * @return 是否形成死叉 */ private boolean isDeathCross(MACDResult macdResult) { List 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柱状线由负转正判断 *

* 条件:前一根柱状线为负,当前柱状线为正 * * @param macdResult MACD计算结果 * @return 是否由负转正 */ private boolean isMacdHistTurningPositive(MACDResult macdResult) { List 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柱状线由正转负判断 *

* 条件:前一根柱状线为正,当前柱状线为负 * * @param macdResult MACD计算结果 * @return 是否由正转负 */ private boolean isMacdHistTurningNegative(MACDResult macdResult) { List 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; } }