import asyncio
import sys
import numpy as np
from datetime import datetime
import MetaTrader5 as mt5
import talib
import pandas as pd
from telegram import Bot

## Telegram ----------------------------
TOKEN = '<TRUFFLEHOG_REDACTED_TELEGRAMBOTTOKEN>'
CHAT_ID = '882283026'
bot = Bot(TOKEN)

## Metatrader ----------------------------
# login = 116237638
# server = "Exness-MT5Trial6"
login = 124385496
server = "Exness-MT5Trial7"
password = "748798lokaldeN#" 
symbol = "XAUUSDm"
volume = 0.01

## Timing ----------------------------
timeframe = mt5.TIMEFRAME_M5
trend_macd = mt5.TIMEFRAME_M5
trend_tf = mt5.TIMEFRAME_M15
time_candle = 30 #second    3 min = 180s, 5 Min = 300s, 15 min = 900s, 20 min = 1200s
candle_close = 56 #timeframe in second
comment = "testing"

## Message Telegram, Timer ---------------------------------------------------------------------------------

async def send_message_async(message):
    await bot.send_message(chat_id=CHAT_ID, text=message)

async def count_down(seconds):
    for i in range(seconds, 0, -1):
        print(i, end='', flush=True)
        await asyncio.sleep(1)
        print('\r', end='', flush=True)
    print("OK!")

## Checking Side For OP ---------------------------------------------------------------------------------
async def macd(symbol, timeframe=trend_macd):
    if not mt5.initialize():
        print("initialize() failed, error code =", mt5.last_error())
        return None
    candles = mt5.copy_rates_from_pos(symbol, timeframe, 0, 500)
    df = pd.DataFrame(candles)
    macd, signal, hist = talib.MACD(df['close'], fastperiod=12, slowperiod=26, signalperiod=9)
    # print(f"MACD Line: {macd.iloc[-1]:.5f}")
    # print(f"Signal Line: {signal.iloc[-1]:.5f}")
    # print(f"MACD Histogram: {hist.iloc[-1]:.5f}")
    if macd.iloc[-1] > signal.iloc[-1]:
        print("MACD: Buy")
        await send_message_async("MACD: Buy")
        return "Buy"
    elif macd.iloc[-1] < signal.iloc[-1]:
        print("MACD: Sell")
        await send_message_async("MACD: Sell")
        return "Sell"

async def twin_range_filter(symbol, timeframe=trend_tf):
    if not mt5.initialize():
        print("initialize() failed, error code =", mt5.last_error())
        return None
    candles = mt5.copy_rates_from_pos(symbol, timeframe, 0, 500)
    df = pd.DataFrame(candles)
    close = df['close']
    def smoothrng(x, t, m):
        wper = t * 2 - 1
        avrng = talib.EMA(np.abs(x.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.iloc[i-1]
            if x.iloc[i] > prev_val:
                rngfilt.iloc[i] = max(prev_val, x.iloc[i] - r.iloc[i])
            else:
                rngfilt.iloc[i] = min(prev_val, x.iloc[i] + r.iloc[i])
        return rngfilt
    filt = rngfilt(close, smrng)
    STR = filt + smrng
    STS = filt - smrng
    FUB = [STR.iloc[0]]
    FLB = [STS.iloc[0]]
    for i in range(1, len(df)):
        FUB.append(STR.iloc[i] if (STR.iloc[i] < STR.iloc[i-1]) or (close.iloc[i-1] > FUB[i-1]) else FUB[i-1])
        FLB.append(STS.iloc[i] if (STS.iloc[i] > STS.iloc[i-1]) or (close.iloc[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(df)):
        last_trf = TRF[-1]
        if (last_trf == FUB[i-1] and close.iloc[i] <= FUB[i]) or (last_trf == FLB[i-1] and close.iloc[i] <= FLB[i]):
            TRF.append(FUB[i])
        elif (last_trf == FUB[i-1] and close.iloc[i] >= FUB[i]) or (last_trf == FLB[i-1] and close.iloc[i] >= FLB[i]):
            TRF.append(FLB[i])
        else:
            TRF.append(FUB[i])
    TRF = np.array(TRF)
    long_signal = (close > np.roll(TRF, 1))[1:]
    short_signal = (close < np.roll(TRF, 1))[1:]
    df['TRF'] = TRF
    df['long_signal'] = np.append([False], long_signal)
    df['short_signal'] = np.append([False], short_signal)
    if df.iloc[-1]['long_signal']:
        print("Trend: BUY")
        await send_message_async("Trend: Buy")
        return "Buy"
    elif df.iloc[-1]['short_signal']:
        print("Trend: SELL")
        await send_message_async("Trend: Sell")
        return "Sell"

## Condition for OP ---------------------------------------------------------------------------------
async def detect_engulfing(symbol, timeframe):
    if not mt5.initialize():
        print("initialize() failed, error code =", mt5.last_error())
        return None
    start_time = datetime.now()
    start_seconds = start_time.second
    wait_seconds = candle_close - start_seconds
    print("Waiting for candle to close. Sleeping for", wait_seconds, "seconds")
    await send_message_async(f"Waiting for candle to close: {wait_seconds} seconds")
    await count_down(wait_seconds)
    candles = mt5.copy_rates_from_pos(symbol, timeframe, 0, 2)
    df = pd.DataFrame(candles)
    for i in range(1, len(df)):
        current = df.iloc[i].copy()
        previous = df.iloc[i-1].copy()
        if np.abs(current['open'] - previous['close']) > 0.005:
            current['open'] = previous['close']
        if previous['open'] > previous['close'] and \
        current['close'] > current['open'] and \
        current['close'] >= previous['open'] and \
        previous['close'] >= current['open'] and \
        current['close'] - current['open'] > previous['open'] - previous['close']:
            print("Bullish Engulfing")
            await send_message_async("Bullish Engulfing")
            return "Bullish Engulfing"
        elif previous['close'] > previous['open'] and \
        current['open'] > current['close'] and \
        current['open'] >= previous['close'] and \
        previous['open'] >= current['close'] and \
        current['open'] - current['close'] > previous['close'] - previous['open']:
            print("Bearish Engulfing")
            await send_message_async("Bearish Engulfing")
            return "Bearish Engulfing"
        else:
            print("No Engulfing")
            await send_message_async("No Engulfing")
            return "No Engulfing"

async def parabolic_sar(symbol, timeframe):
    if not mt5.initialize():
        print("initialize() failed, error code =", mt5.last_error())
        return None
    candles = mt5.copy_rates_from_pos(symbol, timeframe, 0, 100)
    df = pd.DataFrame(candles)
    df['SAR'] = talib.SAR(df['high'], df['low'], acceleration=0.02, maximum=0.2)
    current_price = df['close'].iloc[-1]
    current_sar = df['SAR'].iloc[-1]
    if current_price > current_sar:
        print("Parabolic SAR: Bullish")
        await send_message_async("Parabolic SAR: Bullish")
        return "Bullish"
    else:
        print("Parabolic SAR: Bearish")
        await send_message_async("Parabolic SAR: Bearish")
        return "Bearish"
    
async def bollinger_bands(symbol, timeframe):
    if not mt5.initialize():
        print("initialize() failed, error code =", mt5.last_error())
        return None
    candles = mt5.copy_rates_from_pos(symbol, timeframe, 0, 100)
    df = pd.DataFrame(candles)
    upper_band, middle_band, lower_band = talib.BBANDS(df['close'], timeperiod=20, nbdevup=2, nbdevdn=2, matype=0)
    current_close = df['close'].iloc[-1]
    # print(f"Upper Band: {upper_band.iloc[-1]:.5f}")
    # print(f"Middle Band: {middle_band.iloc[-1]:.5f}")
    # print(f"Lower Band: {lower_band.iloc[-1]:.5f}")
    deviation = 0.5
    if upper_band.iloc[-1] - deviation <= current_close <= upper_band.iloc[-1] + deviation:
        print("Bollinger Bands: Price around upper band")
        await send_message_async("Bollinger Bands: Price around upper band")
    elif lower_band.iloc[-1] - deviation <= current_close <= lower_band.iloc[-1] + deviation:
        print("Bollinger Bands: Price around lower band")
        await send_message_async("Bollinger Bands: Price around lower band")
    elif current_close > upper_band.iloc[-1]:
        print("Bollinger Bands: Overbought")
        await send_message_async("Bollinger Bands: Overbought")
    elif current_close < lower_band.iloc[-1]:
        print("Bollinger Bands: Oversold")
        await send_message_async("Bollinger Bands: Oversold")
    elif current_close > middle_band.iloc[-1]:
        print("Bollinger Bands: Neutral UP")
    elif current_close < middle_band.iloc[-1]:
        print("Bollinger Bands: Neutral Down")

## OP ---------------------------------------------------------------------------------
async def execute_trade(symbol, timeframe, order_type):
    engulfing = await detect_engulfing(symbol, timeframe)
    psar = await parabolic_sar(symbol, timeframe)
    if order_type == "buy" and engulfing == "Bullish Engulfing" and psar == "Bullish":
        await send_order(order_type=order_type)
    elif order_type == "sell" and engulfing == "Bearish Engulfing" and psar == "Bearish":
        await send_order(order_type=order_type)
    else:
        print("Bad Candles & SAR")
        await send_message_async("Bad Candles & SAR")
        await main() 
        
async def send_order(login=login, server=server, password=password, symbol=symbol, volume=volume, order_type=None):
    if not mt5.initialize(login=login, server=server, password=password):
        print("initialize() failed, error code =", mt5.last_error())
        return
    positions = mt5.positions_get(symbol=symbol)
    for position in positions:
        if position.profit > 0:
            print("There's an open position with profit. New order will be canceled.")
            await send_message_async("Existing position is profitable. New order canceled.")
            mt5.shutdown()
            return
    action = mt5.TRADE_ACTION_DEAL
    order_type = mt5.ORDER_TYPE_BUY if order_type == "buy" else mt5.ORDER_TYPE_SELL
    result = mt5.order_send({
        "action": action,
        "symbol": symbol,
        "volume": volume,
        "type": order_type,
        "price": mt5.symbol_info_tick(symbol).ask if order_type == mt5.ORDER_TYPE_BUY else mt5.symbol_info_tick(symbol).bid,
        "deviation": 20,
        "magic": 234000,
        "comment": comment,
        "type_time": mt5.ORDER_TIME_GTC,
        "type_filling": mt5.ORDER_FILLING_FOK,
    })
    if result.retcode == mt5.TRADE_RETCODE_DONE:
        print("Order successful")
        await send_message_async("Order Position open")
        await count_down(3)
    else:
        print("Order failed")
        await send_message_async("Order Failed")
        await count_down(3)
    mt5.shutdown()

## Initial ---------------------------------------------------------------------------------
async def main():
        while True:
            try:
                await send_message_async("Waiting.....")
                macd_signal = await macd(symbol)
                trend_signal = await twin_range_filter(symbol)
                if macd_signal == "Buy" and trend_signal == "Sell":
                    print("Wait Candle for Sell")
                    await send_message_async("Wait Candle for Sell")
                    await execute_trade(symbol, timeframe, order_type="sell")
                    await count_down(5)
                elif macd_signal == "Sell" and trend_signal == "Buy":
                    print("Wait Candle for Buy")
                    await send_message_async("Wait Candle for Buy")
                    await execute_trade(symbol, timeframe, order_type="buy")
                    await count_down(5)
                else:
                    print("No suitable conditions found for trade execution.")
                    await send_message_async("Wait next MACD, No Trade open")
                    await count_down(5)
            except Exception as e:
                print("An error occurred:", e)
                await send_message_async("An error occurred: " + str(e))
                await count_down(5)
                continue


            # await twin_range_filter(symbol)
            # await macd(symbol, timeframe)
            # await bollinger_bands(symbol, timeframe)
            # await execute_trade(symbol, timeframe, order_type="sell")

try:
    asyncio.run(main())
except KeyboardInterrupt:
    print(".....Script stopped by user. Exiting gracefully.....")
    sys.exit(0)