2 files deleted
3 files modified
2 files added
713 ■■■■■ changed files
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxKlineWebSocketClient.java 40 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/BearishSignalDetector.java 132 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/BullishSignalDetector.java 87 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/IndicatorUtils.java 154 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MACDCalculator.java 118 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MacdMaStrategy.java 75 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/内容 107 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxKlineWebSocketClient.java
@@ -247,6 +247,11 @@
     */
    private void handleWebSocketMessage(String message) {
        try {
            if ("pong".equals(message)) {
                log.debug("{}: 收到心跳响应");
                cancelPongTimeout();
                return;
            }
            JSONObject response = JSON.parseObject(message);
            String event = response.getString("event");
@@ -343,28 +348,27 @@
                    List<BigDecimal> historicalPrices1M = kline1MinuteData.stream()
                            .map(Kline::getC)
                            .collect(Collectors.toList());
                    log.info("生成100个1分钟价格数据点成功!");
                    // 使用策略分析最新价格数据
                    MacdMaStrategy.TradingOrder tradingOrderOpen1M = strategy.generateTradingOrder(historicalPrices1M,MacdMaStrategy.OperationType.open.name());
                    if (tradingOrderOpen1M == null ){
                        return;
                    }
                    List<Kline> kline15MinuteData = getKlineDataByInstIdAndBar(instId, "15m");
                    List<BigDecimal> historicalPrices15M = kline15MinuteData.stream()
                            .map(Kline::getC)
                            .collect(Collectors.toList());
                    // 使用策略分析最新价格数据
                    MacdMaStrategy.TradingOrder tradingOrderOpen15M = strategy.generateTradingOrder(historicalPrices15M,MacdMaStrategy.OperationType.open.name());
                    if (tradingOrderOpen15M == null ){
                        return;
                    }
//                    List<Kline> kline15MinuteData = getKlineDataByInstIdAndBar(instId, "15m");
//                    List<BigDecimal> historicalPrices15M = kline15MinuteData.stream()
//                            .map(Kline::getC)
//                            .collect(Collectors.toList());
//                    // 使用策略分析最新价格数据
//                    MacdMaStrategy.TradingOrder tradingOrderOpen15M = strategy.generateTradingOrder(historicalPrices15M,MacdMaStrategy.OperationType.open.name());
//                    if (tradingOrderOpen15M == null ){
//                        return;
//                    }
//
//                    if (!tradingOrderOpen1M.getPosSide().equals(tradingOrderOpen15M.getPosSide())){
//                        return;
//                    }
                    if (!tradingOrderOpen1M.getPosSide().equals(tradingOrderOpen15M.getPosSide())){
                        return;
                    }
                    log.info("1分钟和15分钟K线方向一致,开始执行交易操作!");
//                    log.info("1分钟和15分钟K线方向一致,开始执行交易操作!");
                    Collection<OkxQuantWebSocketClient> allClients = clientManager.getAllClients();
@@ -412,8 +416,6 @@
            requestParam.put("bar", bar);
            requestParam.put("limit", "200");
            String result = ExchangeLoginService.getInstance(ExchangeInfoEnum.OKX_UAT.name()).lineHistory(requestParam);
            log.info("加载OKX-KLINE,{}", result);
            JSONObject json = JSON.parseObject(result);
            String data = json.getString("data");
            
@@ -515,9 +517,7 @@
    private void sendPing() {
        try {
            if (webSocketClient != null && webSocketClient.isOpen()) {
                JSONObject ping = new JSONObject();
                ping.put("op", "ping");
                webSocketClient.send(ping.toJSONString());
                webSocketClient.send("ping");
                log.debug("发送ping请求");
            }
        } catch (Exception e) {
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/BearishSignalDetector.java
File was deleted
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/BullishSignalDetector.java
File was deleted
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/IndicatorUtils.java
New file
@@ -0,0 +1,154 @@
/**
 * 指标计算工具类
 * 封装MACD策略中常用的通用功能,如高低点查找等
 */
package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy;
import java.math.BigDecimal;
import java.util.List;
/**
 * 指标计算工具类,提供MACD策略中常用的通用功能
 */
public class IndicatorUtils {
    /**
     * 找到最近的价格高点索引
     *
     * @param prices 价格列表
     * @param startIdx 起始索引
     * @return 最近的价格高点索引
     */
    public static int findRecentHighIndex(List<BigDecimal> prices, int startIdx) {
        if (prices == null || startIdx < 0 || startIdx >= prices.size()) {
            return -1;
        }
        int highIdx = startIdx;
        BigDecimal highPrice = prices.get(startIdx);
        for (int i = startIdx + 1; i < prices.size(); i++) {
            BigDecimal currentPrice = prices.get(i);
            if (currentPrice.compareTo(highPrice) > 0) {
                highPrice = currentPrice;
                highIdx = i;
            }
        }
        return highIdx;
    }
    /**
     * 找到最近的价格低点索引
     *
     * @param prices 价格列表
     * @param startIdx 起始索引
     * @return 最近的价格低点索引
     */
    public static int findRecentLowIndex(List<BigDecimal> prices, int startIdx) {
        if (prices == null || startIdx < 0 || startIdx >= prices.size()) {
            return -1;
        }
        int lowIdx = startIdx;
        BigDecimal lowPrice = prices.get(startIdx);
        for (int i = startIdx + 1; i < prices.size(); i++) {
            BigDecimal currentPrice = prices.get(i);
            if (currentPrice.compareTo(lowPrice) < 0) {
                lowPrice = currentPrice;
                lowIdx = i;
            }
        }
        return lowIdx;
    }
    /**
     * 找到最近价格高点之前的价格高点索引
     *
     * @param prices 价格列表
     * @param startIdx 起始索引
     * @param recentHighIdx 最近的价格高点索引
     * @return 之前的价格高点索引
     */
    public static int findPreviousHighIndex(List<BigDecimal> prices, int startIdx, int recentHighIdx) {
        if (prices == null || startIdx < 0 || recentHighIdx <= startIdx || recentHighIdx >= prices.size()) {
            return -1;
        }
        int highIdx = startIdx;
        BigDecimal highPrice = prices.get(startIdx);
        for (int i = startIdx + 1; i < recentHighIdx; i++) {
            BigDecimal currentPrice = prices.get(i);
            if (currentPrice.compareTo(highPrice) > 0) {
                highPrice = currentPrice;
                highIdx = i;
            }
        }
        return highIdx;
    }
    /**
     * 找到最近价格低点之前的价格低点索引
     *
     * @param prices 价格列表
     * @param startIdx 起始索引
     * @param recentLowIdx 最近的价格低点索引
     * @return 之前的价格低点索引
     */
    public static int findPreviousLowIndex(List<BigDecimal> prices, int startIdx, int recentLowIdx) {
        if (prices == null || startIdx < 0 || recentLowIdx <= startIdx || recentLowIdx >= prices.size()) {
            return -1;
        }
        int lowIdx = startIdx;
        BigDecimal lowPrice = prices.get(startIdx);
        for (int i = startIdx + 1; i < recentLowIdx; i++) {
            BigDecimal currentPrice = prices.get(i);
            if (currentPrice.compareTo(lowPrice) < 0) {
                lowPrice = currentPrice;
                lowIdx = i;
            }
        }
        return lowIdx;
    }
    /**
     * 寻找最近的价格高点(带有回调确认)
     *
     * @param prices 价格列表
     * @param startIndex 起始索引
     * @param endIndex 结束索引
     * @return 符合条件的价格高点索引,未找到则返回-1
     */
    public static int findRecentHighWithRetrace(List<BigDecimal> prices, int startIndex, int endIndex) {
        if (prices == null || 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;
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MACDCalculator.java
@@ -62,31 +62,30 @@
        }
        // 4. 计算DEA(基于有效DIF数据的EMA)
        List<BigDecimal> deaValues = EMACalculator.calculateEMA(difValues, signalPeriod, true);
        List<BigDecimal> deaValues = EMACalculator.calculateEMA(difValues, signalPeriod, false);
        // 5. 构建并填充结果(仅包含完整的MACD数据)
        // 有效结果数量 = 有效DIF数量 - DEA周期 + 1
        List<PriceData> result = new ArrayList<>(validLength - signalPeriod + 1);
        // 5. 构建并填充结果(包含所有MACD数据)
        List<PriceData> result = new ArrayList<>(deaValues.size());
        // 从DEA开始有效的索引位置开始计算
        for (int i = signalPeriod - 1; i < validLength; i++) {
        // 从第一个DEA值开始构建结果
        for (int i = 0; i < deaValues.size(); i++) {
            int closeIdx = startIdx + i; // 对应原收盘价列表的索引
            // 创建价格数据对象
            PriceData data = new PriceData(closePrices.get(closeIdx));
            // 设置EMA(修复索引计算,使用正确的偏移位置)
            // 设置EMA(使用正确的偏移位置)
            data.setEmaShort(emaShort.get(closeIdx - shortPeriod + 1));
            data.setEmaLong(emaLong.get(closeIdx - longPeriod + 1));
            // 设置DIF、DEA和MACD柱状图
            data.setDif(difValues.get(i));
            data.setDea(deaValues.get(i - signalPeriod + 1)); // 调整DEA的索引位置
            data.setDea(deaValues.get(i)); // DEA索引直接对应
            data.setMacdHist(data.getDif().subtract(data.getDea())); // MACD柱状图 = DIF - DEA
            result.add(data);
        }
        System.out.println(result.get(result.size() -1));
        return new MACDResult(result, startIdx);
    }
@@ -103,5 +102,104 @@
        // 默认参数:短期周期12,长期周期26,信号周期9
        return calculateMACD(closePrices, 12, 26, 9);
    }
}
    /**
     * 判断是否出现顶背离
     * <p>
     * 顶背离:价格创新高,但DIF未创新高,且与价格走势背离
     * 增强空头信号可靠性
     *
     * @param closePrices 原始收盘价列表
     * @param macdResult MACD计算结果
     * @return 是否出现顶背离
     */
    public static boolean isTopDivergence(List<BigDecimal> closePrices, MACDResult macdResult) {
        List<PriceData> macdData = macdResult.getMacdData();
        int startIdx = macdResult.getStartIndex();
        // 确保有足够的数据点进行判断(至少需要2个高点)
        if (macdData.size() < 10) {
            return false;
        }
        // 找到最近的价格高点和对应的DIF值
        int recentPriceHighIdx = IndicatorUtils.findRecentHighIndex(closePrices, startIdx);
        if (recentPriceHighIdx < startIdx + 2 || recentPriceHighIdx == -1) {
            return false;
        }
        // 找到之前的价格高点和对应的DIF值
        int previousPriceHighIdx = IndicatorUtils.findPreviousHighIndex(closePrices, startIdx, recentPriceHighIdx);
        if (previousPriceHighIdx < startIdx || previousPriceHighIdx == -1) {
            return false;
        }
        // 获取对应位置的DIF值
        int recentDifIdx = recentPriceHighIdx - startIdx;
        int previousDifIdx = previousPriceHighIdx - startIdx;
        // 边界检查
        if (recentDifIdx >= macdData.size() || previousDifIdx >= macdData.size()) {
            return false;
        }
        BigDecimal recentPrice = closePrices.get(recentPriceHighIdx);
        BigDecimal previousPrice = closePrices.get(previousPriceHighIdx);
        BigDecimal recentDif = macdData.get(recentDifIdx).getDif();
        BigDecimal previousDif = macdData.get(previousDifIdx).getDif();
        // 顶背离条件:价格创新高,但DIF未创新高
        return recentPrice.compareTo(previousPrice) > 0 &&
               recentDif.compareTo(previousDif) < 0;
    }
    /**
     * 判断是否出现底背离
     * <p>
     * 底背离:价格创新低,但DIF未创新低,且与价格走势背离
     * 增强多头信号可靠性
     *
     * @param closePrices 原始收盘价列表
     * @param macdResult MACD计算结果
     * @return 是否出现底背离
     */
    public static boolean isBottomDivergence(List<BigDecimal> closePrices, MACDResult macdResult) {
        List<PriceData> macdData = macdResult.getMacdData();
        int startIdx = macdResult.getStartIndex();
        // 确保有足够的数据点进行判断(至少需要2个低点)
        if (macdData.size() < 10) {
            return false;
        }
        // 找到最近的价格低点和对应的DIF值
        int recentPriceLowIdx = IndicatorUtils.findRecentLowIndex(closePrices, startIdx);
        if (recentPriceLowIdx < startIdx + 2 || recentPriceLowIdx == -1) {
            return false;
        }
        // 找到之前的价格低点和对应的DIF值
        int previousPriceLowIdx = IndicatorUtils.findPreviousLowIndex(closePrices, startIdx, recentPriceLowIdx);
        if (previousPriceLowIdx < startIdx || previousPriceLowIdx == -1) {
            return false;
        }
        // 获取对应位置的DIF值
        int recentDifIdx = recentPriceLowIdx - startIdx;
        int previousDifIdx = previousPriceLowIdx - startIdx;
        // 边界检查
        if (recentDifIdx >= macdData.size() || previousDifIdx >= macdData.size()) {
            return false;
        }
        BigDecimal recentPrice = closePrices.get(recentPriceLowIdx);
        BigDecimal previousPrice = closePrices.get(previousPriceLowIdx);
        BigDecimal recentDif = macdData.get(recentDifIdx).getDif();
        BigDecimal previousDif = macdData.get(previousDifIdx).getDif();
        // 底背离条件:价格创新低,但DIF未创新低
        return recentPrice.compareTo(previousPrice) < 0 &&
               recentDif.compareTo(previousDif) > 0;
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MacdMaStrategy.java
@@ -93,28 +93,22 @@
        // 1. 计算MACD指标
        MACDResult macdResult = MACDCalculator.calculateMACD(
                closePrices, shortPeriod, longPeriod, signalPeriod);
         log.info( "MACD计算结果:{}", macdResult.getMacdData().get( macdResult.getMacdData().size() -1));
        // 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 (isLongEntryCondition(macdResult, closePrices, volatility.getValue())) {
        if (isLongEntryCondition(macdResult, closePrices)) {
            // 执行开多
            log.info( "多头开仓信号,价格:{}", latestPrice);
            return PositionType.LONG_BUY;
        }
        // 空头开仓条件检查
        if (isShortEntryCondition(macdResult, closePrices, volatility.getValue())) {
        if (isShortEntryCondition(macdResult, closePrices)) {
            // 执行开空
             log.info( "空头开仓信号,价格:{}", latestPrice);
            return PositionType.SHORT_SELL;
@@ -226,10 +220,9 @@
     *
     * @param macdResult MACD计算结果
     * @param closePrices 收盘价序列
     * @param volatility 当前波动率
     * @return 是否满足多头开仓条件
     */
    private boolean isLongEntryCondition(MACDResult macdResult, List<BigDecimal> closePrices, BigDecimal volatility) {
    private boolean isLongEntryCondition(MACDResult macdResult, List<BigDecimal> closePrices) {
        // 2. MACD金叉且柱状线扩张检查
        boolean isMacdFavorable = isMacdGoldenCrossAndExpanding(macdResult);
@@ -237,16 +230,16 @@
        // 3. MACD柱状线必须为正
        boolean macdPositive = isMacdPositive(macdResult);
        
        // 4. 波动率过滤(必须在合理范围内)
        boolean volatilityFilter = isVolatilityInRange(volatility);
        if (macdPositive && volatilityFilter && isMacdFavorable) {
            log.info("多头信号形成, MACD有利状态: {}, 柱状线为正: {}, 波动率过滤: {}",
                    isMacdFavorable, macdPositive, volatilityFilter);
        }
        // 5. 底背离检查(增强多头信号可靠性)
        boolean isBottomDivergence = MACDCalculator.isBottomDivergence(closePrices, macdResult);
        log.info("多头信号形成, MACD有利状态: {}, 柱状线为正: {}, 底背离: {}",
                isMacdFavorable, macdPositive, isBottomDivergence);
        
        // 所有条件必须同时满足
        return macdPositive && volatilityFilter && isMacdFavorable;
        boolean result = macdPositive && (isMacdFavorable || isBottomDivergence);
        return result;
    }
    /**
@@ -254,26 +247,24 @@
     * 
     * @param macdResult MACD计算结果
     * @param closePrices 收盘价序列
     * @param volatility 当前波动率
     * @return 是否满足空头开仓条件
     */
    private boolean isShortEntryCondition(MACDResult macdResult, List<BigDecimal> closePrices, BigDecimal volatility) {
    private boolean isShortEntryCondition(MACDResult macdResult, List<BigDecimal> closePrices) {
        // 2. MACD死叉且柱状线收缩检查
        boolean isMacdFavorable = isMacdDeathCrossAndContracting(macdResult);
        
        // 3. MACD柱状线必须为负
        boolean macdNegative = isMacdNegative(macdResult);
        
        // 4. 波动率过滤(必须在合理范围内)
        boolean volatilityFilter = isVolatilityInRange(volatility);
        if (macdNegative && volatilityFilter && isMacdFavorable) {
            log.info("空头信号形成, MACD有利状态: {}, 柱状线为负: {}, 波动率过滤: {}",
                    isMacdFavorable, macdNegative, volatilityFilter);
        }
        // 5. 顶背离检查(增强空头信号可靠性)
        boolean isTopDivergence = MACDCalculator.isTopDivergence(closePrices, macdResult);
        log.info("空头信号形成, MACD有利状态: {}, 柱状线为负: {}, 顶背离: {}",
                isMacdFavorable, macdNegative, isTopDivergence);
        // 所有条件必须同时满足
        return macdNegative && volatilityFilter && isMacdFavorable;
        boolean result = macdNegative && (isMacdFavorable || isTopDivergence);
        return result;
    }
    
    /**
@@ -346,13 +337,13 @@
        // 金叉判断:DIF从下往上穿过DEA
        boolean isGoldenCross = prevPrev.getDif().compareTo(prevPrev.getDea()) <= 0 && 
                              previous.getDif().compareTo(previous.getDea()) > 0;
        // 柱状线扩张判断:连续正值且绝对值增大
        boolean isExpanding = latest.getMacdHist().compareTo(BigDecimal.ZERO) > 0 &&
        boolean isExpanding = latest.getMacdHist().compareTo(BigDecimal.ZERO) > 0 &&
                            previous.getMacdHist().compareTo(BigDecimal.ZERO) > 0 &&
                            previous.getMacdHist().abs().compareTo(latest.getMacdHist().abs()) < 0;
        
        // 金叉或柱状线扩张任一满足即可
        // 金叉柱状线扩张满足
        return isGoldenCross && isExpanding;
    }
    
@@ -378,14 +369,14 @@
    }
    /**
     * MACD死叉且柱状线收缩检查
     * MACD死叉且柱状线扩张检查
     * <p>
     * 条件:
     * 1. DIF线从上往下穿过DEA线(死叉)
     * 2. MACD柱状线绝对值减小且为负值(动量减弱)
     * 2. MACD柱状线绝对值增大且为负值(动量增强)
     * 
     * @param macdResult MACD计算结果
     * @return 是否形成MACD死叉或柱状线收缩
     * @return 是否形成MACD死叉且柱状线扩张
     */
    private boolean isMacdDeathCrossAndContracting(MACDResult macdResult) {
        List<PriceData> macdData = macdResult.getMacdData();
@@ -401,13 +392,13 @@
        boolean isDeathCross = prevPrev.getDif().compareTo(prevPrev.getDea()) >= 0 && 
                             previous.getDif().compareTo(previous.getDea()) < 0;
        
        // 柱状线收缩判断:连续负值且绝对值减小
        boolean isContracting = latest.getMacdHist().compareTo(BigDecimal.ZERO) < 0 &&
                              previous.getMacdHist().compareTo(BigDecimal.ZERO) < 0 &&
                              previous.getMacdHist().abs().compareTo(latest.getMacdHist().abs()) > 0;
        // 优化后的死叉柱状线条件:空头趋势中,死叉应伴随柱状线扩张(绝对值增大)
        boolean isExpanding = latest.getMacdHist().compareTo(BigDecimal.ZERO) < 0 &&
                previous.getMacdHist().compareTo(BigDecimal.ZERO) < 0 &&
                previous.getMacdHist().abs().compareTo(latest.getMacdHist().abs()) < 0;
        
        // 死叉或柱状线收缩任一满足即可
        return isDeathCross && isContracting;
        // 死叉且柱状线扩张
        return isDeathCross && isExpanding;
    }
    
    /**
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/内容
New file
@@ -0,0 +1,107 @@
MACD 是什麼?三個核心組成介紹
MACD 全名為 Moving Average Convergence Divergence,中文為平滑異同移動平均線,它主要是透過快線 DIF、慢線 DEA、能量柱 Histogram三個成分組成,用以幫助投資者捕捉股價或資產價格的趨勢變化、動能強弱以及潛在的買賣訊號。
MACD
MACD 指標主要由三個核心構成,每個核心皆有各自的涵義,讓投資者對於市場動態清楚明瞭:
快線 DIF(DIFferential Line)-差離值
DIF 是 MACD 的核心快線,是 MACD 中主要用來判斷市場長短期趨勢差異的工具,DIF 波動較快的特性也使得投資者可以透過其捕捉更敏銳的短期變化,當 DIF 上漲時表示短期動能增強,反之則減弱。
快線 DIF(DIFferential Line)
慢線 DEA(DIFference Exponential Average)-訊號線
慢線 DEA 又稱為 Signal 訊號線,曲線通常較為平滑,能夠用以輔佐投資者過濾 DIF 的雜訊,提供更穩定的趨勢確認訊號。當 DIF 穿越 DEA 向上時,常被視作買入訊號(黃金交叉),反之則為賣出訊號(死亡交叉)。
慢線 DEA(DIFference Exponential Average)-訊號線
能量柱(Histogram)
Histogram 是 MACD 的柱狀圖部分,由快線 DIF 減去慢線 DEA 得來,柱狀圖的高度反映快線與慢線的差距,當正柱狀圖(零軸之上)愈來愈高時表示多頭動能增強,負柱狀圖(零軸之下)愈來愈低時表示空頭動能增強,而柱狀圖逐漸收斂時則可能表示趨勢即將反轉。
能量柱(Histogram)
MACD公式計算詳細解析
要深入理解 MACD 指標首先必須先掌握其數值的算法並得知運算結果,MACD 的計算主要基於指數移動平均線(EMA),並通過一系列簡單的數學公式計算得出 DIF、DEA 和柱狀圖。
EMA(Exponential Moving Average)介紹
EMA 中文為指數移動平均線,與簡單移動平均線(SMA)不同的是,EMA 對近期價格數據給予更高的權重,使得它對價格變化的反應更為靈敏。
EMA 的運算公式為:【今日收盤價 × α】 + 【昨日 EMA × (1 − α)】
其中,α(平滑因子)= 2/(N + 1),N 為選定的周期數。例如,12 期 EMA 的 α = 2/(12 + 1) ≈ 0.1538。初次計算 EMA 時,若無前一日的 EMA,可使用該周期的簡單移動平均(SMA)作為起點。EMA 的靈敏性使 MACD 更能快速反映市場動態。
EMA
快線 DIF 計算方式
DIF 作為 MACD 核心之一,計算方式為:EMA(12)-EMA(26)。
EMA 12 代表短期價格趨勢(較快反應),而 EMA 26 代表長期趨勢(較平滑)。當短期 EMA 高於長期 EMA 時,DIF 為正值,表示短期動能強於長期動能,暗示看漲;反之,DIF 為負值則暗示看跌。這之間的差值能幫助投資者快速判斷趨勢的強弱與方向。
慢線 DEA 計算方式
DEA 計算方式為:EMA(DIF,9)
這樣的計算方式將 DIF 的曲線進一步平滑,減少短期價格噪音的干擾,提供更穩定的趨勢指標。DEA 線的作用在於與 DIF 形成交叉訊號,例如 DIF 上穿 DEA 線(黃金交叉)通常被視為買入訊號,而下穿(死亡交叉)則為賣出訊號。
柱狀圖 Histogram 計算方式
Histogram 作為 MACD 指標唯一的圖形,計算方式為:DIF − DEA
柱狀圖直觀顯示快線與慢線的差距,當 DIF 大於 DEA 時,柱狀圖為正時,表示多頭動能增強,反之柱狀圖為負時,表示空頭力量占優。
MACD 公式實盤範例解析
為了更直觀的理解 MACD 在實盤上的計算過程,我們以幣安的 ETHUSDT 圖表進行 MACD 試算。
第一步驟:新增 EMA 指標
由於 MACD 中的快線、慢線計算過程中皆會使用到 EMA 指標,因此我們可以直接新增 EMA 指標並進行參數調整即可計算 MACD 相關參數。
第二步驟:計算 MACD 中的快線 DIF
快線 DIF 的計算方式為 EMA(12)-EMA(26),因此我們只要將兩條 EMA 指標分別設定為 12、26 並進行相減即可,例如下圖中 EMA 12 為 4271.55,EMA 26 為 3941.88,相減即得 329.67,這一數字與 MACD 中顯示的快線 DIF 相同。
第三步驟:計算 MACD 中的慢線 DEA
慢線的公式為:EMA(DIF,9),其中 9 代表周期數,EMA 的計算使用平滑因子 α = 2 ÷ (9 + 1) = 0.2,因此 MACD 中的慢線 DEA 計算公式為:【今日 DIF*0.2】+【昨日 DEA*0.8】。
今日 DIF 為 329.67,而昨日 DEA 為 250.86,套入計算公式 329.67*0.2+250.86*0.8=266.62,這一數字同樣與 MACD 中顯示的慢線 DEA 相同。
第四步驟:計算 MACD 中的柱狀圖 Histogram
只要解出快線及慢線,要求出 MACD 中的柱狀圖就十分容易,僅需將快線減去慢線即可。上述計算出快線 DIF 數值為 329.67,而慢線 DEA 數值為 266.62,相減即可得出柱狀圖為 63.05,如此一來就得出 MACD 指標所需的所有數據。
MACD公式
MACD黃金/死亡交叉案例
黃金交叉與死亡交叉是 MACD 指標中最為人所知的交易訊號,許多交易者會藉由黃金交叉與死亡交叉判斷行情趨勢。
首先我們可以觀察到 ETHUSDT 在 4 月 13 號時快線 DIF 上穿慢線 DEA 形成有效金叉後,接下來不斷上漲,甚至直接開啟以太坊牛市。
MACD金叉案例
而再將時間往回推,可以看到在 2024 年 12 月 9 號時 MACD 快線下穿慢線形成死叉,並在接下來回撤超過 60%,當時若觀察到 MACD 為死叉的投資者即可避免該次下跌甚至進行空單布局。
MACD死叉範例
常見問題
為什麼有的版本能量柱( Histogram) 會*2?
有些版本的 MACD 會將柱狀圖放大一倍,目的是為了讓視覺效果更明顯,投資者即可更直接的觀察快線與慢線之間的差距變化。
金叉、死叉一定會上漲、下跌嗎?
不一定,金融市場中並沒有哪一個指標可以保證上漲、下跌,MACD 也不例外。且包括 MACD 在內的所有指標皆具有一定的延遲性,其中可能包含雜訊,因此建議搭配技術分析或其他指標一同使用。
MACD 的參數可以修改嗎?
MACD 的參數是可以隨個人喜好修改的,例如(5, 13, 5)適用於短期交易,(50, 200, 20) 則適合長期趨勢分析,不過修改 MACD 參數會影響指標的靈敏度,建議回測歷史數據以找到最適合的設定。
小結
本篇文章解析了 MACD 的核心,從 MACD 的計算基礎 EMA 平均線到快線、慢線和柱狀圖三大核心,以及結合 ETHUSDT 圖表展示了 EMA 推導 MACD 數值的計算過程,但指標真正的價值在於如何靈活應用並提升勝率。因此看完這篇文章的你如果對於 MACD 有濃厚興趣的話,趕快打開 MACD 指標並進行深入的覆盤研究吧!
本報告僅供資訊分享之用,內容不構成任何形式的投資建議或決策依據。文中所引用的數據、分析與觀點均基於作者的研究與公開來源,可能存在不確定性或隨時變動的情況。讀者應根據自身情況及風險承受能力,審慎進行投資判斷。如需進一步指導,建議尋求專業顧問意見。