| | |
| | | import lombok.extern.slf4j.Slf4j; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | |
| | | // 计算所有指标 |
| | | calculateIndicators(prices, high, low, close); |
| | | |
| | | // 检查是否为震荡市场,如果是,则无信号 |
| | | if (isRangeMarket()) { |
| | | log.debug("当前市场为震荡行情,不产生信号"); |
| | | return SignalType.NONE; |
| | | // 检查是否为震荡市场,如果是,则执行区间交易策略 |
| | | if (isRangeMarket(prices)) { |
| | | log.info("当前市场为震荡行情,执行区间交易策略"); |
| | | return generateRangeTradingSignal(currentPrice, volume, hasLongPosition, hasShortPosition); |
| | | } |
| | | |
| | | // 黑天鹅事件过滤 |
| | | if (blackSwanFilter(fundingRate, hasLargeTransfer, hasUpcomingEvent)) { |
| | | log.debug("黑天鹅事件过滤触发,不产生信号"); |
| | | log.info("黑天鹅事件过滤触发,不产生信号"); |
| | | return SignalType.NONE; |
| | | } |
| | | |
| | | // 开多信号 |
| | | if (shouldOpenLong(currentPrice, prices, volume, fiveMinPrices, oneHourPrices, fourHourPrices) && !hasLongPosition && !hasShortPosition) { |
| | | log.debug("生成买入信号"); |
| | | log.info("生成买入信号"); |
| | | return SignalType.BUY; |
| | | } |
| | | |
| | | // 开空信号 |
| | | if (shouldOpenShort(currentPrice, volume, fiveMinPrices, oneHourPrices, fourHourPrices) && !hasLongPosition && !hasShortPosition) { |
| | | log.debug("生成卖出信号"); |
| | | if (shouldOpenShort(currentPrice, prices, volume, fiveMinPrices, oneHourPrices, fourHourPrices) && !hasLongPosition && !hasShortPosition) { |
| | | log.info("生成卖出信号"); |
| | | return SignalType.SELL; |
| | | } |
| | | |
| | | // 平多信号 |
| | | if (shouldCloseLong(currentPrice, volume, fiveMinPrices, oneHourPrices, fourHourPrices) && hasLongPosition) { |
| | | log.debug("生成平多信号"); |
| | | log.info("生成平多信号"); |
| | | return SignalType.CLOSE_BUY; |
| | | } |
| | | |
| | | // 平空信号 |
| | | if (shouldCloseShort(currentPrice, volume, fiveMinPrices, oneHourPrices, fourHourPrices) && hasShortPosition) { |
| | | log.debug("生成平空信号"); |
| | | log.info("生成平空信号"); |
| | | return SignalType.CLOSE_SELL; |
| | | } |
| | | |
| | | log.debug("未生成信号"); |
| | | log.info("未生成信号"); |
| | | return SignalType.NONE; |
| | | } |
| | | |
| | |
| | | |
| | | // 创建临时MA指标用于判断趋势 |
| | | MA tempMA = new MA(); |
| | | MACD tempMACD = new MACD(); |
| | | |
| | | // 计算指标 |
| | | tempMA.calculate(prices); |
| | | tempMACD.calculate(prices); |
| | | |
| | | // 简单的趋势判断:短期MA > 长期MA |
| | | return tempMA.getEma5().compareTo(tempMA.getEma20()) > 0; |
| | | // 优化后的多头趋势判断: |
| | | // 1. MA多头排列:短期MA > 中期MA > 长期MA |
| | | // 2. 所有均线向上发散 |
| | | // 3. MACD趋势:DIFF线在DEA线之上 |
| | | boolean isMaBullish = tempMA.getEma5().compareTo(tempMA.getEma10()) > 0 && |
| | | tempMA.getEma10().compareTo(tempMA.getEma20()) > 0; |
| | | |
| | | // 判断均线向上发散(简单方法:近期均线斜率为正) |
| | | boolean isMaDivergingUp = calculateMaSlope(prices, 5) > 0 && |
| | | calculateMaSlope(prices, 10) > 0 && |
| | | calculateMaSlope(prices, 20) > 0; |
| | | |
| | | // MACD趋势判断 |
| | | boolean isMacdBullish = tempMACD.getDif().compareTo(tempMACD.getDea()) > 0; |
| | | |
| | | return isMaBullish && isMaDivergingUp && isMacdBullish; |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | // 创建临时MA指标用于判断趋势 |
| | | MA tempMA = new MA(); |
| | | MACD tempMACD = new MACD(); |
| | | |
| | | // 计算指标 |
| | | tempMA.calculate(prices); |
| | | tempMACD.calculate(prices); |
| | | |
| | | // 简单的趋势判断:短期MA < 长期MA |
| | | return tempMA.getEma5().compareTo(tempMA.getEma20()) < 0; |
| | | // 优化后的空头趋势判断: |
| | | // 1. MA空头排列:短期MA < 中期MA < 长期MA |
| | | // 2. 所有均线向下发散 |
| | | // 3. MACD趋势:DIFF线在DEA线之下 |
| | | boolean isMaBearish = tempMA.getEma5().compareTo(tempMA.getEma10()) < 0 && |
| | | tempMA.getEma10().compareTo(tempMA.getEma20()) < 0; |
| | | |
| | | // 判断均线向下发散(简单方法:近期均线斜率为负) |
| | | boolean isMaDivergingDown = calculateMaSlope(prices, 5) < 0 && |
| | | calculateMaSlope(prices, 10) < 0 && |
| | | calculateMaSlope(prices, 20) < 0; |
| | | |
| | | // MACD趋势判断 |
| | | boolean isMacdBearish = tempMACD.getDif().compareTo(tempMACD.getDea()) < 0; |
| | | |
| | | return isMaBearish && isMaDivergingDown && isMacdBearish; |
| | | } |
| | | |
| | | /** |
| | | * 计算均线斜率 |
| | | * @param prices 价格数据 |
| | | * @param period 均线周期 |
| | | * @return 均线斜率(正数表示向上,负数表示向下) |
| | | */ |
| | | private double calculateMaSlope(List<BigDecimal> prices, int period) { |
| | | if (prices == null || prices.size() < period * 2) { |
| | | return 0; // 数据不足 |
| | | } |
| | | |
| | | // 获取最近两个周期的均线值 |
| | | int endIndex = prices.size() - 1; |
| | | int startIndex = endIndex - period + 1; |
| | | |
| | | // 计算当前周期的均线 |
| | | BigDecimal currentMa = calculateMA(prices.subList(startIndex, endIndex + 1), period); |
| | | |
| | | // 计算前一个周期的均线 |
| | | int prevStartIndex = startIndex - period; |
| | | BigDecimal prevMa = calculateMA(prices.subList(prevStartIndex, startIndex), period); |
| | | |
| | | // 计算斜率(简单差值法) |
| | | return currentMa.subtract(prevMa).doubleValue(); |
| | | } |
| | | |
| | | /** |
| | | * 成交量验证辅助方法 |
| | | * 增强版:当前成交量需大于20周期均量的1.2倍,以过滤无量反弹/回调的假信号 |
| | | * @param volume 成交量列表 |
| | | * @return 是否通过成交量验证 |
| | | */ |
| | |
| | | BigDecimal volumeMA = calculateMA(volume, config.getVolumeMaPeriod()); |
| | | BigDecimal currentVolume = volume.get(volume.size() - 1); |
| | | |
| | | // 成交量需要大于均线 |
| | | return currentVolume.compareTo(volumeMA) > 0; |
| | | // 增强验证:成交量需要大于1.2倍均线 |
| | | return currentVolume.compareTo(volumeMA.multiply(new BigDecimal("1.2"))) > 0; |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | // 使用动态参数计算指标 |
| | | if (config.isEnableDynamicParams()) { |
| | | log.debug("使用动态参数计算指标,波动率: {}", volatility); |
| | | log.info("使用动态参数计算指标,波动率: {}", volatility); |
| | | ma.calculate(prices, volatility); |
| | | macd.calculate(prices, volatility); |
| | | } else { |
| | |
| | | |
| | | /** |
| | | * 检查是否为震荡市场 |
| | | * @param prices 价格数据列表 |
| | | * @return 是否为震荡市场 |
| | | */ |
| | | private boolean isRangeMarket() { |
| | | // 高级MA线收敛 + RSI(40-60) + 布林带收窄 |
| | | private boolean isRangeMarket(List<BigDecimal> prices) { |
| | | // 强化的震荡市场判断条件 |
| | | |
| | | // 1. 高级MA三线收敛(小于2%) |
| | | boolean isMaConverged = advancedMA.calculatePercent().compareTo(new BigDecimal(2)) < 0; |
| | | |
| | | // 2. RSI中性区间(40-60) |
| | | boolean isRsiNeutral = rsi.getRsi().compareTo(new BigDecimal(40)) > 0 && |
| | | rsi.getRsi().compareTo(new BigDecimal(60)) < 0; |
| | | |
| | | // 3. BOLL带宽收窄(小于0.05) |
| | | boolean isBollNarrow = boll.calculateBandWidth().compareTo(new BigDecimal(0.05)) < 0; |
| | | |
| | | return isMaConverged && isRsiNeutral && isBollNarrow; |
| | | |
| | | // 4. MACD柱状图趋近于0(多空力量平衡) |
| | | boolean isMacdBalanced = macd.getMacdBar().abs().compareTo(new BigDecimal(0.01)) < 0; |
| | | |
| | | // 5. KDJ在中间区域波动(30-70) |
| | | boolean isKdjNeutral = kdj.getK().compareTo(new BigDecimal(30)) > 0 && |
| | | kdj.getK().compareTo(new BigDecimal(70)) < 0 && |
| | | kdj.getD().compareTo(new BigDecimal(30)) > 0 && |
| | | kdj.getD().compareTo(new BigDecimal(70)) < 0 && |
| | | kdj.getJ().compareTo(new BigDecimal(30)) > 0 && |
| | | kdj.getJ().compareTo(new BigDecimal(70)) < 0; |
| | | |
| | | // 6. 价格波动范围较小(最近20根K线最高价与最低价的比值小于1.05) |
| | | boolean isPriceVolatilityLow = calculatePriceVolatility(prices).compareTo(new BigDecimal(1.05)) < 0; |
| | | |
| | | // 综合判断:需要满足大部分条件 |
| | | return isMaConverged && isRsiNeutral && isBollNarrow && |
| | | (isMacdBalanced || isKdjNeutral || isPriceVolatilityLow); |
| | | } |
| | | |
| | | /** |
| | | * 计算价格波动率(最近20根K线最高价与最低价的比值) |
| | | * @param prices 价格数据列表 |
| | | * @return 价格波动率 |
| | | */ |
| | | private BigDecimal calculatePriceVolatility(List<BigDecimal> prices) { |
| | | if (prices == null || prices.isEmpty()) { |
| | | return BigDecimal.ONE; |
| | | } |
| | | |
| | | List<BigDecimal> recentPrices = prices.subList(Math.max(0, prices.size() - 20), prices.size()); |
| | | if (recentPrices.isEmpty()) { |
| | | return BigDecimal.ONE; |
| | | } |
| | | |
| | | BigDecimal highest = recentPrices.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ONE); |
| | | BigDecimal lowest = recentPrices.stream().min(BigDecimal::compareTo).orElse(BigDecimal.ONE); |
| | | |
| | | return highest.divide(lowest, 10, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * 根据15分钟时间框架指标确定市场方向(做多/做空/震荡) |
| | |
| | | * @param currentPrice 当前价格 |
| | | * @return 市场方向 |
| | | */ |
| | | public Direction getDirection(List<BigDecimal> prices, List<BigDecimal> high, |
| | | public Direction getDirection(List<BigDecimal> prices, List<BigDecimal> high, |
| | | List<BigDecimal> low, List<BigDecimal> close, |
| | | BigDecimal currentPrice) { |
| | | // 计算所有指标 |
| | | calculateIndicators(prices, high, low, close); |
| | | |
| | | // 检查是否为震荡市场 |
| | | if (isRangeMarket()) { |
| | | if (isRangeMarket(prices)) { |
| | | return Direction.RANGING; |
| | | } |
| | | |
| | | // 检查做多方向条件:MA多头排列 + MACD金叉 + RSI中性(30-70) + BOLL价格在上轨和中轨之间 |
| | | // 优化后的多头趋势条件: |
| | | // 1. MA多头排列(短期MA > 中期MA > 长期MA) |
| | | // 2. MACD趋势(DIFF在DEA之上,代表多头趋势) |
| | | // 3. 价格在BOLL中轨上方 |
| | | // 4. RSI(50-65) 为健康多头区间 |
| | | boolean isMaBullish = ma.getEma5().compareTo(ma.getEma10()) > 0 && |
| | | ma.getEma10().compareTo(ma.getEma20()) > 0; |
| | | boolean isMacdGoldenCross = macd.getDif().compareTo(macd.getDea()) > 0; |
| | | boolean isRsiNeutral = rsi.getRsi().compareTo(new BigDecimal(30)) > 0 && |
| | | rsi.getRsi().compareTo(new BigDecimal(70)) < 0; |
| | | boolean isPriceInUpperMid = currentPrice.compareTo(boll.getMid()) > 0 && |
| | | currentPrice.compareTo(boll.getUpper()) < 0; |
| | | |
| | | if (isMaBullish && isMacdGoldenCross && isRsiNeutral && isPriceInUpperMid) { |
| | | boolean isMacdBullish = macd.getDif().compareTo(macd.getDea()) > 0; |
| | | boolean isPriceAboveBollMid = currentPrice.compareTo(boll.getMid()) > 0; |
| | | boolean isRsiBullish = rsi.getRsi().compareTo(new BigDecimal(50)) > 0 && |
| | | rsi.getRsi().compareTo(new BigDecimal(65)) < 0; |
| | | |
| | | // 检查多头信号(MA多头 + MACD金叉 + 价格在BOLL中轨上方 + RSI(50-65)) |
| | | if (isMaBullish && isMacdBullish && isPriceAboveBollMid && isRsiBullish) { |
| | | return Direction.LONG; |
| | | } |
| | | |
| | | // 检查做空方向条件:MA空头排列 + MACD死叉 + RSI中性(30-70) + BOLL价格在下轨和中轨之间 |
| | | // 优化后的空头趋势条件: |
| | | // 1. MA空头排列(短期MA < 中期MA < 长期MA) |
| | | // 2. MACD趋势(DIFF在DEA之下,代表空头趋势) |
| | | // 3. 价格在BOLL中轨下方 |
| | | // 4. RSI(35-50) 为健康空头区间 |
| | | boolean isMaBearish = ma.getEma5().compareTo(ma.getEma10()) < 0 && |
| | | ma.getEma10().compareTo(ma.getEma20()) < 0; |
| | | boolean isMacdDeathCross = macd.getDif().compareTo(macd.getDea()) < 0; |
| | | boolean isPriceInLowerMid = currentPrice.compareTo(boll.getLower()) > 0 && |
| | | currentPrice.compareTo(boll.getMid()) < 0; |
| | | |
| | | if (isMaBearish && isMacdDeathCross && isRsiNeutral && isPriceInLowerMid) { |
| | | boolean isMacdBearish = macd.getDif().compareTo(macd.getDea()) < 0; |
| | | boolean isPriceBelowBollMid = currentPrice.compareTo(boll.getMid()) < 0; |
| | | boolean isRsiBearish = rsi.getRsi().compareTo(new BigDecimal(35)) > 0 && |
| | | rsi.getRsi().compareTo(new BigDecimal(50)) < 0; |
| | | |
| | | // 检查空头信号(MA空头 + MACD死叉 + 价格在BOLL中轨下方 + RSI(35-50)) |
| | | if (isMaBearish && isMacdBearish && isPriceBelowBollMid && isRsiBearish) { |
| | | return Direction.SHORT; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * 根据用户要求检查是否应该开多仓位 |
| | | * 条件:MA金叉 + MACD金叉 + KDJ金叉 + RSI中性 + 价格高于BOLL中轨 + 多周期确认 + 成交量验证 |
| | | * 根据优化建议检查是否应该开多仓位 |
| | | * 使用主次结合法: |
| | | * - 主信号(趋势):MA多头排列 + MACD金叉 |
| | | * - 辅信号(入场点):价格回调至BOLL中轨附近获得支撑,且出现KDJ金叉或RSI从低位回升至50以上 |
| | | * @param currentPrice 当前价格 |
| | | * @param prices 价格数据 |
| | | * @param volume 成交量列表 |
| | |
| | | List<BigDecimal> fiveMinPrices, |
| | | List<BigDecimal> oneHourPrices, |
| | | List<BigDecimal> fourHourPrices) { |
| | | // MA金叉 (5EMA > 20EMA) |
| | | boolean isMaGoldenCross = ma.getEma5().compareTo(ma.getEma20()) > 0; |
| | | // MACD金叉 (DIF > DEA) |
| | | boolean isMacdGoldenCross = macd.getDif().compareTo(macd.getDea()) > 0; |
| | | // KDJ金叉 (K线上穿D线) |
| | | boolean isKdjGoldenCross = kdj.isGoldenCross(); |
| | | // RSI中性 (30-70) |
| | | boolean isRsiNeutral = rsi.getRsi().compareTo(new BigDecimal(30)) > 0 && |
| | | // 主信号:趋势判断 |
| | | // MA多头排列(短期MA > 中期MA > 长期MA) |
| | | boolean isMaBullish = ma.getEma5().compareTo(ma.getEma10()) > 0 && |
| | | ma.getEma10().compareTo(ma.getEma20()) > 0; |
| | | // MACD趋势(DIFF在DEA之上) |
| | | boolean isMacdBullish = macd.getDif().compareTo(macd.getDea()) > 0; |
| | | |
| | | // 如果主信号不满足,直接返回false |
| | | if (!(isMaBullish && isMacdBullish)) { |
| | | return false; |
| | | } |
| | | |
| | | // 辅信号:入场点判断 |
| | | // 价格在BOLL中轨上方,且未触及上轨(避免追高) |
| | | boolean isPriceInSafeZone = currentPrice.compareTo(boll.getMid()) > 0 && |
| | | currentPrice.compareTo(boll.getUpper()) < 0; |
| | | // 价格靠近BOLL中轨 |
| | | boolean isPriceNearBollMid = currentPrice.compareTo(boll.getMid().multiply(new BigDecimal(0.98))) > 0 && |
| | | currentPrice.compareTo(boll.getMid().multiply(new BigDecimal(1.02))) < 0; |
| | | |
| | | // RSI:健康区间30-70,刚从50中线向上 |
| | | boolean isRsiHealthy = rsi.getRsi().compareTo(new BigDecimal(30)) > 0 && |
| | | rsi.getRsi().compareTo(new BigDecimal(70)) < 0; |
| | | // 价格高于BOLL中轨 |
| | | boolean isPriceAboveBollMid = currentPrice.compareTo(boll.getMid()) > 0; |
| | | boolean isRsiAboveMid = rsi.getRsi().compareTo(new BigDecimal(50)) > 0; |
| | | |
| | | // KDJ:金叉或超卖区域 |
| | | boolean isKdjGoldenCross = kdj.isGoldenCross(); |
| | | boolean isKdjOversold = kdj.getJ().compareTo(new BigDecimal(15)) < 0; |
| | | |
| | | // 入场点条件:价格在安全区域或靠近中轨,且动量健康 |
| | | boolean isEntryPointValid = (isPriceInSafeZone || isPriceNearBollMid) && |
| | | (isRsiHealthy && isRsiAboveMid) && |
| | | (isKdjGoldenCross || isKdjOversold); |
| | | |
| | | // 成交量验证 |
| | | boolean isVolumeConfirmed = volumeConfirm(volume); |
| | | // 量价背离检测 |
| | |
| | | // 多周期确认 |
| | | boolean isMultiTimeframeConfirmed = multiTimeframeConfirm(fiveMinPrices, oneHourPrices, fourHourPrices); |
| | | |
| | | return isMaGoldenCross && isMacdGoldenCross && isKdjGoldenCross && |
| | | isRsiNeutral && isPriceAboveBollMid && isVolumeConfirmed && |
| | | !isPriceVolumeDivergence && isMultiTimeframeConfirmed; |
| | | return isEntryPointValid && isVolumeConfirmed && !isPriceVolumeDivergence && isMultiTimeframeConfirmed; |
| | | } |
| | | |
| | | /** |
| | | * 根据用户要求检查是否应该开空仓位 |
| | | * 条件:MA死叉 + MACD死叉 + KDJ死叉 + RSI中性 + 价格低于BOLL中轨 + 多周期确认 + 成交量验证 |
| | | * 根据优化建议检查是否应该开空仓位 |
| | | * 使用主次结合法: |
| | | * - 主信号(趋势):MA空头排列 + MACD死叉 |
| | | * - 辅信号(入场点):价格反弹至BOLL中轨附近遇阻,且出现KDJ死叉或RSI从高位回落至50以下 |
| | | * @param currentPrice 当前价格 |
| | | * @param prices 价格数据 |
| | | * @param volume 成交量列表 |
| | | * @param fiveMinPrices 5分钟价格数据 |
| | | * @param oneHourPrices 1小时价格数据 |
| | | * @param fourHourPrices 4小时价格数据 |
| | | * @return 是否应该开空 |
| | | */ |
| | | private boolean shouldOpenShort(BigDecimal currentPrice, List<BigDecimal> volume, |
| | | private boolean shouldOpenShort(BigDecimal currentPrice, List<BigDecimal> prices, List<BigDecimal> volume, |
| | | List<BigDecimal> fiveMinPrices, |
| | | List<BigDecimal> oneHourPrices, |
| | | List<BigDecimal> fourHourPrices) { |
| | | // MA死叉 (5EMA < 20EMA) |
| | | boolean isMaDeathCross = ma.getEma5().compareTo(ma.getEma20()) < 0; |
| | | // MACD死叉 (DIF < DEA) |
| | | boolean isMacdDeathCross = macd.getDif().compareTo(macd.getDea()) < 0; |
| | | // KDJ死叉 (K线下穿D线) |
| | | boolean isKdjDeathCross = kdj.isDeathCross(); |
| | | // RSI中性 (30-70) |
| | | boolean isRsiNeutral = rsi.getRsi().compareTo(new BigDecimal(30)) > 0 && |
| | | // 主信号:趋势判断 |
| | | // MA空头排列(短期MA < 中期MA < 长期MA) |
| | | boolean isMaBearish = ma.getEma5().compareTo(ma.getEma10()) < 0 && |
| | | ma.getEma10().compareTo(ma.getEma20()) < 0; |
| | | // MACD趋势(DIFF在DEA之下) |
| | | boolean isMacdBearish = macd.getDif().compareTo(macd.getDea()) < 0; |
| | | |
| | | // 如果主信号不满足,直接返回false |
| | | if (!(isMaBearish && isMacdBearish)) { |
| | | return false; |
| | | } |
| | | |
| | | // 辅信号:入场点判断 |
| | | // 价格在BOLL中轨下方,且未触及下轨(避免追空) |
| | | boolean isPriceInSafeZone = currentPrice.compareTo(boll.getMid()) < 0 && |
| | | currentPrice.compareTo(boll.getLower()) > 0; |
| | | // 价格靠近BOLL中轨 |
| | | boolean isPriceNearBollMid = currentPrice.compareTo(boll.getMid().multiply(new BigDecimal(0.98))) > 0 && |
| | | currentPrice.compareTo(boll.getMid().multiply(new BigDecimal(1.02))) < 0; |
| | | |
| | | // RSI:健康区间30-70,刚从50中线向下 |
| | | boolean isRsiHealthy = rsi.getRsi().compareTo(new BigDecimal(30)) > 0 && |
| | | rsi.getRsi().compareTo(new BigDecimal(70)) < 0; |
| | | // 价格低于BOLL中轨 |
| | | boolean isPriceBelowBollMid = currentPrice.compareTo(boll.getMid()) < 0; |
| | | boolean isRsiBelowMid = rsi.getRsi().compareTo(new BigDecimal(50)) < 0; |
| | | |
| | | // KDJ:死叉或超买区域 |
| | | boolean isKdjDeathCross = kdj.isDeathCross(); |
| | | boolean isKdjOverbought = kdj.getJ().compareTo(new BigDecimal(85)) > 0; |
| | | |
| | | // 入场点条件:价格在安全区域或靠近中轨,且动量健康 |
| | | boolean isEntryPointValid = (isPriceInSafeZone || isPriceNearBollMid) && |
| | | (isRsiHealthy && isRsiBelowMid) && |
| | | (isKdjDeathCross || isKdjOverbought); |
| | | |
| | | // 成交量验证 |
| | | boolean isVolumeConfirmed = volumeConfirm(volume); |
| | | // 量价背离检测 |
| | | boolean isPriceVolumeDivergence = hasPriceVolumeDivergence(prices, volume); |
| | | // 多周期确认(看跌) |
| | | boolean isMultiTimeframeConfirmed = multiTimeframeBearishConfirm(fiveMinPrices, oneHourPrices, fourHourPrices); |
| | | |
| | | return isMaDeathCross && isMacdDeathCross && isKdjDeathCross && |
| | | isRsiNeutral && isPriceBelowBollMid && isVolumeConfirmed && isMultiTimeframeConfirmed; |
| | | return isEntryPointValid && isVolumeConfirmed && !isPriceVolumeDivergence && isMultiTimeframeConfirmed; |
| | | } |
| | | |
| | | /** |
| | | * 根据用户要求检查是否应该平多仓位 |
| | | * 条件:MA死叉 + MACD死叉 + RSI超买 + 价格低于BOLL中轨 + 多周期确认 |
| | | * 根据优化建议检查是否应该平多仓位 |
| | | * 采用分层止盈止损策略: |
| | | * - 保护止损:价格跌破BOLL中轨 |
| | | * - 跟踪止损:价格有效跌破移动平均线 |
| | | * - 最终平仓:趋势反转信号(MA空头排列 + MACD死叉) |
| | | * @param currentPrice 当前价格 |
| | | * @param volume 成交量列表 |
| | | * @param fiveMinPrices 5分钟价格数据 |
| | |
| | | List<BigDecimal> fiveMinPrices, |
| | | List<BigDecimal> oneHourPrices, |
| | | List<BigDecimal> fourHourPrices) { |
| | | // MA死叉 (5EMA < 20EMA) |
| | | boolean isMaDeathCross = ma.getEma5().compareTo(ma.getEma20()) < 0; |
| | | // MACD死叉 (DIF < DEA) |
| | | boolean isMacdDeathCross = macd.getDif().compareTo(macd.getDea()) < 0; |
| | | // RSI超买 (>70) |
| | | boolean isRsiOverbought = rsi.isOverbought(); |
| | | // 价格低于BOLL中轨 |
| | | boolean isPriceBelowBollMid = currentPrice.compareTo(boll.getMid()) < 0; |
| | | // 保护止损:价格跌破BOLL中轨(关键支撑位) |
| | | boolean isStopLossTriggered = currentPrice.compareTo(boll.getMid()) < 0; |
| | | |
| | | // 跟踪止损:价格有效跌破短期均线(5EMA) |
| | | boolean isTrailingStopTriggered = currentPrice.compareTo(ma.getEma5()) < 0; |
| | | |
| | | // 趋势反转信号:MA空头排列 + MACD死叉 |
| | | boolean isMaBearish = ma.getEma5().compareTo(ma.getEma10()) < 0 && |
| | | ma.getEma10().compareTo(ma.getEma20()) < 0; |
| | | boolean isMacdBearish = macd.getDif().compareTo(macd.getDea()) < 0; |
| | | boolean isTrendReversed = isMaBearish && isMacdBearish; |
| | | |
| | | // 多周期确认(看跌) |
| | | boolean isMultiTimeframeConfirmed = multiTimeframeBearishConfirm(fiveMinPrices, oneHourPrices, fourHourPrices); |
| | | |
| | | return isMaDeathCross && isMacdDeathCross && isRsiOverbought && isPriceBelowBollMid && isMultiTimeframeConfirmed; |
| | | |
| | | // 平多条件:保护止损触发 或 跟踪止损触发 或 (趋势反转且多周期确认) |
| | | return isStopLossTriggered || isTrailingStopTriggered || (isTrendReversed && isMultiTimeframeConfirmed); |
| | | } |
| | | |
| | | /** |
| | | * 根据用户要求检查是否应该平空仓位 |
| | | * 条件:MA金叉 + MACD金叉 + RSI超卖 + 价格高于BOLL中轨 + 多周期确认 |
| | | * 根据优化建议检查是否应该平空仓位 |
| | | * 采用分层止盈止损策略: |
| | | * - 保护止损:价格突破BOLL中轨 |
| | | * - 跟踪止损:价格有效突破移动平均线 |
| | | * - 最终平仓:趋势反转信号(MA多头排列 + MACD金叉) |
| | | * @param currentPrice 当前价格 |
| | | * @param volume 成交量列表 |
| | | * @param fiveMinPrices 5分钟价格数据 |
| | |
| | | List<BigDecimal> fiveMinPrices, |
| | | List<BigDecimal> oneHourPrices, |
| | | List<BigDecimal> fourHourPrices) { |
| | | // MA金叉 (5EMA > 20EMA) |
| | | boolean isMaGoldenCross = ma.getEma5().compareTo(ma.getEma20()) > 0; |
| | | // MACD金叉 (DIF > DEA) |
| | | boolean isMacdGoldenCross = macd.getDif().compareTo(macd.getDea()) > 0; |
| | | // RSI超卖 (<30) |
| | | boolean isRsiOversold = rsi.isOversold(); |
| | | // 价格高于BOLL中轨 |
| | | boolean isPriceAboveBollMid = currentPrice.compareTo(boll.getMid()) > 0; |
| | | // 保护止损:价格突破BOLL中轨(关键阻力位) |
| | | boolean isStopLossTriggered = currentPrice.compareTo(boll.getMid()) > 0; |
| | | |
| | | // 跟踪止损:价格有效突破短期均线(5EMA) |
| | | boolean isTrailingStopTriggered = currentPrice.compareTo(ma.getEma5()) > 0; |
| | | |
| | | // 趋势反转信号:MA多头排列 + MACD金叉 |
| | | boolean isMaBullish = ma.getEma5().compareTo(ma.getEma10()) > 0 && |
| | | ma.getEma10().compareTo(ma.getEma20()) > 0; |
| | | boolean isMacdBullish = macd.getDif().compareTo(macd.getDea()) > 0; |
| | | boolean isTrendReversed = isMaBullish && isMacdBullish; |
| | | |
| | | // 多周期确认(看涨) |
| | | boolean isMultiTimeframeConfirmed = multiTimeframeConfirm(fiveMinPrices, oneHourPrices, fourHourPrices); |
| | | |
| | | return isMaGoldenCross && isMacdGoldenCross && isRsiOversold && isPriceAboveBollMid && isMultiTimeframeConfirmed; |
| | | |
| | | // 平空条件:保护止损触发 或 跟踪止损触发 或 (趋势反转且多周期确认) |
| | | return isStopLossTriggered || isTrailingStopTriggered || (isTrendReversed && isMultiTimeframeConfirmed); |
| | | } |
| | | |
| | | /** |
| | |
| | | String.format("BOLL-MID: %s, BOLL-UP: %s, BOLL-DN: %s, ", boll.getMid(), boll.getUpper(), boll.getLower()) + |
| | | String.format("AdvancedMA-Bullish: %s, Bearish: %s, Percent: %s", |
| | | advancedMA.isBullish(), advancedMA.isBearish(), advancedMA.calculatePercent()); |
| | | } |
| | | |
| | | /** |
| | | * 生成区间交易信号 |
| | | * 在震荡行情下,使用BOLL通道作为区间边界,结合KDJ指标生成交易信号 |
| | | * @param currentPrice 当前价格 |
| | | * @param volume 成交量列表 |
| | | * @param hasLongPosition 是否当前持有做多仓位 |
| | | * @param hasShortPosition 是否当前持有做空仓位 |
| | | * @return 交易信号 |
| | | */ |
| | | private SignalType generateRangeTradingSignal(BigDecimal currentPrice, List<BigDecimal> volume, |
| | | boolean hasLongPosition, boolean hasShortPosition) { |
| | | // 区间交易策略逻辑: |
| | | // 1. 价格触及BOLL下轨且KDJ超卖 → 买入信号 |
| | | // 2. 价格触及BOLL上轨且KDJ超买 → 卖出信号 |
| | | // 3. 价格回归BOLL中轨 → 平仓信号 |
| | | |
| | | // 检查KDJ极端超买超卖情况 |
| | | boolean isKdjJExtremeOverbought = kdj.getJ().compareTo(new BigDecimal("100")) > 0; |
| | | boolean isKdjJExtremeOversold = kdj.getJ().compareTo(new BigDecimal("10")) < 0; |
| | | |
| | | // 价格触及BOLL下轨(基础条件) |
| | | boolean isPriceNearBollLower = currentPrice.compareTo(boll.getLower()) >= 0 && |
| | | currentPrice.compareTo(boll.getLower().multiply(new BigDecimal("1.01"))) <= 0; |
| | | |
| | | // 价格触及BOLL上轨(基础条件) |
| | | boolean isPriceNearBollUpper = currentPrice.compareTo(boll.getUpper()) <= 0 && |
| | | currentPrice.compareTo(boll.getUpper().multiply(new BigDecimal("0.99"))) >= 0; |
| | | |
| | | // 当KDJ-J极度超买/超卖时,放宽BOLL边界要求 |
| | | if (isKdjJExtremeOverbought) { |
| | | isPriceNearBollUpper = currentPrice.compareTo(boll.getUpper().multiply(new BigDecimal("0.985"))) >= 0; |
| | | } |
| | | if (isKdjJExtremeOversold) { |
| | | isPriceNearBollLower = currentPrice.compareTo(boll.getLower().multiply(new BigDecimal("1.015"))) <= 0; |
| | | } |
| | | |
| | | // 价格回归BOLL中轨附近 |
| | | boolean isPriceNearBollMid = currentPrice.compareTo(boll.getMid().multiply(new BigDecimal("0.998"))) >= 0 && |
| | | currentPrice.compareTo(boll.getMid().multiply(new BigDecimal("1.002"))) <= 0; |
| | | |
| | | // KDJ超卖(使用调整后的阈值) |
| | | boolean isKdjOversold = kdj.getJ().compareTo(new BigDecimal(15)) < 0; |
| | | |
| | | // KDJ超买(使用调整后的阈值) |
| | | boolean isKdjOverbought = kdj.getJ().compareTo(new BigDecimal(85)) > 0; |
| | | |
| | | // RSI超卖(<30) |
| | | boolean isRsiOversold = rsi.getRsi().compareTo(new BigDecimal(30)) < 0; |
| | | |
| | | // RSI超买(>70) |
| | | boolean isRsiOverbought = rsi.getRsi().compareTo(new BigDecimal(70)) > 0; |
| | | |
| | | // 成交量验证(当前成交量大于20周期均值的1.1倍) |
| | | boolean isVolumeValid = volumeConfirm(volume) && |
| | | volume.get(volume.size() - 1).compareTo(calculateMA(volume, config.getVolumeMaPeriod()).multiply(new BigDecimal("1.1"))) > 0; |
| | | |
| | | // 开多逻辑:价格触及BOLL下轨且KDJ超卖且RSI超卖且有成交量支持 |
| | | if (isPriceNearBollLower && isKdjOversold && isRsiOversold && isVolumeValid && !hasLongPosition && !hasShortPosition) { |
| | | log.info("区间交易:价格触及BOLL下轨({}), KDJ-J值({})超卖,RSI({})超卖,生成买入信号", boll.getLower(), kdj.getJ(), rsi.getRsi()); |
| | | return SignalType.BUY; |
| | | } |
| | | |
| | | // 开空逻辑:价格触及BOLL上轨且KDJ超买且RSI超买且有成交量支持 |
| | | if (isPriceNearBollUpper && isKdjOverbought && isRsiOverbought && isVolumeValid && !hasLongPosition && !hasShortPosition) { |
| | | log.info("区间交易:价格触及BOLL上轨({}), KDJ-J值({})超买,RSI({})超买,生成卖出信号", boll.getUpper(), kdj.getJ(), rsi.getRsi()); |
| | | return SignalType.SELL; |
| | | } |
| | | |
| | | // 平多逻辑:价格回归BOLL中轨 |
| | | if (isPriceNearBollMid && hasLongPosition) { |
| | | log.info("区间交易:价格回归BOLL中轨({}),生成平多信号", boll.getMid()); |
| | | return SignalType.CLOSE_BUY; |
| | | } |
| | | |
| | | // 平空逻辑:价格回归BOLL中轨 |
| | | if (isPriceNearBollMid && hasShortPosition) { |
| | | log.info("区间交易:价格回归BOLL中轨({}),生成平空信号", boll.getMid()); |
| | | return SignalType.CLOSE_SELL; |
| | | } |
| | | |
| | | return SignalType.NONE; |
| | | } |
| | | |
| | | /** |
| | |
| | | // 限制杠杆范围在1x-10x之间 |
| | | leverage = leverage.min(new BigDecimal(10)).max(BigDecimal.ONE); |
| | | |
| | | log.debug("动态杠杆计算 - 基础杠杆: {}, 波动率阈值: {}, 当前波动率: {}, 计算杠杆: {}", |
| | | log.info("动态杠杆计算 - 基础杠杆: {}, 波动率阈值: {}, 当前波动率: {}, 计算杠杆: {}", |
| | | config.getBaseLeverage(), volatilityThreshold, currentVolatility, leverage); |
| | | |
| | | return leverage; |
| | |
| | | return direction == Direction.LONG ? ma.getEma20() : ma.getEma20(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * 黑天鹅事件过滤 |
| | | * 规避重大事件前后30分钟、链上大额转账、异常资金费率 |
| | |
| | | boolean shouldAvoid = isAbnormalFundingRate || hasLargeTransfer || hasUpcomingEvent; |
| | | |
| | | if (shouldAvoid) { |
| | | log.debug("黑天鹅事件过滤触发 - 资金费率异常: {}, 大额转账: {}, 即将发生重大事件: {}", |
| | | log.info("黑天鹅事件过滤触发 - 资金费率异常: {}, 大额转账: {}, 即将发生重大事件: {}", |
| | | isAbnormalFundingRate, hasLargeTransfer, hasUpcomingEvent); |
| | | } |
| | | |