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());
|
}
|
|
}
|