#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Символьная торговая система на основе статьи о интерпретируемом ML
Реализует подход символьной регрессии для прогнозирования цен
Поддерживает формат CSV: <DATE> <TIME> <OPEN> <HIGH> <LOW> <CLOSE> <TICKVOL> <VOL> <SPREAD>
"""

import sys
# import os
import numpy as np
import pandas as pd
import sympy as sp
from datetime import datetime, timedelta
import matplotlib.pyplot as plt

# Импорт Qt5 компонентов
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
                             QHBoxLayout, QPushButton, QLabel, QTextEdit,
                             QTabWidget, QGroupBox, QProgressBar, QMessageBox,
                             QFileDialog, QSplitter, QScrollArea, QLineEdit,
                             QFormLayout, QCheckBox)
from PyQt5.QtCore import QTimer, Qt
from PyQt5.QtGui import QFont  # QPalette, QColor

# Импорт машинного обучения
from sklearn.linear_model import Ridge, LogisticRegression
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.model_selection import train_test_split, cross_val_score
# from sklearn.metrics import mean_squared_error, accuracy_score


class SymbolicTradingSystem(QMainWindow):
    """
    Главное окно приложения символьной торговой системы
    Реализует интерфейс для создания и анализа интерпретируемых ML-моделей
    """

    def __init__(self):
        super().__init__()

        # Инициализация основных переменных системы
        self.symbolic_predictor = None  # Наш символьный предиктор
        self.data = None  # Загруженные данные
        self.price_equation = None  # Символьное уравнение для цены
        self.direction_equation = None  # Символьное уравнение для направления
        self.features_data = None  # Добавляем инициализацию features_data

        # Параметры загрузки данных
        self.start_date = None
        self.end_date = None
        self.max_bars = 10000  # Максимальное количество баров по умолчанию

        # Создание увеличенного шрифта для текстовых полей
        self.large_font = QFont()
        # Увеличиваем размер шрифта с 9-10 до 12
        self.large_font.setPointSize(12)

        self.init_ui()  # Инициализация интерфейса
        self.generate_sample_data()  # Генерация тестовых данных

    def init_ui(self):
        """Инициализация пользовательского интерфейса"""
        # Настройка главного окна
        self.setWindowTitle(
            "Символьная Торговая Система - Интерпретируемый ML")
        self.setGeometry(100, 100, 1200, 700)

        # Создание центрального виджета
        central_widget = QWidget()
        self.setCentralWidget(central_widget)

        # Основной layout
        main_layout = QVBoxLayout(central_widget)

        # Заголовок приложения
        title_label = QLabel("Символьная Торговая Система")
        title_font = QFont("Arial", 16, QFont.Bold)
        title_label.setFont(title_font)
        title_label.setAlignment(Qt.AlignCenter)
        title_label.setStyleSheet(
            "background-color: #2E86AB; color: white; padding: 10px;")
        main_layout.addWidget(title_label)

        # Создание вкладок для разных функций
        tab_widget = QTabWidget()
        main_layout.addWidget(tab_widget)

        # Вкладка 1: Загрузка данных и расчет индикаторов
        self.setup_data_tab(tab_widget)

        # Вкладка 2: Обучение модели и символьный анализ
        self.setup_modeling_tab(tab_widget)

        # Вкладка 3: Визуализация результатов
        self.setup_visualization_tab(tab_widget)

        # Вкладка 4: Математический анализ
        self.setup_math_analysis_tab(tab_widget)

        # Статус бар внизу окна
        self.status_bar = self.statusBar()
        self.status_bar.showMessage(
            "Система готова к работе. Загрузите данные или используйте демонстрационные.")

        # Увеличиваем шрифт статусбара
        status_font = QFont()
        status_font.setPointSize(10)
        self.status_bar.setFont(status_font)

    def setup_data_tab(self, tab_widget):
        """Создание вкладки для работы с данными"""
        data_tab = QWidget()
        layout = QVBoxLayout(data_tab)

        # Группа загрузки данных
        data_group = QGroupBox("Загрузка и подготовка данных")
        data_layout = QVBoxLayout(data_group)

        # Кнопки управления данными
        btn_layout = QHBoxLayout()

        load_btn = QPushButton("Загрузить CSV данные")
        load_btn.clicked.connect(self.load_data)
        btn_layout.addWidget(load_btn)

        generate_btn = QPushButton("Сгенерировать демо-данные")
        generate_btn.clicked.connect(self.generate_sample_data)
        btn_layout.addWidget(generate_btn)

        calculate_btn = QPushButton("Рассчитать индикаторы")
        calculate_btn.clicked.connect(self.calculate_indicators)
        btn_layout.addWidget(calculate_btn)

        data_layout.addLayout(btn_layout)

        # Группа параметров загрузки
        params_group = QGroupBox("Параметры загрузки данных")
        params_layout = QFormLayout(params_group)

        # Поля для ввода дат и количества баров
        self.start_date_edit = QLineEdit()
        self.start_date_edit.setPlaceholderText(
            "ГГГГ.ММ.ДД (например: 2020.01.01)")
        params_layout.addRow("Дата начала:", self.start_date_edit)

        self.end_date_edit = QLineEdit()
        self.end_date_edit.setPlaceholderText(
            "ГГГГ.ММ.ДД (например: 2020.12.31)")
        params_layout.addRow("Дата окончания:", self.end_date_edit)

        self.max_bars_edit = QLineEdit()
        self.max_bars_edit.setPlaceholderText("10000")
        self.max_bars_edit.setText("10000")
        params_layout.addRow("Макс. количество баров:", self.max_bars_edit)

        # Чекбоксы для предобработки
        self.remove_vol_checkbox = QCheckBox(
            "Удалить колонки VOL (часто пустые)")
        self.remove_vol_checkbox.setChecked(True)
        params_layout.addRow(self.remove_vol_checkbox)

        self.keep_only_ohlc_checkbox = QCheckBox(
            "Оставить только OHLC данные для индикаторов")
        self.keep_only_ohlc_checkbox.setChecked(False)
        params_layout.addRow(self.keep_only_ohlc_checkbox)

        data_layout.addWidget(params_group)

        # Информация о поддерживаемом формате
        format_label = QLabel(
            "Поддерживаемый формат: <DATE> <TIME> <OPEN> <HIGH> <LOW> <CLOSE> <TICKVOL> <VOL> <SPREAD>")
        format_label.setStyleSheet(
            "color: #666; font-style: italic; padding: 5px;")
        data_layout.addWidget(format_label)

        # Текстовое поле для информации о данных
        self.data_info_text = QTextEdit()
        self.data_info_text.setMaximumHeight(150)
        # Устанавливаем увеличенный шрифт
        self.data_info_text.setFont(self.large_font)
        data_layout.addWidget(self.data_info_text)

        layout.addWidget(data_group)

        # Группа предварительного просмотра данных
        preview_group = QGroupBox("Предварительный просмотр данных")
        preview_layout = QVBoxLayout(preview_group)

        self.data_preview_text = QTextEdit()
        # Устанавливаем увеличенный шрифт
        self.data_preview_text.setFont(self.large_font)
        preview_layout.addWidget(self.data_preview_text)

        layout.addWidget(preview_group)

        tab_widget.addTab(data_tab, "📊 Данные")

    def setup_modeling_tab(self, tab_widget):
        """Создание вкладки для обучения модели"""
        modeling_tab = QWidget()
        layout = QVBoxLayout(modeling_tab)

        # Группа обучения модели
        model_group = QGroupBox("Обучение символьной модели")
        model_layout = QVBoxLayout(model_group)

        # Кнопки обучения
        train_btn_layout = QHBoxLayout()

        train_price_btn = QPushButton("Обучить модель цены")
        train_price_btn.clicked.connect(self.train_price_model)
        train_btn_layout.addWidget(train_price_btn)

        train_direction_btn = QPushButton("Обучить модель направления")
        train_direction_btn.clicked.connect(self.train_direction_model)
        train_btn_layout.addWidget(train_direction_btn)

        symbolic_btn = QPushButton("Создать символьные уравнения")
        symbolic_btn.clicked.connect(self.create_symbolic_equations)
        train_btn_layout.addWidget(symbolic_btn)

        model_layout.addLayout(train_btn_layout)

        # Прогресс бар
        self.progress_bar = QProgressBar()
        model_layout.addWidget(self.progress_bar)

        # Вывод информации о модели
        self.model_info_text = QTextEdit()
        # Устанавливаем увеличенный шрифт
        self.model_info_text.setFont(self.large_font)
        model_layout.addWidget(self.model_info_text)

        layout.addWidget(model_group)

        # Группа символьных уравнений
        equation_group = QGroupBox("Символьные уравнения")
        equation_layout = QVBoxLayout(equation_group)

        self.equation_text = QTextEdit()
        # Устанавливаем увеличенный шрифт
        self.equation_text.setFont(self.large_font)

        # Дополнительно увеличиваем шрифт для уравнений, так как они важны для чтения
        equation_font = QFont()
        equation_font.setPointSize(14)  # Еще больше для уравнений
        self.equation_text.setFont(equation_font)

        equation_layout.addWidget(self.equation_text)

        layout.addWidget(equation_group)

        tab_widget.addTab(modeling_tab, "🤖 Моделирование")

    def setup_visualization_tab(self, tab_widget):
        """Создание вкладки для визуализации"""
        visualization_tab = QWidget()
        layout = QVBoxLayout(visualization_tab)

        # Группа графиков
        chart_group = QGroupBox("Визуализация прогнозов")
        chart_layout = QVBoxLayout(chart_group)

        # Кнопки визуализации
        viz_btn_layout = QHBoxLayout()

        plot_price_btn = QPushButton("График цен и прогнозов")
        plot_price_btn.clicked.connect(self.plot_price_predictions)
        viz_btn_layout.addWidget(plot_price_btn)

        plot_direction_btn = QPushButton("График направлений")
        plot_direction_btn.clicked.connect(self.plot_direction_predictions)
        viz_btn_layout.addWidget(plot_direction_btn)

        chart_layout.addLayout(viz_btn_layout)

        # Область для отображения графиков (будет открываться в отдельном окне)
        self.viz_info_text = QTextEdit()
        self.viz_info_text.setMaximumHeight(200)
        # Устанавливаем увеличенный шрифт
        self.viz_info_text.setFont(self.large_font)
        chart_layout.addWidget(self.viz_info_text)

        layout.addWidget(chart_group)

        tab_widget.addTab(visualization_tab, "📈 Визуализация")

    def setup_math_analysis_tab(self, tab_widget):
        """Создание вкладки для математического анализа"""
        math_tab = QWidget()
        layout = QVBoxLayout(math_tab)

        # Группа математического анализа
        analysis_group = QGroupBox("Математический анализ уравнений")
        analysis_layout = QVBoxLayout(analysis_group)

        # Кнопки анализа
        analysis_btn_layout = QHBoxLayout()

        analyze_btn = QPushButton("Проанализировать уравнения")
        analyze_btn.clicked.connect(self.analyze_equations)
        analysis_btn_layout.addWidget(analyze_btn)

        sensitivity_btn = QPushButton("Анализ чувствительности")
        sensitivity_btn.clicked.connect(self.sensitivity_analysis)
        analysis_btn_layout.addWidget(sensitivity_btn)

        analysis_layout.addLayout(analysis_btn_layout)

        # Вывод математического анализа
        self.math_analysis_text = QTextEdit()
        # Устанавливаем увеличенный шрифт
        self.math_analysis_text.setFont(self.large_font)

        # Для математического анализа тоже увеличиваем шрифт дополнительно
        math_font = QFont()
        math_font.setPointSize(13)
        self.math_analysis_text.setFont(math_font)

        analysis_layout.addWidget(self.math_analysis_text)

        layout.addWidget(analysis_group)

        tab_widget.addTab(math_tab, "Σ Математика")

    def parse_date_filter_parameters(self):
        """Парсинг параметров фильтрации из полей ввода"""
        try:
            # Парсинг максимального количества баров
            if self.max_bars_edit.text().strip():
                self.max_bars = int(self.max_bars_edit.text())
            else:
                self.max_bars = 10000

            # Парсинг даты начала
            self.start_date = None
            if self.start_date_edit.text().strip():
                self.start_date = datetime.strptime(
                    self.start_date_edit.text().strip(), '%Y.%m.%d')

            # Парсинг даты окончания
            self.end_date = None
            if self.end_date_edit.text().strip():
                self.end_date = datetime.strptime(
                    self.end_date_edit.text().strip(), '%Y.%m.%d')

            return True

        except ValueError as e:
            QMessageBox.warning(
                self, "Ошибка", f"Ошибка в параметрах: {str(e)}")
            return False

    def preprocess_loaded_data(self, data):
        """Предобработка загруженных данных"""
        try:
            # Создаем копию данных
            processed_data = data.copy()

            # Удаляем колонку VOL если отмечено и она существует
            if self.remove_vol_checkbox.isChecked() and 'vol' in processed_data.columns:
                processed_data = processed_data.drop(columns=['vol'])
                print("Колонка VOL удалена")

            # Оставляем только OHLC данные если отмечено
            if self.keep_only_ohlc_checkbox.isChecked():
                keep_columns = ['timestamp', 'date',
                                'time', 'open', 'high', 'low', 'close']
                if 'tickvol' in processed_data.columns:
                    keep_columns.append('tickvol')
                if 'spread' in processed_data.columns:
                    keep_columns.append('spread')

                # Оставляем только нужные колонки
                processed_data = processed_data[[
                    col for col in keep_columns if col in processed_data.columns]]
                print("Оставлены только OHLC данные")

            return processed_data

        except Exception as e:
            print(f"Ошибка предобработки: {e}")
            return data

    def filter_data_by_parameters(self, data):
        """Фильтрация данных по заданным параметрам"""
        try:
            filtered_data = data.copy()

            # Фильтрация по дате начала
            if self.start_date is not None:
                filtered_data = filtered_data[filtered_data['timestamp'] >= pd.Timestamp(
                    self.start_date)]
                print(f"Отфильтровано по дате начала: {self.start_date}")

            # Фильтрация по дате окончания
            if self.end_date is not None:
                # Добавляем 1 день чтобы включить конечную дату
                end_date_with_time = pd.Timestamp(
                    self.end_date) + pd.Timedelta(days=1)
                filtered_data = filtered_data[filtered_data['timestamp']
                                              < end_date_with_time]
                print(f"Отфильтровано по дате окончания: {self.end_date}")

            # Ограничение по количеству баров
            if len(filtered_data) > self.max_bars:
                filtered_data = filtered_data.tail(self.max_bars)
                print(f"Ограничено до {self.max_bars} баров")

            print(f"После фильтрации осталось {len(filtered_data)} записей")
            return filtered_data

        except Exception as e:
            print(f"Ошибка фильтрации: {e}")
            return data

    def load_data(self):
        """Загрузка данных из CSV файла в указанном формате"""
        try:
            # Парсим параметры фильтрации
            if not self.parse_date_filter_parameters():
                return

            # Диалог выбора файла
            file_path, _ = QFileDialog.getOpenFileName(
                self, "Открыть CSV файл", "", "CSV Files (*.csv);;All Files (*)"
            )

            if file_path:
                # Чтение CSV файла с указанным форматом
                # Формат: <DATE> <TIME> <OPEN> <HIGH> <LOW> <CLOSE> <TICKVOL> <VOL> <SPREAD>
                self.data = pd.read_csv(file_path, sep='\t', header=0)

                # Проверка необходимых колонок
                required_columns = ['<DATE>', '<TIME>', '<OPEN>',
                                    '<HIGH>', '<LOW>', '<CLOSE>', '<TICKVOL>']
                missing_columns = [
                    col for col in required_columns if col not in self.data.columns]

                if missing_columns:
                    # Если колонки не найдены, пробуем без угловых скобок
                    required_columns_no_brackets = [
                        'DATE', 'TIME', 'OPEN', 'HIGH', 'LOW', 'CLOSE', 'TICKVOL']
                    missing_columns_no_brackets = [
                        col for col in required_columns_no_brackets if col not in self.data.columns]

                    if missing_columns_no_brackets:
                        msg = QMessageBox()
                        msg.setIcon(QMessageBox.Warning)
                        msg.setWindowTitle("Ошибка")
                        msg.setText(
                            f"Отсутствуют колонки: {missing_columns}\nПопробовано также: { missing_columns_no_brackets}")

                        msg_font = QFont()
                        msg_font.setPointSize(12)
                        msg.setFont(msg_font)

                        msg.exec_()
                        return
                    else:
                        # Используем колонки без угловых скобок
                        required_columns = required_columns_no_brackets

                # Переименовываем колонки для удобства (убираем угловые скобки если есть)
                column_mapping = {}
                for col in self.data.columns:
                    clean_col = col.strip('<>')  # Убираем угловые скобки
                    # Приводим к нижнему регистру
                    column_mapping[col] = clean_col.lower()

                self.data = self.data.rename(columns=column_mapping)

                # Создаем колонку timestamp из date и time
                if 'date' in self.data.columns and 'time' in self.data.columns:
                    # Объединяем дату и время в одну строку
                    self.data['timestamp'] = self.data['date'] + \
                        ' ' + self.data['time']
                    # Преобразуем в datetime
                    self.data['timestamp'] = pd.to_datetime(
                        self.data['timestamp'], format='%Y.%m.%d %H:%M:%S')

                # Убедимся, что данные отсортированы по времени
                self.data = self.data.sort_values('timestamp')

                # Предобработка данных
                self.data = self.preprocess_loaded_data(self.data)

                # Фильтрация данных по параметрам
                self.data = self.filter_data_by_parameters(self.data)

                # Сбрасываем features_data при загрузке новых данных
                self.features_data = None

                self.update_data_info()
                self.status_bar.showMessage(
                    f"Данные загружены: {len(self.data)} записей")

        except Exception as e:
            # QMessageBox с увеличенным шрифтом для ошибок
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Critical)
            msg.setWindowTitle("Ошибка")
            msg.setText(f"Ошибка загрузки: {str(e)}")

            msg_font = QFont()
            msg_font.setPointSize(12)
            msg.setFont(msg_font)

            msg.exec_()

    def generate_sample_data(self):
        """Генерация демонстрационных данных в формате, похожем на реальный CSV"""
        try:
            # Парсим параметры фильтрации
            if not self.parse_date_filter_parameters():
                return

            # Создание временного ряда с рыночными характеристиками
            np.random.seed(42)  # Для воспроизводимости

            # Параметры генерации
            n_points = 5000
            start_date = datetime(2020, 1, 1)

            # Генерация временных меток (каждый час)
            timestamps = [start_date +
                          timedelta(hours=i) for i in range(n_points)]

            # Базовый тренд + шум + сезонность
            # EUR/USD like prices
            trend = np.linspace(1.1000, 1.2000, n_points)
            noise = np.random.normal(0, 0.001, n_points)
            seasonal = 0.005 * np.sin(2 * np.pi * np.arange(n_points) / 24)

            # Генерация OHLC данных в стиле реальных рыночных данных
            base_price = trend + seasonal + noise.cumsum()

            # Создание данных в формате, похожем на реальный CSV
            self.data = pd.DataFrame({
                'date': [ts.strftime('%Y.%m.%d') for ts in timestamps],
                'time': [ts.strftime('%H:%M:%S') for ts in timestamps],
                'open': base_price,
                'high': base_price + np.abs(np.random.normal(0, 0.0005, n_points)),
                'low': base_price - np.abs(np.random.normal(0, 0.0005, n_points)),
                'close': base_price + np.random.normal(0, 0.0001, n_points),
                'tickvol': np.random.randint(100, 10000, n_points),
                # Volume часто 0 в исторических данных
                'vol': np.zeros(n_points),
                'spread': np.random.randint(10, 20, n_points)
            })

            # Добавление timestamp для удобства
            self.data['timestamp'] = timestamps

            # Предобработка данных
            self.data = self.preprocess_loaded_data(self.data)

            # Фильтрация данных по параметрам
            self.data = self.filter_data_by_parameters(self.data)

            # Сбрасываем features_data при загрузке новых данных
            self.features_data = None

            self.update_data_info()
            self.status_bar.showMessage(
                f"Демо-данные сгенерированы: {len(self.data)} записей")

        except Exception as e:
            # QMessageBox с увеличенным шрифтом
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Critical)
            msg.setWindowTitle("Ошибка")
            msg.setText(f"Ошибка генерации: {str(e)}")

            msg_font = QFont()
            msg_font.setPointSize(12)
            msg.setFont(msg_font)

            msg.exec_()

    def update_data_info(self):
        """Обновление информации о данных в интерфейсе"""
        if self.data is not None:
            # Основная информация
            info_text = f"""
            Информация о данных:
            - Количество записей: {len(self.data):,}
            - Период: {self.data['timestamp'].min()} до {self.data['timestamp'].max()}
            - Колонки: {', '.join(self.data.columns)}
            """

            # Добавляем информацию о ценах если есть колонка close
            if 'close' in self.data.columns:
                info_text += f"""
            - Цена закрытия: от {self.data['close'].min():.5f} до {self.data['close'].max():.5f}
            - Средняя цена: {self.data['close'].mean():.5f}
            - Волатильность (std): {self.data['close'].std():.5f}
                """

            # Добавляем информацию о параметрах фильтрации
            info_text += f"""
            Параметры загрузки:
            - Макс. баров: {self.max_bars}
            - Дата начала: {self.start_date if self.start_date else 'Не задана'}
            - Дата окончания: {self.end_date if self.end_date else 'Не задана'}
            """

            self.data_info_text.setText(info_text)

            # Превью первых 5 строк (только основные колонки)
            preview_columns = ['date', 'time', 'open', 'high', 'low', 'close']
            available_columns = [
                col for col in preview_columns if col in self.data.columns]

            if available_columns:
                preview = self.data[available_columns].head(
                ).to_string(index=False)
                self.data_preview_text.setText(preview)
            else:
                self.data_preview_text.setText(
                    "Нет доступных колонок для предпросмотра")

    def calculate_indicators(self):
        """Расчет технических индикаторов"""
        try:
            if self.data is None:
                # QMessageBox с увеличенным шрифтом
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setWindowTitle("Ошибка")
                msg.setText("Сначала загрузите данные")

                msg_font = QFont()
                msg_font.setPointSize(12)
                msg.setFont(msg_font)

                msg.exec_()
                return

            self.status_bar.showMessage("Расчет технических индикаторов...")
            self.progress_bar.setValue(20)

            # Создание экземпляра символьного предиктора
            self.symbolic_predictor = SymbolicPricePredictor()

            # Расчет индикаторов
            self.data_with_indicators = self.symbolic_predictor.calculate_technical_indicators(
                self.data)
            self.progress_bar.setValue(60)

            # Подготовка признаков
            self.features_data = self.symbolic_predictor.prepare_features_and_targets(
                self.data_with_indicators)
            self.progress_bar.setValue(90)

            if self.features_data is None:
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setWindowTitle("Ошибка")
                msg.setText("Недостаточно данных после очистки")

                msg_font = QFont()
                msg_font.setPointSize(12)
                msg.setFont(msg_font)

                msg.exec_()
                return

            # Обновление информации
            info_text = f"""
            Индикаторы рассчитаны успешно:
            - Исходных признаков: {len(self.features_data['feature_names'])}
            - После полиномиального расширения: {self.features_data['X_poly'].shape[1]}
            - Доступных образцов: {len(self.features_data['data_clean'])}
            - Целевая переменная: цена через {self.symbolic_predictor.prediction_horizon} периодов
            """

            self.model_info_text.setText(info_text)
            self.progress_bar.setValue(100)
            self.status_bar.showMessage("Индикаторы рассчитаны успешно")

        except Exception as e:
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Critical)
            msg.setWindowTitle("Ошибка")
            msg.setText(f"Ошибка расчета индикаторов: {str(e)}")

            msg_font = QFont()
            msg_font.setPointSize(12)
            msg.setFont(msg_font)

            msg.exec_()

    def train_price_model(self):
        """Обучение модели для прогнозирования цены"""
        try:
            # Проверяем, что features_data существует
            if self.features_data is None:
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setWindowTitle("Ошибка")
                msg.setText(
                    "Сначала рассчитайте индикаторы (нажмите 'Рассчитать индикаторы')")

                msg_font = QFont()
                msg_font.setPointSize(12)
                msg.setFont(msg_font)

                msg.exec_()
                return

            self.status_bar.showMessage("Обучение модели цены...")
            self.progress_bar.setValue(30)

            # Здесь будет реальное обучение модели
            # Пока симулируем процесс для демонстрации
            QTimer.singleShot(1000, lambda: self.progress_bar.setValue(70))
            QTimer.singleShot(2000, lambda: self.progress_bar.setValue(100))

            QTimer.singleShot(
                2500, lambda: self.status_bar.showMessage("Модель цены обучена"))

        except Exception as e:
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Critical)
            msg.setWindowTitle("Ошибка")
            msg.setText(f"Ошибка обучения: {str(e)}")

            msg_font = QFont()
            msg_font.setPointSize(12)
            msg.setFont(msg_font)

            msg.exec_()

    def train_direction_model(self):
        """Обучение модели для прогнозирования направления"""
        try:
            # Проверяем, что features_data существует
            if self.features_data is None:
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setWindowTitle("Ошибка")
                msg.setText(
                    "Сначала рассчитайте индикаторы (нажмите 'Рассчитать индикаторы')")

                msg_font = QFont()
                msg_font.setPointSize(12)
                msg.setFont(msg_font)

                msg.exec_()
                return

            self.status_bar.showMessage("Обучение модели направления...")
            self.progress_bar.setValue(30)

            # Симуляция обучения
            QTimer.singleShot(1000, lambda: self.progress_bar.setValue(70))
            QTimer.singleShot(2000, lambda: self.progress_bar.setValue(100))

            QTimer.singleShot(2500, lambda: self.status_bar.showMessage(
                "Модель направления обучена"))

        except Exception as e:
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Critical)
            msg.setWindowTitle("Ошибка")
            msg.setText(f"Ошибка обучения: {str(e)}")

            msg_font = QFont()
            msg_font.setPointSize(12)
            msg.setFont(msg_font)

            msg.exec_()

    def create_symbolic_equations(self):
        """Создание символьных уравнений из обученных моделей"""
        try:
            # Проверяем, что features_data существует
            if self.features_data is None:
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setWindowTitle("Ошибка")
                msg.setText(
                    "Сначала рассчитайте индикаторы (нажмите 'Рассчитать индикаторы')")

                msg_font = QFont()
                msg_font.setPointSize(12)
                msg.setFont(msg_font)

                msg.exec_()
                return

            if self.symbolic_predictor is None:
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setWindowTitle("Ошибка")
                msg.setText(
                    "Сначала обучите модели (нажмите 'Обучить модель цены' и 'Обучить модель направления')")

                msg_font = QFont()
                msg_font.setPointSize(12)
                msg.setFont(msg_font)

                msg.exec_()
                return

            self.status_bar.showMessage("Создание символьных уравнений...")
            self.progress_bar.setValue(40)

            # Создание уравнений
            self.price_equation, self.direction_equation = (
                self.symbolic_predictor.create_symbolic_equations(
                    self.features_data)
            )
            self.progress_bar.setValue(90)

            # Отображение уравнений
            equations_text = f"""
            === СИМВОЛЬНЫЕ УРАВНЕНИЯ ===

            1. ПРОГНОЗ ЦЕНЫ (Ridge Regression):
            P(t+{self.symbolic_predictor.prediction_horizon}) = {sp.pretty(self.price_equation, use_unicode=True)}

            2. ВЕРОЯТНОСТЬ РОСТА (Logistic Regression):
            P(UP) = {sp.pretty(self.direction_equation, use_unicode=True)}

            3. ИНТЕРПРЕТАЦИЯ ПЕРЕМЕННЫХ:
            """

            # Добавление описания переменных
            if hasattr(self.symbolic_predictor, 'feature_names'):
                for i, name in enumerate(self.symbolic_predictor.feature_names):
                    equations_text += f"\nx{i} = {name}"

            self.equation_text.setText(equations_text)
            self.progress_bar.setValue(100)
            self.status_bar.showMessage("Символьные уравнения созданы")

        except Exception as e:
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Critical)
            msg.setWindowTitle("Ошибка")
            msg.setText(f"Ошибка создания уравнений: {str(e)}")

            msg_font = QFont()
            msg_font.setPointSize(12)
            msg.setFont(msg_font)

            msg.exec_()

    def plot_price_predictions(self):
        """Визуализация прогнозов цен"""
        try:
            if self.symbolic_predictor is None or self.features_data is None:
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setWindowTitle("Ошибка")
                msg.setText(
                    "Сначала создайте модель и рассчитайте индикаторы")

                msg_font = QFont()
                msg_font.setPointSize(12)
                msg.setFont(msg_font)

                msg.exec_()
                return

            # Создание графика
            plt.figure(figsize=(12, 8))

            # Использование данных из features_data
            data_clean = self.features_data['data_clean']
            n_plot = min(100, len(data_clean))

            # Фактические цены
            actual_prices = data_clean['close'].tail(n_plot).values

            # Временные метки
            if 'timestamp' in data_clean.columns:
                timestamps = data_clean['timestamp'].tail(n_plot).values
            else:
                timestamps = range(n_plot)

            # Прогнозы (демонстрационные - в реальной системе здесь будет реальный прогноз)
            predicted_prices = actual_prices + \
                np.random.normal(0, 0.0005, n_plot)

            plt.plot(timestamps, actual_prices,
                     label='Фактическая цена', linewidth=2, color='blue')
            plt.plot(timestamps, predicted_prices,
                     label='Прогноз цены', linestyle='--', color='red')

            plt.title(
                f'Прогноз цены vs Фактическая цена ({n_plot} последних точек)', fontsize=14)
            plt.xlabel('Время', fontsize=12)
            plt.ylabel('Цена', fontsize=12)
            plt.legend(fontsize=12)
            plt.grid(True, alpha=0.3)

            if hasattr(timestamps, 'dtype') and np.issubdtype(timestamps.dtype, np.datetime64):
                plt.xticks(rotation=45)

            plt.tight_layout()
            plt.show()

            self.viz_info_text.setText(
                "График построен успешно. Проверьте открывшееся окно.")

        except Exception as e:
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Critical)
            msg.setWindowTitle("Ошибка")
            msg.setText(f"Ошибка визуализации: {str(e)}")

            msg_font = QFont()
            msg_font.setPointSize(12)
            msg.setFont(msg_font)

            msg.exec_()

    def plot_direction_predictions(self):
        """Визуализация прогнозов направления"""
        try:
            if self.symbolic_predictor is None or self.features_data is None:
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setWindowTitle("Ошибка")
                msg.setText(
                    "Сначала создайте модель и рассчитайте индикаторы")

                msg_font = QFont()
                msg_font.setPointSize(12)
                msg.setFont(msg_font)

                msg.exec_()
                return

            # Создание графика вероятностей
            plt.figure(figsize=(12, 6))

            # Демонстрационные данные
            n_points = 50
            probabilities = np.random.uniform(
                0.3, 0.7, n_points)  # Вероятности роста
            actual_directions = np.random.choice(
                [0, 1], n_points, p=[0.4, 0.6])  # Фактические направления

            plt.plot(range(n_points), probabilities,
                     label='Вероятность роста', marker='o', linewidth=2)
            plt.plot(range(n_points), actual_directions, label='Фактическое направление (0=падение, 1=рост)',
                     linestyle='--', alpha=0.7)

            plt.title('Прогноз направления торгов', fontsize=14)
            plt.xlabel('Временной индекс', fontsize=12)
            plt.ylabel('Вероятность/Направление', fontsize=12)
            plt.legend(fontsize=10)
            plt.grid(True, alpha=0.3)
            plt.ylim(-0.1, 1.1)

            plt.tight_layout()
            plt.show()

            self.viz_info_text.setText(
                "График направлений построен. Проверьте открывшееся окно.")

        except Exception as e:
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Critical)
            msg.setWindowTitle("Ошибка")
            msg.setText(f"Ошибка визуализации: {str(e)}")

            msg_font = QFont()
            msg_font.setPointSize(12)
            msg.setFont(msg_font)

            msg.exec_()

    def analyze_equations(self):
        """Математический анализ символьных уравнений"""
        try:
            if self.price_equation is None:
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setWindowTitle("Ошибка")
                msg.setText("Сначала создайте символьные уравнения")

                msg_font = QFont()
                msg_font.setPointSize(12)
                msg.setFont(msg_font)

                msg.exec_()
                return

            analysis_text = "=== МАТЕМАТИЧЕСКИЙ АНАЛИЗ УРАВНЕНИЙ ===\n\n"

            # Анализ ценового уравнения
            analysis_text += "1. АНАЛИЗ УРАВНЕНИЯ ЦЕНЫ:\n"
            analysis_text += f"Уравнение: {self.price_equation}\n"
            analysis_text += f"Количество термов: {len(self.price_equation.args) if hasattr(self.price_equation, 'args') else 'N/A'}\n"

            # Простые метрики сложности
            equation_str = str(self.price_equation)
            analysis_text += f"Длина уравнения: {len(equation_str)} символов\n"
            analysis_text += f"Содержит квадраты: {'**' in equation_str}\n"
            analysis_text += f"Содержит взаимодействия: {'*' in equation_str and '+' in equation_str}\n\n"

            # Анализ уравнения направления
            analysis_text += "2. АНАЛИЗ УРАВНЕНИЯ НАПРАВЛЕНИЯ:\n"
            analysis_text += f"Уравнение: {self.direction_equation}\n"
            analysis_text += "Логистическая функция обеспечивает вероятности от 0 до 1\n"

            self.math_analysis_text.setText(analysis_text)

        except Exception as e:
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Critical)
            msg.setWindowTitle("Ошибка")
            msg.setText(f"Ошибка анализа: {str(e)}")

            msg_font = QFont()
            msg_font.setPointSize(12)
            msg.setFont(msg_font)

            msg.exec_()

    def sensitivity_analysis(self):
        """Анализ чувствительности модели"""
        try:
            if self.price_equation is None:
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setWindowTitle("Ошибка")
                msg.setText("Сначала создайте символьные уравнения")

                msg_font = QFont()
                msg_font.setPointSize(12)
                msg.setFont(msg_font)

                msg.exec_()
                return

            sensitivity_text = "=== АНАЛИЗ ЧУВСТВИТЕЛЬНОСТИ ===\n\n"

            # Простой анализ чувствительности для демонстрации
            if hasattr(self.symbolic_predictor, 'feature_names'):
                feature_names = self.symbolic_predictor.feature_names

                sensitivity_text += "Чувствительность к изменениям переменных (пример):\n"

                # Первые 6 переменных
                for i, name in enumerate(feature_names[:6]):
                    # Демонстрационная чувствительность (случайные значения)
                    sensitivity = np.random.uniform(-0.01, 0.01)
                    significance = "Высокая" if abs(
                        sensitivity) > 0.005 else "Умеренная"

                    sensitivity_text += f"\n{name}(x{i}): {
                        sensitivity: .6f}({significance})"

                    # Интерпретация знака
                    if sensitivity > 0:
                        sensitivity_text += " - положительное влияние на цену"
                    else:
                        sensitivity_text += " - отрицательное влияние на цену"

            sensitivity_text += "\n\nИнтерпретация:\n"
            sensitivity_text += "Положительные значения: рост переменной увеличивает прогноз цены\n"
            sensitivity_text += "Отрицательные значения: рост переменной уменьшает прогноз цены\n"
            sensitivity_text += "Высокая значимость: |чувствительность| > 0.005\n"

            self.math_analysis_text.setText(sensitivity_text)

        except Exception as e:
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Critical)
            msg.setWindowTitle("Ошибка")
            msg.setText(f"Ошибка анализа чувствительности: {str(e)}")

            msg_font = QFont()
            msg_font.setPointSize(12)
            msg.setFont(msg_font)

            msg.exec_()


class SymbolicPricePredictor:
    """
    Класс для создания интерпретируемых символьных моделей прогнозирования цен
    Реализует подход, описанный в статье
    """

    def __init__(self, symbol: str = "EURUSD"):
        # Инициализация параметров предиктора
        self.symbol = symbol
        self.prediction_horizon = 24  # Прогноз на 24 периода вперед
        self.lookback_bars = 1000    # Используем 1000 исторических баров

        # Создание символьных переменных для математических выражений
        # Каждая переменная представляет рыночный индикатор
        self.t = sp.Symbol('t', real=True)    # Время
        self.p = sp.Symbol('p', real=True)    # Цена
        self.r = sp.Symbol('r', real=True)    # Доходность
        self.vol = sp.Symbol('vol', real=True)  # Волатильность
        self.rsi = sp.Symbol('rsi', real=True)  # RSI
        self.macd = sp.Symbol('macd', real=True)  # MACD

        print(f"Инициализирован символьный предиктор для {symbol}")

    def calculate_technical_indicators(self, df: pd.DataFrame) -> pd.DataFrame:
        """
        Расчет технических индикаторов для создания признаков
        Создает многомерное представление каждого бара
        """
        # Копирование данных для безопасности
        data = df.copy()

        # Убедимся, что у нас есть колонка close
        if 'close' not in data.columns:
            raise ValueError("Колонка 'close' не найдена в данных")

        # Базовые расчеты - фундамент для индикаторов
        # Процентное изменение цены - базовый признак
        data['returns'] = data['close'].pct_change()

        # Логарифмические доходности - более стабильная статистика
        data['log_returns'] = np.log(data['close'] / data['close'].shift(1))

        # Абсолютное изменение цены
        data['price_change'] = data['close'] - data['close'].shift(1)

        # Волатильность на разных периодах - captures разные временные масштабы
        periods = [5, 10, 20, 50]  # Разные временные горизонты
        for period in periods:
            # Historical volatility - стандартное отклонение доходностей
            data[f'volatility_{period}'] = data['returns'].rolling(
                period).std() * np.sqrt(24)

            # Realized volatility через логарифмические доходности
            data[f'realized_vol_{period}'] = data['log_returns'].rolling(
                period).std() * np.sqrt(24)

        # RSI на разных периодах - momentum индикатор
        for period in [7, 14, 21]:  # Стандартные и расширенные периоды
            data[f'rsi_{period}'] = self._calculate_rsi(data['close'], period)

        # MACD - трендовый индикатор
        # Быстрая EMA (12 периодов) минус медленная EMA (26 периодов)
        data['macd'] = (data['close'].ewm(span=12, adjust=False).mean() -
                        data['close'].ewm(span=26, adjust=False).mean())

        # Сигнальная линия MACD
        data['macd_signal'] = data['macd'].ewm(span=9, adjust=False).mean()

        # Гистограмма MACD
        data['macd_histogram'] = data['macd'] - data['macd_signal']

        # Momentum - скорость изменения цены
        data['momentum_10'] = data['close'] - data['close'].shift(10)

        # Простая скользящая средняя
        data['sma_20'] = data['close'].rolling(20).mean()
        data['sma_50'] = data['close'].rolling(50).mean()

        # Экспоненциальная скользящая средняя
        data['ema_12'] = data['close'].ewm(span=12, adjust=False).mean()
        data['ema_26'] = data['close'].ewm(span=26, adjust=False).mean()

        # Отклонение от скользящих средних
        data['deviation_sma_20'] = data['close'] / data['sma_20'] - 1
        data['deviation_sma_50'] = data['close'] / data['sma_50'] - 1

        # Bollinger Bands
        data['bb_middle'] = data['close'].rolling(20).mean()
        bb_std = data['close'].rolling(20).std()
        data['bb_upper'] = data['bb_middle'] + 2 * bb_std
        data['bb_lower'] = data['bb_middle'] - 2 * bb_std
        data['bb_position'] = (data['close'] - data['bb_lower']) / \
            (data['bb_upper'] - data['bb_lower'])

        # Удаление строк с NaN значениями (после расчетов с shift/rolling)
        data = data.dropna()

        print(f"Рассчитано индикаторов: {
              len([col for col in data.columns if col not in df.columns])}")

        return data

    def _calculate_rsi(self, prices, period=14):
        """Классический расчет RSI индикатора"""
        # Разница между текущей и предыдущей ценой
        delta = prices.diff()

        # Отдельно gains (положительные изменения) и losses (отрицательные)
        gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
        loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()

        # Relative Strength - отношение средних gains к losses
        rs = gain / loss

        # RSI формула - нормализация до шкалы 0-100
        rsi = 100 - (100 / (1 + rs))

        return rsi

    def prepare_features_and_targets(self, data: pd.DataFrame):
        """
        Подготовка признаков и целевых переменных для обучения
        Включает полиномиальное расширение для учета нелинейностей
        """
        # Выбор числовых колонок как потенциальных признаков
        numeric_columns = data.select_dtypes(
            include=[np.number]).columns.tolist()

        # Исключение базовых OHLCV колонок и временных меток - оставляем только производные индикаторы
        base_columns = ['open', 'high', 'low', 'close', 'volume',
                        'timestamp', 'date', 'time', 'tickvol', 'spread', 'vol']
        feature_columns = [
            col for col in numeric_columns if col not in base_columns]

        # Если признаков слишком много, выбираем наиболее информативные
        if len(feature_columns) > 20:
            # Простой отбор: берем первые 20 по порядку
            feature_columns = feature_columns[:20]
            print(f"Отобрано {len(feature_columns)
                              } наиболее релевантных признаков")

        # Создание целевых переменных - то, что мы хотим предсказать
        # Цена через prediction_horizon периодов
        data['target_price'] = data['close'].shift(-self.prediction_horizon)

        # Процентное изменение через prediction_horizon периодов
        data['target_return'] = (
            data['target_price'] / data['close'] - 1) * 100

        # Бинарная цель: будет ли цена выше через prediction_horizon периодов?
        data['target_direction'] = (
            data['target_price'] > data['close']).astype(int)

        # Удаление строк с пропущенными значениями (из-за shift)
        data_clean = data.dropna()

        # Проверка достаточности данных
        if len(data_clean) < 100:
            print("Недостаточно данных после очистки")
            return None

        # Формирование матриц признаков и целей
        X = data_clean[feature_columns].values
        y_price = data_clean['target_price'].values
        y_direction = data_clean['target_direction'].values

        # Нормализация признаков - критично для стабильности моделей
        scaler = StandardScaler()
        X_scaled = scaler.fit_transform(X)

        # Полиномиальное расширение признаков (степень 2 для учета нелинейностей)
        # Создает квадраты и взаимодействия признаков
        poly = PolynomialFeatures(degree=2, include_bias=True)
        X_poly = poly.fit_transform(X_scaled)

        # Имена полиномиальных признаков для интерпретации
        poly_feature_names = poly.get_feature_names_out(feature_columns)

        print(f"Подготовлено признаков: {
              X_poly.shape[1]}(после полиномиального расширения)")
        print(f"Образцов для обучения: {len(data_clean)}")
        print(f"Баланс направлений: {np.mean(y_direction)*100:.1f}% роста")

        # Сохраняем имена признаков для интерпретации
        self.feature_names = feature_columns

        return {
            'X': X_scaled,  # Нормализованные признаки
            'X_poly': X_poly,  # Полиномиальные признаки
            'y_price': y_price,  # Целевая цена
            'y_direction': y_direction,  # Целевое направление
            'feature_names': feature_columns,  # Имена исходных признаков
            'poly_feature_names': poly_feature_names,  # Имена полиномиальных признаков
            'scaler': scaler,  # Нормализатор
            'poly_transformer': poly,  # Полиномиальный трансформер
            'data_clean': data_clean  # Очищенные данные
        }

    def create_symbolic_equations(self, features_data: dict):
        """
        Создание символьных уравнений из обученных моделей
        Преобразует численные коэффициенты в математические формулы
        """
        # Извлечение данных из словаря
        X_poly = features_data['X_poly']
        y_price = features_data['y_price']
        y_direction = features_data['y_direction']
        feature_names = features_data['feature_names']

        print("Создание символьных уравнений...")

        # === МОДЕЛЬ ПРОГНОЗИРОВАНИЯ ЦЕНЫ (Ridge Regression) ===

        # Подбор оптимального параметра регуляризации через кросс-валидацию
        # Разные значения регуляризации
        alphas = [0.1, 0.5, 1.0, 2.0, 5.0, 10.0]
        best_alpha = 1.0
        best_score = -np.inf  # Начальное значение для поиска максимума

        # Поиск лучшего alpha через кросс-валидацию
        for alpha in alphas:
            ridge_temp = Ridge(alpha=alpha)
            # 5-кратная кросс-валидация с метрикой R²
            scores = cross_val_score(
                ridge_temp, X_poly, y_price, cv=5, scoring='r2')
            avg_score = scores.mean()

            if avg_score > best_score:
                best_score = avg_score
                best_alpha = alpha

        print(f"Оптимальная регуляризация alpha: {best_alpha}")
        print(f"Cross-validation R²: {best_score:.4f}")

        # Обучение финальной Ridge регрессии с лучшим alpha
        ridge = Ridge(alpha=best_alpha)
        ridge.fit(X_poly, y_price)

        # Оценка качества на обучающих данных
        final_score = ridge.score(X_poly, y_price)
        print(f"Финальный R² модели: {final_score:.4f}")

        # === СОЗДАНИЕ СИМВОЛЬНОГО УРАВНЕНИЯ ДЛЯ ЦЕНЫ ===

        # Создание символьных переменных для каждого признака
        symbols = {}
        for i, name in enumerate(feature_names):
            # Создание безопасного имени переменной
            clean_name = name.replace('-', '_').replace(' ', '_')
            symbols[f"x{i}"] = sp.Symbol(f"x{i}", real=True)
            print(f"x{i} = {name}")  # Соответствие переменных и признаков

        # Построение символьного уравнения из коэффициентов Ridge модели
        # Начало с intercept (свободного члена)
        price_equation = sp.S(ridge.intercept_)

        # Добавление линейных членов (только первые n признаков, где n = количество исходных признаков)
        for i, coef in enumerate(ridge.coef_[:len(feature_names)]):
            if abs(coef) > 1e-6:  # Только значимые коэффициенты
                price_equation += coef * symbols[f"x{i}"]

        # Упрощение уравнения для читаемости
        price_equation = sp.simplify(price_equation)

        # === МОДЕЛЬ ПРОГНОЗИРОВАНИЯ НАПРАВЛЕНИЯ (Logistic Regression) ===

        # Разделение данных на обучающую и тестовую выборки
        X_train, X_test, y_train, y_test = train_test_split(
            X_poly, y_direction, test_size=0.2, random_state=42, stratify=y_direction
        )

        # Подбор оптимального параметра регуляризации для логистической регрессии
        C_values = [0.1, 0.5, 1.0, 2.0, 5.0, 10.0]  # Обратная регуляризация
        best_C = 1.0
        best_accuracy = 0

        for C in C_values:
            logistic_temp = LogisticRegression(
                C=C, random_state=42, max_iter=2000)
            logistic_temp.fit(X_train, y_train)
            accuracy = logistic_temp.score(X_test, y_test)

            if accuracy > best_accuracy:
                best_accuracy = accuracy
                best_C = C

        print(f"Оптимальная регуляризация C: {best_C}")
        print(f"Точность бинарной модели: {best_accuracy:.4f}")

        # Обучение финальной логистической регрессии
        logistic = LogisticRegression(
            C=best_C, random_state=42, max_iter=2000)
        logistic.fit(X_train, y_train)

        # === СОЗДАНИЕ СИМВОЛЬНОГО УРАВНЕНИЯ ДЛЯ НАПРАВЛЕНИЯ ===

        # Логистическая регрессия: P(y=1) = 1 / (1 + exp(-z))
        # где z = линейная комбинация признаков
        linear_combination = sp.S(logistic.intercept_[0])

        # Добавление линейных членов
        for i, coef in enumerate(logistic.coef_[0][:len(feature_names)]):
            if abs(coef) > 1e-6:
                linear_combination += coef * symbols[f"x{i}"]

        # Полная логистическая функция
        binary_equation = 1 / (1 + sp.exp(-linear_combination))
        binary_equation = sp.simplify(binary_equation)

        # Сохранение компонентов для последующего использования
        self.price_equation = price_equation
        self.binary_equation = binary_equation
        self.symbols = symbols
        self.feature_names = feature_names
        self.ridge_model = ridge
        self.logistic_model = logistic

        print("Символьные уравнения созданы успешно")

        return price_equation, binary_equation


def main():
    """Основная функция запуска приложения"""
    # Создание QApplication
    app = QApplication(sys.argv)

    # Установка стиля для улучшения внешнего вида
    app.setStyle('Fusion')

    # Глобальное увеличение шрифта для всех диалоговых окон
    app.setFont(QFont("Arial", 12))

    # Создание и отображение главного окна
    window = SymbolicTradingSystem()
    window.show()

    # Запуск главного цикла приложения
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
