import MetaTrader5 as mt5
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime, timedelta

# Spread and commission constants
COMMISSION = 0.00001  # Deal commission (share)
SPREAD = 0.00001  # Spread (share)

# Connect to MetaTrader 5
if not mt5.initialize():
    print("initialize() failed")
    mt5.shutdown()


def get_data(symbol, timeframe, start_date, end_date):
    rates = mt5.copy_rates_range(symbol, timeframe, start_date, end_date)
    df = pd.DataFrame(rates)
    df["time"] = pd.to_datetime(df["time"], unit="s")
    df.set_index("time", inplace=True)
    return df


def calculate_var(returns, confidence_level=0.95):
    return np.percentile(returns, (1 - confidence_level) * 100)


def place_grid_orders(price, var, grid_size=5):
    grid_levels = [price * (1 + i * var) for i in range(-grid_size, grid_size + 1)]
    return grid_levels


def backtest_grid_strategy(data, symbol, initial_balance=10000, risk_per_trade=0.01):
    balance = initial_balance
    positions = pd.DataFrame(columns=["entry_price", "size", "side"])
    equity = {}

    monthly_data = data.resample("ME").last()

    for date, row in monthly_data.iterrows():
        current_price = row["close"]

        # Close profitable positions
        closed_positions = positions[
            (positions["side"] == "buy")
            & (current_price > positions["entry_price"] * 1.1)
            | (positions["side"] == "sell")
            & (current_price < positions["entry_price"] * 0.9)
        ]

        for _, position in closed_positions.iterrows():
            exit_price = (
                current_price * (1 - SPREAD)
                if position["side"] == "buy"
                else current_price * (1 + SPREAD)
            )
            profit = (
                (exit_price - position["entry_price"])
                * position["size"]
                * (1 if position["side"] == "buy" else -1)
            )
            profit -= (
                position["size"] * current_price * COMMISSION * 2
            )  # Open and close commission
            balance += profit

        positions = positions.drop(closed_positions.index)

        # Place new orders
        returns = data.loc[:date, "close"].pct_change().dropna()
        var = calculate_var(returns)
        grid_levels = place_grid_orders(current_price, var)

        new_positions = []
        for level in grid_levels:
            if level < current_price:
                size = (balance * risk_per_trade) / abs(current_price - level)
                entry_price = level * (1 + SPREAD)  # Consider spread when buying
                new_positions.append(
                    {"entry_price": entry_price, "size": size, "side": "buy"}
                )
            elif level > current_price:
                size = (balance * risk_per_trade) / abs(current_price - level)
                entry_price = level * (1 - SPREAD)  # Consider spread when selling
                new_positions.append(
                    {"entry_price": entry_price, "size": size, "side": "sell"}
                )

        positions = pd.concat(
            [positions, pd.DataFrame(new_positions)], ignore_index=True
        )

        # Calculate commission for opening new positions
        balance -= sum(
            pos["size"] * current_price * COMMISSION for pos in new_positions
        )

        equity[date] = balance + sum(
            (current_price - pos["entry_price"])
            * pos["size"]
            * (1 if pos["side"] == "buy" else -1)
            for _, pos in positions.iterrows()
        )

    return pd.Series(equity)


# Parameters
symbol = "EURUSD"
timeframe = mt5.TIMEFRAME_D1
start_date = datetime(2010, 1, 1)
end_date = datetime(2023, 1, 1)

# Get data
data = get_data(symbol, timeframe, start_date, end_date)

# Strategy backtesting
equity_curve = backtest_grid_strategy(data, symbol)

# Visualize results
plt.figure(figsize=(12, 6))
plt.plot(equity_curve)
plt.title(f"Equity Curve for Grid Strategy on {symbol}")
plt.xlabel("Date")
plt.ylabel("Equity")
plt.show()

# Calculate metrics
total_return = (
    (equity_curve.iloc[-1] - equity_curve.iloc[0]) / equity_curve.iloc[0] * 100
)
sharpe_ratio = (
    np.sqrt(12) * equity_curve.pct_change().mean() / equity_curve.pct_change().std()
)
max_drawdown = (equity_curve / equity_curve.cummax() - 1).min() * 100

print(f"Total Return: {total_return:.2f}%")
print(f"Sharpe Ratio: {sharpe_ratio:.2f}")
print(f"Max Drawdown: {max_drawdown:.2f}%")

# Close connection to MetaTrader 5
mt5.shutdown()
