src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxKlineWebSocketClient.java
@@ -337,24 +337,13 @@ MacdMaStrategy strategy = new MacdMaStrategy(); // 生成100个15分钟价格数据点 List<Kline> kline15MinuteData = getKlineDataByInstIdAndBar(instId, "1D"); List<BigDecimal> fiveMinPrices = kline15MinuteData.stream() List<Kline> kline15MinuteData = getKlineDataByInstIdAndBar(instId, "15m"); List<BigDecimal> historicalPrices = kline15MinuteData.stream() .map(Kline::getC) .collect(Collectors.toList()); try { // 执行策略 strategy.execute(fiveMinPrices); boolean skip = strategy.getSkip(); if (skip){ log.info("跳过"); return; } System.out.println("策略初始化成功!"); } catch (Exception e) { System.err.println("策略初始化失败:" + e.getMessage()); e.printStackTrace(); } log.info("生成100个15分钟价格数据点成功!"); // 使用策略分析最新价格数据 MacdMaStrategy.TradingOrder tradingOrder = strategy.generateTradingOrder(historicalPrices); Collection<OkxQuantWebSocketClient> allClients = clientManager.getAllClients(); //如果为空,则直接返回 @@ -365,14 +354,10 @@ for (OkxQuantWebSocketClient client : clientManager.getAllClients()) { String accountName = client.getAccountName(); if (accountName != null) { // 根据信号执行交易操作 TradeRequestParam tradeRequestParam = new TradeRequestParam(); tradeRequestParam = strategy.getOrderParamOpen(tradeRequestParam); String posSide = tradeRequestParam.getPosSide(); String side = tradeRequestParam.getSide(); BigDecimal posHold = PositionsWs.getAccountMap(PositionsWs.initAccountName(accountName, posSide)).get("pos"); if (posHold != null && posHold.compareTo(BigDecimal.ZERO) > 0){ tradeRequestParam = strategy.getOrderParamClose(tradeRequestParam); } String posSide = tradingOrder.getPosSide(); String side = tradingOrder.getSide(); String currentPrice = String.valueOf(closePx); tradeRequestParam = caoZuoService.caoZuoStrategy(accountName, currentPrice, posSide); String clOrdId = WsParamBuild.getOrderNum(side); src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/BearishSignalDetector.java
New file @@ -0,0 +1,132 @@ package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy; import java.math.BigDecimal; import java.util.List; /** * 示例用法 * // 计算MACD(获取包含起始索引的结果) * MACDCalculator.MACDResult macdResult = MACDCalculator.calculateMACD(closePrices, 12, 26, 9); * * // 检测做空信号 * boolean bearishSignal = BearishSignalDetector.isBearishSignalFormed(macdResult, closePrices); */ public class BearishSignalDetector { /** * 判断是否形成做空信号 * * @param macdResult 包含MACD数据和有效起始索引的结果对象 * @param closePrices 收盘价列表 * @return 是否形成信号 */ public static boolean isBearishSignalFormed(MACDResult macdResult, List<BigDecimal> closePrices) { List<PriceData> macdData = macdResult.getMacdData(); int startIdx = macdResult.getStartIndex(); // 校验数据长度(至少需要两个有效DIF点) if (macdData.size() < 2 || closePrices.size() < startIdx + macdData.size()) { return false; } int currentMacdIndex = macdData.size() - 1; int currentCloseIndex = startIdx + currentMacdIndex; // 1. 判断顶背离 boolean isTopDivergence = isTopDivergence(macdData, closePrices, currentMacdIndex, startIdx); // 2. 判断死叉 boolean isDeathCross = isDeathCross(macdData, currentMacdIndex); return isTopDivergence && isDeathCross; } /** * 判断顶背离(需确保earlierHigh在recentHigh之前) */ private static boolean isTopDivergence(List<PriceData> macdData, List<BigDecimal> closePrices, int currentMacdIndex, int startIdx) { // 计算对应closePrices的索引 int currentCloseIndex = startIdx + currentMacdIndex; if (currentCloseIndex < 10 || macdData.size() < 5) { return false; } // 寻找最近的价格高点(至少在当前点的前2根K线范围内) int recentHighCloseIndex = findRecentHighWithRetrace(closePrices, currentCloseIndex - 5, currentCloseIndex); if (recentHighCloseIndex == -1) { return false; } // 寻找更早的高点(确保在recentHigh之前) int earlierHighCloseIndex = findRecentHighWithRetrace(closePrices, startIdx, recentHighCloseIndex - 1); if (earlierHighCloseIndex == -1) { return false; } // 转换为macdData的索引 int recentHighMacdIndex = recentHighCloseIndex - startIdx; int earlierHighMacdIndex = earlierHighCloseIndex - startIdx; // 判断价格创新高但DIF走低 BigDecimal recentHighPrice = closePrices.get(recentHighCloseIndex); BigDecimal earlierHighPrice = closePrices.get(earlierHighCloseIndex); boolean isPriceHigher = recentHighPrice.compareTo(earlierHighPrice) > 0; BigDecimal recentDif = macdData.get(recentHighMacdIndex).getDif(); BigDecimal earlierDif = macdData.get(earlierHighMacdIndex).getDif(); boolean isDifLower = recentDif.compareTo(earlierDif) < 0; return isPriceHigher && isDifLower; } /** * 寻找有效的高点(高点后需有至少1根K线下跌) */ private static int findRecentHighWithRetrace(List<BigDecimal> prices, int startIndex, int endIndex) { if (startIndex < 0 || endIndex >= prices.size() || startIndex >= endIndex) { return -1; } int highIndex = -1; BigDecimal highPrice = BigDecimal.ZERO; // 从右向左搜索,找到第一个有效高点 for (int i = endIndex; i >= startIndex; i--) { if (prices.get(i).compareTo(highPrice) > 0) { highPrice = prices.get(i); highIndex = i; } // 检查高点后是否有回调 if (highIndex != -1 && i < endIndex) { if (prices.get(i + 1).compareTo(highPrice) < 0) { return highIndex; // 找到确认回调的高点 } } } return highIndex; } /** * 判断是否形成死叉(DIF下穿DEA) * * @param macdData MACD计算结果数据列表 * @param currentIndex 当前数据点索引 * @return 是否形成死叉 */ private static boolean isDeathCross(List<PriceData> macdData, int currentIndex) { if (currentIndex < 1) { return false; } PriceData current = macdData.get(currentIndex); PriceData previous = macdData.get(currentIndex - 1); // 前一期DIF >= DEA,当前期DIF < DEA return previous.getDif().compareTo(previous.getDea()) >= 0 && current.getDif().compareTo(current.getDea()) < 0; } } src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/BullishSignalDetector.java
New file @@ -0,0 +1,87 @@ package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy; import java.math.BigDecimal; import java.util.List; /** * 示例用法 * // 计算MACD结果(包含起始索引) * MACDCalculator.MACDResult macdResult = MACDCalculator.calculateMACD(closePrices, 12, 26, 9); * * // 检测做多信号 * boolean bullishSignal = BullishSignalDetector.isBullishSignalFormed(macdResult, closePrices); */ public class BullishSignalDetector { public static boolean isBullishSignalFormed(MACDResult macdResult, List<BigDecimal> closePrices) { List<PriceData> macdData = macdResult.getMacdData(); int startIdx = macdResult.getStartIndex(); // 校验MACD数据和收盘价长度合法性 if (closePrices.size() < 34 || macdData.size() < 2 || startIdx + macdData.size() > closePrices.size()) { return false; } int currentMacdIndex = macdData.size() - 1; int currentCloseIndex = startIdx + currentMacdIndex; // 1. 金叉判断 boolean isGoldenCross = isGoldenCross(macdData, currentMacdIndex); // 2. MACD柱动量增强 boolean isMacdHistExpanding = isMacdHistExpanding(macdData, currentMacdIndex); // 3. 价格突破前15周期高点 boolean isPriceBreakout = isPriceBreakout(closePrices, currentCloseIndex, 15); return isGoldenCross && isMacdHistExpanding && isPriceBreakout; } private static boolean isGoldenCross(List<PriceData> macdData, int currentMacdIndex) { if (currentMacdIndex < 1 || currentMacdIndex >= macdData.size()) { return false; } PriceData current = macdData.get(currentMacdIndex); PriceData previous = macdData.get(currentMacdIndex - 1); return previous.getDif().compareTo(previous.getDea()) <= 0 && current.getDif().compareTo(current.getDea()) > 0; } private static boolean isMacdHistExpanding(List<PriceData> macdData, int currentMacdIndex) { if (currentMacdIndex < 2 || currentMacdIndex >= macdData.size()) { return false; } BigDecimal currentHist = macdData.get(currentMacdIndex).getMacdHist(); BigDecimal prevHist = macdData.get(currentMacdIndex - 1).getMacdHist(); BigDecimal prevPrevHist = macdData.get(currentMacdIndex - 2).getMacdHist(); boolean isPositive = currentHist.compareTo(BigDecimal.ZERO) > 0; boolean isExpanding = prevPrevHist.compareTo(prevHist) <= 0 && prevHist.compareTo(currentHist) < 0; return isPositive && isExpanding; } private static boolean isPriceBreakout(List<BigDecimal> closePrices, int currentCloseIndex, int period) { if (currentCloseIndex < period || period <= 0) { return false; } int startSearchIdx = Math.max(currentCloseIndex - period, 0); BigDecimal maxPreviousPrice = closePrices.get(startSearchIdx); for (int i = startSearchIdx + 1; i < currentCloseIndex; i++) { if (i >= closePrices.size()) { break; } BigDecimal price = closePrices.get(i); if (price.compareTo(maxPreviousPrice) > 0) { maxPreviousPrice = price; } } return closePrices.get(currentCloseIndex).compareTo(maxPreviousPrice) > 0; } } src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/EMACalculator.java
New file @@ -0,0 +1,89 @@ /** * 指数移动平均线(EMA)计算器 * <p> * EMA(Exponential Moving Average)是一种加权移动平均线,对近期价格赋予更高权重, * 对远期价格赋予较低权重,能够更敏感地反映价格变化趋势。 * 本计算器提供了EMA的多种计算方式,支持使用SMA作为初始值或使用第一个价格作为初始值。 */ package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.List; /** * 指数移动平均线(EMA)计算器 * * <p>计算公式:EMA(today) = Price(today) * k + EMA(yesterday) * (1 - k)</p> * <p>其中:k = 2 / (period + 1),period为EMA的周期</p> */ public class EMACalculator { /** * 计算价格序列的指数移动平均线(EMA) * * @param prices 价格序列,使用BigDecimal确保计算精度 * @param period EMA计算周期 * @param initialSMA 是否使用SMA(简单移动平均线)作为初始值 * @return 计算得到的EMA序列,与输入价格序列一一对应 * @throws IllegalArgumentException 当输入参数无效时抛出异常 */ public static List<BigDecimal> calculateEMA(List<BigDecimal> prices, int period, boolean initialSMA) { if (prices == null || prices.isEmpty() || period <= 0) { throw new IllegalArgumentException("Invalid input parameters."); } if (initialSMA && prices.size() < period) { throw new IllegalArgumentException("Prices list too short for initial SMA."); } // 计算权重因子k = 2 / (period + 1) BigDecimal alpha = BigDecimal.valueOf(2.0).divide(BigDecimal.valueOf(period + 1), 10, RoundingMode.HALF_UP); List<BigDecimal> ema = new ArrayList<>(); if (initialSMA) { // 使用SMA作为初始EMA值(前period个价格的平均值) BigDecimal sum = BigDecimal.ZERO; for (int i = 0; i < period; i++) { sum = sum.add(prices.get(i)); } BigDecimal sma = sum.divide(BigDecimal.valueOf(period), 10, RoundingMode.HALF_UP); ema.add(sma); // 从第period+1个数据点开始计算后续EMA值 for (int i = period; i < prices.size(); i++) { BigDecimal price = prices.get(i); BigDecimal prevEMA = ema.get(ema.size() - 1); // EMA计算公式:Price(today) * alpha + EMA(yesterday) * (1 - alpha) BigDecimal emaToday = price.multiply(alpha) .add(prevEMA.multiply(BigDecimal.ONE.subtract(alpha))) .setScale(10, RoundingMode.HALF_UP); ema.add(emaToday); } } else { // 使用第一个价格作为初始EMA值,并从第二个数据点开始计算 ema.add(prices.get(0)); for (int i = 1; i < prices.size(); i++) { BigDecimal price = prices.get(i); BigDecimal prevEMA = ema.get(ema.size() - 1); // EMA计算公式:Price(today) * alpha + EMA(yesterday) * (1 - alpha) BigDecimal emaToday = price.multiply(alpha) .add(prevEMA.multiply(BigDecimal.ONE.subtract(alpha))) .setScale(10, RoundingMode.HALF_UP); ema.add(emaToday); } } return ema; } /** * 计算价格序列的指数移动平均线(EMA),默认使用SMA作为初始值 * * @param prices 价格序列 * @param period EMA计算周期 * @return 计算得到的EMA序列 */ public static List<BigDecimal> calculateEMA(List<BigDecimal> prices, int period) { return calculateEMA(prices, period, true); } } src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MACDCalculator.java
New file @@ -0,0 +1,105 @@ package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; /** * MACD(Moving Average Convergence Divergence)指标计算器 * <p> * MACD指标由三部分组成: * 1. DIF(Difference):短期EMA与长期EMA的差值 * 2. DEA(Signal Line):DIF的指数移动平均线,作为MACD的信号线 * 3. MACD柱状图(Histogram):DIF与DEA的差值,反映市场动量 * <p> * 默认参数:短期周期=12,长期周期=26,信号周期=9 */ public class MACDCalculator { /** * 计算MACD指标 * * @param closePrices 收盘价列表(使用BigDecimal确保计算精度) * @param shortPeriod 短期EMA周期(通常为12) * @param longPeriod 长期EMA周期(通常为26) * @param signalPeriod DEA的周期(通常为9) * @return 包含MACD各部分数据的PriceData列表 * @throws IllegalArgumentException 如果数据点不足或参数无效 */ public static MACDResult calculateMACD(List<BigDecimal> closePrices, int shortPeriod, int longPeriod, int signalPeriod) { // 参数校验:确保数据点足够 if (closePrices == null || closePrices.isEmpty()) { throw new IllegalArgumentException("Close prices list cannot be null or empty."); } if (shortPeriod <= 0 || longPeriod <= 0 || signalPeriod <= 0) { throw new IllegalArgumentException("All periods must be positive integers."); } if (shortPeriod >= longPeriod) { throw new IllegalArgumentException("Short period must be less than long period."); } if (closePrices.size() < Math.max(shortPeriod, longPeriod)) { throw new IllegalArgumentException("Insufficient data points for the specified periods."); } // 1. 计算短期和长期EMA(使用SMA作为初始值,提高计算准确性) List<BigDecimal> emaShort = EMACalculator.calculateEMA(closePrices, shortPeriod, true); List<BigDecimal> emaLong = EMACalculator.calculateEMA(closePrices, longPeriod, true); // 2. 确定公共有效起始点(从较长周期的EMA开始计算) int startIdx = Math.max(shortPeriod, longPeriod) - 1; // 因为EMA从第period个数据点开始有效 int validLength = closePrices.size() - startIdx; // 有效数据点数量 // 3. 计算DIF(仅在有效区间内计算) List<BigDecimal> difValues = new ArrayList<>(validLength); for (int i = 0; i < validLength; i++) { // 计算EMA的有效索引 int idxEmaShort = startIdx - shortPeriod + 1 + i; // 短期EMA的当前索引 int idxEmaLong = startIdx - longPeriod + 1 + i; // 长期EMA的当前索引 // DIF = 短期EMA - 长期EMA BigDecimal dif = emaShort.get(idxEmaShort).subtract(emaLong.get(idxEmaLong)); difValues.add(dif); } // 4. 计算DEA(基于有效DIF数据的EMA) List<BigDecimal> deaValues = EMACalculator.calculateEMA(difValues, signalPeriod, true); // 5. 构建并填充结果(仅包含完整的MACD数据) // 有效结果数量 = 有效DIF数量 - DEA周期 + 1 List<PriceData> result = new ArrayList<>(validLength - signalPeriod + 1); // 从DEA开始有效的索引位置开始计算 for (int i = signalPeriod - 1; i < validLength; i++) { int closeIdx = startIdx + i; // 对应原收盘价列表的索引 // 创建价格数据对象 PriceData data = new PriceData(closePrices.get(closeIdx)); // 设置EMA(注意索引偏移计算) data.setEmaShort(emaShort.get(i + shortPeriod - 1)); data.setEmaLong(emaLong.get(i + longPeriod - 1)); // 设置DIF、DEA和MACD柱状图 data.setDif(difValues.get(i)); data.setDea(deaValues.get(i - signalPeriod + 1)); // 调整DEA的索引位置 data.setMacdHist(data.getDif().subtract(data.getDea())); // MACD柱状图 = DIF - DEA result.add(data); } return new MACDResult(result, startIdx); } /** * 使用默认参数计算MACD指标 * <p> * 默认参数:短期周期=12,长期周期=26,信号周期=9 * * @param closePrices 收盘价列表 * @return 包含MACD各部分数据的PriceData列表 */ public static MACDResult calculateMACD(List<BigDecimal> closePrices) { // 默认参数:短期周期12,长期周期26,信号周期9 return calculateMACD(closePrices, 12, 26, 9); } } src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MACDResult.java
New file @@ -0,0 +1,35 @@ /** * MACD计算结果类 * <p> * 用于封装MACD指标计算的结果数据,包括完整的MACD数据序列和数据的起始索引信息, * 方便策略模块获取和使用MACD计算结果。 */ package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy; import lombok.Data; import java.util.List; /** * MACD计算结果封装类 * 使用@Data注解自动生成getter、setter、equals、hashCode和toString方法 */ @Data public class MACDResult { /** MACD完整数据序列,包含每个价格点对应的DIF、DEA和MACD柱状图值 */ private List<PriceData> macdData; /** 在原始价格序列中的起始索引,表示MACD数据的计算起点 */ private int startIndex; /** * 构造函数,创建MACD计算结果对象 * * @param result 计算得到的MACD数据序列 * @param startIdx 数据在原始价格序列中的起始索引 */ public MACDResult(List<PriceData> result, int startIdx) { this.macdData = result; this.startIndex = startIdx; } } src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MACD_MA_Strategy_Documentation
New file @@ -0,0 +1,182 @@ MACD和MA组合交易策略文档 策略概述 该策略是一个综合的技术分析交易系统,结合了EMA指标、MACD指标、价格突破信号和波动率过滤,形成了一套完整的开仓、平仓和持仓管理机制。 策略核心逻辑 开仓条件 多头开仓条件 EMA金叉:短期EMA(12周期)上穿长期EMA(26周期) MACD柱状线扩张+金叉: DIF线(MACD线)上穿DEA线(信号线)形成金叉 MACD柱状线在零轴上方且呈扩张趋势 价格突破前高:价格突破前15个周期的高点 波动率过滤:波动率在0.5%~5%之间 空头开仓条件 EMA死叉:短期EMA(12周期)下穿长期EMA(26周期) MACD柱状线收缩+死叉: DIF线下穿DEA线形成死叉 MACD柱状线在零轴下方且呈收缩趋势 价格跌破前低:价格跌破前低并形成顶背离 波动率过滤:波动率在0.5%~5%之间 平仓条件 平仓逻辑遵循以下优先级: 止损触发:当价格达到预设的止损比例时平仓 止盈触发:当价格达到预设的止盈比例时平仓 MACD反向信号:当出现反向的MACD信号时平仓 平仓流程 plaintext 开始平仓检查 | v 是否持仓? --否--> 结束 | v 检查止损 --触发止损--> 平仓 | v 检查止盈 --触发止盈--> 平仓 | v 检查MACD反向信号 --出现死叉/金叉--> 平仓 | v 保持持仓 策略参数 默认参数 短期EMA周期:12 长期EMA周期:26 MACD信号线周期:9 波动率计算周期:20 止损比例:1% 止盈比例:2% 自定义参数 用户可以根据需要调整以下参数: shortPeriod:短期EMA周期 longPeriod:长期EMA周期 signalPeriod:MACD信号线周期 volatilityPeriod:波动率计算周期 stopLossRatio:止损比例 takeProfitRatio:止盈比例 策略实现细节 关键技术指标 EMA指标:使用指数移动平均线,对近期价格赋予更高权重 MACD指标: DIF:短期EMA与长期EMA的差值 DEA:DIF的指数移动平均线 MACD柱状图:DIF与DEA的差值 波动率指标:使用标准差与平均值的比值计算波动率 信号检测 金叉/死叉检测:通过比较连续两个周期的DIF和DEA值判断交叉信号 柱状线扩张/收缩检测:通过比较连续三个周期的MACD柱状线值判断趋势 价格突破检测:通过比较当前价格与前15个周期的最高/最低价判断突破信号 波动率过滤:确保波动率在0.5%~5%的合理范围内 代码结构 主要类 MacdMaStrategy:策略主类,包含开仓、平仓逻辑 MACDCalculator:MACD指标计算类 EMACalculator:EMA指标计算类 Volatility:波动率计算类 BullishSignalDetector:多头信号检测类 BearishSignalDetector:空头信号检测类 PriceData:价格数据实体类 MACDResult:MACD计算结果封装类 核心方法 analyze() :主分析方法,处理价格数据并生成交易信号 generateTradingOrder() :生成交易指令,返回包含side和posSide的组合 isLongEntryCondition() :多头开仓条件检查 isShortEntryCondition() :空头开仓条件检查 shouldClosePosition() :平仓条件检查 isStopLossTriggered() :止损触发检查 isTakeProfitTriggered() :止盈触发检查 使用示例 java // 创建策略实例(使用默认参数) MacdMaStrategy strategy = new MacdMaStrategy(); // 示例:模拟历史价格数据 List<BigDecimal> historicalPrices = new ArrayList<>(); for (int i = 0; i < 50; i++) { historicalPrices.add(new BigDecimal("100.00").add(new BigDecimal(i * 0.5))); } // 模拟实时价格流处理 for (int i = 0; i < 20; i++) { BigDecimal newPrice = new BigDecimal("125.00").add(new BigDecimal(i * 0.2)); historicalPrices.add(newPrice); // 使用策略分析最新价格数据并生成交易指令 MacdMaStrategy.TradingOrder order = strategy.generateTradingOrder(historicalPrices); // 根据指令执行交易操作 if (order != null) { if (order.getSide().equals("buy") && order.getPosSide().equals("long")) { System.out.println("[交易操作] 买入开多"); } else if (order.getSide().equals("sell") && order.getPosSide().equals("short")) { System.out.println("[交易操作] 卖出开空"); } else if (order.getSide().equals("sell") && order.getPosSide().equals("long")) { System.out.println("[交易操作] 卖出平多"); } else if (order.getSide().equals("buy") && order.getPosSide().equals("short")) { System.out.println("[交易操作] 买入平空"); } } } 交易指令说明 策略生成的交易指令包含以下组合: 操作类型 side posSide 说明 开多 buy long 买入开多 开空 sell short 卖出开空 平多 sell long 卖出平多 平空 buy short 买入平空 策略优势 多维度验证:结合了趋势指标(EMA)、动量指标(MACD)、价格形态和波动率过滤 严格的风险控制:设置了明确的止损和止盈机制 自适应市场:通过波动率过滤避免在极端市场条件下交易 清晰的交易逻辑:开仓和平仓条件明确,易于理解和验证 注意事项 数据质量:策略依赖于高质量的历史价格数据 参数优化:建议根据不同的交易品种和时间周期优化参数 回测验证:在实盘交易前进行充分的回测验证 市场适应性:策略可能在某些市场环境下表现更好,建议结合其他分析方法 总结 该MACD和MA组合交易策略是一个稳健的技术分析系统,通过多维度的指标验证和严格的风险控制,为交易者提供了一套系统化的交易决策框架。策略的实现遵循了模块化设计原则,便于维护和扩展。 src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MacdMaStrategy.java
@@ -1,267 +1,558 @@ /** * MACD和MA组合交易策略实现类 * 基于15分钟K线数据生成交易信号并确定持仓方向 * * 该策略综合考虑了EMA指标、MACD指标、价格突破信号和波动率因素, * 形成了一套完整的开仓、平仓和持仓管理机制。 */ package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy; import com.xcong.excoin.modules.okxNewPrice.indicator.*; import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums; import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.OrderParamEnums; import com.xcong.excoin.modules.okxNewPrice.okxWs.param.TradeRequestParam; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.List; /** * MACD+MA复合交易策略实现类 * * 【策略核心思想】 * 结合移动平均线(MA)的趋势判断能力和MACD指标的动量分析能力,构建一个兼顾趋势跟踪和入场时机的复合交易策略。 * * 【策略主要特点】 * 1. **趋势导向**:以长期MA(100日)作为ETH市场趋势的主要判断依据 * 2. **精确入场**:利用MACD指标的动量变化和短期MA(30日)的支撑/阻力作用确定最佳入场点 * 3. **风险控制**:通过RSI和波动率指标过滤掉风险较高的交易信号,并设置明确的止损止盈 * 4. **分层离场**:结合技术指标、移动平均线和风险控制构建多重离场机制 * 5. **动态调整**:MACD周期根据市场波动率自动调整,适应ETH高波动特性 * * 【核心交易逻辑】 * 1. **趋势判断**:当前价格高于长期MA(100日)判定为牛市,低于则为熊市 * 2. **入场条件**: * - 牛市:MACD多头信号(DIF>DEA) 且 价格>短期MA(30日) 且 MACD柱状图>0 且 RSI在合理区间 * - 熊市:MACD空头信号(DIF<DEA) 且 价格<短期MA(30日) 且 MACD柱状图<0 且 RSI在合理区间 * 3. **离场条件**: * - 牛市多头持仓:MACD空头信号(DIF<DEA) 或 价格跌破短期MA 或 触及止损/止盈 * - 熊市空头持仓:MACD多头信号(DIF>DEA) 或 价格突破短期MA 或 触及止损/止盈 * 4. **过滤条件**: * - 高风险过滤:RSI>65时不追多,RSI<35时不追空 * - 低波动过滤:波动率<0.5%或>5%时不进行交易 * * 【适用场景】 * 专为ETH合约设计,适用于ETH高波动、24/7交易的市场环境,适合中短线趋势交易。 * * 【风险提示】 * 1. 策略需要至少200个价格数据点才能有效运行 * 2. 在极端市场条件下(如黑天鹅事件)可能会产生较大亏损 * 3. 建议结合其他风险控制手段(如止损设置)使用 * MACD和MA组合交易策略实现 * <p> * 该策略利用EMA交叉、MACD指标、价格突破信号和波动率过滤, * 为15分钟K线级别交易提供综合决策支持。 */ public class MacdMaStrategy { /** * 策略使用的技术指标实例(适配ETH合约特点) */ // MACD指标:用于判断价格动量和趋势变化 private final MACD macd = new MACD(); // 30日移动平均线:ETH波动较大,使用更短的周期捕捉趋势变化 private final MovingAverage ma30 = new MovingAverage(30); // 100日移动平均线:ETH作为高波动资产,长期趋势判断使用更短周期 private final MovingAverage ma100 = new MovingAverage(100); // RSI指标(10周期):ETH波动快,使用更短周期提高响应速度 private final RSI rsi = new RSI(10); // 波动率指标(15周期):ETH波动频繁,使用更短周期捕捉市场变化 private final Volatility volatility = new Volatility(15); // 最新价格:用于策略判断 private BigDecimal lastPrice; // 当前市场趋势:牛市/熊市 private TrendDirection trend; /** 持仓状态枚举 */ 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 PositionType currentPosition; // 当前持仓状态 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.currentPosition = PositionType.NONE; this.entryPrice = BigDecimal.ZERO; this.entryTime = 0; } /** * 分析最新价格数据并生成交易信号 * * @param prices 历史价格数据列表 * @throws IllegalArgumentException 如果价格数据不足200个 * @param closePrices 收盘价序列 * @return 生成的交易信号(LONG、SHORT或NONE) */ public void execute(List<BigDecimal> prices) { // 验证输入数据完整性:策略需要至少200个价格数据点 if (prices.size() < 200) { throw new IllegalArgumentException("至少需要200个价格数据点才能运行该策略"); public PositionType analyze(List<BigDecimal> closePrices) { // 数据检查:确保有足够的数据点进行计算 if (closePrices == null || closePrices.size() < 34) { return PositionType.NONE; // 数据不足,无法生成信号 } updateIndicators(prices); trend = getTrend(); // 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 (currentPosition == PositionType.NONE) { // 多头开仓条件检查 if (isLongEntryCondition(macdResult, closePrices, volatility.getValue())) { // 执行开多 this.currentPosition = PositionType.LONG; this.entryPrice = latestPrice; this.entryTime = System.currentTimeMillis(); return PositionType.LONG; } // 空头开仓条件检查 if (isShortEntryCondition(macdResult, closePrices, volatility.getValue())) { // 执行开空 this.currentPosition = PositionType.SHORT; this.entryPrice = latestPrice; this.entryTime = System.currentTimeMillis(); return PositionType.SHORT; } // 无信号 return PositionType.NONE; } else { // 4. 检查平仓条件 if (shouldClosePosition(macdResult, closePrices, latestPrice)) { // 执行平仓 PositionType closedPosition = currentPosition; this.currentPosition = PositionType.NONE; this.entryPrice = BigDecimal.ZERO; this.entryTime = 0; return PositionType.NONE; // 返回空仓信号表示平仓 } // 保持当前持仓 return currentPosition; } } /** * 第二步:确定当前市场趋势 * 交易指令类,封装side和posSide的组合 */ public TrendDirection getTrend(){ return determineTrend(); 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 boolean getSkip(){ return shouldSkipTrade(); public String getPosSide() { return posSide; } @Override public String toString() { return String.format("TradingOrder{side='%s', posSide='%s'}", side, posSide); } } /** * 第四步:是否允许开仓 */ public TradeRequestParam getOrderParamOpen(TradeRequestParam tradeRequestParam){ return checkEntrySignal(tradeRequestParam); } /** * 第五步:是否允许平仓 */ public TradeRequestParam getOrderParamClose(TradeRequestParam tradeRequestParam){ return checkExitSignal(tradeRequestParam); } /** * 更新所有技术指标的计算结果 * 分析历史价格数据并生成交易指令 * * @param prices 历史价格数据列表 * @param historicalPrices 历史价格序列 * @return 交易指令(包含side和posSide),如果没有交易信号则返回null */ private void updateIndicators(List<BigDecimal> prices) { // 先计算波动率指标,因为MACD需要用它来动态调整周期 volatility.calculate(prices); // 计算MACD指标并传入波动率参数,实现动态周期调整 macd.calculate(prices, volatility.getValue()); // 计算移动平均线指标 ma30.calculate(prices); // 计算30日移动平均线 ma100.calculate(prices); // 计算100日移动平均线 // 计算RSI指标 rsi.calculate(prices); // 更新最新价格 lastPrice = prices.get(prices.size()-1); 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"); } else if (signal == PositionType.NONE && currentPosition != PositionType.NONE) { // 平仓操作 if (currentPosition == PositionType.LONG) { // 平多:卖出平多(side 填写 sell;posSide 填写 long ) return new TradingOrder("sell", "long"); } else if (currentPosition == PositionType.SHORT) { // 平空:买入平空(side 填写 buy; posSide 填写 short ) return new TradingOrder("buy", "short"); } } /** * 确定当前市场趋势(牛市/熊市) * */ private TrendDirection determineTrend() { BigDecimal currentMa100 = ma100.getMa(); // 获取当前100日移动平均线 // 根据最新价格与100日MA的关系判断趋势:价格高于100日MA为牛市,否则为熊市 return lastPrice.compareTo(currentMa100) > 0 ? TrendDirection.BULLISH : TrendDirection.BEARISH; // 没有交易信号 return null; } /** * 检查是否需要跳过当前交易 * 多头开仓条件检查 * * @return true表示需要跳过交易,false表示可以进行交易 * @param macdResult MACD计算结果 * @param closePrices 收盘价序列 * @param volatility 当前波动率 * @return 是否满足多头开仓条件 */ private boolean shouldSkipTrade() { // 波动率过滤:当波动率小于1%时,市场活跃度不足,跳过交易 if (volatility.getValue().compareTo(new BigDecimal("0.01")) < 0) { private boolean isLongEntryCondition(MACDResult macdResult, List<BigDecimal> closePrices, BigDecimal volatility) { // 1. EMA金叉检查(短期EMA > 长期EMA) boolean emaGoldenCross = isEmaGoldenCross(macdResult); // 2. MACD柱状线扩张+金叉检查 boolean macdGoldenCross = isMacdGoldenCrossAndExpanding(macdResult); // 3. 价格突破前高检查 boolean priceBreakout = BullishSignalDetector.isBullishSignalFormed(macdResult, closePrices); // 4. 波动率过滤检查(0.5% ~ 5%) boolean volatilityFilter = isVolatilityInRange(volatility); // 所有条件必须同时满足 return emaGoldenCross && macdGoldenCross && priceBreakout && volatilityFilter; } /** * 空头开仓条件检查 * * @param macdResult MACD计算结果 * @param closePrices 收盘价序列 * @param volatility 当前波动率 * @return 是否满足空头开仓条件 */ private boolean isShortEntryCondition(MACDResult macdResult, List<BigDecimal> closePrices, BigDecimal volatility) { // 1. EMA死叉检查(短期EMA < 长期EMA) boolean emaDeathCross = isEmaDeathCross(macdResult); // 2. MACD柱状线收缩+死叉检查 boolean macdDeathCross = isMacdDeathCrossAndContracting(macdResult); // 3. 价格跌破前低检查 boolean priceBreakdown = BearishSignalDetector.isBearishSignalFormed(macdResult, closePrices); // 4. 波动率过滤检查(0.5% ~ 5%) boolean volatilityFilter = isVolatilityInRange(volatility); // 所有条件必须同时满足 return emaDeathCross && macdDeathCross && priceBreakdown && volatilityFilter; } /** * 平仓条件检查 * * @param macdResult MACD计算结果 * @param closePrices 收盘价序列 * @param currentPrice 当前价格 * @return 是否应该平仓 */ private boolean shouldClosePosition(MACDResult macdResult, List<BigDecimal> closePrices, BigDecimal currentPrice) { // 1. 检查止损条件 if (isStopLossTriggered(currentPrice)) { return true; } // RSI极端值过滤: // 1. 牛市中RSI>65表示超买,避免追高 // 2. 熊市中RSI<35表示超卖,避免追空 if (trend == TrendDirection.BULLISH && rsi.getRsi().compareTo(new BigDecimal(65)) > 0) { // 2. 检查止盈条件 if (isTakeProfitTriggered(currentPrice)) { return true; } if (trend == TrendDirection.BEARISH && rsi.getRsi().compareTo(new BigDecimal(35)) < 0) { // 3. 检查MACD反向信号 if (isMacdReversalSignal(macdResult)) { return true; } return false; } /** * 检查是否满足入场信号条件 * EMA金叉检查 * * @param macdResult MACD计算结果 * @return 是否形成EMA金叉 */ private TradeRequestParam checkEntrySignal(TradeRequestParam tradeRequestParam) { String poSide = null; String side = null; BigDecimal currentMa30 = ma30.getMa(); // 获取当前30日移动平均线 // 获取MACD的最新值用于判断动量方向 BigDecimal currentDif = macd.getDif(); BigDecimal currentDea = macd.getDea(); BigDecimal macdBar = macd.getMacdBar(); // 获取MACD柱状图值 // 获取RSI的最新值 BigDecimal currentRsi = rsi.getRsi(); // 获取波动率的最新值 BigDecimal currentVolatility = volatility.getValue(); // 根据市场趋势判断入场条件 if (trend == TrendDirection.BULLISH) { // 牛市入场条件增强: // 1. MACD多头信号(DIF>DEA) // 2. MACD柱状图为正(确认多头力量) // 3. 价格在30日MA之上且有一定偏离(避免假突破) // 4. RSI处于合理区间(40-65),避免在超买区域入场 // 5. 波动率适中(>0.5%且<5%),避免极端波动环境 boolean macdBull = currentDif.compareTo(currentDea) > 0; boolean macdBarPositive = macdBar.compareTo(BigDecimal.ZERO) > 0; BigDecimal priceMaDiff = lastPrice.subtract(currentMa30); boolean priceAboveMAWithStrength = priceMaDiff.compareTo(currentMa30.multiply(new BigDecimal("0.005"))) > 0; boolean rsiInRange = currentRsi.compareTo(new BigDecimal(40)) > 0 && currentRsi.compareTo(new BigDecimal(65)) < 0; boolean volatilityModerate = currentVolatility.compareTo(new BigDecimal("0.005")) > 0 && currentVolatility.compareTo(new BigDecimal("0.05")) < 0; if (macdBull && macdBarPositive && priceAboveMAWithStrength && rsiInRange && volatilityModerate) { poSide = CoinEnums.POSSIDE_LONG.getCode(); side = CoinEnums.SIDE_BUY.getCode(); private boolean isEmaGoldenCross(MACDResult macdResult) { List<PriceData> macdData = macdResult.getMacdData(); if (macdData.size() < 2) { return false; } } else if (trend == TrendDirection.BEARISH){ // 熊市入场条件增强: // 1. MACD空头信号(DIF<DEA) // 2. MACD柱状图为负(确认空头力量) // 3. 价格在30日MA之下且有一定偏离(避免假突破) // 4. RSI处于合理区间(35-60),避免在超卖区域入场 // 5. 波动率适中(>0.5%且<5%),避免极端波动环境 boolean macdBear = currentDif.compareTo(currentDea) < 0; boolean macdBarNegative = macdBar.compareTo(BigDecimal.ZERO) < 0; BigDecimal priceMaDiff = currentMa30.subtract(lastPrice); boolean priceBelowMAWithStrength = priceMaDiff.compareTo(currentMa30.multiply(new BigDecimal("0.005"))) > 0; boolean rsiInRange = currentRsi.compareTo(new BigDecimal(35)) > 0 && currentRsi.compareTo(new BigDecimal(60)) < 0; boolean volatilityModerate = currentVolatility.compareTo(new BigDecimal("0.005")) > 0 && currentVolatility.compareTo(new BigDecimal("0.05")) < 0; if (macdBear && macdBarNegative && priceBelowMAWithStrength && rsiInRange && volatilityModerate) { poSide = CoinEnums.POSSIDE_SHORT.getCode(); side = CoinEnums.SIDE_SELL.getCode(); } } tradeRequestParam.setPosSide(poSide); tradeRequestParam.setSide(side); return tradeRequestParam; 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 TradeRequestParam checkExitSignal(TradeRequestParam tradeRequestParam) { BigDecimal currentMa30 = ma30.getMa(); // 获取当前30日移动平均线 // 获取MACD的最新值用于判断动量变化 BigDecimal currentDif = macd.getDif(); BigDecimal currentDea = macd.getDea(); String posSide = tradeRequestParam.getPosSide(); if (posSide == CoinEnums.POSSIDE_LONG.getCode()) { // 多头持仓离场条件: // 1. MACD转为空头信号(DIF<DEA) // 2. 价格跌破30日MA boolean macdExit = currentDif.compareTo(currentDea) < 0; boolean priceExit = lastPrice.compareTo(currentMa30) < 0; if (macdExit && priceExit) { tradeRequestParam.setSide(CoinEnums.SIDE_SELL.getCode()); } } else if (posSide == CoinEnums.POSSIDE_SHORT.getCode()){ // 空头持仓离场条件: // 1. MACD转为多头信号(DIF>DEA) // 2. 价格突破30日MA boolean macdExit = currentDif.compareTo(currentDea) > 0; boolean priceExit = lastPrice.compareTo(currentMa30) > 0; if (macdExit && priceExit) { tradeRequestParam.setSide(CoinEnums.SIDE_BUY.getCode()); } } return tradeRequestParam; 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); // 枚举定义 enum PositionStatus { FLAT, LONG, SHORT } enum TrendDirection { BULLISH, BEARISH } // 当前短期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); PriceData prevPrev = macdData.get(macdData.size() - 3); // 1. MACD金叉检查(DIF上穿DEA) boolean goldenCross = previous.getDif().compareTo(previous.getDea()) <= 0 && latest.getDif().compareTo(latest.getDea()) > 0; // 2. MACD柱状线扩张检查 boolean histogramExpanding = prevPrev.getMacdHist().compareTo(previous.getMacdHist()) <= 0 && 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() < 3) { return false; } PriceData latest = macdData.get(macdData.size() - 1); PriceData previous = macdData.get(macdData.size() - 2); PriceData prevPrev = macdData.get(macdData.size() - 3); // 1. MACD死叉检查(DIF下穿DEA) boolean deathCross = previous.getDif().compareTo(previous.getDea()) >= 0 && latest.getDif().compareTo(latest.getDea()) < 0; // 2. MACD柱状线收缩检查(绝对值减小) boolean histogramContracting = prevPrev.getMacdHist().abs().compareTo( previous.getMacdHist().abs()) >= 0 && 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.5"); BigDecimal maxVolatility = new BigDecimal("5.0"); return volatility.compareTo(minVolatility) >= 0 && volatility.compareTo(maxVolatility) <= 0; } /** * 止损触发检查 * * @param currentPrice 当前价格 * @return 是否触发止损 */ private boolean isStopLossTriggered(BigDecimal currentPrice) { if (entryPrice.compareTo(BigDecimal.ZERO) == 0) { return false; } if (currentPosition == PositionType.LONG) { // 多头持仓:价格下跌超过止损比例 BigDecimal stopLossPrice = entryPrice.multiply( BigDecimal.ONE.subtract(stopLossRatio)); return currentPrice.compareTo(stopLossPrice) < 0; } else if (currentPosition == PositionType.SHORT) { // 空头持仓:价格上涨超过止损比例 BigDecimal stopLossPrice = entryPrice.multiply( BigDecimal.ONE.add(stopLossRatio)); return currentPrice.compareTo(stopLossPrice) > 0; } return false; } /** * 止盈触发检查 * * @param currentPrice 当前价格 * @return 是否触发止盈 */ private boolean isTakeProfitTriggered(BigDecimal currentPrice) { if (entryPrice.compareTo(BigDecimal.ZERO) == 0) { return false; } if (currentPosition == PositionType.LONG) { // 多头持仓:价格上涨超过止盈比例 BigDecimal takeProfitPrice = entryPrice.multiply( BigDecimal.ONE.add(takeProfitRatio)); return currentPrice.compareTo(takeProfitPrice) > 0; } else if (currentPosition == PositionType.SHORT) { // 空头持仓:价格下跌超过止盈比例 BigDecimal takeProfitPrice = entryPrice.multiply( BigDecimal.ONE.subtract(takeProfitRatio)); return currentPrice.compareTo(takeProfitPrice) < 0; } return false; } /** * MACD反向信号检查 * * @param macdResult MACD计算结果 * @return 是否出现MACD反向信号 */ private boolean isMacdReversalSignal(MACDResult macdResult) { if (currentPosition == PositionType.LONG) { // 多头持仓:检查MACD死叉信号 return isMacdDeathCrossAndContracting(macdResult); } else if (currentPosition == PositionType.SHORT) { // 空头持仓:检查MACD金叉信号 return isMacdGoldenCrossAndExpanding(macdResult); } return false; } /** * 获取当前持仓状态 * * @return 当前持仓状态 */ public PositionType getCurrentPosition() { return currentPosition; } /** * 获取开仓价格 * * @return 开仓价格 */ public BigDecimal getEntryPrice() { return entryPrice; } /** * 获取开仓时间戳 * * @return 开仓时间戳 */ public long getEntryTime() { return entryTime; } /** * 重置策略状态(清空持仓) */ public void reset() { this.currentPosition = PositionType.NONE; this.entryPrice = BigDecimal.ZERO; this.entryTime = 0; } /** * 示例主方法,展示如何使用MACD和MA组合交易策略 * * @param args 命令行参数(未使用) */ public static void main(String[] args) { // 创建策略实例(使用默认参数) MacdMaStrategy strategy = new MacdMaStrategy(); // 示例:模拟历史价格数据(实际应用中应从数据源获取) List<BigDecimal> historicalPrices = new ArrayList<>(); // 生成一些示例价格数据(这里仅作演示) // 实际应用中应替换为真实的历史K线数据 for (int i = 0; i < 50; i++) { // 模拟价格数据(示例中使用简单递增的价格) historicalPrices.add(new BigDecimal("100.00").add(new BigDecimal(i * 0.5))); } // 模拟实时价格流处理 System.out.println("===== MACD和MA组合交易策略示例 ====="); System.out.println("开始处理价格数据并生成交易信号..."); // 模拟实时数据流(假设我们有更多的价格数据) for (int i = 0; i < 20; i++) { // 添加新的价格数据(这里仅作演示) BigDecimal newPrice = new BigDecimal("125.00").add(new BigDecimal(i * 0.2)); historicalPrices.add(newPrice); // 使用策略分析最新价格数据并生成交易指令 TradingOrder order = strategy.generateTradingOrder(historicalPrices); // 输出交易信号和指令 System.out.printf("价格: %s, 当前持仓: %s, 交易指令: %s\n", newPrice.setScale(2, RoundingMode.HALF_UP), strategy.getCurrentPosition().name(), order != null ? order.toString() : "无交易指令"); // 示例:在实际应用中,你可能需要根据指令执行交易操作 if (order != null) { if (order.getSide().equals("buy") && order.getPosSide().equals("long")) { System.out.println("[交易操作] 买入开多"); } else if (order.getSide().equals("sell") && order.getPosSide().equals("short")) { System.out.println("[交易操作] 卖出开空"); } else if (order.getSide().equals("sell") && order.getPosSide().equals("long")) { System.out.println("[交易操作] 卖出平多"); } else if (order.getSide().equals("buy") && order.getPosSide().equals("short")) { System.out.println("[交易操作] 买入平空"); } } } // 打印策略最终状态 System.out.println("\n===== 策略最终状态 ====="); System.out.println("当前持仓: " + strategy.getCurrentPosition().name()); System.out.println("开仓价格: " + strategy.getEntryPrice()); System.out.println("开仓时间戳: " + strategy.getEntryTime()); } } src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MacdMaStrategyMain.java
File was deleted src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MacdMaStrategyTest.java
New file @@ -0,0 +1,196 @@ /** * MACD和MA组合交易策略测试类 * 用于验证策略在不同市场条件下的表现 */ package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; /** * 策略测试类,提供多种测试场景验证策略功能 */ public class MacdMaStrategyTest { public static void main(String[] args) { // 测试场景1:简单上涨趋势 testSimpleUptrend(); // 测试场景2:简单下跌趋势 testSimpleDowntrend(); // 测试场景3:震荡市场 testSidewaysMarket(); // 测试场景4:波动率过滤 testVolatilityFilter(); } /** * 测试简单上涨趋势场景 */ private static void testSimpleUptrend() { System.out.println("\n===== 测试场景1:简单上涨趋势 ====="); MacdMaStrategy strategy = new MacdMaStrategy(); List<BigDecimal> prices = new ArrayList<>(); // 生成初始价格数据 for (int i = 0; i < 50; i++) { prices.add(new BigDecimal("100.00").add(new BigDecimal(i * 0.5))); } // 模拟上涨趋势 for (int i = 0; i < 30; i++) { BigDecimal newPrice = new BigDecimal("125.00").add(new BigDecimal(i * 0.3)); prices.add(newPrice); MacdMaStrategy.TradingOrder order = strategy.generateTradingOrder(prices); System.out.printf("价格: %.2f, 当前持仓: %s, 交易指令: %s\n", newPrice.doubleValue(), strategy.getCurrentPosition().name(), order != null ? order.toString() : "无交易指令"); } } /** * 测试简单下跌趋势场景 */ private static void testSimpleDowntrend() { System.out.println("\n===== 测试场景2:简单下跌趋势 ====="); MacdMaStrategy strategy = new MacdMaStrategy(); List<BigDecimal> prices = new ArrayList<>(); // 生成初始价格数据 for (int i = 0; i < 50; i++) { prices.add(new BigDecimal("150.00").subtract(new BigDecimal(i * 0.5))); } // 模拟下跌趋势 for (int i = 0; i < 30; i++) { BigDecimal newPrice = new BigDecimal("125.00").subtract(new BigDecimal(i * 0.3)); prices.add(newPrice); MacdMaStrategy.TradingOrder order = strategy.generateTradingOrder(prices); System.out.printf("价格: %.2f, 当前持仓: %s, 交易指令: %s\n", newPrice.doubleValue(), strategy.getCurrentPosition().name(), order != null ? order.toString() : "无交易指令"); } } /** * 测试震荡市场场景 */ private static void testSidewaysMarket() { System.out.println("\n===== 测试场景3:震荡市场 ====="); MacdMaStrategy strategy = new MacdMaStrategy(); List<BigDecimal> prices = new ArrayList<>(); // 生成初始价格数据 for (int i = 0; i < 50; i++) { prices.add(new BigDecimal("120.00")); } // 模拟震荡市场 for (int i = 0; i < 30; i++) { // 生成上下波动的价格 double price = 120.0 + Math.sin(i * 0.5) * 5.0; BigDecimal newPrice = new BigDecimal(price).setScale(2, BigDecimal.ROUND_HALF_UP); prices.add(newPrice); MacdMaStrategy.TradingOrder order = strategy.generateTradingOrder(prices); System.out.printf("价格: %.2f, 当前持仓: %s, 交易指令: %s\n", newPrice.doubleValue(), strategy.getCurrentPosition().name(), order != null ? order.toString() : "无交易指令"); } } /** * 测试波动率过滤功能 */ private static void testVolatilityFilter() { System.out.println("\n===== 测试场景4:波动率过滤 ====="); MacdMaStrategy strategy = new MacdMaStrategy(); List<BigDecimal> prices = new ArrayList<>(); // 生成初始价格数据 for (int i = 0; i < 50; i++) { prices.add(new BigDecimal("100.00")); } // 模拟低波动率市场 System.out.println("\n--- 低波动率市场测试 ---"); for (int i = 0; i < 10; i++) { BigDecimal newPrice = new BigDecimal("100.00").add(new BigDecimal(i * 0.1)); prices.add(newPrice); MacdMaStrategy.TradingOrder order = strategy.generateTradingOrder(prices); System.out.printf("价格: %.2f, 当前持仓: %s, 交易指令: %s\n", newPrice.doubleValue(), strategy.getCurrentPosition().name(), order != null ? order.toString() : "无交易指令"); } // 模拟高波动率市场 System.out.println("\n--- 高波动率市场测试 ---"); for (int i = 0; i < 10; i++) { double price = 100.0 + Math.random() * 10.0; BigDecimal newPrice = new BigDecimal(price).setScale(2, BigDecimal.ROUND_HALF_UP); prices.add(newPrice); MacdMaStrategy.TradingOrder order = strategy.generateTradingOrder(prices); System.out.printf("价格: %.2f, 当前持仓: %s, 交易指令: %s\n", newPrice.doubleValue(), strategy.getCurrentPosition().name(), order != null ? order.toString() : "无交易指令"); } } /** * 测试止损和止盈功能 */ private static void testStopLossAndTakeProfit() { System.out.println("\n===== 测试场景5:止损和止盈 ====="); // 创建策略实例,设置1%止损和2%止盈 MacdMaStrategy strategy = new MacdMaStrategy(12, 26, 9, 20, new BigDecimal("0.01"), new BigDecimal("0.02")); List<BigDecimal> prices = new ArrayList<>(); // 生成初始价格数据 for (int i = 0; i < 50; i++) { prices.add(new BigDecimal("100.00").add(new BigDecimal(i * 0.5))); } // 开多仓 strategy.generateTradingOrder(prices); System.out.printf("开仓价格: %.2f, 止损价格: %.2f, 止盈价格: %.2f\n", strategy.getEntryPrice().doubleValue(), strategy.getEntryPrice().multiply(new BigDecimal("0.99")).doubleValue(), strategy.getEntryPrice().multiply(new BigDecimal("1.02")).doubleValue()); // 测试止损触发 System.out.println("\n--- 测试止损触发 ---"); for (int i = 0; i < 5; i++) { BigDecimal newPrice = strategy.getEntryPrice().subtract(new BigDecimal(i * 0.5)); prices.add(newPrice); MacdMaStrategy.TradingOrder order = strategy.generateTradingOrder(prices); System.out.printf("价格: %.2f, 当前持仓: %s, 交易指令: %s\n", newPrice.doubleValue(), strategy.getCurrentPosition().name(), order != null ? order.toString() : "无交易指令"); if (strategy.getCurrentPosition() == MacdMaStrategy.PositionType.NONE) { break; } } } } src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MovingAverage.java
File was deleted src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/PriceData.java
New file @@ -0,0 +1,162 @@ /** * 价格数据实体类 * <p> * 用于存储K线的价格数据及其衍生指标值。作为MACD和MA策略计算过程中的数据载体, * 包含收盘价、指数移动平均线、MACD指标等关键价格和指标信息。 */ package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy; import java.math.BigDecimal; /** * 价格数据类,封装单条K线的价格信息及相关技术指标数据 * 使用BigDecimal确保金融计算的精确性 */ public class PriceData { /** 收盘价 */ private BigDecimal close; /** 短期指数移动平均线(通常为12周期) */ private BigDecimal emaShort; /** 长期指数移动平均线(通常为26周期) */ private BigDecimal emaLong; /** DIF值(短期EMA与长期EMA的差值) */ private BigDecimal dif; /** DEA值(DIF的移动平均线,通常为9周期EMA) */ private BigDecimal dea; /** MACD柱状图值(DIF与DEA的差值) */ private BigDecimal macdHist; /** * 构造函数,创建价格数据对象 * * @param close 收盘价 */ public PriceData(BigDecimal close) { this.close = close; } /** * 获取收盘价 * * @return 收盘价 */ public BigDecimal getClose() { return close; } /** * 设置收盘价 * * @param close 收盘价 */ public void setClose(BigDecimal close) { this.close = close; } /** * 获取短期EMA值 * * @return 短期EMA值 */ public BigDecimal getEmaShort() { return emaShort; } /** * 设置短期EMA值 * * @param emaShort 短期EMA值 */ public void setEmaShort(BigDecimal emaShort) { this.emaShort = emaShort; } /** * 获取长期EMA值 * * @return 长期EMA值 */ public BigDecimal getEmaLong() { return emaLong; } /** * 设置长期EMA值 * * @param emaLong 长期EMA值 */ public void setEmaLong(BigDecimal emaLong) { this.emaLong = emaLong; } /** * 获取DIF值 * * @return DIF值 */ public BigDecimal getDif() { return dif; } /** * 设置DIF值 * * @param dif DIF值 */ public void setDif(BigDecimal dif) { this.dif = dif; } /** * 获取DEA值 * * @return DEA值 */ public BigDecimal getDea() { return dea; } /** * 设置DEA值 * * @param dea DEA值 */ public void setDea(BigDecimal dea) { this.dea = dea; } /** * 获取MACD柱状图值 * * @return MACD柱状图值 */ public BigDecimal getMacdHist() { return macdHist; } /** * 设置MACD柱状图值 * * @param macdHist MACD柱状图值 */ public void setMacdHist(BigDecimal macdHist) { this.macdHist = macdHist; } /** * 转换为字符串表示 * * @return 格式化的字符串表示 */ @Override public String toString() { return String.format("PriceData{close=%.2f, EMA_short=%.2f, EMA_long=%.2f, DIF=%.2f, DEA=%.2f, MACD_hist=%.2f}", close.doubleValue(), emaShort.doubleValue(), emaLong.doubleValue(), dif.doubleValue(), dea.doubleValue(), macdHist.doubleValue()); } } src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/Volatility.java
@@ -1,90 +1,125 @@ /** * 波动率指标计算类 * <p> * 波动率是衡量金融市场价格波动程度的指标,通常用价格的标准差与平均值的比率表示。 * 本类实现了基于滚动窗口的波动率计算,通过标准差与平均值的比值计算出百分比形式的波动率。 */ package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.LinkedList; import java.util.List; /** * 波动率指标实现类 * <p>波动率计算原理:使用标准差与平均值的比值,以百分比形式表示价格的波动程度。</p> * <p>计算公式:波动率 = (标准差 / 平均值) * 100%</p> * * 波动率是衡量价格波动程度的技术指标,反映市场的活跃度和风险水平。 * 本类通过计算价格的标准差来衡量波动率,使用高精度BigDecimal计算避免精度损失。 * <p>使用示例:</p> * <pre> * // 初始化20日波动率计算器 * Volatility vol = new Volatility(20); * * 【核心功能】 * 1. 计算指定周期内价格的波动率(标准差) * 2. 使用牛顿迭代法实现BigDecimal的平方根计算,确保金融计算的高精度要求 * 3. 提供获取最新计算结果的接口 * // 动态添加每日价格 * priceFeed.subscribe(price -> { * vol.addPrice(price); * vol.calculate(); * }); * * 【使用场景】 * - 在MACD+MA策略中,用于过滤低波动率的市场环境(波动率<1%时跳过交易) * - 适用于各种时间周期的价格数据,通常使用20日或30日周期 * 【注意事项】 * - 波动率指标对市场流动性和交易活跃度敏感 * - 低波动率可能预示着市场趋势即将发生变化 * // 判断是否满足低波动条件(<1%) * if (vol.getValue().compareTo(new BigDecimal("1.00")) < 0) { * System.out.println("低波动市场,暂停交易"); * } * </pre> */ public class Volatility { /** * 波动率计算的周期 */ /** 波动率计算的周期(如20日波动率) */ private final int period; /** * 最新计算的波动率值 */ /** 当前计算出的波动率值(百分比形式) */ private BigDecimal volatility = BigDecimal.ZERO; /** 使用LinkedList存储滚动窗口内的价格数据,便于添加和删除操作 */ private LinkedList<BigDecimal> priceWindow = new LinkedList<>(); /** 窗口内价格的总和,用于快速计算平均值 */ private BigDecimal sum = BigDecimal.ZERO; /** 窗口内价格平方的总和,用于快速计算方差 */ private BigDecimal sumSquares = BigDecimal.ZERO; /** * 构造方法 * 构造函数,创建指定周期的波动率计算器 * * @param period 波动率计算的周期 * @param period 波动率计算周期,如20表示计算20日波动率 */ public Volatility(int period) { this.period = period; } /** * 计算波动率 * 添加新价格到计算窗口,并维护窗口内的价格数据 * 采用滑动窗口方式,当价格数量超过周期时,自动移除最早的价格 * * @param prices 历史价格数据列表 * @param price 新的价格数据,使用BigDecimal确保计算精度 * @throws IllegalArgumentException 当价格为null时抛出异常 */ public void calculate(List<BigDecimal> prices) { // 如果价格数据不足计算周期,不进行计算 if (prices.size() < period) { public void addPrice(BigDecimal price) { if (price == null) { throw new IllegalArgumentException("Price cannot be null"); } // 当窗口大小达到周期时,移除最早的价格,并从总和中减去 if (priceWindow.size() == period) { BigDecimal removed = priceWindow.removeFirst(); sum = sum.subtract(removed); sumSquares = sumSquares.subtract(removed.pow(2)); } // 添加新价格到窗口,并更新总和 priceWindow.add(price); sum = sum.add(price); sumSquares = sumSquares.add(price.pow(2)); } /** * 计算当前窗口内价格的波动率 * 使用标准差与平均值的比值计算波动率百分比 */ public void calculate() { // 数据点不足,无法计算波动率 if (priceWindow.size() < period) { return; } BigDecimal sum = BigDecimal.ZERO; BigDecimal avg = calculateAverage(prices); // 计算平均价格 // 计算平均值:sum / period BigDecimal avg = sum.divide(new BigDecimal(period), 8, RoundingMode.HALF_UP); // 计算每个价格与平均价格的偏差平方和 for (int i = prices.size()-period; i < prices.size(); i++) { BigDecimal dev = prices.get(i).subtract(avg); sum = sum.add(dev.pow(2)); // 防止除以零的情况 if (avg.compareTo(BigDecimal.ZERO) == 0) { volatility = BigDecimal.ZERO; return; } // 计算方差,保留8位小数 BigDecimal variance = sum.divide(new BigDecimal(period), 8, RoundingMode.HALF_UP); // 计算方差:(sumSquares / period) - avg^2 BigDecimal variance = sumSquares.divide(new BigDecimal(period), 8, RoundingMode.HALF_UP) .subtract(avg.pow(2)); // 计算标准差(波动率),使用牛顿迭代法确保高精度 volatility = sqrt(variance, 8); // 确保方差非负(防止浮点数计算误差导致负数方差) variance = variance.max(BigDecimal.ZERO); // 计算标准差:sqrt(variance) BigDecimal stdDev = sqrt(variance, 8); // 计算波动率:(标准差 / 平均值) * 100% volatility = stdDev.divide(avg, 8, RoundingMode.HALF_UP) .multiply(new BigDecimal(100)) .setScale(2, RoundingMode.HALF_UP); } /** * 计算价格平均值 * * @param prices 历史价格数据列表 * @return 平均价格 */ private BigDecimal calculateAverage(List<BigDecimal> prices) { 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); } /** * 计算BigDecimal的平方根(牛顿迭代法) * 计算BigDecimal的平方根(使用牛顿迭代法) * * @param value 要计算平方根的数值 * @param scale 结果的精度(小数位数) @@ -112,7 +147,7 @@ /** * 获取最新的波动率计算结果 * * @return 波动率值 * @return 波动率值,以百分比形式表示(例如:2.5表示2.5%) */ public BigDecimal getValue() { return volatility;