from backtesting import Strategy, Backtest
import pandas as pd
import numpy as np
import talib

# Fungsi untuk menghitung indikator
def macd(data, fastperiod=12, slowperiod=26, signalperiod=9):
    close = data.Close
    macd, signal, _ = talib.MACD(close, fastperiod=fastperiod, slowperiod=slowperiod, signalperiod=signalperiod)
    macd_signal = pd.Series(0, index=data.index, name='macd_signal')
    macd_signal[macd > signal] = 1  # Assign 1 for buy signal
    macd_signal[macd < signal] = 0  # Assign 0 for sell signal
    return macd_signal

def twin_range_filter(data):
    close = data.Close

    def smoothrng(x, t, m):
        wper = t * 2 - 1
        diff = np.array([0] + [x[i] - x[i-1] for i in range(1, len(x))])
        avrng = talib.EMA(np.abs(diff), timeperiod=t)
        smoothrng = talib.EMA(avrng, timeperiod=wper) * m
        return smoothrng

    per1, mult1, per2, mult2 = 27, 1.6, 55, 2.0
    smrng1 = smoothrng(close, per1, mult1)
    smrng2 = smoothrng(close, per2, mult2)
    smrng = (smrng1 + smrng2) / 2

    def rngfilt(x, r):
        rngfilt = x.copy()
        for i in range(1, len(x)):
            prev_val = rngfilt[i-1]
            if x[i] > prev_val:
                rngfilt[i] = max(prev_val, x[i] - r[i])
            else:
                rngfilt[i] = min(prev_val, x[i] + r[i])
        return rngfilt

    filt = rngfilt(close, smrng)
    STR = filt + smrng
    STS = filt - smrng
    FUB = [STR[0]]
    FLB = [STS[0]]
    for i in range(1, len(data)):
        FUB.append(STR[i] if (STR[i] < STR[i-1]) or (close[i-1] > FUB[i-1]) else FUB[i-1])
        FLB.append(STS[i] if (STS[i] > STS[i-1]) or (close[i-1] < FLB[i-1]) else FLB[i-1])
    FUB = np.array(FUB)
    FLB = np.array(FLB)
    TRF = [FUB[0]]
    for i in range(1, len(data)):
        last_trf = TRF[-1]
        if (last_trf == FUB[i-1] and close[i] <= FUB[i]) or (last_trf == FLB[i-1] and close[i] <= FLB[i]):
            TRF.append(FUB[i])
        elif (last_trf == FUB[i-1] and close[i] >= FUB[i]) or (last_trf == FLB[i-1] and close[i] >= FLB[i]):
            TRF.append(FLB[i])
        else:
            TRF.append(FUB[i])
    TRF = np.array(TRF)[:len(data)]
    
    long_signal = (close > np.roll(TRF, 1))[1:]
    short_signal = (close < np.roll(TRF, 1))[1:]
    
    trf = pd.Series(0, index=data.index, name='trf')
    trf[long_signal] = 1  # Assign 1 for buy signal
    trf[~long_signal] = 0  # Assign 0 for sell signal
    
    return trf

def bollinger_bands(data, timeperiod=20, nbdevup=2, nbdevdn=2, matype=0):
    close = data.Close
    upper_band, middle_band, lower_band = talib.BBANDS(data.close, timeperiod=timeperiod, nbdevup=nbdevup, nbdevdn=nbdevdn, matype=matype)
    bband = pd.Series(0, index=data.index, name='bband')
    deviation = 1
    # Kondisi oversold atau around lower band
    bband[(close < lower_band - deviation) | ((lower_band - deviation <= close) & (close <= lower_band + deviation))] = 1
    # Kondisi overbought atau around upper band
    bband[(close > upper_band + deviation) | ((upper_band - deviation <= close) & (close <= upper_band + deviation))] = 0
    return bband

# Strategi trading
class MyStrategy(Strategy):
    def init(self):
        super().init()

    def init(self):
        self.macd = self.I(macd, self.data)
        self.trf = self.I(twin_range_filter, self.data)
        self.bband = self.I(bollinger_bands, self.data)

    def next(self):
        if self.macd[-1] == 0 and self.trf[-1] == 1 and self.bband[-1] == 1:
            self.buy()
        elif self.macd[-1] == 1 and self.trf[-1] == 0 and self.bband[-1] == 0:
            self.sell()

# Backtest
df = pd.read_csv("C:\\Users\\rozzy\\data_mt5.csv")

#Check if NA values are in data
df=df[df['tick_volume']!=0]
df.reset_index(drop=True, inplace=True)

data = df.copy()
data['time'] = pd.to_datetime(data['time'])
data.set_index('time', inplace=True)
data = data.rename(columns={'time': 'Date', 'open': 'Open', 'high': 'High', 'low': 'Low', 'close': 'Close', 'tick_volume': 'Volume'})

bt = Backtest(data, MyStrategy, cash=10000, commission=.002)
stat = bt.run()
print(stat)

bt.plot()