From 2911432f3d146791edda5e911909d2360953842b Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Tue, 23 Dec 2025 18:05:58 +0800
Subject: [PATCH] feat(indicator): 优化交易策略并调整日志级别

---
 src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingStrategy.java |  237 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 237 insertions(+), 0 deletions(-)

diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingStrategy.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingStrategy.java
new file mode 100644
index 0000000..6510022
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingStrategy.java
@@ -0,0 +1,237 @@
+package com.xcong.excoin.modules.okxNewPrice.indicator;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 15分钟时间粒度的交易策略实现
+ * 专门针对100个15分钟数据点设计的策略,包含明确的多空方向选择和开仓平仓方法
+ */
+@Slf4j
+public class FifteenMinuteTradingStrategy {
+
+    @Getter
+    @Setter
+    public static class TradingResult {
+        private Direction direction;      // 多空方向
+        private PositionSignal signal;    // 开仓平仓信号
+        private String indicatorStatus;   // 指标状态
+        
+        public TradingResult(Direction direction, PositionSignal signal, String indicatorStatus) {
+            this.direction = direction;
+            this.signal = signal;
+            this.indicatorStatus = indicatorStatus;
+        }
+    }
+
+    public enum Direction {
+        LONG,    // 多头方向
+        SHORT,   // 空头方向
+        RANGING  // 震荡行情
+    }
+
+    public enum PositionSignal {
+        OPEN_LONG,    // 开多仓
+        OPEN_SHORT,   // 开空仓
+        CLOSE_LONG,   // 平多仓
+        CLOSE_SHORT,  // 平空仓
+        HOLD,         // 持有
+        STAY_OUT      // 观望
+    }
+
+    private final MA ma;
+    private final AdvancedMA advancedMA;
+    private final BOLL boll;
+    private final KDJ kdj;
+    private final MACD macd;
+    private final RSI rsi;
+
+    public FifteenMinuteTradingStrategy() {
+        // 15分钟数据优化的参数配置
+        this.ma = new MA();
+        this.advancedMA = new AdvancedMA();
+        this.boll = new BOLL(20, 2.0);  // BOLL使用默认20周期
+        this.kdj = new KDJ(9);          // KDJ使用默认9周期
+        this.macd = new MACD();         // MACD使用默认12/26/9周期
+        this.rsi = new RSI(14);         // RSI使用默认14周期
+    }
+
+    /**
+     * 计算所有指标
+     * @param prices 15分钟价格数据(至少100个数据点)
+     */
+    private void calculateIndicators(List<BigDecimal> prices) {
+        ma.calculate(prices);
+        advancedMA.calculateTripleEMA(prices);
+        boll.calculate(prices);
+        kdj.calculate(prices);
+        macd.calculate(prices);
+        rsi.calculate(prices);
+    }
+
+    /**
+     * 判断市场是否处于震荡行情
+     * @return 是否为震荡行情
+     */
+    private boolean isRangeMarket() {
+        // AdvancedMA三线粘合 + RSI(40-60) + BOLL带宽收窄
+        boolean isMaConverged = advancedMA.calculatePercent().compareTo(new BigDecimal(2)) < 0;
+        boolean isRsiNeutral = rsi.getRsi().compareTo(new BigDecimal(40)) > 0 && 
+                              rsi.getRsi().compareTo(new BigDecimal(60)) < 0;
+        boolean isBollNarrow = boll.calculateBandWidth().compareTo(new BigDecimal(0.05)) < 0;
+
+        return isMaConverged && isRsiNeutral && isBollNarrow;
+    }
+
+    /**
+     * 获取多空方向选择
+     * @param prices 15分钟价格数据(100个数据点)
+     * @return 多空方向
+     */
+    public Direction getDirection(List<BigDecimal> prices) {
+        if (prices == null || prices.size() < 100) {
+            throw new IllegalArgumentException("需要至少100个15分钟价格数据点");
+        }
+
+        calculateIndicators(prices);
+
+        // 震荡过滤
+        if (isRangeMarket()) {
+            return Direction.RANGING;
+        }
+
+        BigDecimal currentPrice = prices.get(prices.size() - 1);
+
+        // 多头信号判断:MA多头排列 + MACD金叉 + RSI(30-70) + BOLL价格在上轨与中轨之间
+        boolean isLongSignal = 
+            ma.getEma5().compareTo(ma.getEma10()) > 0 &&  // MA5 > MA10
+            ma.getEma10().compareTo(ma.getEma20()) > 0 && // MA10 > MA20
+            macd.getDif().compareTo(macd.getDea()) > 0 && // MACD金叉
+            rsi.getRsi().compareTo(new BigDecimal(30)) > 0 && rsi.getRsi().compareTo(new BigDecimal(70)) < 0 && // RSI(30-70)
+            currentPrice.compareTo(boll.getMid()) > 0 && currentPrice.compareTo(boll.getUpper()) < 0; // BOLL价格在上轨与中轨之间
+
+        // 空头信号判断:MA空头排列 + MACD死叉 + RSI(30-70) + BOLL价格在下轨与中轨之间
+        boolean isShortSignal = 
+            ma.getEma5().compareTo(ma.getEma10()) < 0 &&  // MA5 < MA10
+            ma.getEma10().compareTo(ma.getEma20()) < 0 && // MA10 < MA20
+            macd.getDif().compareTo(macd.getDea()) < 0 && // MACD死叉
+            rsi.getRsi().compareTo(new BigDecimal(30)) > 0 && rsi.getRsi().compareTo(new BigDecimal(70)) < 0 && // RSI(30-70)
+            currentPrice.compareTo(boll.getMid()) < 0 && currentPrice.compareTo(boll.getLower()) > 0; // BOLL价格在下轨与中轨之间
+
+        if (isLongSignal) {
+            return Direction.LONG;
+        } else if (isShortSignal) {
+            return Direction.SHORT;
+        } else {
+            return Direction.RANGING;
+        }
+    }
+
+    /**
+     * 获取开仓平仓策略信号
+     * @param prices 15分钟价格数据(100个数据点)
+     * @param hasLongPosition 当前是否持有多仓
+     * @param hasShortPosition 当前是否持有空仓
+     * @return 开仓平仓信号
+     */
+    public PositionSignal getPositionSignal(List<BigDecimal> prices, boolean hasLongPosition, boolean hasShortPosition) {
+        if (prices == null || prices.size() < 100) {
+            throw new IllegalArgumentException("需要至少100个15分钟价格数据点");
+        }
+
+        calculateIndicators(prices);
+
+        // 震荡过滤
+        if (isRangeMarket()) {
+            return PositionSignal.STAY_OUT;
+        }
+
+        BigDecimal currentPrice = prices.get(prices.size() - 1);
+
+        // 开多信号:MA金叉 + MACD金叉 + KDJ金叉 + RSI中性 + 价格在BOLL中轨上方
+        boolean shouldOpenLong = 
+            ma.getEma5().compareTo(ma.getEma20()) > 0 &&  // MA金叉(5日EMA上穿20日EMA)
+            macd.getDif().compareTo(macd.getDea()) > 0 && macd.getMacdBar().compareTo(BigDecimal.ZERO) > 0 && // MACD金叉且柱状图为正
+            kdj.isGoldenCross() && // KDJ金叉
+            rsi.getRsi().compareTo(new BigDecimal(30)) > 0 && rsi.getRsi().compareTo(new BigDecimal(70)) < 0 && // RSI中性
+            currentPrice.compareTo(boll.getMid()) > 0; // 价格在BOLL中轨上方
+
+        // 开空信号:MA死叉 + MACD死叉 + KDJ死叉 + RSI中性 + 价格在BOLL中轨下方
+        boolean shouldOpenShort = 
+            ma.getEma5().compareTo(ma.getEma20()) < 0 &&  // MA死叉(5日EMA下穿20日EMA)
+            macd.getDif().compareTo(macd.getDea()) < 0 && macd.getMacdBar().compareTo(BigDecimal.ZERO) < 0 && // MACD死叉且柱状图为负
+            kdj.isDeathCross() && // KDJ死叉
+            rsi.getRsi().compareTo(new BigDecimal(30)) > 0 && rsi.getRsi().compareTo(new BigDecimal(70)) < 0 && // RSI中性
+            currentPrice.compareTo(boll.getMid()) < 0; // 价格在BOLL中轨下方
+
+        // 平多信号:MA死叉 + MACD死叉 + RSI超买 + 价格跌破BOLL中轨
+        boolean shouldCloseLong = 
+            (ma.getEma5().compareTo(ma.getEma20()) < 0 && // MA死叉
+            macd.getDif().compareTo(macd.getDea()) < 0 && // MACD死叉
+            (rsi.isOverbought() || rsi.isExtremelyOverbought())) || // RSI超买
+            currentPrice.compareTo(boll.getMid()) < 0; // 价格跌破BOLL中轨
+
+        // 平空信号:MA金叉 + MACD金叉 + RSI超卖 + 价格突破BOLL中轨
+        boolean shouldCloseShort = 
+            (ma.getEma5().compareTo(ma.getEma20()) > 0 && // MA金叉
+            macd.getDif().compareTo(macd.getDea()) > 0 && // MACD金叉
+            (rsi.isOversold() || rsi.isExtremelyOversold())) || // RSI超卖
+            currentPrice.compareTo(boll.getMid()) > 0; // 价格突破BOLL中轨
+
+        // 确定开仓信号
+        if (shouldOpenLong && !hasLongPosition && !hasShortPosition) {
+            return PositionSignal.OPEN_LONG;
+        } else if (shouldOpenShort && !hasLongPosition && !hasShortPosition) {
+            return PositionSignal.OPEN_SHORT;
+        }
+
+        // 确定平仓信号
+        if (shouldCloseLong && hasLongPosition) {
+            return PositionSignal.CLOSE_LONG;
+        } else if (shouldCloseShort && hasShortPosition) {
+            return PositionSignal.CLOSE_SHORT;
+        }
+
+        // 无信号
+        return hasLongPosition || hasShortPosition ? PositionSignal.HOLD : PositionSignal.STAY_OUT;
+    }
+
+    /**
+     * 综合获取交易结果
+     * @param prices 15分钟价格数据(100个数据点)
+     * @param hasLongPosition 当前是否持有多仓
+     * @param hasShortPosition 当前是否持有空仓
+     * @return 包含多空方向和开仓平仓信号的完整交易结果
+     */
+    public TradingResult getTradingResult(List<BigDecimal> prices, boolean hasLongPosition, boolean hasShortPosition) {
+        Direction direction = getDirection(prices);
+        PositionSignal signal = getPositionSignal(prices, hasLongPosition, hasShortPosition);
+        String indicatorStatus = getIndicatorStatus();
+        
+        return new TradingResult(direction, signal, indicatorStatus);
+    }
+
+    /**
+     * 获取当前指标状态
+     * @return 指标状态字符串
+     */
+    private String getIndicatorStatus() {
+        return String.format("MA5: %s, MA20: %s, " +
+                        "MACD-DIF: %s, MACD-DEA: %s, MACD-BAR: %s, " +
+                        "KDJ-K: %s, KDJ-D: %s, KDJ-J: %s, " +
+                        "RSI: %s, " +
+                        "BOLL-UP: %s, BOLL-MID: %s, BOLL-DN: %s, " +
+                        "AdvancedMA-Bullish: %s, Bearish: %s, Percent: %s",
+                ma.getEma5(), ma.getEma20(),
+                macd.getDif(), macd.getDea(), macd.getMacdBar(),
+                kdj.getK(), kdj.getD(), kdj.getJ(),
+                rsi.getRsi(),
+                boll.getUpper(), boll.getMid(), boll.getLower(),
+                advancedMA.isBullish(), advancedMA.isBearish(), advancedMA.calculatePercent());
+    }
+
+}
\ No newline at end of file

--
Gitblit v1.9.1