import tkinter as tk
from tkinter import messagebox, OptionMenu, StringVar
import ccxt
import logging
from datetime import datetime
import pandas as pd
import time
import hmac
import hashlib
import requests
from logging.handlers import RotatingFileHandler

# Установить уровень логирования
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s %(levelname)s - %(message)s',
    handlers=[
        RotatingFileHandler(r'C:\Users\NOD\trading_bot.log', maxBytes=5000000, encoding='utf-8', mode='a+'),
        logging.StreamHandler()
    ]
)

# Конфигурация API (замените YOUR_API_KEY и YOUR_SECRET_KEY своими настоящими ключами)
api_key = 'mx0vgl9KtaKYrVqgxi'
secret = '067d53047cf245f0b5e6b2f9e987c4c6'

# Функция для генерации сигнатуры
def generate_signature(api_key, secret, req_time, params=''):
    message = api_key + str(req_time) + params
    signature = hmac.new(secret.encode('utf-8'), message.encode('utf-8'), hashlib.sha256).hexdigest()
    return signature

# Глобальная переменная для статуса подключения
connected = False

# Глобальная переменная для символов
symbols = []
units = []

# Инициализация биржи MEXC через библиотеку ccxt для получения маркетинговой информации
try:
    exchange = ccxt.mexc({
        'apiKey': api_key,
        'secret': secret,
        'options': {
            'recvWindow': 5000,  # например, 5000 мс
        }
    })
    exchange.load_markets()  # Загрузка символов биржи
    symbols = list(exchange.symbols)  # Сохранение списка символов
    if not symbols:
        raise ValueError('Не удалось загрузить символы биржи.')
    logging.info('Биржа MEXC успешно инициализирована.')
    connected = True
except Exception as e:
    logging.error('Ошибка инициализации биржи MEXC: %s', e)
    connected = False

# Глобальные переменные
open_positions = []
session_profit = 0.0
session_start_time = None
trading_active = False
paused = False
trade_history = []

# Функция для обновления списка символов в OptionMenu на основе поискового запроса
def update_symbol_menu(search_query):
    filtered_symbols = [symbol for symbol in symbols if search_query.lower() in symbol.lower()]
    menu = symbol_menu['menu']
    menu.delete(0, 'end')
    for symbol in filtered_symbols:
        menu.add_command(label=symbol, command=lambda s=symbol: selected_symbol.set(s))
    if filtered_symbols:
        selected_symbol.set(filtered_symbols[0])
        update_units(selected_symbol.get())

# Функция для обновления списка торговых единиц на основе выбранного символа
def update_units(symbol):
    global units
    if symbol in exchange.markets:
        market = exchange.markets[symbol]
        base_currency = market['base']
        quote_currency = market['quote']
        units = [base_currency, quote_currency]
    else:
        units = []
    update_unit_menu()

# Функция для обновления меню торговых единиц
def update_unit_menu():
    menu = unit_menu['menu']
    menu.delete(0, 'end')
    for unit in units:
        menu.add_command(label=unit, command=lambda u=unit: selected_unit.set(u))
    if units:
        selected_unit.set(units[0])

# Функция для приостановки торговли
def pause_trade():
    global paused
    paused = True
    button_trade.config(text='Торговля на паузе')
    logging.info('Торговля приостановлена пользователем.')

# Функция для синхронизации времени (основа для дальнейших временных расчетов)
def synchronize_time():
    global connected
    try:
        exchange.load_time_difference()
        logging.info('Синхронизация времени с биржей.')
        connected = True
    except Exception as e:
        logging.error('Ошибка синхронизации времени: %s', e)
        connected = False

# Функция для обновления UI
def update_ui():
    symbol = selected_symbol.get()
    current_price = get_current_price(symbol)
    if current_price:
        label_current_price.config(text=f'Текущая цена: {current_price:.2f}')

    if session_start_time:
        elapsed_time = datetime.now() - session_start_time
        label_session_time.config(text=f'Время сессии: {elapsed_time}')

    connection_status.config(bg='green' if connected else 'red')
    update_open_positions_display()
    root.after(1000, update_ui)

def synchronize_time():
    global connected
    try:
        server_time = exchange.fetch_time()  # Получение серверного времени
        local_time = int(time.time() * 1000)  # Местное время в миллисекундах
        exchange.load_time_difference()  # Сохранение временной разницы
        logging.info('Синхронизация времени с биржей.')
        logging.info(f'Серверное время: {datetime.utcfromtimestamp(server_time / 1000)}')
        logging.info(f'Локальное время: {datetime.utcfromtimestamp(local_time / 1000)}')
        logging.info(f'Временная разница: {exchange.time_difference} мс')
        connected = True
    except Exception as e:
        logging.error('Ошибка синхронизации времени: %s', e)
        connected = False
        
# Функция для проверки символа
def validate_symbol(symbol):
    return exchange.symbols is not None and symbol in exchange.symbols

# Функция для получения текущей цены
def get_current_price(symbol):
    try:
        if not validate_symbol(symbol):
            return None

        ticker = exchange.fetch_ticker(symbol)
        return ticker['last']
    except Exception as e:
        logging.error('Ошибка при получении текущей цены: %s', e)
        messagebox.showerror('Ошибка', f'Не удалось получить текущую цену для {symbol}.')
        return None

# Функция для покупки на заданную сумму в валюте
def buy(symbol, cost):
    try:
        if cost <= 0:
            messagebox.showerror('Ошибка', 'Сумма должна быть положительной.')
            return None

        current_price = get_current_price(symbol)
        if current_price is None or current_price <= 0:
            messagebox.showerror('Ошибка', 'Не удалось получить корректную текущую цену.')
            return None

        market = exchange.market(symbol)
        min_volume = market['limits']['amount']['min']

        # Вычисление количества единиц покупки с округлением
        amount_units = cost / current_price
        precision = market['precision']['amount']
        if isinstance(precision, int):
            amount_units = round(amount_units)
        else:
            amount_units = float(amount_units)

        logging.info(f'Текущая цена: {current_price}')
        logging.info(f'Вычисленное количество единиц: {amount_units}')
        logging.info(f'Минимальный объем для {symbol}: {min_volume}')

        if amount_units < min_volume:
            messagebox.showerror('Ошибка', f'Минимальный объем для торговли: {min_volume}')
            return None

        if amount_units <= 0:
            messagebox.showerror('Ошибка', 'Количество единиц должно быть положительным.')
            return None

        order = place_order(symbol, amount_units, current_price, 'BID')
        if order:
            open_positions.append({'symbol': symbol, 'amount': amount_units, 'buy_price': current_price, 'order_id': order['id']})
            return current_price
    except Exception as e:
        logging.error('Ошибка покупки: %s', e)
        messagebox.showerror('Ошибка', f'Не удалось купить: {e}')
        return None

# Функция для размещения ордера
def place_order(symbol, quantity, price, trade_type='BID'):
    endpoint = '/open/api/v2/order/place'
    url = f"https://www.mexc.com{endpoint}"

    req_time = int(time.time() * 1000)
    params = {
        "symbol": symbol,
        "quantity": quantity,
        "price": price,
        "trade_type": trade_type,
        "order_type": "LIMIT_ORDER"
    }

    sign = generate_signature(api_key, secret, req_time, f"symbol={symbol}&quantity={quantity}&price={price}&trade_type={trade_type}&order_type=LIMIT_ORDER")

    headers = {
        "ApiKey": api_key,
        "Request-Time": str(req_time),
        "Signature": sign,
        "Content-Type": "application/json"
    }

    response = requests.post(url, headers=headers, json=params)

    if response.status_code == 200:
        data = response.json()
        if data['code'] == 200:
            logging.info('Order placed: %s', data)
            return data['data']
        else:
            logging.error('Failed to place order: %s', data)
    else:
        logging.error('HTTP Error: %d', response.status_code)

# Загрузка состояния торговли из логов
def load_trade_state():
    global session_profit, open_positions
    try:
        with open('trading_bot_state.log', 'r') as f:
            lines = f.readlines()
            open_positions = []
            for line in lines:
                action, symbol, amount, price, profit = line.strip().split(',')
                if action == 'buy':
                    open_positions.append({'symbol': symbol, 'amount': float(amount), 'price': float(price)})
                session_profit += float(profit)
    except FileNotFoundError:
        logging.info('Лог состояния торговли не найден.')

# Сохранение состояния торговли
def save_trade_state(action, order, profit=0):
    with open('trading_bot_state.log', 'a') as f:
        f.write(f"{action},{order['symbol']},{order['amount']},{order['price']},{profit}\n")

# Экспорт истории торгов в Excel
def export_to_excel():
    df = pd.DataFrame(trade_history)
    df.to_excel('trade_history.xlsx', index=False)

# Функция для обновления и отображения открытых позиций
def update_open_positions_display():
    open_orders_text.delete(1.0, tk.END)
    for position in open_positions:
        current_profit = calculate_current_profit(position)
        open_orders_text.insert(tk.END, f"{position['symbol']} {position['amount']} @ {position['buy_price']} текущая прибыль: {current_profit:.2f}\n")

# Функция для расчета текущей прибыли
def calculate_current_profit(position):
    current_price = get_current_price(position['symbol'])
    if current_price is not None:
        profit = (current_price - position['buy_price']) * position['amount']
        return profit
    return 0.0

# Функция для начала/продолжения торговли
def execute_trade():
    global session_start_time, trading_active, paused
    if paused:
        paused = False
        button_trade.config(text='Торговля активна')
        return

    try:
        # Инициализация торговой сессии
        if not trading_active:
            session_start_time = datetime.now()
            button_trade.config(text='Торговля активна')
            trading_active = True

        symbol = selected_symbol.get()
        cost = float(entry_amount.get())
        dip_threshold = float(entry_dip.get()) / 100
        rise_threshold = float(entry_rise.get()) / 100

        current_price = get_current_price(symbol)
        if current_price is None:
            logging.error('Ошибка: недоступна текущая цена.')
            return

        # Проверка необходимости на первой покупке
        if not hasattr(execute_trade, "baseline_price") or not open_positions:
            new_buy_price = buy(symbol, cost)
            if new_buy_price:
                execute_trade.baseline_price = new_buy_price

        # Если первая покупка уже произведена, следим за торговой логикой
        else:
            # Покупка при просадке
            if current_price <= execute_trade.baseline_price * (1 - dip_threshold):
                new_buy_price = buy(symbol, cost)
                if new_buy_price:
                    execute_trade.baseline_price = new_buy_price

            # Продажа при росте
            if current_price >= execute_trade.baseline_price * (1 + rise_threshold):
                for pos in open_positions[:]:  # Срез для безопасности изменения во время итерации
                    sell(pos['symbol'], pos['amount'])
                    open_positions.remove(pos)

        label_positions.config(text=f"Открытых сделок: {len(open_positions)}")
        label_profit.config(text=f"Прибыль сессии: {session_profit:.2f}")

    except ValueError:
        logging.error('Ошибка: некорректный ввод.')
        messagebox.showerror('Ошибка', 'Некорректный ввод. Убедитесь, что все значения заданы корректно.')

# Функция для продолжения прошлой сессии
def continue_previous_session():
    load_trade_state()
    execute_trade()

# Функция для продажи
def sell(symbol, amount):
    try:
        current_price = get_current_price(symbol)
        if current_price is None or current_price <= 0:
            messagebox.showerror('Ошибка', 'Не удалось получить корректную текущую цену.')
            return None

        market = exchange.market(symbol)
        min_volume = market['limits']['amount']['min']

        if amount < min_volume:
            messagebox.showerror('Ошибка', f"Минимальный объем для торговли: {min_volume}")
            return None

        order = exchange.create_market_sell_order(symbol, amount)
        logging.info('Продажа: %s', order)
        if open_positions:
            last_buy_order = open_positions.pop()
            profit = (order['price'] - last_buy_order['price']) * amount
            save_trade_state('sell', order, profit)
            logging.info('Торговая история: %s', {'type': 'sell', 'order': order, 'profit': profit})
        return order['price']
    except Exception as e:
        logging.error('Ошибка продажи: %s', e)
        messagebox.showerror('Ошибка', f"Не удалось продать: {e}")
        return None

# Создание интерфейса
root = tk.Tk()
root.title('MEXC Trading Bot')

# Инициализация состояния
load_trade_state()

logging.info(f'Местное время компьютера: {datetime.now()}')
             
# Элементы интерфейса
label_search = tk.Label(root, text='Поиск символа:')
label_search.pack()

entry_search = tk.Entry(root)
entry_search.pack()
entry_search.bind('<KeyRelease>', lambda event: update_symbol_menu(entry_search.get()))

label_symbol = tk.Label(root, text='Символ:')
label_symbol.pack()

selected_symbol = StringVar(root)
symbol_menu = OptionMenu(root, selected_symbol, '')
symbol_menu.pack()

label_unit = tk.Label(root, text='Торговая единица:')
label_unit.pack()
selected_unit = StringVar(root)
unit_menu = OptionMenu(root, selected_unit, '')
unit_menu.pack()

# Теперь можно вызывать update_symbol_menu, гарантируя, что unit_menu уже определено
update_symbol_menu('')

label_balance = tk.Label(root, text="Баланс")
label_balance.pack()

label_min_volume = tk.Label(root, text="Мин. объем:")
label_min_volume.pack()

label_amount = tk.Label(root, text='Сумма (в фиатной или криптовалюте):')
label_amount.pack()
entry_amount = tk.Entry(root)
entry_amount.pack()
entry_amount.insert(tk.END, '10')

label_dip = tk.Label(root, text='Покупка при просадке (%):')
label_dip.pack()
entry_dip = tk.Entry(root)
entry_dip.pack()
entry_dip.insert(tk.END, '0.5')

label_rise = tk.Label(root, text='Продажа при росте (%):')
label_rise.pack()
entry_rise = tk.Entry(root)
entry_rise.pack()
entry_rise.insert(tk.END, '1')

button_trade = tk.Button(root, text='Начать торговлю', command=execute_trade)
button_trade.pack()

button_continue = tk.Button(root, text='Продолжить прошлую сессию', command=continue_previous_session)
button_continue.pack()

button_pause = tk.Button(root, text='Пауза', command=pause_trade)
button_pause.pack()

label_current_price = tk.Label(root, text='Текущая цена:')
label_current_price.pack()

label_positions = tk.Label(root, text='Открытых сделок: 0')
label_positions.pack()

label_profit = tk.Label(root, text='Прибыль сессии: 0.00')
label_profit.pack()

label_session_time = tk.Label(root, text='Время сессии: 00:00:00')
label_session_time.pack()

label_trade_history = tk.Label(root, text='История ордеров:')
label_trade_history.pack()
text_history = tk.Text(root, height=10, width=50)
text_history.pack()

label_open_orders = tk.Label(root, text='Текущие ордера:')
label_open_orders.pack()
open_orders_text = tk.Text(root, height=10, width=50)
open_orders_text.pack()

connection_status = tk.Label(root, text='Статус подключения', width=20, bg='red')
connection_status.pack(anchor='se')

# Синхронизация времени
synchronize_time()

# Обновление UI
update_ui()

root.mainloop()           внеси изменения в этот код с сохраненем всех функций и логики бота используя эту инструкцию # python
from mexc_sdk import Spot
spot = Spot(api_key='apiKey', api_secret='apiSecret')

client.ping()
Check Server Time
client.time()
Exchange Information
client.exchangeInfo(options: any)
options:{symbol, symbols}
/**
 * choose one parameter
 * 
 * symbol : 
 *      example "BNBBTC";
 * 
 * symbols :
 *      array of symbol
 *      example ["BTCUSDT","BNBBTC"];
 * 
 */
Recent Trades List
client.trades(symbol: string, options: any = { limit: 500 })
options:{limit}
/**
 * 
 * limit : 
 *      Number of returned data
 *      Default 500; 
 *      max 1000;
 * 
 */
Order Book
client.depth(symbol: string, options: any = { limit: 100 })
options:{limit}
/**
 * limit : 
 *      Number of returned data
 *      Default 100;
 *      max 5000;
 *      Valid:[5, 10, 20, 50, 100, 500, 1000, 5000]
 * 
 */
Old Trade Lookup
client.historicalTrades(symbol: string, options: any = { limit: 500 })
options:{limit, fromId}
/**
 * 
 * limit : 
 *      Number of returned data
 *      Default 500;
 *      max 1000;
 * 
 * fromId:
 *      Trade id to fetch from. Default gets most recent trades
 * 
 */
Aggregate Trades List
client.aggTrades(symbol: string, options: any = { limit: 500 })
options:{fromId, startTime, endTime, limit}
/**
 * 
 * fromId : 
 *      id to get aggregate trades from INCLUSIVE
 * 
 * startTime:
 *      start at
 * 
 * endTime:
 *      end at
 * 
 * limit : 
 *      Number of returned data
 *      Default 500;
 *      max 1000;
 * 
 */
kline Data
client.klines(symbol: string, interval: string, options: any = { limit: 500 })
options:{ startTime, endTime, limit}
/**
 * 
 * interval : 
 *      m :minute; 
 *      h :Hour; 
 *      d :day; 
 *      w :week; 
 *      M :month
 *      example : "1m"
 * 
 * startTime : 
 *      start at
 * 
 * endTime :
 *      end at
 * 
 * limit : 
 *      Number of returned data
 *      Default 500;
 *      max 1000;
 * 
 */
Current Average Price
client.avgPrice(symbol: string)
24hr Ticker Price Change Statistics
client.ticker24hr(symbol?: string)
Symbol Price Ticker
client.tickerPrice(symbol?: string)
Symbol Order Book Ticker
client.bookTicker(symbol?: string)
Trade
Test New Order
client.newOrderTest(symbol: string, side: string, orderType: string, options: any = {})
options:{ timeInForce, quantity, quoteOrderQty, price, newClientOrderId, stopPrice, icebergQty, newOrderRespType, recvWindow}
/**
 * 
 * side:
 *      Order side 
 *      ENUM:
 *        BUY
 *        SELL
 * 
 * orderType:
 *      Order type
 *      ENUM:
 *        LIMIT
 *        MARKET
 *        STOP_LOSS
 *        STOP_LOSS_LIMIT
 *        TAKE_PROFIT
 *        TAKE_PROFIT_LIMIT
 *        LIMIT_MAKER
 * 
 * timeInForce : 
 *      How long an order will be active before expiration.
 *      GTC: Active unless the order is canceled
 *      IOC: Order will try to fill the order as much as it can before the order expires
 *      FOK: Active unless the full order cannot be filled upon execution.
 * 
 * quantity :
 *      target quantity
 * 
 * quoteOrderQty :
 *      Specify the total spent or received
 * 
 * price :
 *      target price
 * 
 * newClientOrderId :
 *      A unique id among open orders. Automatically generated if not sent
 * 
 * stopPrice :
 *      sed with STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, and TAKE_PROFIT_LIMIT orders
 * 
 * icebergQty :
 *      Used with LIMIT, STOP_LOSS_LIMIT, and TAKE_PROFIT_LIMIT to create an iceberg order
 * 
 * newOrderRespType : 
 *      Set the response JSON. ACK, RESULT, or FULL;
 *      MARKET and LIMIT order types default to FULL, all other orders default to ACK
 * 
 * recvWindow : 
 *      Delay accept time
 *      The value cannot be greater than 60000
 *      defaults: 5000
 * 
 */
New Order
client.newOrder(symbol: string, side: string, orderType: string, options: any = {})
options:{ timeInForce, quantity, quoteOrderQty, price, newClientOrderId, stopPrice, icebergQty, newOrderRespType, recvWindow}
/**
 * 
 * side:
 *      Order side 
 *      ENUM:
 *        BUY
 *        SELL
 * 
 * orderType:
 *      Order type
 *      ENUM:
 *        LIMIT
 *        MARKET
 *        STOP_LOSS
 *        STOP_LOSS_LIMIT
 *        TAKE_PROFIT
 *        TAKE_PROFIT_LIMIT
 *        LIMIT_MAKER
 * 
 * timeInForce : 
 *      How long an order will be active before expiration.
 *      GTC: Active unless the order is canceled
 *      IOC: Order will try to fill the order as much as it can before the order expires
 *      FOK: Active unless the full order cannot be filled upon execution.
 * 
 * quantity :
 *      target quantity
 * 
 * quoteOrderQty :
 *      Specify the total spent or received
 * 
 * price :
 *      target price
 * 
 * newClientOrderId :
 *      A unique id among open orders. Automatically generated if not sent
 * 
 * stopPrice :
 *      sed with STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, and TAKE_PROFIT_LIMIT orders
 * 
 * icebergQty :
 *      Used with LIMIT, STOP_LOSS_LIMIT, and TAKE_PROFIT_LIMIT to create an iceberg order
 * 
 * newOrderRespType : 
 *      Set the response JSON. ACK, RESULT, or FULL;
 *      MARKET and LIMIT order types default to FULL, all other orders default to ACK
 * 
 * recvWindow : 
 *      Delay accept time
 *      The value cannot be greater than 60000
 *      defaults: 5000
 * 
 */
cancel-order
client.cancelOrder(symbol: string, options:any = {})
options:{ orderId, origClientOrderId, newClientOrderId}
/**
 * 
 * Either orderId or origClientOrderId must be sent
 * 
 * orderId:
 *      target orderId
 * 
 * origClientOrderId:
 *      target origClientOrderId
 * 
 * newClientOrderId:
 *      Used to uniquely identify this cancel. Automatically generated by default.
 * 
 */
Cancel all Open Orders on a Symbol
client.cancelOpenOrders(symbol: string)
Query Order
client.queryOrder(symbol: string, options:any = {})
options:{ orderId, origClientOrderId}
/**
 * 
 * Either orderId or origClientOrderId must be sent
 * 
 * orderId:
 *      target orderId
 * 
 * origClientOrderId:
 *      target origClientOrderId
 * 
 */
Current Open Orders
client.openOrders(symbol: string)
All Orders
client.allOrders(symbol: string, options: any = { limit: 500 })
options:{ orderId, startTime, endTime, limit}

/**
 * 
 * orderId:
 *      target orderId
 * 
 * startTime:
 *      start at
 * 
 * endTime:
 *      end at
 * 
 * limit : 
 *      Number of returned data
 *      Default 500;
 *      max 1000;
 * 
 */
Account Information
client.accountInfo()
Account Trade List
client.accountTradeList(symbol: string, options:any = { limit: 500 })
options:{ orderId, startTime, endTime, fromId, limit}

/**
 * 
 * orderId:
 *      target orderId
 * 
 * startTime:
 *      start at
 * 
 * endTime:
 *      end at
 * 
 * fromId:
 *      TradeId to fetch from. Default gets most recent trades
 * 
 * limit : 
 *      Number of returned data
 *      Default 500;
 *      max 1000;
 * 
 */