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