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

# Логирование
logging.basicConfig(filename=‘trading_bot.log’, level=logging.INFO)

# Конфигурация 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,
    })
    exchange.load_markets()  # Загрузка символов биржи
    symbols = list(exchange.symbols)  # Сохранение списка символов
    if not symbols:
        raise ValueError(“Не удалось загрузить символы биржи.”)
    logging.info(“Биржа MEXC успешно инициализирована.”)
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"Время сессии: {str(elapsed_time).split(‘.’)[0]}”)

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

# Функция для проверки символа
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
        amount_units = round(amount_units, market[‘precision’][‘amount’])

        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)
            log_trade(‘sell’, order, 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()

# Элементы интерфейса
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()


### Описание изменений:
1. Функция continue_previous_session: Теперь загружает состояние прошлой сессии и запускает торговлю, если это необходимо.
2. Кнопка “Продолжить прошлую сессию”: Добавлена в интерфейс и связана с функцией continue_previous_session. Это позволит вам продолжить торговлю с состоянием из предыдущей сессии.
3. Проверка на первую покупку: В коде добавлена логика для выполнения первой покупки, если открытых позиций нет, тем самым обеспечивая старт торговой сессии корректно.
ERROR:root:Ошибка покупки: ‘float’ object cannot be interpreted as an integer
Ошибка, которую вы видите, указывает на то, что где-то в коде происходит попытка использовать объект типа float в качестве целого числа. Это может случиться, например, если где-то используется метод, ожидающий целочисленный аргумент, а передан дробный.

Чаще всего это случается при таких операциях, как округление, использование range(), индексация и других местах, где ожидается целое число.

В данном контексте, вероятно, проблема возникает при делении или округлении числа. Давайте проверим и исправим эту часть:

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
        amount_units = round(amount_units, market[‘precision’][‘amount’])
        
        # Убедимся, что результат округления является целым числом, если это необходимо
        if isinstance(market[‘precision’][‘amount’], 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


### Объяснение изменений:
1. Округление и проверка типа: После вычисления amount_units, я добавил проверку, чтобы убедиться, что если market['precision']['amount'] указывает количество чисел после запятой и это значение является целым, amount_units будет округлено до целого числа с помощью round. В противном случае, будет преобразовано в float.

Обычно интерфейсы обменников требуют дробные числа в формате, который допускает определенное количество знаков после запятой, поэтому важно удостовериться, что amount_units соответствует этим требованиям.

Если ошибка сохраняется или произойдет в другом месте, убедитесь, что каждая операция, где ожидается целое число, не получает дробные значения.
Добавь изменения в исходный код