import MetaTrader5 as mt5
import pandas as pd
import numpy as np
from collections import defaultdict
import matplotlib.pyplot as plt  # Added matplotlib.pyplot import
from datetime import datetime, timedelta

# Initialize MetaTrader5
if not mt5.initialize():
    print("initialize() failed")
    mt5.shutdown()

# Set data request parameters
symbol = "EURUSD"
timeframe = mt5.TIMEFRAME_H4
start_date = pd.Timestamp("2017-01-01")
end_date = pd.Timestamp("2024-07-01")

# Download OHLC data
rates = mt5.copy_rates_range(symbol, timeframe, start_date, end_date)
ohlc_data = pd.DataFrame(rates)
ohlc_data["time"] = pd.to_datetime(ohlc_data["time"], unit="s")

# Transform data to price change directions
ohlc_data["direction"] = np.where(ohlc_data["close"].diff() > 0, "up", "down")


# Function for searching for buy patterns
def find_buy_patterns(data, pattern_length):
    patterns = defaultdict(list)
    for i in range(len(data) - pattern_length - 6):
        pattern = tuple(data["direction"][i : i + pattern_length])
        if data["direction"][i + pattern_length + 6] == "up":
            patterns[pattern].append(True)
        else:
            patterns[pattern].append(False)
    return patterns


# Function for searching for sell patterns
def find_sell_patterns(data, pattern_length):
    patterns = defaultdict(list)
    for i in range(len(data) - pattern_length - 6):
        pattern = tuple(data["direction"][i : i + pattern_length])
        if data["direction"][i + pattern_length + 6] == "down":
            patterns[pattern].append(True)
        else:
            patterns[pattern].append(False)
    return patterns


# Function for calculating win rate and pattern occurrence frequency
def calculate_winrate_and_frequency(patterns):
    results = []
    for pattern, outcomes in patterns.items():
        winrate = np.mean(outcomes) * 100
        frequency = len(outcomes)
        results.append((pattern, winrate, frequency))
    results.sort(key=lambda x: x[1], reverse=True)
    return results


# Pattern search parameters
pattern_lengths = range(3, 70)  # Pattern lengths from 3 to 25
all_buy_patterns = {}
all_sell_patterns = {}

for pattern_length in pattern_lengths:
    buy_patterns = find_buy_patterns(ohlc_data, pattern_length)
    sell_patterns = find_sell_patterns(ohlc_data, pattern_length)
    all_buy_patterns[pattern_length] = buy_patterns
    all_sell_patterns[pattern_length] = sell_patterns

# Calculate win rate and occurrence frequency for all buy patterns
all_buy_results = []
for pattern_length, patterns in all_buy_patterns.items():
    results = calculate_winrate_and_frequency(patterns)
    all_buy_results.extend(results)

# Calculate win rate and occurrence frequency for all sell patterns
all_sell_results = []
for pattern_length, patterns in all_sell_patterns.items():
    results = calculate_winrate_and_frequency(patterns)
    all_sell_results.extend(results)

# Sort patterns that occurred more than 50 times
filtered_buy_results = [result for result in all_buy_results if result[2] > 10]
filtered_sell_results = [result for result in all_sell_results if result[2] > 10]

# Sort and select 300 best buy patterns
filtered_buy_results.sort(key=lambda x: x[1], reverse=True)
top_300_buy_patterns = filtered_buy_results[:300]

# Sort and select 300 best sell patterns
filtered_sell_results.sort(key=lambda x: x[1], reverse=True)
top_300_sell_patterns = filtered_sell_results[:300]


# Function for checking if the last prices correspond to patterns
def check_patterns(data, patterns):
    pattern_length = len(patterns[0][0])
    last_pattern = tuple(data["direction"][-pattern_length:])
    matching_patterns = [pattern for pattern in patterns if pattern[0] == last_pattern]
    return matching_patterns


# Check if the last prices correspond to buy and sell patterns
matching_buy_patterns = check_patterns(ohlc_data, top_300_buy_patterns)
matching_sell_patterns = check_patterns(ohlc_data, top_300_sell_patterns)

# Calculate the cumulative chance for rise and fall
total_buy_winrate = (
    np.mean([pattern[1] for pattern in matching_buy_patterns])
    if matching_buy_patterns
    else 0
)
total_sell_winrate = (
    np.mean([pattern[1] for pattern in matching_sell_patterns])
    if matching_sell_patterns
    else 0
)


# Add pattern visualization function
def visualize_patterns(patterns, title, filename):
    patterns = patterns[:20]  # Take top 20 for clarity
    patterns.reverse()  # Expand the list for correct display on the chart

    fig, ax = plt.subplots(figsize=(12, 8))

    winrates = [p[1] for p in patterns]
    frequencies = [p[2] for p in patterns]
    labels = [" ".join(p[0]) for p in patterns]

    ax.barh(range(len(patterns)), winrates, align="center", color="skyblue", zorder=10)
    ax.set_yticks(range(len(patterns)))
    ax.set_yticklabels(labels)
    ax.invert_yaxis()  # Invert Y axis to display the best patterns above

    ax.set_xlabel("Winrate (%)")
    ax.set_title(title)

    # Add occurrence frequency
    for i, v in enumerate(winrates):
        ax.text(v + 1, i, f"Freq: {frequencies[i]}", va="center")

    plt.tight_layout()
    plt.savefig(filename)
    plt.close()


# Visualize top buy and sell patterns
visualize_patterns(
    top_300_buy_patterns, f"Top 20 Buy Patterns for {symbol}", "top_buy_patterns.png"
)
visualize_patterns(
    top_300_sell_patterns, f"Top 20 Sell Patterns for {symbol}", "top_sell_patterns.png"
)


# Trading simulation function
def simulate_trade(data, direction, entry_price, take_profit, stop_loss):
    for i, row in data.iterrows():
        current_price = row["close"]

        if direction == "BUY":
            if current_price >= entry_price + take_profit:
                return {"profit": take_profit, "duration": i}
            elif current_price <= entry_price - stop_loss:
                return {"profit": -stop_loss, "duration": i}
        else:  # SELL
            if current_price <= entry_price - take_profit:
                return {"profit": take_profit, "duration": i}
            elif current_price >= entry_price + stop_loss:
                return {"profit": -stop_loss, "duration": i}

    # If the loop is completed without reaching TP or SL, close by the current price
    last_price = data["close"].iloc[-1]
    profit = (
        (last_price - entry_price) if direction == "BUY" else (entry_price - last_price)
    )
    return {"profit": profit, "duration": len(data)}


# Backtest function
def backtest_pattern_system(data, buy_patterns, sell_patterns):
    equity_curve = [10000]  # Initial capital $10,000
    trades = []

    for i in range(len(data) - max(len(p[0]) for p in buy_patterns + sell_patterns)):
        current_data = data.iloc[: i + 1]
        last_pattern = tuple(current_data["direction"].iloc[-len(buy_patterns[0][0]) :])

        matching_buy = [p for p in buy_patterns if p[0] == last_pattern]
        matching_sell = [p for p in sell_patterns if p[0] == last_pattern]

        if matching_buy and not matching_sell:
            entry_price = current_data["close"].iloc[-1]
            take_profit = 0.001  # 10 pips
            stop_loss = 0.0005  # 5 pips
            trade_result = simulate_trade(
                data.iloc[i + 1 :], "BUY", entry_price, take_profit, stop_loss
            )
            trades.append(trade_result)
            equity_curve.append(
                equity_curve[-1] + trade_result["profit"] * 10000
            )  # Multiply by 10000 to convert to USD
        elif matching_sell and not matching_buy:
            entry_price = current_data["close"].iloc[-1]
            take_profit = 0.001  # 10 pips
            stop_loss = 0.0005  # 5 pips
            trade_result = simulate_trade(
                data.iloc[i + 1 :], "SELL", entry_price, take_profit, stop_loss
            )
            trades.append(trade_result)
            equity_curve.append(
                equity_curve[-1] + trade_result["profit"] * 10000
            )  # Multiply by 10000 to convert to USD
        else:
            equity_curve.append(equity_curve[-1])

    return equity_curve, trades


# Perform backtest
equity_curve, trades = backtest_pattern_system(
    ohlc_data, top_300_buy_patterns, top_300_sell_patterns
)

# Visualize backtest results
plt.figure(figsize=(12, 6))
plt.plot(equity_curve)
plt.title("Equity Curve")
plt.xlabel("Trades")
plt.ylabel("Equity ($)")
plt.savefig("equity_curve.png")
plt.close()

# Calculate backtest statistics
total_profit = equity_curve[-1] - equity_curve[0]
win_rate = (
    sum(1 for trade in trades if trade["profit"] > 0) / len(trades) if trades else 0
)
average_profit = sum(trade["profit"] for trade in trades) / len(trades) if trades else 0

print(f"\nBacktest Results:")
print(f"Total Profit: ${total_profit:.2f}")
print(f"Win Rate: {win_rate:.2%}")
print(f"Average Profit per Trade: ${average_profit*10000:.2f}")
print(f"Total Trades: {len(trades)}")

# Display statistics
print(f"Matching buy patterns: {len(matching_buy_patterns)}")
print(f"Matching sell patterns: {len(matching_sell_patterns)}")
print(f"Total buy winrate: {total_buy_winrate:.2f}%")
print(f"Total sell winrate: {total_sell_winrate:.2f}%")

# MetaTrader 5 shutdown
mt5.shutdown()
