# Reminder
# Bars are counted from left to right! so 1, 2 and 3 in this program and 4th is the current bar which is moving.

import time
import signal
import threading
import pandas as pd
import MetaTrader5 as mt5
from datetime import datetime

CURRENCY_PAIRS = ['EURUSD', 'USDJPY', 'GBPUSD', 'AUDUSD', 'USDCAD', 'USDCHF']
TIMEFRAMES = [mt5.TIMEFRAME_M1, mt5.TIMEFRAME_M5,
              mt5.TIMEFRAME_M10, mt5.TIMEFRAME_M15, mt5.TIMEFRAME_H1]
COUNT_BARS = 4  # Adjusted to retrieve 4 bars (including the current one)


def market_order(symbol, order_type, entry, sl):

    order_dict = {'buy': mt5.ORDER_TYPE_BUY_STOP,
                  'sell': mt5.ORDER_TYPE_SELL_STOP}

    request = {
        "action": mt5.TRADE_ACTION_PENDING,
        "symbol": symbol,
        "volume": 0.01,
        "type": order_dict[order_type],
        "price": entry,
        "sl": sl,
        "deviation": 0,
        "magic": 24001,
        "comment": "Trailing stop-loss",
        "type_time": mt5.ORDER_TIME_GTC,
        "type_filling": mt5.ORDER_FILLING_RETURN,
    }

    result = mt5.order_send(request)
    if result.retcode == mt5.TRADE_RETCODE_DONE:
        print("Market order successfully placed!\n")
        return result
    else:
        print(f"An error occurred: {result.comment}")
        return None


def determine_bars_type(bars_data):
    bar_types = []
    bar_times = []

    for i in range(len(bars_data)):
        open_price = bars_data['open'].iloc[i]
        close_price = bars_data['close'].iloc[i]
        bar_time = bars_data['time'].iloc[i].strftime('%H:%M:%S')

        bar_type = 'Bull' if close_price > open_price else 'Bear'
        bar_body = open_price - close_price

        if bar_body == 0:
            bar_type = 'Small Body Doji'

        bar_types.append(bar_type)
        bar_times.append(bar_time)

    return bar_types, bar_times


def trail_sl(symbol, order_ticket, timeframe, stop_loss_dict):
    while True:
        bars = mt5.copy_rates_from_pos(symbol, timeframe, 0, 4)
        bars_df = pd.DataFrame(bars)
        bar1_high = bars_df['high'].iloc[0]
        bar2_high = bars_df['high'].iloc[1]
        bar3_high = bars_df['high'].iloc[2]
        bar1_low = bars_df['low'].iloc[0]
        bar2_low = bars_df['low'].iloc[1]
        bar3_low = bars_df['low'].iloc[2]

        symbol_info = mt5.symbol_info(symbol).point
        num_decimal_places = len(str(symbol_info).split('.')[-1])

        if mt5.positions_get(ticket=order_ticket):
            position_type = mt5.positions_get(ticket=order_ticket)[0].type
            if position_type == mt5.ORDER_TYPE_SELL:
                stop_loss_value = round(max(
                    bar1_high, bar2_high, bar3_high), num_decimal_places)  # Sell order S/L
            else:
                stop_loss_value = round(min(bar1_low, bar2_low,
                                            bar3_low), num_decimal_places)  # Buy order S/L

            if stop_loss_value != stop_loss_dict[symbol]:
                current_sl = mt5.positions_get(ticket=order_ticket)[0].sl
                if stop_loss_value != current_sl:
                    request = {
                        "action": mt5.TRADE_ACTION_SLTP,
                        "position": order_ticket,
                        "symbol": symbol,
                        "magic": 24001,
                        "sl": stop_loss_value
                    }
                    result = mt5.order_send(request)
                    if result.retcode == mt5.TRADE_RETCODE_DONE:
                        print(
                            f"[{datetime.now()}] Trailing Stop Loss for Order {order_ticket} updated. New S/L: {stop_loss_value}")
                        print()
                        stop_loss_dict[symbol] = stop_loss_value
                    elif result.retcode == 10025:  # Ignore error code 10025
                        pass
                    else:
                        print(
                            f"[{datetime.now()}] Failed to update Trailing Stop Loss for Order {order_ticket}: {result.comment}")
                        print(f"Error code: {result.retcode}")
                        print()

        time.sleep(1)  # Wait for 1 second before checking again


def custom_strategy(bar2_type, bar2_time, bar3_type, recent_bar_data, currency_pair, timeframe, current_bar, stop_loss_dict):
    timeframe_dict = {mt5.TIMEFRAME_M1: 'M1', mt5.TIMEFRAME_M5: 'M5',
                      mt5.TIMEFRAME_M10: 'M10', mt5.TIMEFRAME_M15: 'M15', mt5.TIMEFRAME_H1: 'H1'}
    bar2_high = recent_bar_data['high'].iloc[1]
    bar2_low = recent_bar_data['low'].iloc[1]
    bar3_high = recent_bar_data['high'].iloc[2]
    bar3_low = recent_bar_data['low'].iloc[2]

    tick = mt5.symbol_info_tick(currency_pair)

    symbol_info = mt5.symbol_info(currency_pair).point
    num_decimal_places = len(str(symbol_info).split('.')[-1])

    if bar2_type == 'Bear' and bar3_type == 'Bull':  # 2nd one is red, 3rd is green
        pips_conversion = symbol_info * 15
        entry = round(bar3_high+pips_conversion, num_decimal_places)
        stop_loss = round(bar3_low-pips_conversion, num_decimal_places)
        if entry >= tick.ask:
            print(f"[{datetime.now()}] Bar 2 is {bar2_type} at {bar2_time} and Bar 3 is {bar3_type} for {currency_pair} {timeframe_dict[timeframe]}")
            print(f"Buy Entry: {entry} S/L: {stop_loss}")
            order_result = market_order(currency_pair, "buy", entry, stop_loss)
            if order_result:
                stop_loss_dict[currency_pair] = stop_loss
                thread = threading.Thread(target=trail_sl, args=(
                    currency_pair, order_result.order, timeframe, stop_loss_dict))
                thread.daemon = True
                thread.start()
    elif bar2_type == 'Bull' and bar3_type == 'Bear':  # 2nd one is green, 3rd is red
        pips_conversion = symbol_info * 5
        entry = round(bar3_low-pips_conversion, num_decimal_places)
        stop_loss = round(bar3_high+pips_conversion, num_decimal_places)
        if entry <= tick.bid:
            print(f"[{datetime.now()}] Bar 2 is {bar2_type} at {bar2_time} and Bar 3 is {bar3_type} for {currency_pair} {timeframe_dict[timeframe]}")
            print(f"Sell Entry: {entry} S/L: {stop_loss}")
            order_result = market_order(
                currency_pair, "sell", entry, stop_loss)
            if order_result:
                stop_loss_dict[currency_pair] = stop_loss
                thread = threading.Thread(target=trail_sl, args=(
                    currency_pair, order_result.order, timeframe, stop_loss_dict))
                thread.daemon = True
                thread.start()


def signal_handler(signal, frame):
    print("Ctrl+C pressed. Deleting pending orders...")

    # Get all pending orders
    orders = mt5.orders_get()

    # Iterate over the orders and delete pending orders
    for order in orders:
        if order.type in [mt5.ORDER_TYPE_BUY_STOP, mt5.ORDER_TYPE_SELL_STOP]:
            request = {
                "action": mt5.TRADE_ACTION_REMOVE,
                "order": order.ticket
            }
            result = mt5.order_send(request)
            if result.retcode == mt5.TRADE_RETCODE_DONE:
                print(f"Pending order {order.ticket} cancelled successfully!")
            else:
                print(
                    f"Failed to cancel pending order {order.ticket}: {result.comment}")

    print("Pending orders deleted. Exiting...")
    exit(0)


# Register the signal handler for Ctrl+C
signal.signal(signal.SIGINT, signal_handler)


def main():
    if not mt5.initialize():
        print("initialize() failed")
        mt5.shutdown()
        return

    print("Script started, checking for trades...\n")
    previous_bar_time = {}
    stop_loss_dict = {}

    while True:
        try:
            for currency_pair in CURRENCY_PAIRS:
                for timeframe in TIMEFRAMES:
                    bars = mt5.copy_rates_from_pos(
                        currency_pair, timeframe, 0, COUNT_BARS)
                    bars_df = pd.DataFrame(bars)
                    bars_df['time'] = pd.to_datetime(bars_df['time'], unit='s')

                    current_bar_time = bars_df['time'].iloc[-1]

                    if current_bar_time != previous_bar_time.get((currency_pair, timeframe)):
                        # Adjusted to select the current bar
                        current_bar = bars_df.iloc[-1:]
                        # Adjusted to select the last 3 bars
                        recent_bars = bars_df.iloc[:-1]

                        bar_types, bar_times = determine_bars_type(recent_bars)
                        bar1_type, bar2_type, bar3_type = bar_types[:3]
                        bar1_time, bar2_time, bar3_time = bar_times[:3]

                        # Call the custom_strategy function with the bar properties
                        custom_strategy(bar2_type, bar2_time, bar3_type,
                                        recent_bars, currency_pair, timeframe, current_bar, stop_loss_dict)

                        # Update previous_bar_time before checking for pending orders
                        previous_bar_time[(
                            currency_pair, timeframe)] = current_bar_time

                # Check for pending orders and delete them if necessary
                orders = mt5.orders_get(symbol=currency_pair)
                for order in orders:
                    if order.type in [mt5.ORDER_TYPE_BUY_STOP, mt5.ORDER_TYPE_SELL_STOP] and pd.to_datetime(
                            order.time_setup, unit='s') < current_bar_time:
                        # Delete the pending order
                        request = {
                            "action": mt5.TRADE_ACTION_REMOVE,
                            "order": order.ticket
                        }
                        result = mt5.order_send(request)
                        if result.retcode == mt5.TRADE_RETCODE_DONE:
                            print(
                                f"Pending order {order.ticket} cancelled successfully!")
                            print()
                        else:
                            print(
                                f"Failed to cancel pending order {order.ticket}: {result.comment}")
                            print()

        except Exception as e:
            print(f"An error occurred: {e}")

        time.sleep(1)  # Delay between data updates


if __name__ == '__main__':
    print("\nHello debug!")
    main()
