File size: 4,393 Bytes
f5cd2d3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package com.rods.backtestingstrategies.strategy;

import com.rods.backtestingstrategies.entity.Candle;
import com.rods.backtestingstrategies.entity.TradeSignal;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * MACD (Moving Average Convergence Divergence) Strategy.
 *
 * MACD Line   = EMA(fastPeriod) - EMA(slowPeriod)
 * Signal Line = EMA(signalPeriod) of MACD Line
 *
 * BUY  → MACD crosses above Signal Line
 * SELL → MACD crosses below Signal Line
 */
@Component
public class MacdStrategy implements Strategy {

    private final int fastPeriod;
    private final int slowPeriod;
    private final int signalPeriod;

    public MacdStrategy() {
        // Standard MACD(12, 26, 9)
        this.fastPeriod = 12;
        this.slowPeriod = 26;
        this.signalPeriod = 9;
    }

    public MacdStrategy(int fastPeriod, int slowPeriod, int signalPeriod) {
        this.fastPeriod = fastPeriod;
        this.slowPeriod = slowPeriod;
        this.signalPeriod = signalPeriod;
    }

    @Override
    public TradeSignal evaluate(List<Candle> candles, int index) {

        // Need enough data: slowPeriod + signalPeriod candles minimum
        int minRequired = slowPeriod + signalPeriod;
        if (index < minRequired) {
            return TradeSignal.hold();
        }

        Candle candle = candles.get(index);

        // Calculate MACD and Signal for current and previous index
        double currMacd = calculateMacd(candles, index);
        double prevMacd = calculateMacd(candles, index - 1);

        double currSignal = calculateSignalLine(candles, index);
        double prevSignal = calculateSignalLine(candles, index - 1);

        // MACD crosses above Signal → BUY
        if (prevMacd <= prevSignal && currMacd > currSignal) {
            return TradeSignal.buy(candle);
        }

        // MACD crosses below Signal → SELL
        if (prevMacd >= prevSignal && currMacd < currSignal) {
            return TradeSignal.sell(candle);
        }

        return TradeSignal.hold();
    }

    /**
     * Calculate MACD line = EMA(fast) - EMA(slow)
     */
    private double calculateMacd(List<Candle> candles, int index) {
        double fastEma = calculateEma(candles, index, fastPeriod);
        double slowEma = calculateEma(candles, index, slowPeriod);
        return fastEma - slowEma;
    }

    /**
     * Calculate Signal line = EMA(signalPeriod) of MACD values
     */
    private double calculateSignalLine(List<Candle> candles, int index) {
        // We need 'signalPeriod' MACD values ending at 'index'
        double multiplier = 2.0 / (signalPeriod + 1);

        // Seed with the oldest MACD value in the window
        double signalEma = calculateMacd(candles, index - signalPeriod + 1);

        for (int i = index - signalPeriod + 2; i <= index; i++) {
            double macdValue = calculateMacd(candles, i);
            signalEma = (macdValue - signalEma) * multiplier + signalEma;
        }

        return signalEma;
    }

    /**
     * Calculate Exponential Moving Average at a given index.
     * Uses the standard EMA formula with SMA as the seed value.
     */
    private double calculateEma(List<Candle> candles, int index, int period) {
        if (index < period - 1) {
            // Not enough data, return SMA
            return sma(candles, index, Math.min(period, index + 1));
        }

        double multiplier = 2.0 / (period + 1);

        // Seed EMA with SMA of the first 'period' candles
        double ema = sma(candles, period - 1, period);

        // Calculate EMA from period to index
        for (int i = period; i <= index; i++) {
            double price = candles.get(i).getClosePrice();
            ema = (price - ema) * multiplier + ema;
        }

        return ema;
    }

    /**
     * Simple Moving Average helper
     */
    private double sma(List<Candle> candles, int endIndex, int period) {
        double sum = 0.0;
        int start = Math.max(0, endIndex - period + 1);
        for (int i = start; i <= endIndex; i++) {
            sum += candles.get(i).getClosePrice();
        }
        return sum / (endIndex - start + 1);
    }

    @Override
    public String getName() {
        return "MACD (" + fastPeriod + ", " + slowPeriod + ", " + signalPeriod + ")";
    }

    @Override
    public StrategyType getType() {
        return StrategyType.MACD;
    }
}