Перейти к публикации
  • Сейчас на странице   Всего пользователей: 0   (0 пользователей, 0 гостей)

Rooster

Программирование[11]

var  

304 пользователя проголосовало

У вас нет прав на голосование в этом опросе, или на просмотр результатов опроса. Пожалуйста, войдите или зарегистрируйтесь для голосования в опросе.

Рекомендованные сообщения

ну да, нейронки читали документацию, в отличие от тебя

Arzanis и Drakonian понравилось это

Торжество разума в том, чтобы уживаться с теми, у кого этого разума нет. Вольтер.
Чтобы хорошо высыпаться, нужно спать 8 часов в день. И еще столько же ночью.

Поделиться сообщением


Ссылка на сообщение

Нейронки тупые, но некоторые люди еще тупее. Мы это знали.

Drakonian понравилось это

 

DB

59221730.png


Я - гений, ёпта

bfe7003be27e8e81ce6a7d2d8192e9ae.jpg


22


msg-93176-0-72842500-1438846470_thumb.jpg

Поделиться сообщением


Ссылка на сообщение
(изменено)

Ребят помогите пожалуйста с кодом. python
Пробую создать аналог игры Краш (онлайн-казино)
С помощью ИИ написал простенький код для тестов, но у меня на дистанции не выдаёт реальное мат ожидание.
Я ставлю мин/макс множитель 1.11 (как в казино). ставка фиксированная мин/макс 100. Это только для тестов.
Делаю раза 3-4 по 1 миллион тестовых раундов и всё равно получаю положительное мат ожидание, но этого не может быть! Я должен сыграть в минус или в 0. Если я не решу эту проблему, то двигаться дальше я не смогу! Помогите пожалуйста!!!

Вот формула генерации числа Краша (без процента казино (чистая)). Может быть в ней ошибка?


def generate_crash_number():
    """Генерирует случайное число краша"""
    # Генерируем равномерно распределённое число U ∈ [0, 1)
    U = random.uniform(0, 1)
    # Вычисляем множитель по формуле
    if U >= 0.99999:  # Защита от деления на ноль
        crash_multiplier = 100000.00
    else:
        crash_multiplier = 1.0 / (1.0 - U)
    # Ограничиваем множитель диапазоном [1.00, 100000.00] и округляем до 2 знаков
    crash_multiplier = max(min(crash_multiplier, 100000.00), 1.00)
    return round(crash_multiplier, 2)

Вот ниже я прикреплю код.

 

Может у кого есть доступ к ГПТ 5? Спросите у него, что за фигня?
Qwen говорит, что ошибки в коде нет.
Что генерация и вычисления тоже верны!
А вот математика говорит, что профита 470-480 тысяч на 1 миллион раундов (ставка 100, множитель 1.11) просто не может быть.

 

  Код
import sys
import random
import math
import os
from datetime import datetime
from collections import deque
from typing import List, Tuple, Dict, Any
from PyQt6 import QtWidgets, QtCore, QtGui
from PyQt6.QtCore import QThread, pyqtSignal, QTimer, QDateTime, Qt, QTime
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QFormLayout,
    QLabel, QPushButton, QMessageBox, QGroupBox,
    QSpinBox, QDoubleSpinBox, QCheckBox, QTableWidget, QTableWidgetItem,
    QHeaderView, QTabWidget, QTimeEdit, QFrame, QScrollArea, QSizePolicy
)
import matplotlib
matplotlib.use('QtAgg')
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure

# --- APP_CONFIG ---
APP_CONFIG = {
    'APP_NAME': "Crash",
    'APP_NAME_VERSION': "(v1.0)",
    'STATS_FILE_NAME': "stats.txt",
    'COLOR_BACKGROUND': "#1e1e2d",
    'COLOR_SURFACE': "#2d2d44",
    'COLOR_SURFACE_LIGHT': "#3d3d54",
    'COLOR_TEXT': "#e0e0ff",
    'COLOR_BORDER': "#444466",
    'COLOR_PRIMARY': "#bb86fc",
    'COLOR_SECONDARY': "#03dac6",
    'COLOR_POSITIVE': "#00cc66",
    'COLOR_NEGATIVE': "#ff5555",
    'COLOR_WARNING': "#ffaa00",
    'COLOR_INFO': "#66b3ff",
    'TEXT_LABELS': {
        'app_title': "Crash",
        'save_settings': "Сохранить настройки",
        'reset_settings': "Сбросить настройки",
        'min_bet_label': "Минимальная ставка:",
        'max_bet_label': "Максимальная ставка:",
    },
}

# --- Crash Number Generator ---
def generate_crash_number():
    """Генерирует случайное число краша"""
    # Генерируем равномерно распределённое число U ∈ [0, 1)
    U = random.uniform(0, 1)
    # Вычисляем множитель по формуле
    if U >= 0.99999:  # Защита от деления на ноль
        crash_multiplier = 100000.00
    else:
        crash_multiplier = 1.0 / (1.0 - U)
    # Ограничиваем множитель диапазоном [1.00, 100000.00] и округляем до 2 знаков
    crash_multiplier = max(min(crash_multiplier, 100000.00), 1.00)
    return round(crash_multiplier, 2)

# --- Crash History Table (для отображения последних 100 значений) ---
class CrashHistoryTable(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.history = []
        self.init_ui()

    def init_ui(self):
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)
        # Заголовок
        header_layout = QHBoxLayout()
        header_label = QLabel("История последних значений краша")
        header_label.setStyleSheet(f"color: {APP_CONFIG['COLOR_SECONDARY']}; font-size: 12pt; font-weight: bold;")
        header_label.setToolTip("Последние 100 значений коэффициента краша")
        header_layout.addWidget(header_label)
        header_layout.addStretch()
        self.layout.addLayout(header_layout)
        # Таблица
        self.table = QTableWidget()
        self.table.setColumnCount(2)
        self.table.setHorizontalHeaderLabels(["#", "Краш"])
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
        self.table.verticalHeader().setVisible(False)
        self.table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
        self.table.setSelectionMode(QTableWidget.SelectionMode.NoSelection)
        self.table.setStyleSheet(f"""
            QTableWidget {{
                font-size: 9pt;
                color: {APP_CONFIG['COLOR_TEXT']};
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                gridline-color: {APP_CONFIG['COLOR_BORDER']};
                alternate-background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
            }}
            QTableWidget::item {{
                padding: 4px;
                border-bottom: 1px solid {APP_CONFIG['COLOR_BORDER']};
            }}
            QHeaderView::section {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                color: {APP_CONFIG['COLOR_TEXT']};
                padding: 6px;
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                font-weight: bold;
            }}
            QTableWidget::item:selected {{
                background-color: {APP_CONFIG['COLOR_PRIMARY']};
                color: white;
            }}
        """)
        self.table.setToolTip("Таблица с последними 100 значениями краша")
        self.layout.addWidget(self.table)

    def update_history(self, crash_history: List[float]):
        """Обновляет таблицу всей историей крашей"""
        self.history = crash_history[-100:] if len(crash_history) > 100 else crash_history
        self.table.setRowCount(len(self.history))
        for i, value in enumerate(reversed(self.history)):
            # Номер
            num_item = QTableWidgetItem(str(len(self.history) - i))
            num_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
            self.table.setItem(i, 0, num_item)
            # Значение краша
            crash_item = QTableWidgetItem(f"{value:.2f}x")
            crash_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
            # Цвет в зависимости от значения
            if value >= 10:
                crash_item.setForeground(QtGui.QColor(APP_CONFIG['COLOR_POSITIVE']))
                crash_item.setFont(QtGui.QFont("Arial", 9, QtGui.QFont.Weight.Bold))
            elif value >= 2:
                crash_item.setForeground(QtGui.QColor(APP_CONFIG['COLOR_SECONDARY']))
            else:
                crash_item.setForeground(QtGui.QColor(APP_CONFIG['COLOR_NEGATIVE']))
            self.table.setItem(i, 1, crash_item)
        # Выравнивание текста по центру
        for i in range(self.table.rowCount()):
            for j in range(self.table.columnCount()):
                item = self.table.item(i, j)
                if item:
                    item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)

# --- Actions History Table (для отображения последних 100 действий) ---
class ActionsHistoryTable(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.history = []
        self.init_ui()

    def init_ui(self):
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)
        # Заголовок
        header_layout = QHBoxLayout()
        header_label = QLabel("История последних действий")
        header_label.setStyleSheet(f"color: {APP_CONFIG['COLOR_SECONDARY']}; font-size: 12pt; font-weight: bold;")
        header_label.setToolTip("Последние 100 записей о действиях в тестировании")
        header_layout.addWidget(header_label)
        header_layout.addStretch()
        self.layout.addLayout(header_layout)
        # Таблица
        self.table = QTableWidget()
        self.table.setColumnCount(10)
        self.table.setHorizontalHeaderLabels([
            "Дата/Время", "Раунд", "Банк", "Ставка", "Множитель", 
            "Краш", "Результат", "Новый банк", "Профит", "ROI"
        ])
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
        self.table.verticalHeader().setVisible(False)
        self.table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
        self.table.setSelectionMode(QTableWidget.SelectionMode.NoSelection)
        self.table.setStyleSheet(f"""
            QTableWidget {{
                font-size: 8pt;
                color: {APP_CONFIG['COLOR_TEXT']};
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                gridline-color: {APP_CONFIG['COLOR_BORDER']};
                alternate-background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
            }}
            QTableWidget::item {{
                padding: 2px;
                border-bottom: 1px solid {APP_CONFIG['COLOR_BORDER']};
            }}
            QHeaderView::section {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                color: {APP_CONFIG['COLOR_TEXT']};
                padding: 4px;
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                font-weight: bold;
            }}
            QTableWidget::item:selected {{
                background-color: {APP_CONFIG['COLOR_PRIMARY']};
                color: white;
            }}
        """)
        self.table.setToolTip("Таблица с последними 100 записями о действиях в тестировании")
        self.layout.addWidget(self.table)

    def update_history(self, actions_history: List[Dict[str, Any]]):
        """Обновляет таблицу всей историей действий"""
        self.history = actions_history[-100:] if len(actions_history) > 100 else actions_history
        self.table.setRowCount(len(self.history))
        for i, data in enumerate(reversed(self.history)):
            timestamp = data['timestamp'].strftime("%H:%M:%S.%f")[:-3]  # Обрезаем до миллисекунд
            self.table.setItem(i, 0, QTableWidgetItem(timestamp))
            self.table.setItem(i, 1, QTableWidgetItem(str(data['round_num'])))
            self.table.setItem(i, 2, QTableWidgetItem(f"{data['balance_before']:.2f}"))
            self.table.setItem(i, 3, QTableWidgetItem(str(data['bet'])))
            self.table.setItem(i, 4, QTableWidgetItem(f"{data['multiplier']:.2f}"))
            self.table.setItem(i, 5, QTableWidgetItem(f"{data['crash_value']:.2f}"))
            # Результат
            if data['win']:
                result_text = f"+{data['reward']:.2f}"
                result_item = QTableWidgetItem(result_text)
                result_item.setForeground(QtGui.QColor(APP_CONFIG['COLOR_POSITIVE']))
                result_item.setFont(QtGui.QFont("Arial", 8, QtGui.QFont.Weight.Bold))
            else:
                result_text = f"-{data['bet']:.2f}"
                result_item = QTableWidgetItem(result_text)
                result_item.setForeground(QtGui.QColor(APP_CONFIG['COLOR_NEGATIVE']))
                result_item.setFont(QtGui.QFont("Arial", 8, QtGui.QFont.Weight.Bold))
            self.table.setItem(i, 6, result_item)
            self.table.setItem(i, 7, QTableWidgetItem(f"{data['balance_after']:.2f}"))
            self.table.setItem(i, 8, QTableWidgetItem(f"{data['profit']:.2f}"))
            self.table.setItem(i, 9, QTableWidgetItem(f"{data['roi']:.2f}%"))
        # Выравнивание текста по центру
        for i in range(self.table.rowCount()):
            for j in range(self.table.columnCount()):
                item = self.table.item(i, j)
                if item:
                    item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)

# --- Testing Thread ---
class TestingWorker(QThread):
    # Сигналы для обновления GUI
    batch_finished = pyqtSignal(dict)  # Передаем данные батча
    testing_finished = pyqtSignal(dict)  # итоговая статистика
    testing_stopped = pyqtSignal()
    error_occurred = pyqtSignal(str)
    plot_data_ready = pyqtSignal(list, list)  # данные для графика

    def __init__(self, num_rounds: int, min_bet: int, max_bet: int, 
                 min_multiplier: float, max_multiplier: float, 
                 initial_balance: float, update_interval: int):
        super().__init__()
        self.num_rounds = num_rounds
        self.min_bet = min_bet
        self.max_bet = max_bet
        self.min_multiplier = min_multiplier
        self.max_multiplier = max_multiplier
        self.initial_balance = initial_balance
        self.update_interval = update_interval  # 0 = только в конце
        self._is_running = True
        self.balance = initial_balance
        self.crash_history = []
        self.actions_history = []
        self.testing_rounds_data = []
        self.testing_profit_data = []
        self.total_wins = 0
        self.total_losses = 0
        self.current_win_streak = 0
        self.current_loss_streak = 0
        self.max_win_streak = 0
        self.max_loss_streak = 0
        self.total_invested = 0  # Для правильного расчета ROI
        self.processed_rounds = 0

    def stop(self):
        self._is_running = False

    def run(self):
        try:
            batch_data = {
                'rounds': [],
                'crash_values': [],
                'bets': [],
                'multipliers': [],
                'wins': [],
                'rewards': [],
                'balances_before': [],
                'balances_after': [],
                'profits': [],
                'rois': [],
                'winrates': [],
                'timestamps': [],
                'total_wins_list': [],
                'total_losses_list': [],
                'max_win_streaks': [],
                'max_loss_streaks': []
            }
            for i in range(1, self.num_rounds + 1):
                if not self._is_running:
                    self.testing_stopped.emit()
                    return
                # 1. Генерация краша
                crash_value = generate_crash_number()
                self.crash_history.append(crash_value)
                # 2. Генерация случайной ставки и множителя
                bet = random.randint(self.min_bet, self.max_bet)
                multiplier = round(random.uniform(self.min_multiplier, self.max_multiplier), 2)
                # 3. Симуляция результата
                balance_before = self.balance
                win = crash_value >= multiplier
                reward = 0
                if bet > 0:
                    self.total_invested += bet  # Увеличиваем общую сумму ставок
                    self.balance -= bet
                    if win:
                        win_amount = bet * multiplier
                        self.balance += win_amount
                        reward = win_amount - bet  # Чистый выигрыш
                        self.total_wins += 1
                        self.current_win_streak += 1
                        self.current_loss_streak = 0
                        if self.current_win_streak > self.max_win_streak:
                            self.max_win_streak = self.current_win_streak
                    else:
                        reward = -bet  # Потеря ставки
                        self.total_losses += 1
                        self.current_loss_streak += 1
                        self.current_win_streak = 0
                        if self.current_loss_streak > self.max_loss_streak:
                            self.max_loss_streak = self.current_loss_streak
                profit = self.balance - self.initial_balance
                # Исправленный расчет ROI
                roi = (profit / self.total_invested * 100) if self.total_invested > 0 else 0
                winrate = (self.total_wins / i * 100) if i > 0 else 0  # Winrate от общего количества раундов
                # 4. Сохранение данных для истории действий
                action_data = {
                    'round_num': i,
                    'crash_value': crash_value,
                    'bet': bet,
                    'multiplier': multiplier,
                    'win': win,
                    'reward': reward,
                    'balance_before': balance_before,
                    'balance_after': self.balance,
                    'profit': profit,
                    'roi': roi,
                    'winrate': winrate,
                    'timestamp': datetime.now(),
                    'total_wins': self.total_wins,
                    'total_losses': self.total_losses,
                    'max_win_streak': self.max_win_streak,
                    'max_loss_streak': self.max_loss_streak
                }
                self.actions_history.append(action_data)
                # 5. Обновление данных для графика
                self.testing_rounds_data.append(i)
                self.testing_profit_data.append(profit)
                # 6. Подготовка данных для батча (если нужно обновление)
                if self.update_interval > 0:
                    batch_data['rounds'].append(i)
                    batch_data['crash_values'].append(crash_value)
                    batch_data['bets'].append(bet)
                    batch_data['multipliers'].append(multiplier)
                    batch_data['wins'].append(win)
                    batch_data['rewards'].append(reward)
                    batch_data['balances_before'].append(balance_before)
                    batch_data['balances_after'].append(self.balance)
                    batch_data['profits'].append(profit)
                    batch_data['rois'].append(roi)
                    batch_data['winrates'].append(winrate)
                    batch_data['timestamps'].append(datetime.now())
                    batch_data['total_wins_list'].append(self.total_wins)
                    batch_data['total_losses_list'].append(self.total_losses)
                    batch_data['max_win_streaks'].append(self.max_win_streak)
                    batch_data['max_loss_streaks'].append(self.max_loss_streak)
                self.processed_rounds += 1
                # 7. Эмит сигналов при достижении интервала обновления
                if self.update_interval > 0 and self.processed_rounds % self.update_interval == 0:
                    batch_stats = {
                        'processed_rounds': self.processed_rounds,
                        'total_rounds': self.num_rounds,
                        'current_balance': self.balance,
                        'current_profit': profit,
                        'current_roi': roi,
                        'current_winrate': winrate,
                        'total_wins': self.total_wins,
                        'total_losses': self.total_losses,
                        'max_win_streak': self.max_win_streak,
                        'max_loss_streak': self.max_loss_streak,
                        'crash_history': self.crash_history.copy(),
                        'actions_history': self.actions_history.copy(),
                        'plot_rounds': self.testing_rounds_data.copy(),
                        'plot_profits': self.testing_profit_data.copy()
                    }
                    self.batch_finished.emit(batch_stats)
                    # Очищаем батч данные
                    batch_data = {
                        'rounds': [],
                        'crash_values': [],
                        'bets': [],
                        'multipliers': [],
                        'wins': [],
                        'rewards': [],
                        'balances_before': [],
                        'balances_after': [],
                        'profits': [],
                        'rois': [],
                        'winrates': [],
                        'timestamps': [],
                        'total_wins_list': [],
                        'total_losses_list': [],
                        'max_win_streaks': [],
                        'max_loss_streaks': []
                    }
                # Небольшая задержка только для очень маленьких интервалов
                if self.update_interval > 0 and self.update_interval < 10:
                    self.msleep(1)
            # Тестирование завершено
            final_stats = {
                'total_rounds': self.num_rounds,
                'final_balance': self.balance,
                'total_profit': self.balance - self.initial_balance,
                'roi': ((self.balance - self.initial_balance) / self.total_invested * 100) if self.total_invested > 0 else 0,
                'winrate': (self.total_wins / self.num_rounds * 100) if self.num_rounds > 0 else 0,
                'total_wins': self.total_wins,
                'total_losses': self.total_losses,
                'max_win_streak': self.max_win_streak,
                'max_loss_streak': self.max_loss_streak,
                'crash_history': self.crash_history,
                'actions_history': self.actions_history,
                'plot_rounds': self.testing_rounds_data,
                'plot_profits': self.testing_profit_data
            }
            self.testing_finished.emit(final_stats)
        except Exception as e:
            self.error_occurred.emit(str(e))

# --- Main Window ---
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle(f"{APP_CONFIG['APP_NAME']} {APP_CONFIG['APP_NAME_VERSION']}")
        self.setGeometry(100, 100, 1400, 900)
        self.setStyleSheet(f"""
            QMainWindow {{
                background-color: {APP_CONFIG['COLOR_BACKGROUND']};
            }}
            QWidget {{
                background-color: {APP_CONFIG['COLOR_BACKGROUND']};
                color: {APP_CONFIG['COLOR_TEXT']};
                font-family: 'Segoe UI', Arial, sans-serif;
            }}
            QLabel {{
                color: {APP_CONFIG['COLOR_TEXT']};
                background-color: transparent;
            }}
            QGroupBox {{
                border: 2px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 8px;
                margin-top: 10px;
                padding: 10px;
                font-weight: bold;
                color: {APP_CONFIG['COLOR_TEXT']};
                background-color: {APP_CONFIG['COLOR_SURFACE']};
            }}
            QGroupBox::title {{
                subline-offset: -15px;
                padding: 0px 5px 0px 5px;
                color: {APP_CONFIG['COLOR_SECONDARY']};
            }}
            QPushButton {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                padding: 8px 15px;
                font-weight: bold;
                color: {APP_CONFIG['COLOR_TEXT']};
                min-width: 100px;
            }}
            QPushButton:hover {{
                background-color: {APP_CONFIG['COLOR_PRIMARY']};
                border: 1px solid {APP_CONFIG['COLOR_SECONDARY']};
            }}
            QPushButton:pressed {{
                background-color: {APP_CONFIG['COLOR_SECONDARY']};
            }}
            QPushButton:disabled {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                color: #888888;
                border: 1px solid #555555;
            }}
            QSpinBox, QDoubleSpinBox {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                color: {APP_CONFIG['COLOR_TEXT']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 4px;
                padding: 4px;
                selection-background-color: {APP_CONFIG['COLOR_PRIMARY']};
            }}
            QSpinBox::up-button, QDoubleSpinBox::up-button {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                border: none;
                width: 16px;
            }}
            QSpinBox::down-button, QDoubleSpinBox::down-button {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                border: none;
                width: 16px;
            }}
            QCheckBox {{
                color: {APP_CONFIG['COLOR_TEXT']};
            }}
            QTabWidget::pane {{
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                background-color: {APP_CONFIG['COLOR_BACKGROUND']};
            }}
            QTabBar::tab {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                color: {APP_CONFIG['COLOR_TEXT']};
                padding: 8px 15px;
                margin-right: 2px;
                border-top-left-radius: 4px;
                border-top-right-radius: 4px;
            }}
            QTabBar::tab:selected {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                color: {APP_CONFIG['COLOR_SECONDARY']};
                font-weight: bold;
            }}
            QTabBar::tab:hover {{
                background-color: {APP_CONFIG['COLOR_PRIMARY']};
            }}
            QHeaderView::section {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                color: {APP_CONFIG['COLOR_TEXT']};
                padding: 6px;
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                font-weight: bold;
            }}
        """)
        # Инициализация UI
        self.init_ui()

    def init_ui(self):
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        self.main_layout = QVBoxLayout(central_widget)
        self.main_layout.setSpacing(10)
        self.setup_main_tabs()

    def setup_main_tabs(self):
        self.tabs = QTabWidget()
        self.tab_main = self.create_main_tab_interface()
        self.tab_settings = self.create_settings_tab_interface()
        self.tab_testing = self.create_testing_tab_interface()
        self.tab_history = self.create_history_tables_tab_interface()  # Новая вкладка
        self.tabs.addTab(self.tab_main, "Главная")
        self.tabs.addTab(self.tab_settings, "Настройки")
        self.tabs.addTab(self.tab_testing, "Тестирование")
        self.tabs.addTab(self.tab_history, "Таблицы (История)")  # Добавляем новую вкладку
        self.main_layout.addWidget(self.tabs)

    def create_main_tab_interface(self):
        tab = QWidget()
        layout = QVBoxLayout()
        layout.setSpacing(15)
        tab.setLayout(layout)
        # Вкладка теперь полностью пустая, как требовалось
        layout.addStretch()
        return tab

    def create_settings_tab_interface(self):
        tab = QWidget()
        layout = QVBoxLayout(tab)
        layout.setSpacing(15)
        # Заголовок
        header_label = QLabel("Настройки тестирования")
        header_label.setStyleSheet(f"color: {APP_CONFIG['COLOR_SECONDARY']}; font-size: 16pt; font-weight: bold; padding: 10px;")
        header_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        header_label.setToolTip("Настройка параметров для симуляции игры Crash")
        layout.addWidget(header_label)
        # Основные настройки
        main_settings_group = QGroupBox("Основные параметры")
        main_settings_layout = QFormLayout()
        main_settings_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight)
        main_settings_layout.setHorizontalSpacing(20)
        main_settings_layout.setVerticalSpacing(10)
        main_settings_group.setToolTip("Основные параметры симуляции")

        # Начальный банк
        self.initial_balance_input = QSpinBox()
        self.initial_balance_input.setRange(0, 10000000)
        self.initial_balance_input.setValue(0)
        self.initial_balance_input.setGroupSeparatorShown(True)
        self.initial_balance_input.setToolTip("Начальный баланс для тестирования")
        main_settings_layout.addRow(QLabel("Начальный банк:"), self.initial_balance_input)

        # Минимальная ставка
        self.min_bet_input = QSpinBox()
        self.min_bet_input.setRange(0, 10000000)
        self.min_bet_input.setValue(100)
        self.min_bet_input.setGroupSeparatorShown(True)
        self.min_bet_input.setToolTip("Минимальный размер ставки для одной игры")
        main_settings_layout.addRow(QLabel(APP_CONFIG['TEXT_LABELS']['min_bet_label']), self.min_bet_input)

        # Максимальная ставка
        self.max_bet_input = QSpinBox()
        self.max_bet_input.setRange(0, 10000000)
        self.max_bet_input.setValue(100)
        self.max_bet_input.setGroupSeparatorShown(True)
        self.max_bet_input.setToolTip("Максимальный размер ставки для одной игры")
        main_settings_layout.addRow(QLabel("Максимальная ставка:"), self.max_bet_input)

        # Минимальный множитель
        self.min_multiplier_input = QDoubleSpinBox()
        self.min_multiplier_input.setRange(1.01, 100.0)
        self.min_multiplier_input.setSingleStep(0.1)
        self.min_multiplier_input.setDecimals(2)
        self.min_multiplier_input.setValue(1.11)
        self.min_multiplier_input.setToolTip("Минимальный множитель для кэшаута")
        main_settings_layout.addRow(QLabel("Мин множитель:"), self.min_multiplier_input)

        # Максимальный множитель
        self.max_multiplier_input = QDoubleSpinBox()
        self.max_multiplier_input.setRange(1.01, 100.0)
        self.max_multiplier_input.setSingleStep(0.1)
        self.max_multiplier_input.setDecimals(2)
        self.max_multiplier_input.setValue(1.11)
        self.max_multiplier_input.setToolTip("Максимальный множитель для кэшаута")
        main_settings_layout.addRow(QLabel("Макс множитель:"), self.max_multiplier_input)

        main_settings_group.setLayout(main_settings_layout)
        layout.addWidget(main_settings_group)
        # Кнопки действий
        buttons_layout = QHBoxLayout()
        buttons_layout.addStretch()
        self.save_settings_button = QPushButton(APP_CONFIG['TEXT_LABELS']['save_settings'])
        self.save_settings_button.clicked.connect(self.save_settings)
        self.save_settings_button.setStyleSheet(f"""
            QPushButton {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                padding: 10px 20px;
                font-weight: bold;
                color: {APP_CONFIG['COLOR_TEXT']};
                min-width: 150px;
            }}
            QPushButton:hover {{
                background-color: {APP_CONFIG['COLOR_POSITIVE']};
                border: 1px solid {APP_CONFIG['COLOR_SECONDARY']};
            }}
        """)
        self.save_settings_button.setToolTip("Сохранить текущие настройки")
        buttons_layout.addWidget(self.save_settings_button)
        self.reset_settings_button = QPushButton(APP_CONFIG['TEXT_LABELS']['reset_settings'])
        self.reset_settings_button.clicked.connect(self.restore_default_settings)
        self.reset_settings_button.setStyleSheet(f"""
            QPushButton {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                padding: 10px 20px;
                font-weight: bold;
                color: {APP_CONFIG['COLOR_TEXT']};
                min-width: 150px;
            }}
            QPushButton:hover {{
                background-color: {APP_CONFIG['COLOR_WARNING']};
                border: 1px solid {APP_CONFIG['COLOR_SECONDARY']};
            }}
        """)
        self.reset_settings_button.setToolTip("Сбросить все настройки к значениям по умолчанию")
        buttons_layout.addWidget(self.reset_settings_button)
        buttons_layout.addStretch()
        layout.addLayout(buttons_layout)
        layout.addStretch()
        return tab

    def create_testing_tab_interface(self):
        tab = QWidget()
        layout = QVBoxLayout(tab)
        layout.setSpacing(15)
        # Заголовок
        header_label = QLabel("Тестирование стратегии")
        header_label.setStyleSheet(f"color: {APP_CONFIG['COLOR_SECONDARY']}; font-size: 16pt; font-weight: bold; padding: 10px;")
        header_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        header_label.setToolTip("Запуск и управление симуляцией игры Crash")
        layout.addWidget(header_label)
        # Панель управления тестированием
        control_group = QGroupBox("Управление тестированием")
        control_layout = QFormLayout()
        control_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight)
        control_layout.setHorizontalSpacing(20)
        control_layout.setVerticalSpacing(10)
        control_group.setToolTip("Параметры и управление процессом тестирования")

        self.num_rounds_input = QSpinBox()
        self.num_rounds_input.setRange(1, 100000000)
        self.num_rounds_input.setValue(1000000)
        self.num_rounds_input.setGroupSeparatorShown(True)
        self.num_rounds_input.setToolTip("Общее количество раундов для симуляции")
        control_layout.addRow(QLabel("Количество раундов:"), self.num_rounds_input)

        self.update_interval_input = QSpinBox()
        self.update_interval_input.setRange(0, 100000000)
        self.update_interval_input.setValue(0)
        self.update_interval_input.setGroupSeparatorShown(True)
        self.update_interval_input.setToolTip("Интервал обновления интерфейса в раундах (0 = только в конце)")
        control_layout.addRow(QLabel("Интервал обновления интерфейса:"), self.update_interval_input)

        # Кнопки
        buttons_layout = QHBoxLayout()
        buttons_layout.addStretch()
        self.start_testing_button = QPushButton("Начать тестирование")
        self.start_testing_button.clicked.connect(self.start_testing)
        self.start_testing_button.setStyleSheet(f"""
            QPushButton {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                padding: 10px 20px;
                font-weight: bold;
                color: {APP_CONFIG['COLOR_TEXT']};
                min-width: 180px;
            }}
            QPushButton:hover {{
                background-color: {APP_CONFIG['COLOR_POSITIVE']};
                border: 1px solid {APP_CONFIG['COLOR_SECONDARY']};
            }}
        """)
        self.start_testing_button.setToolTip("Запустить симуляцию игры с текущими настройками")
        buttons_layout.addWidget(self.start_testing_button)

        self.stop_testing_button = QPushButton("Завершить тестирование")
        self.stop_testing_button.clicked.connect(self.stop_testing)
        self.stop_testing_button.setEnabled(False)
        self.stop_testing_button.setStyleSheet(f"""
            QPushButton {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                padding: 10px 20px;
                font-weight: bold;
                color: {APP_CONFIG['COLOR_TEXT']};
                min-width: 180px;
            }}
            QPushButton:hover {{
                background-color: {APP_CONFIG['COLOR_NEGATIVE']};
                border: 1px solid {APP_CONFIG['COLOR_SECONDARY']};
            }}
            QPushButton:disabled {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                color: #888888;
                border: 1px solid #555555;
            }}
        """)
        self.stop_testing_button.setToolTip("Остановить текущую симуляцию")
        buttons_layout.addWidget(self.stop_testing_button)
        buttons_layout.addStretch()
        control_layout.addRow(buttons_layout)
        control_group.setLayout(control_layout)
        layout.addWidget(control_group)

        # Панель статистики тестирования
        self.testing_stats_group = QGroupBox("Статистика тестирования")
        stats_layout = QFormLayout()
        stats_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight)
        stats_layout.setHorizontalSpacing(20)
        stats_layout.setVerticalSpacing(8)
        self.testing_stats_group.setToolTip("Текущие результаты симуляции")

        # Статус
        self.lbl_testing_status = QLabel("Ожидание запуска...")
        self.lbl_testing_status.setStyleSheet(f"color: {APP_CONFIG['COLOR_INFO']}; font-weight: bold;")
        self.lbl_testing_status.setToolTip("Текущий статус тестирования")
        stats_layout.addRow(QLabel("Статус:"), self.lbl_testing_status)

        # Прогресс
        self.lbl_testing_rounds = QLabel("0 / 0")
        self.lbl_testing_rounds.setStyleSheet(f"color: {APP_CONFIG['COLOR_SECONDARY']}; font-weight: bold;")
        self.lbl_testing_rounds.setToolTip("Прогресс выполнения тестирования")
        stats_layout.addRow(QLabel("Прогресс:"), self.lbl_testing_rounds)

        # Профит
        self.lbl_testing_profit = QLabel("0.00")
        self.lbl_testing_profit.setStyleSheet(f"color: {APP_CONFIG['COLOR_INFO']}; font-weight: bold;")
        self.lbl_testing_profit.setToolTip("Текущий профит (доход/убыток)")
        stats_layout.addRow(QLabel("Профит:"), self.lbl_testing_profit)

        # ROI
        self.lbl_testing_roi = QLabel("0.00%")
        self.lbl_testing_roi.setStyleSheet(f"color: {APP_CONFIG['COLOR_INFO']}; font-weight: bold;")
        self.lbl_testing_roi.setToolTip("Возврат на инвестиции (ROI) в процентах")
        stats_layout.addRow(QLabel("ROI:"), self.lbl_testing_roi)

        # Винрейт
        self.lbl_testing_winrate = QLabel("0.00%")
        self.lbl_testing_winrate.setStyleSheet(f"color: {APP_CONFIG['COLOR_INFO']}; font-weight: bold;")
        self.lbl_testing_winrate.setToolTip("Процент выигранных раундов")
        stats_layout.addRow(QLabel("Винрейт:"), self.lbl_testing_winrate)

        # --- ИЗМЕНЕНИЯ НАЧАЛИСЬ ЗДЕСЬ ---
        # Победы
        self.lbl_testing_wins = QLabel("0")
        self.lbl_testing_wins.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold;")
        self.lbl_testing_wins.setToolTip("Количество выигранных раундов")
        stats_layout.addRow(QLabel("Победы:"), self.lbl_testing_wins)
        # Поражения
        self.lbl_testing_losses = QLabel("0")
        self.lbl_testing_losses.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")
        self.lbl_testing_losses.setToolTip("Количество проигранных раундов")
        stats_layout.addRow(QLabel("Поражения:"), self.lbl_testing_losses)
        # Макс. серия побед
        self.lbl_testing_max_win_streak = QLabel("0")
        self.lbl_testing_max_win_streak.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold;")
        self.lbl_testing_max_win_streak.setToolTip("Максимальная серия побед подряд за всё время тестирования")
        stats_layout.addRow(QLabel("Макс. серия побед (за всё время):"), self.lbl_testing_max_win_streak)
        # Макс. серия поражений
        self.lbl_testing_max_loss_streak = QLabel("0")
        self.lbl_testing_max_loss_streak.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")
        self.lbl_testing_max_loss_streak.setToolTip("Максимальная серия поражений подряд за всё время тестирования")
        stats_layout.addRow(QLabel("Макс. серия поражений (за всё время):"), self.lbl_testing_max_loss_streak)
        # --- ИЗМЕНЕНИЯ ЗАКОНЧИЛИСЬ ---
        
        self.testing_stats_group.setLayout(stats_layout)
        layout.addWidget(self.testing_stats_group)

        # График тестирования
        self.testing_graph_panel = self.build_testing_graph_panel()
        layout.addWidget(self.testing_graph_panel)
        layout.addStretch()
        return tab

    def create_history_tables_tab_interface(self):
        """Создает новую вкладку с таблицами истории"""
        tab = QWidget()
        layout = QVBoxLayout(tab)
        layout.setSpacing(15)
        
        # Заголовок
        header_label = QLabel("Таблицы истории")
        header_label.setStyleSheet(f"color: {APP_CONFIG['COLOR_SECONDARY']}; font-size: 16pt; font-weight: bold; padding: 10px;")
        header_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        header_label.setToolTip("Подробная история крашей и действий")
        layout.addWidget(header_label)
        
        # Таблицы истории
        tables_layout = QHBoxLayout()
        # Таблица истории крашей
        self.crash_history_table = CrashHistoryTable()
        self.crash_history_table.setToolTip("Таблица с последними 100 значениями краша")
        tables_layout.addWidget(self.crash_history_table)
        # Таблица истории действий
        self.actions_history_table = ActionsHistoryTable()
        self.actions_history_table.setToolTip("Таблица с последними 100 записями о действиях")
        tables_layout.addWidget(self.actions_history_table)
        layout.addLayout(tables_layout)
        layout.addStretch()
        return tab

    def build_testing_graph_panel(self):
        """Создает панель с графиком тестирования."""
        panel = QGroupBox("График профита (Тестирование)")
        layout = QVBoxLayout(panel)
        layout.setContentsMargins(10, 10, 10, 10)
        panel.setToolTip("График динамики изменения профита по ходу тестирования")
        
        self.testing_graph_figure = Figure(figsize=(8, 4), dpi=100)
        self.testing_graph_canvas = FigureCanvas(self.testing_graph_figure)
        self.testing_graph_ax = self.testing_graph_figure.add_subplot(111)
        self.testing_graph_ax.set_title("Профит по раундам", color=APP_CONFIG['COLOR_TEXT'], fontsize=12)
        self.testing_graph_ax.set_xlabel("Раунд", color=APP_CONFIG['COLOR_TEXT'])
        self.testing_graph_ax.set_ylabel("Профит", color=APP_CONFIG['COLOR_TEXT'])
        self.testing_graph_ax.grid(True, color=APP_CONFIG['COLOR_BORDER'])
        self.testing_graph_ax.set_facecolor(APP_CONFIG['COLOR_SURFACE'])
        self.testing_graph_figure.patch.set_facecolor(APP_CONFIG['COLOR_BACKGROUND'])
        self.testing_graph_ax.tick_params(colors=APP_CONFIG['COLOR_TEXT'])
        self.testing_graph_ax.spines['bottom'].set_color(APP_CONFIG['COLOR_BORDER'])
        self.testing_graph_ax.spines['top'].set_color(APP_CONFIG['COLOR_BORDER'])
        self.testing_graph_ax.spines['left'].set_color(APP_CONFIG['COLOR_BORDER'])
        self.testing_graph_ax.spines['right'].set_color(APP_CONFIG['COLOR_BORDER'])
        self.testing_graph_ax.title.set_color(APP_CONFIG['COLOR_TEXT'])
        self.testing_graph_ax.xaxis.label.set_color(APP_CONFIG['COLOR_TEXT'])
        self.testing_graph_ax.yaxis.label.set_color(APP_CONFIG['COLOR_TEXT'])
        layout.addWidget(self.testing_graph_canvas)
        return panel

    def start_testing(self):
        try:
            if hasattr(self, 'testing_worker') and self.testing_worker.isRunning():
                QMessageBox.warning(self, "Предупреждение", "Тестирование уже запущено.")
                return
            # Считываем настройки
            num_rounds = self.num_rounds_input.value()
            update_interval = self.update_interval_input.value()
            min_bet = self.min_bet_input.value()
            max_bet = self.max_bet_input.value()
            min_multiplier = self.min_multiplier_input.value()
            max_multiplier = self.max_multiplier_input.value()
            initial_balance = self.initial_balance_input.value()
            # Обновляем UI
            self.start_testing_button.setEnabled(False)
            self.stop_testing_button.setEnabled(True)
            self.lbl_testing_status.setText("Тестирование...")
            self.lbl_testing_status.setStyleSheet(f"color: {APP_CONFIG['COLOR_WARNING']}; font-weight: bold;")
            self.lbl_testing_rounds.setText(f"0 / {num_rounds:,}")
            # Создаем и запускаем поток тестирования
            self.testing_worker = TestingWorker(
                num_rounds, min_bet, max_bet, min_multiplier, max_multiplier,
                initial_balance, update_interval
            )
            self.testing_worker.batch_finished.connect(self.on_testing_batch_finished)
            self.testing_worker.testing_finished.connect(self.on_testing_finished)
            self.testing_worker.testing_stopped.connect(self.on_testing_stopped)
            self.testing_worker.error_occurred.connect(self.on_testing_error)
            self.testing_worker.plot_data_ready.connect(self.update_testing_graph)
            self.testing_worker.start()
        except Exception as e:
            QMessageBox.critical(self, "Ошибка", f"Произошла ошибка при запуске тестирования: {str(e)}")

    def stop_testing(self):
        if hasattr(self, 'testing_worker') and self.testing_worker.isRunning():
            self.testing_worker.stop()
            self.lbl_testing_status.setText("Остановка...")
            self.lbl_testing_status.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")

    def on_testing_batch_finished(self, batch_stats: dict):
        """Вызывается по завершении батча раундов."""
        try:
            # Обновляем текстовую статистику
            self.lbl_testing_rounds.setText(f"{batch_stats['processed_rounds']:,} / {batch_stats['total_rounds']:,}")
            # Профит
            profit_value = batch_stats['current_profit']
            self.lbl_testing_profit.setText(f"{profit_value:+.2f}")
            if profit_value >= 0:
                self.lbl_testing_profit.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold; font-size: 11pt;")
            else:
                self.lbl_testing_profit.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold; font-size: 11pt;")
            # ROI
            roi_value = batch_stats['current_roi']
            self.lbl_testing_roi.setText(f"{roi_value:+.2f}%")
            if roi_value >= 0:
                self.lbl_testing_roi.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold;")
            else:
                self.lbl_testing_roi.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")
            # Винрейт
            winrate_value = batch_stats['current_winrate']
            self.lbl_testing_winrate.setText(f"{winrate_value:.2f}%")
            if winrate_value >= 50:
                self.lbl_testing_winrate.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold;")
            else:
                self.lbl_testing_winrate.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")
            # Победы/Поражения
            self.lbl_testing_wins.setText(str(batch_stats['total_wins']))
            self.lbl_testing_losses.setText(str(batch_stats['total_losses']))
            # Серии
            self.lbl_testing_max_win_streak.setText(str(batch_stats['max_win_streak']))
            self.lbl_testing_max_loss_streak.setText(str(batch_stats['max_loss_streak']))
            # Обновляем таблицы (теперь они находятся на другой вкладке)
            self.crash_history_table.update_history(batch_stats['crash_history'])
            self.actions_history_table.update_history(batch_stats['actions_history'])
            # Обновляем график
            if len(batch_stats['plot_rounds']) > 1000:
                rounds_to_send = batch_stats['plot_rounds'][-1000:]
                profits_to_send = batch_stats['plot_profits'][-1000:]
            else:
                rounds_to_send = batch_stats['plot_rounds']
                profits_to_send = batch_stats['plot_profits']
            self.update_testing_graph(rounds_to_send, profits_to_send)
        except Exception as e:
            pass

    def update_testing_graph(self, rounds_list: list, profit_list: list):
        """Вызывается для обновления графика."""
        try:
            # Обновляем график
            self.testing_graph_ax.clear()
            if rounds_list and profit_list:
                self.testing_graph_ax.plot(rounds_list, profit_list,
                                         color=APP_CONFIG['COLOR_SECONDARY'], linewidth=1.5)
                # Заливка под графиком
                self.testing_graph_ax.fill_between(rounds_list, profit_list, alpha=0.3, color=APP_CONFIG['COLOR_SECONDARY'])
            self.testing_graph_ax.set_title("Профит по раундам", color=APP_CONFIG['COLOR_TEXT'], fontsize=12)
            self.testing_graph_ax.set_xlabel("Раунд", color=APP_CONFIG['COLOR_TEXT'])
            self.testing_graph_ax.set_ylabel("Профит", color=APP_CONFIG['COLOR_TEXT'])
            self.testing_graph_ax.grid(True, color=APP_CONFIG['COLOR_BORDER'])
            self.testing_graph_canvas.draw()
        except Exception as e:
            pass

    def on_testing_finished(self, final_stats: dict):
        """Вызывается по завершении тестирования."""
        try:
            self.start_testing_button.setEnabled(True)
            self.stop_testing_button.setEnabled(False)
            self.lbl_testing_status.setText("Завершено")
            self.lbl_testing_status.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold;")
            # Обновляем всю статистику
            # Профит
            profit_value = final_stats['total_profit']
            self.lbl_testing_profit.setText(f"{profit_value:+.2f}")
            if profit_value >= 0:
                self.lbl_testing_profit.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold; font-size: 12pt;")
            else:
                self.lbl_testing_profit.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold; font-size: 12pt;")
            # ROI
            roi_value = final_stats['roi']
            self.lbl_testing_roi.setText(f"{roi_value:+.2f}%")
            if roi_value >= 0:
                self.lbl_testing_roi.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold; font-size: 11pt;")
            else:
                self.lbl_testing_roi.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold; font-size: 11pt;")
            # Винрейт
            winrate_value = final_stats['winrate']
            self.lbl_testing_winrate.setText(f"{winrate_value:.2f}%")
            if winrate_value >= 50:
                self.lbl_testing_winrate.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold; font-size: 11pt;")
            else:
                self.lbl_testing_winrate.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold; font-size: 11pt;")
            self.lbl_testing_wins.setText(str(final_stats['total_wins']))
            self.lbl_testing_losses.setText(str(final_stats['total_losses']))
            self.lbl_testing_max_win_streak.setText(str(final_stats['max_win_streak']))
            self.lbl_testing_max_loss_streak.setText(str(final_stats['max_loss_streak']))
            # Обновляем таблицы и график (таблицы теперь на другой вкладке)
            self.crash_history_table.update_history(final_stats['crash_history'])
            self.actions_history_table.update_history(final_stats['actions_history'])
            self.update_testing_graph(final_stats['plot_rounds'], final_stats['plot_profits'])
            QMessageBox.information(self, "Тестирование завершено",
                                  f"Тестирование {final_stats['total_rounds']:,} раундов завершено.\n"
                                  f"Итоговый профит: {final_stats['total_profit']:+.2f}\n"
                                  f"ROI: {final_stats['roi']:+.2f}%\n"
                                  f"Винрейт: {final_stats['winrate']:.2f}%")
        except Exception as e:
            pass

    def on_testing_stopped(self):
        """Вызывается при остановке тестирования пользователем."""
        self.start_testing_button.setEnabled(True)
        self.stop_testing_button.setEnabled(False)
        self.lbl_testing_status.setText("Остановлено")
        self.lbl_testing_status.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")
        QMessageBox.information(self, "Тестирование остановлено", "Тестирование было остановлено пользователем.")

    def on_testing_error(self, error_msg: str):
        """Вызывается при ошибке в процессе тестирования."""
        self.start_testing_button.setEnabled(True)
        self.stop_testing_button.setEnabled(False)
        self.lbl_testing_status.setText("Ошибка")
        self.lbl_testing_status.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")
        QMessageBox.critical(self, "Ошибка тестирования", f"Произошла ошибка во время тестирования:\n{error_msg}")

    def save_settings(self):
        QMessageBox.information(self, "Настройки", "Настройки сохранены (имитация).")
        self.statusBar().showMessage("Настройки сохранены", 3000)

    def restore_default_settings(self):
        reply = QMessageBox.question(self, 'Сброс настроек', 'Вы уверены, что хотите сбросить все настройки?',
                                    QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
                                    QMessageBox.StandardButton.No)
        if reply == QMessageBox.StandardButton.Yes:
            try:
                # Сброс основных настроек
                self.initial_balance_input.setValue(0)
                self.min_bet_input.setValue(100)
                self.max_bet_input.setValue(100)
                self.min_multiplier_input.setValue(1.11)
                self.max_multiplier_input.setValue(1.11)
                self.statusBar().showMessage("Настройки сброшены", 3000)
            except Exception as e:
                QMessageBox.critical(self, "Ошибка", f"Произошла ошибка при сбросе настроек: {str(e)}")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

 

 

Crash.txt


Изменено пользователем yellyex

moonfangtopich написал 29.08.2019 в 14:57:
У вас недостаточно широкий кругозор, пацаны

Я странствия этого еблана видел в покерных топанах, а потом в таверне - это один из самых безумных людей на форуме. Я искренне надеялся, что его зов о помощи останется незамеченным, но нет, нашелся доброволец и вот уже три страницы мы пожинаем плоды

Поделиться сообщением


Ссылка на сообщение

ну если говорят что нет ошибки, значит нет ошибки, они же не просто так заменили прогеров за ними будущее 

scarppy понравилось это

Поделиться сообщением


Ссылка на сообщение
(изменено)
godspeed написал 10 минут назад:

ну если говорят что нет ошибки, значит нет ошибки, они же не просто так заменили прогеров за ними будущее 

 

Задача:

Мин/Макс ставка = 100

Мин/Макс множитель (автовывод) = 1.11

Начальный банк = 0. Но разрешено производить расчёты, если банк <= 0.

Если число Краша >= множитель, то выигрыш.

Выигрыш: банк = ставка * множитель - ставка.

Если число Краша < множитель, то проигрыш.

Проигрыш: банк = банк - ставка Нужно сыграть 1 миллион раундов.

 

Посчитай итоговый профит.

 

2025-08-16_00-49-00.png

2025-08-16_00-49-16.png

 

 

Тут ошибка либо у меня, либо у ИИ! Я уже не ебу что делать! Я сдаюсь! ГПТ 5 памаги! :)
 

Но ведь шанс того, что Краш будет больше или равно 1.11 составляет 90.09%.

 

Qwen3-Coder:

 

Вы абсолютно правы, и за это приношу свои извинения. В предыдущем расчете была допущена ошибка в интерпретации формулы и, как следствие, в вычислении вероятности.

Давайте пересчитаем правильно, используя формулу из кода generate_crash_number:

краш = 1.0 / (1.0 - U), где U — случайное число от 0 до 1.

Найдем U, при котором краш = 1.11:

1.11 = 1.0 / (1.0 - U)

1.0 - U = 1.0 / 1.11

U = 1.0 - (1.0 / 1.11) = 1.0 - 0.9009009... ≈ 0.0990991

Вероятность U < 0.0990991 равна 0.0990991.

Следовательно, вероятность краш >= 1.11 (выигрыш) равна P_win = 1 - 0.0990991 = 0.9009009, или примерно 90.09%.

Вероятность краш < 1.11 (проигрыш) равна P_lose = 0.0990991, или примерно 9.91%.

Теперь пересчитаем профит с правильными вероятностями:

Условия:

Количество раундов: N = 1,000,000

Ставка: S = 100 (фиксированная)

Множитель: M = 1.11 (фиксированный)

Чистый выигрыш за раунд: W = S * M - S = 100 * 1.11 - 100 = 11

Проигрыш за раунд: L = -S = -100

P_win = 0.9009009

P_lose = 0.0990991

Расчёт:

Ожидаемое количество выигрышей и проигрышей:

Ожидаемое_кол_во_выигрышей = N * P_win = 1,000,000 * 0.9009009 ≈ 900,901

Ожидаемое_кол_во_проигрышей = N * P_lose = 1,000,000 * 0.0990991 ≈ 99,099

Общий выигрыш:

Общий_выигрыш = Ожидаемое_кол_во_выигрышей * W = 900,901 * 11 = 9,909,911

Общий проигрыш (сумма всех потерянных ставок):

Общий_проигрыш = Ожидаемое_кол_во_проигрышей * S = 99,099 * 100 = 9,909,900

Итоговый профит:

Профит = Общий_выигрыш - Общий_проигрыш = 9,909,911 - 9,909,900 = 11

Правильный вывод:

При заданных условиях (ставка 100, автовывод 1.11, 1 миллион раундов) ожидаемый итоговый профит составляет примерно +11.

Спасибо за вашу внимательность!


Изменено пользователем yellyex

moonfangtopich написал 29.08.2019 в 14:57:
У вас недостаточно широкий кругозор, пацаны

Я странствия этого еблана видел в покерных топанах, а потом в таверне - это один из самых безумных людей на форуме. Я искренне надеялся, что его зов о помощи останется незамеченным, но нет, нашелся доброволец и вот уже три страницы мы пожинаем плоды

Поделиться сообщением


Ссылка на сообщение
yellyex написал 1 час назад:

Ребят помогите пожалуйста с кодом. python
Пробую создать аналог игры Краш (онлайн-казино)
С помощью ИИ написал простенький код для тестов, но у меня на дистанции не выдаёт реальное мат ожидание.
Я ставлю мин/макс множитель 1.11 (как в казино). ставка фиксированная мин/макс 100. Это только для тестов.
Делаю раза 3-4 по 1 миллион тестовых раундов и всё равно получаю положительное мат ожидание, но этого не может быть! Я должен сыграть в минус или в 0. Если я не решу эту проблему, то двигаться дальше я не смогу! Помогите пожалуйста!!!

Вот формула генерации числа Краша (без процента казино (чистая)). Может быть в ней ошибка?


def generate_crash_number():
    """Генерирует случайное число краша"""
    # Генерируем равномерно распределённое число U ∈ [0, 1)
    U = random.uniform(0, 1)
    # Вычисляем множитель по формуле
    if U >= 0.99999:  # Защита от деления на ноль
        crash_multiplier = 100000.00
    else:
        crash_multiplier = 1.0 / (1.0 - U)
    # Ограничиваем множитель диапазоном [1.00, 100000.00] и округляем до 2 знаков
    crash_multiplier = max(min(crash_multiplier, 100000.00), 1.00)
    return round(crash_multiplier, 2)

Вот ниже я прикреплю код.

 

Может у кого есть доступ к ГПТ 5? Спросите у него, что за фигня?
Qwen говорит, что ошибки в коде нет.
Что генерация и вычисления тоже верны!
А вот математика говорит, что профита 470-480 тысяч на 1 миллион раундов (ставка 100, множитель 1.11) просто не может быть.

 

  Код

import sys
import random
import math
import os
from datetime import datetime
from collections import deque
from typing import List, Tuple, Dict, Any
from PyQt6 import QtWidgets, QtCore, QtGui
from PyQt6.QtCore import QThread, pyqtSignal, QTimer, QDateTime, Qt, QTime
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QFormLayout,
    QLabel, QPushButton, QMessageBox, QGroupBox,
    QSpinBox, QDoubleSpinBox, QCheckBox, QTableWidget, QTableWidgetItem,
    QHeaderView, QTabWidget, QTimeEdit, QFrame, QScrollArea, QSizePolicy
)
import matplotlib
matplotlib.use('QtAgg')
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure

# --- APP_CONFIG ---
APP_CONFIG = {
    'APP_NAME': "Crash",
    'APP_NAME_VERSION': "(v1.0)",
    'STATS_FILE_NAME': "stats.txt",
    'COLOR_BACKGROUND': "#1e1e2d",
    'COLOR_SURFACE': "#2d2d44",
    'COLOR_SURFACE_LIGHT': "#3d3d54",
    'COLOR_TEXT': "#e0e0ff",
    'COLOR_BORDER': "#444466",
    'COLOR_PRIMARY': "#bb86fc",
    'COLOR_SECONDARY': "#03dac6",
    'COLOR_POSITIVE': "#00cc66",
    'COLOR_NEGATIVE': "#ff5555",
    'COLOR_WARNING': "#ffaa00",
    'COLOR_INFO': "#66b3ff",
    'TEXT_LABELS': {
        'app_title': "Crash",
        'save_settings': "Сохранить настройки",
        'reset_settings': "Сбросить настройки",
        'min_bet_label': "Минимальная ставка:",
        'max_bet_label': "Максимальная ставка:",
    },
}

# --- Crash Number Generator ---
def generate_crash_number():
    """Генерирует случайное число краша"""
    # Генерируем равномерно распределённое число U ∈ [0, 1)
    U = random.uniform(0, 1)
    # Вычисляем множитель по формуле
    if U >= 0.99999:  # Защита от деления на ноль
        crash_multiplier = 100000.00
    else:
        crash_multiplier = 1.0 / (1.0 - U)
    # Ограничиваем множитель диапазоном [1.00, 100000.00] и округляем до 2 знаков
    crash_multiplier = max(min(crash_multiplier, 100000.00), 1.00)
    return round(crash_multiplier, 2)

# --- Crash History Table (для отображения последних 100 значений) ---
class CrashHistoryTable(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.history = []
        self.init_ui()

    def init_ui(self):
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)
        # Заголовок
        header_layout = QHBoxLayout()
        header_label = QLabel("История последних значений краша")
        header_label.setStyleSheet(f"color: {APP_CONFIG['COLOR_SECONDARY']}; font-size: 12pt; font-weight: bold;")
        header_label.setToolTip("Последние 100 значений коэффициента краша")
        header_layout.addWidget(header_label)
        header_layout.addStretch()
        self.layout.addLayout(header_layout)
        # Таблица
        self.table = QTableWidget()
        self.table.setColumnCount(2)
        self.table.setHorizontalHeaderLabels(["#", "Краш"])
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
        self.table.verticalHeader().setVisible(False)
        self.table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
        self.table.setSelectionMode(QTableWidget.SelectionMode.NoSelection)
        self.table.setStyleSheet(f"""
            QTableWidget {{
                font-size: 9pt;
                color: {APP_CONFIG['COLOR_TEXT']};
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                gridline-color: {APP_CONFIG['COLOR_BORDER']};
                alternate-background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
            }}
            QTableWidget::item {{
                padding: 4px;
                border-bottom: 1px solid {APP_CONFIG['COLOR_BORDER']};
            }}
            QHeaderView::section {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                color: {APP_CONFIG['COLOR_TEXT']};
                padding: 6px;
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                font-weight: bold;
            }}
            QTableWidget::item:selected {{
                background-color: {APP_CONFIG['COLOR_PRIMARY']};
                color: white;
            }}
        """)
        self.table.setToolTip("Таблица с последними 100 значениями краша")
        self.layout.addWidget(self.table)

    def update_history(self, crash_history: List[float]):
        """Обновляет таблицу всей историей крашей"""
        self.history = crash_history[-100:] if len(crash_history) > 100 else crash_history
        self.table.setRowCount(len(self.history))
        for i, value in enumerate(reversed(self.history)):
            # Номер
            num_item = QTableWidgetItem(str(len(self.history) - i))
            num_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
            self.table.setItem(i, 0, num_item)
            # Значение краша
            crash_item = QTableWidgetItem(f"{value:.2f}x")
            crash_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
            # Цвет в зависимости от значения
            if value >= 10:
                crash_item.setForeground(QtGui.QColor(APP_CONFIG['COLOR_POSITIVE']))
                crash_item.setFont(QtGui.QFont("Arial", 9, QtGui.QFont.Weight.Bold))
            elif value >= 2:
                crash_item.setForeground(QtGui.QColor(APP_CONFIG['COLOR_SECONDARY']))
            else:
                crash_item.setForeground(QtGui.QColor(APP_CONFIG['COLOR_NEGATIVE']))
            self.table.setItem(i, 1, crash_item)
        # Выравнивание текста по центру
        for i in range(self.table.rowCount()):
            for j in range(self.table.columnCount()):
                item = self.table.item(i, j)
                if item:
                    item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)

# --- Actions History Table (для отображения последних 100 действий) ---
class ActionsHistoryTable(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.history = []
        self.init_ui()

    def init_ui(self):
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)
        # Заголовок
        header_layout = QHBoxLayout()
        header_label = QLabel("История последних действий")
        header_label.setStyleSheet(f"color: {APP_CONFIG['COLOR_SECONDARY']}; font-size: 12pt; font-weight: bold;")
        header_label.setToolTip("Последние 100 записей о действиях в тестировании")
        header_layout.addWidget(header_label)
        header_layout.addStretch()
        self.layout.addLayout(header_layout)
        # Таблица
        self.table = QTableWidget()
        self.table.setColumnCount(10)
        self.table.setHorizontalHeaderLabels([
            "Дата/Время", "Раунд", "Банк", "Ставка", "Множитель", 
            "Краш", "Результат", "Новый банк", "Профит", "ROI"
        ])
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
        self.table.verticalHeader().setVisible(False)
        self.table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
        self.table.setSelectionMode(QTableWidget.SelectionMode.NoSelection)
        self.table.setStyleSheet(f"""
            QTableWidget {{
                font-size: 8pt;
                color: {APP_CONFIG['COLOR_TEXT']};
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                gridline-color: {APP_CONFIG['COLOR_BORDER']};
                alternate-background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
            }}
            QTableWidget::item {{
                padding: 2px;
                border-bottom: 1px solid {APP_CONFIG['COLOR_BORDER']};
            }}
            QHeaderView::section {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                color: {APP_CONFIG['COLOR_TEXT']};
                padding: 4px;
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                font-weight: bold;
            }}
            QTableWidget::item:selected {{
                background-color: {APP_CONFIG['COLOR_PRIMARY']};
                color: white;
            }}
        """)
        self.table.setToolTip("Таблица с последними 100 записями о действиях в тестировании")
        self.layout.addWidget(self.table)

    def update_history(self, actions_history: List[Dict[str, Any]]):
        """Обновляет таблицу всей историей действий"""
        self.history = actions_history[-100:] if len(actions_history) > 100 else actions_history
        self.table.setRowCount(len(self.history))
        for i, data in enumerate(reversed(self.history)):
            timestamp = data['timestamp'].strftime("%H:%M:%S.%f")[:-3]  # Обрезаем до миллисекунд
            self.table.setItem(i, 0, QTableWidgetItem(timestamp))
            self.table.setItem(i, 1, QTableWidgetItem(str(data['round_num'])))
            self.table.setItem(i, 2, QTableWidgetItem(f"{data['balance_before']:.2f}"))
            self.table.setItem(i, 3, QTableWidgetItem(str(data['bet'])))
            self.table.setItem(i, 4, QTableWidgetItem(f"{data['multiplier']:.2f}"))
            self.table.setItem(i, 5, QTableWidgetItem(f"{data['crash_value']:.2f}"))
            # Результат
            if data['win']:
                result_text = f"+{data['reward']:.2f}"
                result_item = QTableWidgetItem(result_text)
                result_item.setForeground(QtGui.QColor(APP_CONFIG['COLOR_POSITIVE']))
                result_item.setFont(QtGui.QFont("Arial", 8, QtGui.QFont.Weight.Bold))
            else:
                result_text = f"-{data['bet']:.2f}"
                result_item = QTableWidgetItem(result_text)
                result_item.setForeground(QtGui.QColor(APP_CONFIG['COLOR_NEGATIVE']))
                result_item.setFont(QtGui.QFont("Arial", 8, QtGui.QFont.Weight.Bold))
            self.table.setItem(i, 6, result_item)
            self.table.setItem(i, 7, QTableWidgetItem(f"{data['balance_after']:.2f}"))
            self.table.setItem(i, 8, QTableWidgetItem(f"{data['profit']:.2f}"))
            self.table.setItem(i, 9, QTableWidgetItem(f"{data['roi']:.2f}%"))
        # Выравнивание текста по центру
        for i in range(self.table.rowCount()):
            for j in range(self.table.columnCount()):
                item = self.table.item(i, j)
                if item:
                    item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)

# --- Testing Thread ---
class TestingWorker(QThread):
    # Сигналы для обновления GUI
    batch_finished = pyqtSignal(dict)  # Передаем данные батча
    testing_finished = pyqtSignal(dict)  # итоговая статистика
    testing_stopped = pyqtSignal()
    error_occurred = pyqtSignal(str)
    plot_data_ready = pyqtSignal(list, list)  # данные для графика

    def __init__(self, num_rounds: int, min_bet: int, max_bet: int, 
                 min_multiplier: float, max_multiplier: float, 
                 initial_balance: float, update_interval: int):
        super().__init__()
        self.num_rounds = num_rounds
        self.min_bet = min_bet
        self.max_bet = max_bet
        self.min_multiplier = min_multiplier
        self.max_multiplier = max_multiplier
        self.initial_balance = initial_balance
        self.update_interval = update_interval  # 0 = только в конце
        self._is_running = True
        self.balance = initial_balance
        self.crash_history = []
        self.actions_history = []
        self.testing_rounds_data = []
        self.testing_profit_data = []
        self.total_wins = 0
        self.total_losses = 0
        self.current_win_streak = 0
        self.current_loss_streak = 0
        self.max_win_streak = 0
        self.max_loss_streak = 0
        self.total_invested = 0  # Для правильного расчета ROI
        self.processed_rounds = 0

    def stop(self):
        self._is_running = False

    def run(self):
        try:
            batch_data = {
                'rounds': [],
                'crash_values': [],
                'bets': [],
                'multipliers': [],
                'wins': [],
                'rewards': [],
                'balances_before': [],
                'balances_after': [],
                'profits': [],
                'rois': [],
                'winrates': [],
                'timestamps': [],
                'total_wins_list': [],
                'total_losses_list': [],
                'max_win_streaks': [],
                'max_loss_streaks': []
            }
            for i in range(1, self.num_rounds + 1):
                if not self._is_running:
                    self.testing_stopped.emit()
                    return
                # 1. Генерация краша
                crash_value = generate_crash_number()
                self.crash_history.append(crash_value)
                # 2. Генерация случайной ставки и множителя
                bet = random.randint(self.min_bet, self.max_bet)
                multiplier = round(random.uniform(self.min_multiplier, self.max_multiplier), 2)
                # 3. Симуляция результата
                balance_before = self.balance
                win = crash_value >= multiplier
                reward = 0
                if bet > 0:
                    self.total_invested += bet  # Увеличиваем общую сумму ставок
                    self.balance -= bet
                    if win:
                        win_amount = bet * multiplier
                        self.balance += win_amount
                        reward = win_amount - bet  # Чистый выигрыш
                        self.total_wins += 1
                        self.current_win_streak += 1
                        self.current_loss_streak = 0
                        if self.current_win_streak > self.max_win_streak:
                            self.max_win_streak = self.current_win_streak
                    else:
                        reward = -bet  # Потеря ставки
                        self.total_losses += 1
                        self.current_loss_streak += 1
                        self.current_win_streak = 0
                        if self.current_loss_streak > self.max_loss_streak:
                            self.max_loss_streak = self.current_loss_streak
                profit = self.balance - self.initial_balance
                # Исправленный расчет ROI
                roi = (profit / self.total_invested * 100) if self.total_invested > 0 else 0
                winrate = (self.total_wins / i * 100) if i > 0 else 0  # Winrate от общего количества раундов
                # 4. Сохранение данных для истории действий
                action_data = {
                    'round_num': i,
                    'crash_value': crash_value,
                    'bet': bet,
                    'multiplier': multiplier,
                    'win': win,
                    'reward': reward,
                    'balance_before': balance_before,
                    'balance_after': self.balance,
                    'profit': profit,
                    'roi': roi,
                    'winrate': winrate,
                    'timestamp': datetime.now(),
                    'total_wins': self.total_wins,
                    'total_losses': self.total_losses,
                    'max_win_streak': self.max_win_streak,
                    'max_loss_streak': self.max_loss_streak
                }
                self.actions_history.append(action_data)
                # 5. Обновление данных для графика
                self.testing_rounds_data.append(i)
                self.testing_profit_data.append(profit)
                # 6. Подготовка данных для батча (если нужно обновление)
                if self.update_interval > 0:
                    batch_data['rounds'].append(i)
                    batch_data['crash_values'].append(crash_value)
                    batch_data['bets'].append(bet)
                    batch_data['multipliers'].append(multiplier)
                    batch_data['wins'].append(win)
                    batch_data['rewards'].append(reward)
                    batch_data['balances_before'].append(balance_before)
                    batch_data['balances_after'].append(self.balance)
                    batch_data['profits'].append(profit)
                    batch_data['rois'].append(roi)
                    batch_data['winrates'].append(winrate)
                    batch_data['timestamps'].append(datetime.now())
                    batch_data['total_wins_list'].append(self.total_wins)
                    batch_data['total_losses_list'].append(self.total_losses)
                    batch_data['max_win_streaks'].append(self.max_win_streak)
                    batch_data['max_loss_streaks'].append(self.max_loss_streak)
                self.processed_rounds += 1
                # 7. Эмит сигналов при достижении интервала обновления
                if self.update_interval > 0 and self.processed_rounds % self.update_interval == 0:
                    batch_stats = {
                        'processed_rounds': self.processed_rounds,
                        'total_rounds': self.num_rounds,
                        'current_balance': self.balance,
                        'current_profit': profit,
                        'current_roi': roi,
                        'current_winrate': winrate,
                        'total_wins': self.total_wins,
                        'total_losses': self.total_losses,
                        'max_win_streak': self.max_win_streak,
                        'max_loss_streak': self.max_loss_streak,
                        'crash_history': self.crash_history.copy(),
                        'actions_history': self.actions_history.copy(),
                        'plot_rounds': self.testing_rounds_data.copy(),
                        'plot_profits': self.testing_profit_data.copy()
                    }
                    self.batch_finished.emit(batch_stats)
                    # Очищаем батч данные
                    batch_data = {
                        'rounds': [],
                        'crash_values': [],
                        'bets': [],
                        'multipliers': [],
                        'wins': [],
                        'rewards': [],
                        'balances_before': [],
                        'balances_after': [],
                        'profits': [],
                        'rois': [],
                        'winrates': [],
                        'timestamps': [],
                        'total_wins_list': [],
                        'total_losses_list': [],
                        'max_win_streaks': [],
                        'max_loss_streaks': []
                    }
                # Небольшая задержка только для очень маленьких интервалов
                if self.update_interval > 0 and self.update_interval < 10:
                    self.msleep(1)
            # Тестирование завершено
            final_stats = {
                'total_rounds': self.num_rounds,
                'final_balance': self.balance,
                'total_profit': self.balance - self.initial_balance,
                'roi': ((self.balance - self.initial_balance) / self.total_invested * 100) if self.total_invested > 0 else 0,
                'winrate': (self.total_wins / self.num_rounds * 100) if self.num_rounds > 0 else 0,
                'total_wins': self.total_wins,
                'total_losses': self.total_losses,
                'max_win_streak': self.max_win_streak,
                'max_loss_streak': self.max_loss_streak,
                'crash_history': self.crash_history,
                'actions_history': self.actions_history,
                'plot_rounds': self.testing_rounds_data,
                'plot_profits': self.testing_profit_data
            }
            self.testing_finished.emit(final_stats)
        except Exception as e:
            self.error_occurred.emit(str(e))

# --- Main Window ---
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle(f"{APP_CONFIG['APP_NAME']} {APP_CONFIG['APP_NAME_VERSION']}")
        self.setGeometry(100, 100, 1400, 900)
        self.setStyleSheet(f"""
            QMainWindow {{
                background-color: {APP_CONFIG['COLOR_BACKGROUND']};
            }}
            QWidget {{
                background-color: {APP_CONFIG['COLOR_BACKGROUND']};
                color: {APP_CONFIG['COLOR_TEXT']};
                font-family: 'Segoe UI', Arial, sans-serif;
            }}
            QLabel {{
                color: {APP_CONFIG['COLOR_TEXT']};
                background-color: transparent;
            }}
            QGroupBox {{
                border: 2px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 8px;
                margin-top: 10px;
                padding: 10px;
                font-weight: bold;
                color: {APP_CONFIG['COLOR_TEXT']};
                background-color: {APP_CONFIG['COLOR_SURFACE']};
            }}
            QGroupBox::title {{
                subline-offset: -15px;
                padding: 0px 5px 0px 5px;
                color: {APP_CONFIG['COLOR_SECONDARY']};
            }}
            QPushButton {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                padding: 8px 15px;
                font-weight: bold;
                color: {APP_CONFIG['COLOR_TEXT']};
                min-width: 100px;
            }}
            QPushButton:hover {{
                background-color: {APP_CONFIG['COLOR_PRIMARY']};
                border: 1px solid {APP_CONFIG['COLOR_SECONDARY']};
            }}
            QPushButton:pressed {{
                background-color: {APP_CONFIG['COLOR_SECONDARY']};
            }}
            QPushButton:disabled {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                color: #888888;
                border: 1px solid #555555;
            }}
            QSpinBox, QDoubleSpinBox {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                color: {APP_CONFIG['COLOR_TEXT']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 4px;
                padding: 4px;
                selection-background-color: {APP_CONFIG['COLOR_PRIMARY']};
            }}
            QSpinBox::up-button, QDoubleSpinBox::up-button {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                border: none;
                width: 16px;
            }}
            QSpinBox::down-button, QDoubleSpinBox::down-button {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                border: none;
                width: 16px;
            }}
            QCheckBox {{
                color: {APP_CONFIG['COLOR_TEXT']};
            }}
            QTabWidget::pane {{
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                background-color: {APP_CONFIG['COLOR_BACKGROUND']};
            }}
            QTabBar::tab {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                color: {APP_CONFIG['COLOR_TEXT']};
                padding: 8px 15px;
                margin-right: 2px;
                border-top-left-radius: 4px;
                border-top-right-radius: 4px;
            }}
            QTabBar::tab:selected {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                color: {APP_CONFIG['COLOR_SECONDARY']};
                font-weight: bold;
            }}
            QTabBar::tab:hover {{
                background-color: {APP_CONFIG['COLOR_PRIMARY']};
            }}
            QHeaderView::section {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                color: {APP_CONFIG['COLOR_TEXT']};
                padding: 6px;
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                font-weight: bold;
            }}
        """)
        # Инициализация UI
        self.init_ui()

    def init_ui(self):
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        self.main_layout = QVBoxLayout(central_widget)
        self.main_layout.setSpacing(10)
        self.setup_main_tabs()

    def setup_main_tabs(self):
        self.tabs = QTabWidget()
        self.tab_main = self.create_main_tab_interface()
        self.tab_settings = self.create_settings_tab_interface()
        self.tab_testing = self.create_testing_tab_interface()
        self.tab_history = self.create_history_tables_tab_interface()  # Новая вкладка
        self.tabs.addTab(self.tab_main, "Главная")
        self.tabs.addTab(self.tab_settings, "Настройки")
        self.tabs.addTab(self.tab_testing, "Тестирование")
        self.tabs.addTab(self.tab_history, "Таблицы (История)")  # Добавляем новую вкладку
        self.main_layout.addWidget(self.tabs)

    def create_main_tab_interface(self):
        tab = QWidget()
        layout = QVBoxLayout()
        layout.setSpacing(15)
        tab.setLayout(layout)
        # Вкладка теперь полностью пустая, как требовалось
        layout.addStretch()
        return tab

    def create_settings_tab_interface(self):
        tab = QWidget()
        layout = QVBoxLayout(tab)
        layout.setSpacing(15)
        # Заголовок
        header_label = QLabel("Настройки тестирования")
        header_label.setStyleSheet(f"color: {APP_CONFIG['COLOR_SECONDARY']}; font-size: 16pt; font-weight: bold; padding: 10px;")
        header_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        header_label.setToolTip("Настройка параметров для симуляции игры Crash")
        layout.addWidget(header_label)
        # Основные настройки
        main_settings_group = QGroupBox("Основные параметры")
        main_settings_layout = QFormLayout()
        main_settings_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight)
        main_settings_layout.setHorizontalSpacing(20)
        main_settings_layout.setVerticalSpacing(10)
        main_settings_group.setToolTip("Основные параметры симуляции")

        # Начальный банк
        self.initial_balance_input = QSpinBox()
        self.initial_balance_input.setRange(0, 10000000)
        self.initial_balance_input.setValue(0)
        self.initial_balance_input.setGroupSeparatorShown(True)
        self.initial_balance_input.setToolTip("Начальный баланс для тестирования")
        main_settings_layout.addRow(QLabel("Начальный банк:"), self.initial_balance_input)

        # Минимальная ставка
        self.min_bet_input = QSpinBox()
        self.min_bet_input.setRange(0, 10000000)
        self.min_bet_input.setValue(100)
        self.min_bet_input.setGroupSeparatorShown(True)
        self.min_bet_input.setToolTip("Минимальный размер ставки для одной игры")
        main_settings_layout.addRow(QLabel(APP_CONFIG['TEXT_LABELS']['min_bet_label']), self.min_bet_input)

        # Максимальная ставка
        self.max_bet_input = QSpinBox()
        self.max_bet_input.setRange(0, 10000000)
        self.max_bet_input.setValue(100)
        self.max_bet_input.setGroupSeparatorShown(True)
        self.max_bet_input.setToolTip("Максимальный размер ставки для одной игры")
        main_settings_layout.addRow(QLabel("Максимальная ставка:"), self.max_bet_input)

        # Минимальный множитель
        self.min_multiplier_input = QDoubleSpinBox()
        self.min_multiplier_input.setRange(1.01, 100.0)
        self.min_multiplier_input.setSingleStep(0.1)
        self.min_multiplier_input.setDecimals(2)
        self.min_multiplier_input.setValue(1.11)
        self.min_multiplier_input.setToolTip("Минимальный множитель для кэшаута")
        main_settings_layout.addRow(QLabel("Мин множитель:"), self.min_multiplier_input)

        # Максимальный множитель
        self.max_multiplier_input = QDoubleSpinBox()
        self.max_multiplier_input.setRange(1.01, 100.0)
        self.max_multiplier_input.setSingleStep(0.1)
        self.max_multiplier_input.setDecimals(2)
        self.max_multiplier_input.setValue(1.11)
        self.max_multiplier_input.setToolTip("Максимальный множитель для кэшаута")
        main_settings_layout.addRow(QLabel("Макс множитель:"), self.max_multiplier_input)

        main_settings_group.setLayout(main_settings_layout)
        layout.addWidget(main_settings_group)
        # Кнопки действий
        buttons_layout = QHBoxLayout()
        buttons_layout.addStretch()
        self.save_settings_button = QPushButton(APP_CONFIG['TEXT_LABELS']['save_settings'])
        self.save_settings_button.clicked.connect(self.save_settings)
        self.save_settings_button.setStyleSheet(f"""
            QPushButton {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                padding: 10px 20px;
                font-weight: bold;
                color: {APP_CONFIG['COLOR_TEXT']};
                min-width: 150px;
            }}
            QPushButton:hover {{
                background-color: {APP_CONFIG['COLOR_POSITIVE']};
                border: 1px solid {APP_CONFIG['COLOR_SECONDARY']};
            }}
        """)
        self.save_settings_button.setToolTip("Сохранить текущие настройки")
        buttons_layout.addWidget(self.save_settings_button)
        self.reset_settings_button = QPushButton(APP_CONFIG['TEXT_LABELS']['reset_settings'])
        self.reset_settings_button.clicked.connect(self.restore_default_settings)
        self.reset_settings_button.setStyleSheet(f"""
            QPushButton {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                padding: 10px 20px;
                font-weight: bold;
                color: {APP_CONFIG['COLOR_TEXT']};
                min-width: 150px;
            }}
            QPushButton:hover {{
                background-color: {APP_CONFIG['COLOR_WARNING']};
                border: 1px solid {APP_CONFIG['COLOR_SECONDARY']};
            }}
        """)
        self.reset_settings_button.setToolTip("Сбросить все настройки к значениям по умолчанию")
        buttons_layout.addWidget(self.reset_settings_button)
        buttons_layout.addStretch()
        layout.addLayout(buttons_layout)
        layout.addStretch()
        return tab

    def create_testing_tab_interface(self):
        tab = QWidget()
        layout = QVBoxLayout(tab)
        layout.setSpacing(15)
        # Заголовок
        header_label = QLabel("Тестирование стратегии")
        header_label.setStyleSheet(f"color: {APP_CONFIG['COLOR_SECONDARY']}; font-size: 16pt; font-weight: bold; padding: 10px;")
        header_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        header_label.setToolTip("Запуск и управление симуляцией игры Crash")
        layout.addWidget(header_label)
        # Панель управления тестированием
        control_group = QGroupBox("Управление тестированием")
        control_layout = QFormLayout()
        control_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight)
        control_layout.setHorizontalSpacing(20)
        control_layout.setVerticalSpacing(10)
        control_group.setToolTip("Параметры и управление процессом тестирования")

        self.num_rounds_input = QSpinBox()
        self.num_rounds_input.setRange(1, 100000000)
        self.num_rounds_input.setValue(1000000)
        self.num_rounds_input.setGroupSeparatorShown(True)
        self.num_rounds_input.setToolTip("Общее количество раундов для симуляции")
        control_layout.addRow(QLabel("Количество раундов:"), self.num_rounds_input)

        self.update_interval_input = QSpinBox()
        self.update_interval_input.setRange(0, 100000000)
        self.update_interval_input.setValue(0)
        self.update_interval_input.setGroupSeparatorShown(True)
        self.update_interval_input.setToolTip("Интервал обновления интерфейса в раундах (0 = только в конце)")
        control_layout.addRow(QLabel("Интервал обновления интерфейса:"), self.update_interval_input)

        # Кнопки
        buttons_layout = QHBoxLayout()
        buttons_layout.addStretch()
        self.start_testing_button = QPushButton("Начать тестирование")
        self.start_testing_button.clicked.connect(self.start_testing)
        self.start_testing_button.setStyleSheet(f"""
            QPushButton {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                padding: 10px 20px;
                font-weight: bold;
                color: {APP_CONFIG['COLOR_TEXT']};
                min-width: 180px;
            }}
            QPushButton:hover {{
                background-color: {APP_CONFIG['COLOR_POSITIVE']};
                border: 1px solid {APP_CONFIG['COLOR_SECONDARY']};
            }}
        """)
        self.start_testing_button.setToolTip("Запустить симуляцию игры с текущими настройками")
        buttons_layout.addWidget(self.start_testing_button)

        self.stop_testing_button = QPushButton("Завершить тестирование")
        self.stop_testing_button.clicked.connect(self.stop_testing)
        self.stop_testing_button.setEnabled(False)
        self.stop_testing_button.setStyleSheet(f"""
            QPushButton {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                padding: 10px 20px;
                font-weight: bold;
                color: {APP_CONFIG['COLOR_TEXT']};
                min-width: 180px;
            }}
            QPushButton:hover {{
                background-color: {APP_CONFIG['COLOR_NEGATIVE']};
                border: 1px solid {APP_CONFIG['COLOR_SECONDARY']};
            }}
            QPushButton:disabled {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                color: #888888;
                border: 1px solid #555555;
            }}
        """)
        self.stop_testing_button.setToolTip("Остановить текущую симуляцию")
        buttons_layout.addWidget(self.stop_testing_button)
        buttons_layout.addStretch()
        control_layout.addRow(buttons_layout)
        control_group.setLayout(control_layout)
        layout.addWidget(control_group)

        # Панель статистики тестирования
        self.testing_stats_group = QGroupBox("Статистика тестирования")
        stats_layout = QFormLayout()
        stats_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight)
        stats_layout.setHorizontalSpacing(20)
        stats_layout.setVerticalSpacing(8)
        self.testing_stats_group.setToolTip("Текущие результаты симуляции")

        # Статус
        self.lbl_testing_status = QLabel("Ожидание запуска...")
        self.lbl_testing_status.setStyleSheet(f"color: {APP_CONFIG['COLOR_INFO']}; font-weight: bold;")
        self.lbl_testing_status.setToolTip("Текущий статус тестирования")
        stats_layout.addRow(QLabel("Статус:"), self.lbl_testing_status)

        # Прогресс
        self.lbl_testing_rounds = QLabel("0 / 0")
        self.lbl_testing_rounds.setStyleSheet(f"color: {APP_CONFIG['COLOR_SECONDARY']}; font-weight: bold;")
        self.lbl_testing_rounds.setToolTip("Прогресс выполнения тестирования")
        stats_layout.addRow(QLabel("Прогресс:"), self.lbl_testing_rounds)

        # Профит
        self.lbl_testing_profit = QLabel("0.00")
        self.lbl_testing_profit.setStyleSheet(f"color: {APP_CONFIG['COLOR_INFO']}; font-weight: bold;")
        self.lbl_testing_profit.setToolTip("Текущий профит (доход/убыток)")
        stats_layout.addRow(QLabel("Профит:"), self.lbl_testing_profit)

        # ROI
        self.lbl_testing_roi = QLabel("0.00%")
        self.lbl_testing_roi.setStyleSheet(f"color: {APP_CONFIG['COLOR_INFO']}; font-weight: bold;")
        self.lbl_testing_roi.setToolTip("Возврат на инвестиции (ROI) в процентах")
        stats_layout.addRow(QLabel("ROI:"), self.lbl_testing_roi)

        # Винрейт
        self.lbl_testing_winrate = QLabel("0.00%")
        self.lbl_testing_winrate.setStyleSheet(f"color: {APP_CONFIG['COLOR_INFO']}; font-weight: bold;")
        self.lbl_testing_winrate.setToolTip("Процент выигранных раундов")
        stats_layout.addRow(QLabel("Винрейт:"), self.lbl_testing_winrate)

        # --- ИЗМЕНЕНИЯ НАЧАЛИСЬ ЗДЕСЬ ---
        # Победы
        self.lbl_testing_wins = QLabel("0")
        self.lbl_testing_wins.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold;")
        self.lbl_testing_wins.setToolTip("Количество выигранных раундов")
        stats_layout.addRow(QLabel("Победы:"), self.lbl_testing_wins)
        # Поражения
        self.lbl_testing_losses = QLabel("0")
        self.lbl_testing_losses.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")
        self.lbl_testing_losses.setToolTip("Количество проигранных раундов")
        stats_layout.addRow(QLabel("Поражения:"), self.lbl_testing_losses)
        # Макс. серия побед
        self.lbl_testing_max_win_streak = QLabel("0")
        self.lbl_testing_max_win_streak.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold;")
        self.lbl_testing_max_win_streak.setToolTip("Максимальная серия побед подряд за всё время тестирования")
        stats_layout.addRow(QLabel("Макс. серия побед (за всё время):"), self.lbl_testing_max_win_streak)
        # Макс. серия поражений
        self.lbl_testing_max_loss_streak = QLabel("0")
        self.lbl_testing_max_loss_streak.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")
        self.lbl_testing_max_loss_streak.setToolTip("Максимальная серия поражений подряд за всё время тестирования")
        stats_layout.addRow(QLabel("Макс. серия поражений (за всё время):"), self.lbl_testing_max_loss_streak)
        # --- ИЗМЕНЕНИЯ ЗАКОНЧИЛИСЬ ---
        
        self.testing_stats_group.setLayout(stats_layout)
        layout.addWidget(self.testing_stats_group)

        # График тестирования
        self.testing_graph_panel = self.build_testing_graph_panel()
        layout.addWidget(self.testing_graph_panel)
        layout.addStretch()
        return tab

    def create_history_tables_tab_interface(self):
        """Создает новую вкладку с таблицами истории"""
        tab = QWidget()
        layout = QVBoxLayout(tab)
        layout.setSpacing(15)
        
        # Заголовок
        header_label = QLabel("Таблицы истории")
        header_label.setStyleSheet(f"color: {APP_CONFIG['COLOR_SECONDARY']}; font-size: 16pt; font-weight: bold; padding: 10px;")
        header_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        header_label.setToolTip("Подробная история крашей и действий")
        layout.addWidget(header_label)
        
        # Таблицы истории
        tables_layout = QHBoxLayout()
        # Таблица истории крашей
        self.crash_history_table = CrashHistoryTable()
        self.crash_history_table.setToolTip("Таблица с последними 100 значениями краша")
        tables_layout.addWidget(self.crash_history_table)
        # Таблица истории действий
        self.actions_history_table = ActionsHistoryTable()
        self.actions_history_table.setToolTip("Таблица с последними 100 записями о действиях")
        tables_layout.addWidget(self.actions_history_table)
        layout.addLayout(tables_layout)
        layout.addStretch()
        return tab

    def build_testing_graph_panel(self):
        """Создает панель с графиком тестирования."""
        panel = QGroupBox("График профита (Тестирование)")
        layout = QVBoxLayout(panel)
        layout.setContentsMargins(10, 10, 10, 10)
        panel.setToolTip("График динамики изменения профита по ходу тестирования")
        
        self.testing_graph_figure = Figure(figsize=(8, 4), dpi=100)
        self.testing_graph_canvas = FigureCanvas(self.testing_graph_figure)
        self.testing_graph_ax = self.testing_graph_figure.add_subplot(111)
        self.testing_graph_ax.set_title("Профит по раундам", color=APP_CONFIG['COLOR_TEXT'], fontsize=12)
        self.testing_graph_ax.set_xlabel("Раунд", color=APP_CONFIG['COLOR_TEXT'])
        self.testing_graph_ax.set_ylabel("Профит", color=APP_CONFIG['COLOR_TEXT'])
        self.testing_graph_ax.grid(True, color=APP_CONFIG['COLOR_BORDER'])
        self.testing_graph_ax.set_facecolor(APP_CONFIG['COLOR_SURFACE'])
        self.testing_graph_figure.patch.set_facecolor(APP_CONFIG['COLOR_BACKGROUND'])
        self.testing_graph_ax.tick_params(colors=APP_CONFIG['COLOR_TEXT'])
        self.testing_graph_ax.spines['bottom'].set_color(APP_CONFIG['COLOR_BORDER'])
        self.testing_graph_ax.spines['top'].set_color(APP_CONFIG['COLOR_BORDER'])
        self.testing_graph_ax.spines['left'].set_color(APP_CONFIG['COLOR_BORDER'])
        self.testing_graph_ax.spines['right'].set_color(APP_CONFIG['COLOR_BORDER'])
        self.testing_graph_ax.title.set_color(APP_CONFIG['COLOR_TEXT'])
        self.testing_graph_ax.xaxis.label.set_color(APP_CONFIG['COLOR_TEXT'])
        self.testing_graph_ax.yaxis.label.set_color(APP_CONFIG['COLOR_TEXT'])
        layout.addWidget(self.testing_graph_canvas)
        return panel

    def start_testing(self):
        try:
            if hasattr(self, 'testing_worker') and self.testing_worker.isRunning():
                QMessageBox.warning(self, "Предупреждение", "Тестирование уже запущено.")
                return
            # Считываем настройки
            num_rounds = self.num_rounds_input.value()
            update_interval = self.update_interval_input.value()
            min_bet = self.min_bet_input.value()
            max_bet = self.max_bet_input.value()
            min_multiplier = self.min_multiplier_input.value()
            max_multiplier = self.max_multiplier_input.value()
            initial_balance = self.initial_balance_input.value()
            # Обновляем UI
            self.start_testing_button.setEnabled(False)
            self.stop_testing_button.setEnabled(True)
            self.lbl_testing_status.setText("Тестирование...")
            self.lbl_testing_status.setStyleSheet(f"color: {APP_CONFIG['COLOR_WARNING']}; font-weight: bold;")
            self.lbl_testing_rounds.setText(f"0 / {num_rounds:,}")
            # Создаем и запускаем поток тестирования
            self.testing_worker = TestingWorker(
                num_rounds, min_bet, max_bet, min_multiplier, max_multiplier,
                initial_balance, update_interval
            )
            self.testing_worker.batch_finished.connect(self.on_testing_batch_finished)
            self.testing_worker.testing_finished.connect(self.on_testing_finished)
            self.testing_worker.testing_stopped.connect(self.on_testing_stopped)
            self.testing_worker.error_occurred.connect(self.on_testing_error)
            self.testing_worker.plot_data_ready.connect(self.update_testing_graph)
            self.testing_worker.start()
        except Exception as e:
            QMessageBox.critical(self, "Ошибка", f"Произошла ошибка при запуске тестирования: {str(e)}")

    def stop_testing(self):
        if hasattr(self, 'testing_worker') and self.testing_worker.isRunning():
            self.testing_worker.stop()
            self.lbl_testing_status.setText("Остановка...")
            self.lbl_testing_status.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")

    def on_testing_batch_finished(self, batch_stats: dict):
        """Вызывается по завершении батча раундов."""
        try:
            # Обновляем текстовую статистику
            self.lbl_testing_rounds.setText(f"{batch_stats['processed_rounds']:,} / {batch_stats['total_rounds']:,}")
            # Профит
            profit_value = batch_stats['current_profit']
            self.lbl_testing_profit.setText(f"{profit_value:+.2f}")
            if profit_value >= 0:
                self.lbl_testing_profit.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold; font-size: 11pt;")
            else:
                self.lbl_testing_profit.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold; font-size: 11pt;")
            # ROI
            roi_value = batch_stats['current_roi']
            self.lbl_testing_roi.setText(f"{roi_value:+.2f}%")
            if roi_value >= 0:
                self.lbl_testing_roi.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold;")
            else:
                self.lbl_testing_roi.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")
            # Винрейт
            winrate_value = batch_stats['current_winrate']
            self.lbl_testing_winrate.setText(f"{winrate_value:.2f}%")
            if winrate_value >= 50:
                self.lbl_testing_winrate.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold;")
            else:
                self.lbl_testing_winrate.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")
            # Победы/Поражения
            self.lbl_testing_wins.setText(str(batch_stats['total_wins']))
            self.lbl_testing_losses.setText(str(batch_stats['total_losses']))
            # Серии
            self.lbl_testing_max_win_streak.setText(str(batch_stats['max_win_streak']))
            self.lbl_testing_max_loss_streak.setText(str(batch_stats['max_loss_streak']))
            # Обновляем таблицы (теперь они находятся на другой вкладке)
            self.crash_history_table.update_history(batch_stats['crash_history'])
            self.actions_history_table.update_history(batch_stats['actions_history'])
            # Обновляем график
            if len(batch_stats['plot_rounds']) > 1000:
                rounds_to_send = batch_stats['plot_rounds'][-1000:]
                profits_to_send = batch_stats['plot_profits'][-1000:]
            else:
                rounds_to_send = batch_stats['plot_rounds']
                profits_to_send = batch_stats['plot_profits']
            self.update_testing_graph(rounds_to_send, profits_to_send)
        except Exception as e:
            pass

    def update_testing_graph(self, rounds_list: list, profit_list: list):
        """Вызывается для обновления графика."""
        try:
            # Обновляем график
            self.testing_graph_ax.clear()
            if rounds_list and profit_list:
                self.testing_graph_ax.plot(rounds_list, profit_list,
                                         color=APP_CONFIG['COLOR_SECONDARY'], linewidth=1.5)
                # Заливка под графиком
                self.testing_graph_ax.fill_between(rounds_list, profit_list, alpha=0.3, color=APP_CONFIG['COLOR_SECONDARY'])
            self.testing_graph_ax.set_title("Профит по раундам", color=APP_CONFIG['COLOR_TEXT'], fontsize=12)
            self.testing_graph_ax.set_xlabel("Раунд", color=APP_CONFIG['COLOR_TEXT'])
            self.testing_graph_ax.set_ylabel("Профит", color=APP_CONFIG['COLOR_TEXT'])
            self.testing_graph_ax.grid(True, color=APP_CONFIG['COLOR_BORDER'])
            self.testing_graph_canvas.draw()
        except Exception as e:
            pass

    def on_testing_finished(self, final_stats: dict):
        """Вызывается по завершении тестирования."""
        try:
            self.start_testing_button.setEnabled(True)
            self.stop_testing_button.setEnabled(False)
            self.lbl_testing_status.setText("Завершено")
            self.lbl_testing_status.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold;")
            # Обновляем всю статистику
            # Профит
            profit_value = final_stats['total_profit']
            self.lbl_testing_profit.setText(f"{profit_value:+.2f}")
            if profit_value >= 0:
                self.lbl_testing_profit.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold; font-size: 12pt;")
            else:
                self.lbl_testing_profit.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold; font-size: 12pt;")
            # ROI
            roi_value = final_stats['roi']
            self.lbl_testing_roi.setText(f"{roi_value:+.2f}%")
            if roi_value >= 0:
                self.lbl_testing_roi.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold; font-size: 11pt;")
            else:
                self.lbl_testing_roi.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold; font-size: 11pt;")
            # Винрейт
            winrate_value = final_stats['winrate']
            self.lbl_testing_winrate.setText(f"{winrate_value:.2f}%")
            if winrate_value >= 50:
                self.lbl_testing_winrate.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold; font-size: 11pt;")
            else:
                self.lbl_testing_winrate.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold; font-size: 11pt;")
            self.lbl_testing_wins.setText(str(final_stats['total_wins']))
            self.lbl_testing_losses.setText(str(final_stats['total_losses']))
            self.lbl_testing_max_win_streak.setText(str(final_stats['max_win_streak']))
            self.lbl_testing_max_loss_streak.setText(str(final_stats['max_loss_streak']))
            # Обновляем таблицы и график (таблицы теперь на другой вкладке)
            self.crash_history_table.update_history(final_stats['crash_history'])
            self.actions_history_table.update_history(final_stats['actions_history'])
            self.update_testing_graph(final_stats['plot_rounds'], final_stats['plot_profits'])
            QMessageBox.information(self, "Тестирование завершено",
                                  f"Тестирование {final_stats['total_rounds']:,} раундов завершено.\n"
                                  f"Итоговый профит: {final_stats['total_profit']:+.2f}\n"
                                  f"ROI: {final_stats['roi']:+.2f}%\n"
                                  f"Винрейт: {final_stats['winrate']:.2f}%")
        except Exception as e:
            pass

    def on_testing_stopped(self):
        """Вызывается при остановке тестирования пользователем."""
        self.start_testing_button.setEnabled(True)
        self.stop_testing_button.setEnabled(False)
        self.lbl_testing_status.setText("Остановлено")
        self.lbl_testing_status.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")
        QMessageBox.information(self, "Тестирование остановлено", "Тестирование было остановлено пользователем.")

    def on_testing_error(self, error_msg: str):
        """Вызывается при ошибке в процессе тестирования."""
        self.start_testing_button.setEnabled(True)
        self.stop_testing_button.setEnabled(False)
        self.lbl_testing_status.setText("Ошибка")
        self.lbl_testing_status.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")
        QMessageBox.critical(self, "Ошибка тестирования", f"Произошла ошибка во время тестирования:\n{error_msg}")

    def save_settings(self):
        QMessageBox.information(self, "Настройки", "Настройки сохранены (имитация).")
        self.statusBar().showMessage("Настройки сохранены", 3000)

    def restore_default_settings(self):
        reply = QMessageBox.question(self, 'Сброс настроек', 'Вы уверены, что хотите сбросить все настройки?',
                                    QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
                                    QMessageBox.StandardButton.No)
        if reply == QMessageBox.StandardButton.Yes:
            try:
                # Сброс основных настроек
                self.initial_balance_input.setValue(0)
                self.min_bet_input.setValue(100)
                self.max_bet_input.setValue(100)
                self.min_multiplier_input.setValue(1.11)
                self.max_multiplier_input.setValue(1.11)
                self.statusBar().showMessage("Настройки сброшены", 3000)
            except Exception as e:
                QMessageBox.critical(self, "Ошибка", f"Произошла ошибка при сбросе настроек: {str(e)}")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

 

 

Crash.txt 56 kB · 0 скачиваний

 

Ты всё делаешь правильно с точки зрения генерации crash-множителя — твоя формула `1/(1-U)` абсолютно каноническая, это стандартная для "Crash"-игры модель (см. [разбор на Habr и GitHub](https://habr.com/ru/articles/681742/)). В казино для честной игры математическое ожидание должно быть **меньше 1** из-за комиссии (house edge), иначе казино не имеет прибыли. У тебя её нет.

### В чём проблема?

**Ты получаешь положительное матожидание**, потому что у тебя:  
- **Нет house edge (комиссии казино)**!  
- Ты каждую ставку делаешь на фиксированный минимальный возможный множитель **1.11**, т.е. ловишь самую частую, короткую игру.  
- Множитель генерируется по честной модели без вычета комиссии.

---

## Математика crash'a

**Если crash генерируется формулой:**  
```
mult = 1 / (1 - U)
```
где U ∈ [0,1),

- **P(дожить до заданного x):** 1 - 1/x  
- **Математическое ожидание выигрыша на фиксированном множителе x:**
  - `E(x) = (1 - 1/x) * x = x - 1`
- **А матожидание возврата ставки:**  
  - `E(x)/x = 1 - 1/x`  
  - При x=1.11: 1 - 1/1.11 ≈ 0.099  
  - То есть выигрышные ставки часты, но отыгрываются потерями.

**НО! Без house edge** твоя система ЧЕСТНАЯ, а на длинной дистанции — даже чуть положительная из-за округлений и особенностей float (мелкие погрешности из-за округления до двух знаков).

---

## Как это решается в казино

Настоящее казино ставит хедж:  
**Множитель рассчитывается с учётом комиссии!**  
- Например, house edge = 1%  
- Формула:  
```
mult = (1 / (1 - U)) * (1 - house_edge)
или
mult = 1 / ((1 - U) / (1 - house_edge))
```
- При x=1.11 коэффициент выплаты всегда меньше честного.

---

## Как исправить

**Добавь комиссию казино (house edge) в генератор множителя!**

Внеси её прямо в функцию `generate_crash_number()`:

```python
def generate_crash_number(house_edge=0.01):  # 1% комиссия казино
    U = random.uniform(0, 1)
    if U >= 0.99999:
        crash_multiplier = 100000.00
    else:
        crash_multiplier = (1.0 / (1.0 - U)) * (1.0 - house_edge)
    crash_multiplier = max(min(crash_multiplier, 100000.00), 1.00)
    return round(crash_multiplier, 2)
```

**Теперь мат. ожидание будет отрицательным на любой дистанции!**  
- Например, для house_edge=0.01 (1% комиссия) и cashout на 1.11:
  - `E = (1 - 1/1.11) * 1.11 * 0.99 = 0.098 * 1.11 * 0.99 ≈ 0.108`  
  - То есть проигрыш в среднем -0.01 (или -1%) на ставку.

---

## Итог

**Без house edge** — система честная, с положительным или нулевым мат.ожиданием.  
**Добавишь house edge** — всё станет как в настоящем Crash, игрок всегда проигрывает на дистанции.

### Проверь свою функцию генерации множителя и внеси house edge.  
**Проблема не в рандоме, а в отсутствии комиссии казино!**

Soulless1989 понравилось это

 

DB

59221730.png


Я - гений, ёпта

bfe7003be27e8e81ce6a7d2d8192e9ae.jpg


22


msg-93176-0-72842500-1438846470_thumb.jpg

Поделиться сообщением


Ссылка на сообщение
(изменено)

Так она по чистому мат ожиданию не должна давать +!
Она должна быть около нуля + - .
Но ни как не +470к.
И не в жёсткий минус тоже. 
Процент казино вообще роли не играет.
Тут ошибка в математике и в коде.

Это алиса

2025-08-16_00-59-24.png


Изменено пользователем yellyex

moonfangtopich написал 29.08.2019 в 14:57:
У вас недостаточно широкий кругозор, пацаны

Я странствия этого еблана видел в покерных топанах, а потом в таверне - это один из самых безумных людей на форуме. Я искренне надеялся, что его зов о помощи останется незамеченным, но нет, нашелся доброволец и вот уже три страницы мы пожинаем плоды

Поделиться сообщением


Ссылка на сообщение
yellyex написал 2 минуты назад:

Так она по чистому мат ожиданию не должна давать +!
Она должна быть около нуля + - .
Но ни как не +470к.
И не в жёсткий минус тоже. 
Процент казино вообще роли не играет.
Тут ошибка в математике и в коде.

Ты абсолютно прав: по честной математике crash-игры (без комиссии) твой результат должен быть **около нуля** (на огромной дистанции: ± несколько тысяч на миллион попыток — это нормально из-за случайности), но никак не несколько сотен тысяч в плюс! Рассмотрим причины, почему у тебя возможно получается такой сильный плюс.

---

### 1. **Математика Crash без комиссии**

Если всегда кэшаут делать на 1.11, то у честного crash-а (без house edge) для ставки X:

- **Вероятность не проиграть:**  
  \( P(\text{Win at 1.11}) = 1 - \frac{1}{1.11} \approx 0.0991 \)
- **Ожидание выигрыша с каждой ставки:**  
  \( E = (X \times 1.11) \times P(\text{Win}) - X \times (1 - P(\text{Win})) \)
  При X = 100:
  - Win: +11 при ~9.91% вероятности (100 * 1.11 - 100)
  - Lose: -100 при ~90.09% вероятности

**Теоретически, сумма должна стремиться к нулю на очень длинной дистанции!**

---

### 2. **Проблема в коде:**
Посмотрим, что у тебя происходит:

- **Генерация crash-множителя** выглядит ок:
  ```python
  def generate_crash_number():
      U = random.uniform(0, 1)
      if U >= 0.99999:
          crash_multiplier = 100000.00
      else:
          crash_multiplier = 1.0 / (1.0 - U)
      crash_multiplier = max(min(crash_multiplier, 100000.00), 1.00)
      return round(crash_multiplier, 2)
  ```
  - Тут всё правильно (round до 2 знаков на миллион игр чуть повышает ожидание, но не настолько!).

- **Выбор ставки и множителя**:
  ```python
  bet = random.randint(self.min_bet, self.max_bet)
  multiplier = round(random.uniform(self.min_multiplier, self.max_multiplier), 2)
  ```
  **Нужна проверка настроек:**
  - Если min_bet = max_bet = 100, будет всегда ставка 100.
  - Если min_multiplier = max_multiplier = 1.11, будет всегда ставка на 1.11.

  **ОК, значит генерация ставок и множителя не приводит к ошибкам.**

---

### 3. **Главная тонкость: ОКРУГЛЕНИЕ!**

Тонкий момент, из-за которого в реальном crash-казино тоже бывают ошибки расчёта, если не округлять правильно.

Типичная проблема:
- Ты делаешь `round(crash_multiplier, 2)`, т.е. множители как бы становятся дискретными: **1.00, 1.01, ..., 1.11, 1.12 ...**
- А ставка делается на ровно **1.11**.  
- **Это сдвигает вероятность выпадения "краша" ровно на 1.11**. В реальном crash игровом движке выигрыш при краш = 1.110... не входит, только >1.11.

**Проверь — кэшаут происходит при краш >= множитель или > множителя?**

В твоём коде:
```python
win = crash_value >= multiplier
```
То есть если `crash_value == 1.11` (т.е. округлилось до 1.11), это победа.

Но в теории при честной игре должно быть:
- Выигрыш только если множитель меньше фактического краша (т.е. **строго больше**), иначе честно говорить: на границе — проигрыш.

---
#### ВАЖНО:  
Но при двойном округлении:  
- **Если U чуть больше чем дает исходный 1.11, но после округления до 2 знаков краш = 1.11,** ты получаешь win, хотя по математике должен был проиграть (ведь фактический crash чуть меньше 1.11, просто округлился!).  
- На длинных дистанциях это даёт "халявные" выигрыши, которых быть не должно! И именно это объясняет твой плюс в ~470к на миллион попыток.

---

### **КАК ИСПРАВИТЬ**
1. **Убрать округление crash_multiplier до 2 знаков для внутренней логики выигрыша/проигрыша!**
2. Округлять только для отображения (UI), а НЕ для расчёта исхода раунда!

**Правильно:**
- Для расчёта:
  ```python
  # НЕ округлять здесь!
  def generate_crash_number(): 
      U = random.uniform(0, 1)
      if U >= 0.99999:
          crash_multiplier = 100000.0
      else:
          crash_multiplier = 1.0 / (1.0 - U)
      crash_multiplier = max(min(crash_multiplier, 100000.0), 1.00)
      return crash_multiplier
  ```
- А для вывода на экран:
  ```python
  print(f"{crash_multiplier:.2f}")
  ```

---

### **Bottom line**
**Твой баг — в округлении crash-множителя ДО сравнения с множителем кэшаута!**  
- Если убрать округление — долгосрочный профит будет близок к нулю (шум ±1000-5000 на миллион игр).

**Сводка:**
- Округляй только для UI, не для логики выигрыша.
- Сравнивай точные значения float.
- Тогда матожидание будет корректным.

---

**Проверь свой код с этой правкой, и прибыль исчезнет!**

---  
Если нужен рабочий кусок исправленного кода — напиши, приведу пример.  
Если хочешь обсудить, как тестировать — объясню, как считать руками и в numpy.


 

DB

59221730.png


Я - гений, ёпта

bfe7003be27e8e81ce6a7d2d8192e9ae.jpg


22


msg-93176-0-72842500-1438846470_thumb.jpg

Поделиться сообщением


Ссылка на сообщение

@Arzanis Это что у тебя за чат?


moonfangtopich написал 29.08.2019 в 14:57:
У вас недостаточно широкий кругозор, пацаны

Я странствия этого еблана видел в покерных топанах, а потом в таверне - это один из самых безумных людей на форуме. Я искренне надеялся, что его зов о помощи останется незамеченным, но нет, нашелся доброволец и вот уже три страницы мы пожинаем плоды

Поделиться сообщением


Ссылка на сообщение
(изменено)

- Выигрыш только если множитель меньше фактического краша (т.е. **строго больше**), иначе честно говорить: на границе — проигрыш.
По правилам игры победа присуждается при: Число Краша >= Множитель

 

Обновил код. Добавил округление не до 2 знаков, а до 5 или 10. В настройках можно указать.
Тогда итоговый профит уже начинает скакать!

 

  Crash

 

import sys
import random
import math
import os
from datetime import datetime
from collections import deque
from typing import List, Tuple, Dict, Any
from PyQt6 import QtWidgets, QtCore, QtGui
from PyQt6.QtCore import QThread, pyqtSignal, QTimer, QDateTime, Qt, QTime
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QFormLayout,
    QLabel, QPushButton, QMessageBox, QGroupBox,
    QSpinBox, QDoubleSpinBox, QCheckBox, QTableWidget, QTableWidgetItem,
    QHeaderView, QTabWidget, QTimeEdit, QFrame, QScrollArea, QSizePolicy
)
import matplotlib
matplotlib.use('QtAgg')
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
# --- APP_CONFIG ---
APP_CONFIG = {
    'APP_NAME': "Crash",
    'APP_NAME_VERSION': "(v1.0)",
    'STATS_FILE_NAME': "stats.txt",
    'COLOR_BACKGROUND': "#1e1e2d",
    'COLOR_SURFACE': "#2d2d44",
    'COLOR_SURFACE_LIGHT': "#3d3d54",
    'COLOR_TEXT': "#e0e0ff",
    'COLOR_BORDER': "#444466",
    'COLOR_PRIMARY': "#bb86fc",
    'COLOR_SECONDARY': "#03dac6",
    'COLOR_POSITIVE': "#00cc66",
    'COLOR_NEGATIVE': "#ff5555",
    'COLOR_WARNING': "#ffaa00",
    'COLOR_INFO': "#66b3ff",
    'TEXT_LABELS': {
        'app_title': "Crash",
        'save_settings': "Сохранить настройки",
        'reset_settings': "Сбросить настройки",
        'min_bet_label': "Минимальная ставка:",
        'max_bet_label': "Максимальная ставка:",
    },
}
# --- Crash Number Generator ---
def generate_crash_number(precision: int = 2):
    """Генерирует случайное число краша"""
    # Генерируем равномерно распределённое число U ∈ [0, 1)
    U = random.uniform(0, 1)
    # Вычисляем множитель по формуле
    if U >= 0.99999:  # Защита от деления на ноль
        crash_multiplier = 100000.00
    else:
        crash_multiplier = 1.0 / (1.0 - U)
    # Ограничиваем множитель диапазоном [1.00, 100000.00] и округляем до заданной точности
    crash_multiplier = max(min(crash_multiplier, 100000.00), 1.00)
    return round(crash_multiplier, precision)
# --- Crash History Table (для отображения последних 100 значений) ---
class CrashHistoryTable(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.history = []
        self.precision = 2 # Default precision
        self.init_ui()
    def init_ui(self):
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)
        # Заголовок
        header_layout = QHBoxLayout()
        header_label = QLabel("История последних значений краша")
        header_label.setStyleSheet(f"color: {APP_CONFIG['COLOR_SECONDARY']}; font-size: 12pt; font-weight: bold;")
        header_label.setToolTip("Последние 100 значений коэффициента краша")
        header_layout.addWidget(header_label)
        header_layout.addStretch()
        self.layout.addLayout(header_layout)
        # Таблица
        self.table = QTableWidget()
        self.table.setColumnCount(2)
        self.table.setHorizontalHeaderLabels(["#", "Краш"])
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
        self.table.verticalHeader().setVisible(False)
        self.table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
        self.table.setSelectionMode(QTableWidget.SelectionMode.NoSelection)
        self.table.setStyleSheet(f"""
            QTableWidget {{
                font-size: 9pt;
                color: {APP_CONFIG['COLOR_TEXT']};
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                gridline-color: {APP_CONFIG['COLOR_BORDER']};
                alternate-background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
            }}
            QTableWidget::item {{
                padding: 4px;
                border-bottom: 1px solid {APP_CONFIG['COLOR_BORDER']};
            }}
            QHeaderView::section {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                color: {APP_CONFIG['COLOR_TEXT']};
                padding: 6px;
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                font-weight: bold;
            }}
            QTableWidget::item:selected {{
                background-color: {APP_CONFIG['COLOR_PRIMARY']};
                color: white;
            }}
        """)
        self.table.setToolTip("Таблица с последними 100 значениями краша")
        self.layout.addWidget(self.table)
    def set_precision(self, precision: int):
        """Установить точность округления"""
        self.precision = precision
        # Перерисовать таблицу с новой точностью, если она уже заполнена
        if self.history:
             self.update_history(self.history)

    def update_history(self, crash_history: List[float]):
        """Обновляет таблицу всей историей крашей"""
        self.history = crash_history[-100:] if len(crash_history) > 100 else crash_history
        self.table.setRowCount(len(self.history))
        for i, value in enumerate(reversed(self.history)):
            # Номер
            num_item = QTableWidgetItem(str(len(self.history) - i))
            num_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
            self.table.setItem(i, 0, num_item)
            # Значение краша
            crash_item = QTableWidgetItem(f"{value:.{self.precision}f}x")
            crash_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
            # Цвет в зависимости от значения
            if value >= 10:
                crash_item.setForeground(QtGui.QColor(APP_CONFIG['COLOR_POSITIVE']))
                crash_item.setFont(QtGui.QFont("Arial", 9, QtGui.QFont.Weight.Bold))
            elif value >= 2:
                crash_item.setForeground(QtGui.QColor(APP_CONFIG['COLOR_SECONDARY']))
            else:
                crash_item.setForeground(QtGui.QColor(APP_CONFIG['COLOR_NEGATIVE']))
            self.table.setItem(i, 1, crash_item)
        # Выравнивание текста по центру
        for i in range(self.table.rowCount()):
            for j in range(self.table.columnCount()):
                item = self.table.item(i, j)
                if item:
                    item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
# --- Actions History Table (для отображения последних 100 действий) ---
class ActionsHistoryTable(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.history = []
        self.precision = 2 # Default precision
        self.init_ui()
    def init_ui(self):
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)
        # Заголовок
        header_layout = QHBoxLayout()
        header_label = QLabel("История последних действий")
        header_label.setStyleSheet(f"color: {APP_CONFIG['COLOR_SECONDARY']}; font-size: 12pt; font-weight: bold;")
        header_label.setToolTip("Последние 100 записей о действиях в тестировании")
        header_layout.addWidget(header_label)
        header_layout.addStretch()
        self.layout.addLayout(header_layout)
        # Таблица
        self.table = QTableWidget()
        self.table.setColumnCount(10)
        self.table.setHorizontalHeaderLabels([
            "Дата/Время", "Раунд", "Банк", "Ставка", "Множитель", 
            "Краш", "Результат", "Новый банк", "Профит", "ROI"
        ])
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
        self.table.verticalHeader().setVisible(False)
        self.table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
        self.table.setSelectionMode(QTableWidget.SelectionMode.NoSelection)
        self.table.setStyleSheet(f"""
            QTableWidget {{
                font-size: 8pt;
                color: {APP_CONFIG['COLOR_TEXT']};
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                gridline-color: {APP_CONFIG['COLOR_BORDER']};
                alternate-background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
            }}
            QTableWidget::item {{
                padding: 2px;
                border-bottom: 1px solid {APP_CONFIG['COLOR_BORDER']};
            }}
            QHeaderView::section {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                color: {APP_CONFIG['COLOR_TEXT']};
                padding: 4px;
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                font-weight: bold;
            }}
            QTableWidget::item:selected {{
                background-color: {APP_CONFIG['COLOR_PRIMARY']};
                color: white;
            }}
        """)
        self.table.setToolTip("Таблица с последними 100 записями о действиях в тестировании")
        self.layout.addWidget(self.table)
    def set_precision(self, precision: int):
        """Установить точность округления"""
        self.precision = precision
        # Перерисовать таблицу с новой точностью, если она уже заполнена
        if self.history:
             self.update_history(self.history)

    def update_history(self, actions_history: List[Dict[str, Any]]):
        """Обновляет таблицу всей историей действий"""
        self.history = actions_history[-100:] if len(actions_history) > 100 else actions_history
        self.table.setRowCount(len(self.history))
        format_str = f".{self.precision}f"
        for i, data in enumerate(reversed(self.history)):
            timestamp = data['timestamp'].strftime("%H:%M:%S.%f")[:-3]  # Обрезаем до миллисекунд
            self.table.setItem(i, 0, QTableWidgetItem(timestamp))
            self.table.setItem(i, 1, QTableWidgetItem(str(data['round_num'])))
            self.table.setItem(i, 2, QTableWidgetItem(f"{data['balance_before']:{format_str}}"))
            self.table.setItem(i, 3, QTableWidgetItem(str(data['bet'])))
            self.table.setItem(i, 4, QTableWidgetItem(f"{data['multiplier']:{format_str}}"))
            self.table.setItem(i, 5, QTableWidgetItem(f"{data['crash_value']:{format_str}}"))
            # Результат
            if data['win']:
                result_text = f"+{data['reward']:{format_str}}"
                result_item = QTableWidgetItem(result_text)
                result_item.setForeground(QtGui.QColor(APP_CONFIG['COLOR_POSITIVE']))
                result_item.setFont(QtGui.QFont("Arial", 8, QtGui.QFont.Weight.Bold))
            else:
                result_text = f"-{data['bet']:{format_str}}"
                result_item = QTableWidgetItem(result_text)
                result_item.setForeground(QtGui.QColor(APP_CONFIG['COLOR_NEGATIVE']))
                result_item.setFont(QtGui.QFont("Arial", 8, QtGui.QFont.Weight.Bold))
            self.table.setItem(i, 6, result_item)
            self.table.setItem(i, 7, QTableWidgetItem(f"{data['balance_after']:{format_str}}"))
            self.table.setItem(i, 8, QTableWidgetItem(f"{data['profit']:{format_str}}"))
            self.table.setItem(i, 9, QTableWidgetItem(f"{data['roi']:{format_str}}%"))
        # Выравнивание текста по центру
        for i in range(self.table.rowCount()):
            for j in range(self.table.columnCount()):
                item = self.table.item(i, j)
                if item:
                    item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
# --- Testing Thread ---
class TestingWorker(QThread):
    # Сигналы для обновления GUI
    batch_finished = pyqtSignal(dict)  # Передаем данные батча
    testing_finished = pyqtSignal(dict)  # итоговая статистика
    testing_stopped = pyqtSignal()
    error_occurred = pyqtSignal(str)
    plot_data_ready = pyqtSignal(list, list)  # данные для графика
    def __init__(self, num_rounds: int, min_bet: int, max_bet: int, 
                 min_multiplier: float, max_multiplier: float, 
                 initial_balance: float, update_interval: int, precision: int = 2):
        super().__init__()
        self.num_rounds = num_rounds
        self.min_bet = min_bet
        self.max_bet = max_bet
        self.min_multiplier = min_multiplier
        self.max_multiplier = max_multiplier
        self.initial_balance = initial_balance
        self.update_interval = update_interval  # 0 = только в конце
        self.precision = precision # Новое поле для точности
        self._is_running = True
        self.balance = initial_balance
        self.crash_history = []
        self.actions_history = []
        self.testing_rounds_data = []
        self.testing_profit_data = []
        self.total_wins = 0
        self.total_losses = 0
        self.current_win_streak = 0
        self.current_loss_streak = 0
        self.max_win_streak = 0
        self.max_loss_streak = 0
        self.total_invested = 0  # Для правильного расчета ROI
        self.processed_rounds = 0
    def stop(self):
        self._is_running = False
    def run(self):
        try:
            batch_data = {
                'rounds': [],
                'crash_values': [],
                'bets': [],
                'multipliers': [],
                'wins': [],
                'rewards': [],
                'balances_before': [],
                'balances_after': [],
                'profits': [],
                'rois': [],
                'winrates': [],
                'timestamps': [],
                'total_wins_list': [],
                'total_losses_list': [],
                'max_win_streaks': [],
                'max_loss_streaks': []
            }
            for i in range(1, self.num_rounds + 1):
                if not self._is_running:
                    self.testing_stopped.emit()
                    return
                # 1. Генерация краша с заданной точностью
                crash_value = generate_crash_number(self.precision)
                self.crash_history.append(crash_value)
                # 2. Генерация случайной ставки и множителя с заданной точностью
                bet = random.randint(self.min_bet, self.max_bet)
                multiplier = round(random.uniform(self.min_multiplier, self.max_multiplier), self.precision)
                # 3. Симуляция результата
                balance_before = self.balance
                win = crash_value >= multiplier
                reward = 0
                if bet > 0:
                    self.total_invested += bet  # Увеличиваем общую сумму ставок
                    self.balance -= bet
                    if win:
                        win_amount = bet * multiplier
                        self.balance += win_amount
                        reward = win_amount - bet  # Чистый выигрыш
                        self.total_wins += 1
                        self.current_win_streak += 1
                        self.current_loss_streak = 0
                        if self.current_win_streak > self.max_win_streak:
                            self.max_win_streak = self.current_win_streak
                    else:
                        reward = -bet  # Потеря ставки
                        self.total_losses += 1
                        self.current_loss_streak += 1
                        self.current_win_streak = 0
                        if self.current_loss_streak > self.max_loss_streak:
                            self.max_loss_streak = self.current_loss_streak
                profit = self.balance - self.initial_balance
                # Исправленный расчет ROI
                roi = (profit / self.total_invested * 100) if self.total_invested > 0 else 0
                winrate = (self.total_wins / i * 100) if i > 0 else 0  # Winrate от общего количества раундов
                # 4. Сохранение данных для истории действий
                action_data = {
                    'round_num': i,
                    'crash_value': crash_value,
                    'bet': bet,
                    'multiplier': multiplier,
                    'win': win,
                    'reward': reward,
                    'balance_before': balance_before,
                    'balance_after': self.balance,
                    'profit': profit,
                    'roi': roi,
                    'winrate': winrate,
                    'timestamp': datetime.now(),
                    'total_wins': self.total_wins,
                    'total_losses': self.total_losses,
                    'max_win_streak': self.max_win_streak,
                    'max_loss_streak': self.max_loss_streak
                }
                self.actions_history.append(action_data)
                # 5. Обновление данных для графика
                self.testing_rounds_data.append(i)
                self.testing_profit_data.append(profit)
                # 6. Подготовка данных для батча (если нужно обновление)
                if self.update_interval > 0:
                    batch_data['rounds'].append(i)
                    batch_data['crash_values'].append(crash_value)
                    batch_data['bets'].append(bet)
                    batch_data['multipliers'].append(multiplier)
                    batch_data['wins'].append(win)
                    batch_data['rewards'].append(reward)
                    batch_data['balances_before'].append(balance_before)
                    batch_data['balances_after'].append(self.balance)
                    batch_data['profits'].append(profit)
                    batch_data['rois'].append(roi)
                    batch_data['winrates'].append(winrate)
                    batch_data['timestamps'].append(datetime.now())
                    batch_data['total_wins_list'].append(self.total_wins)
                    batch_data['total_losses_list'].append(self.total_losses)
                    batch_data['max_win_streaks'].append(self.max_win_streak)
                    batch_data['max_loss_streaks'].append(self.max_loss_streak)
                self.processed_rounds += 1
                # 7. Эмит сигналов при достижении интервала обновления
                if self.update_interval > 0 and self.processed_rounds % self.update_interval == 0:
                    batch_stats = {
                        'processed_rounds': self.processed_rounds,
                        'total_rounds': self.num_rounds,
                        'current_balance': self.balance,
                        'current_profit': profit,
                        'current_roi': roi,
                        'current_winrate': winrate,
                        'total_wins': self.total_wins,
                        'total_losses': self.total_losses,
                        'max_win_streak': self.max_win_streak,
                        'max_loss_streak': self.max_loss_streak,
                        'crash_history': self.crash_history.copy(),
                        'actions_history': self.actions_history.copy(),
                        'plot_rounds': self.testing_rounds_data.copy(),
                        'plot_profits': self.testing_profit_data.copy()
                    }
                    self.batch_finished.emit(batch_stats)
                    # Очищаем батч данные
                    batch_data = {
                        'rounds': [],
                        'crash_values': [],
                        'bets': [],
                        'multipliers': [],
                        'wins': [],
                        'rewards': [],
                        'balances_before': [],
                        'balances_after': [],
                        'profits': [],
                        'rois': [],
                        'winrates': [],
                        'timestamps': [],
                        'total_wins_list': [],
                        'total_losses_list': [],
                        'max_win_streaks': [],
                        'max_loss_streaks': []
                    }
                # Небольшая задержка только для очень маленьких интервалов
                if self.update_interval > 0 and self.update_interval < 10:
                    self.msleep(1)
            # Тестирование завершено
            final_stats = {
                'total_rounds': self.num_rounds,
                'final_balance': self.balance,
                'total_profit': self.balance - self.initial_balance,
                'roi': ((self.balance - self.initial_balance) / self.total_invested * 100) if self.total_invested > 0 else 0,
                'winrate': (self.total_wins / self.num_rounds * 100) if self.num_rounds > 0 else 0,
                'total_wins': self.total_wins,
                'total_losses': self.total_losses,
                'max_win_streak': self.max_win_streak,
                'max_loss_streak': self.max_loss_streak,
                'crash_history': self.crash_history,
                'actions_history': self.actions_history,
                'plot_rounds': self.testing_rounds_data,
                'plot_profits': self.testing_profit_data
            }
            self.testing_finished.emit(final_stats)
        except Exception as e:
            self.error_occurred.emit(str(e))
# --- Main Window ---
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle(f"{APP_CONFIG['APP_NAME']} {APP_CONFIG['APP_NAME_VERSION']}")
        self.setGeometry(100, 100, 1400, 900)
        self.setStyleSheet(f"""
            QMainWindow {{
                background-color: {APP_CONFIG['COLOR_BACKGROUND']};
            }}
            QWidget {{
                background-color: {APP_CONFIG['COLOR_BACKGROUND']};
                color: {APP_CONFIG['COLOR_TEXT']};
                font-family: 'Segoe UI', Arial, sans-serif;
            }}
            QLabel {{
                color: {APP_CONFIG['COLOR_TEXT']};
                background-color: transparent;
            }}
            QGroupBox {{
                border: 2px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 8px;
                margin-top: 10px;
                padding: 10px;
                font-weight: bold;
                color: {APP_CONFIG['COLOR_TEXT']};
                background-color: {APP_CONFIG['COLOR_SURFACE']};
            }}
            QGroupBox::title {{
                subline-offset: -15px;
                padding: 0px 5px 0px 5px;
                color: {APP_CONFIG['COLOR_SECONDARY']};
            }}
            QPushButton {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                padding: 8px 15px;
                font-weight: bold;
                color: {APP_CONFIG['COLOR_TEXT']};
                min-width: 100px;
            }}
            QPushButton:hover {{
                background-color: {APP_CONFIG['COLOR_PRIMARY']};
                border: 1px solid {APP_CONFIG['COLOR_SECONDARY']};
            }}
            QPushButton:pressed {{
                background-color: {APP_CONFIG['COLOR_SECONDARY']};
            }}
            QPushButton:disabled {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                color: #888888;
                border: 1px solid #555555;
            }}
            QSpinBox, QDoubleSpinBox {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                color: {APP_CONFIG['COLOR_TEXT']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 4px;
                padding: 4px;
                selection-background-color: {APP_CONFIG['COLOR_PRIMARY']};
            }}
            QSpinBox::up-button, QDoubleSpinBox::up-button {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                border: none;
                width: 16px;
            }}
            QSpinBox::down-button, QDoubleSpinBox::down-button {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                border: none;
                width: 16px;
            }}
            QCheckBox {{
                color: {APP_CONFIG['COLOR_TEXT']};
            }}
            QTabWidget::pane {{
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                background-color: {APP_CONFIG['COLOR_BACKGROUND']};
            }}
            QTabBar::tab {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                color: {APP_CONFIG['COLOR_TEXT']};
                padding: 8px 15px;
                margin-right: 2px;
                border-top-left-radius: 4px;
                border-top-right-radius: 4px;
            }}
            QTabBar::tab:selected {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                color: {APP_CONFIG['COLOR_SECONDARY']};
                font-weight: bold;
            }}
            QTabBar::tab:hover {{
                background-color: {APP_CONFIG['COLOR_PRIMARY']};
            }}
            QHeaderView::section {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                color: {APP_CONFIG['COLOR_TEXT']};
                padding: 6px;
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                font-weight: bold;
            }}
        """)
        # Инициализация UI
        self.init_ui()
    def init_ui(self):
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        self.main_layout = QVBoxLayout(central_widget)
        self.main_layout.setSpacing(10)
        self.setup_main_tabs()
    def setup_main_tabs(self):
        self.tabs = QTabWidget()
        self.tab_main = self.create_main_tab_interface()
        self.tab_settings = self.create_settings_tab_interface()
        self.tab_testing = self.create_testing_tab_interface()
        self.tab_history = self.create_history_tables_tab_interface()  # Новая вкладка
        self.tabs.addTab(self.tab_main, "Главная")
        self.tabs.addTab(self.tab_settings, "Настройки")
        self.tabs.addTab(self.tab_testing, "Тестирование")
        self.tabs.addTab(self.tab_history, "Таблицы (История)")  # Добавляем новую вкладку
        self.main_layout.addWidget(self.tabs)
    def create_main_tab_interface(self):
        tab = QWidget()
        layout = QVBoxLayout()
        layout.setSpacing(15)
        tab.setLayout(layout)
        # Вкладка теперь полностью пустая, как требовалось
        layout.addStretch()
        return tab
    def create_settings_tab_interface(self):
        tab = QWidget()
        layout = QVBoxLayout(tab)
        layout.setSpacing(15)
        # Заголовок
        header_label = QLabel("Настройки тестирования")
        header_label.setStyleSheet(f"color: {APP_CONFIG['COLOR_SECONDARY']}; font-size: 16pt; font-weight: bold; padding: 10px;")
        header_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        header_label.setToolTip("Настройка параметров для симуляции игры Crash")
        layout.addWidget(header_label)
        # Основные настройки
        main_settings_group = QGroupBox("Основные параметры")
        main_settings_layout = QFormLayout()
        main_settings_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight)
        main_settings_layout.setHorizontalSpacing(20)
        main_settings_layout.setVerticalSpacing(10)
        main_settings_group.setToolTip("Основные параметры симуляции")
        # Начальный банк
        self.initial_balance_input = QSpinBox()
        self.initial_balance_input.setRange(0, 10000000)
        self.initial_balance_input.setValue(0)
        self.initial_balance_input.setGroupSeparatorShown(True)
        self.initial_balance_input.setToolTip("Начальный баланс для тестирования")
        main_settings_layout.addRow(QLabel("Начальный банк:"), self.initial_balance_input)
        # Минимальная ставка
        self.min_bet_input = QSpinBox()
        self.min_bet_input.setRange(0, 10000000)
        self.min_bet_input.setValue(100)
        self.min_bet_input.setGroupSeparatorShown(True)
        self.min_bet_input.setToolTip("Минимальный размер ставки для одной игры")
        main_settings_layout.addRow(QLabel(APP_CONFIG['TEXT_LABELS']['min_bet_label']), self.min_bet_input)
        # Максимальная ставка
        self.max_bet_input = QSpinBox()
        self.max_bet_input.setRange(0, 10000000)
        self.max_bet_input.setValue(100)
        self.max_bet_input.setGroupSeparatorShown(True)
        self.max_bet_input.setToolTip("Максимальный размер ставки для одной игры")
        main_settings_layout.addRow(QLabel("Максимальная ставка:"), self.max_bet_input)
        # Минимальный множитель
        self.min_multiplier_input = QDoubleSpinBox()
        self.min_multiplier_input.setRange(1.01, 100.0)
        self.min_multiplier_input.setSingleStep(0.1)
        self.min_multiplier_input.setDecimals(2)
        self.min_multiplier_input.setValue(1.11)
        self.min_multiplier_input.setToolTip("Минимальный множитель для кэшаута")
        main_settings_layout.addRow(QLabel("Мин множитель:"), self.min_multiplier_input)
        # Максимальный множитель
        self.max_multiplier_input = QDoubleSpinBox()
        self.max_multiplier_input.setRange(1.01, 100.0)
        self.max_multiplier_input.setSingleStep(0.1)
        self.max_multiplier_input.setDecimals(2)
        self.max_multiplier_input.setValue(1.11)
        self.max_multiplier_input.setToolTip("Максимальный множитель для кэшаута")
        main_settings_layout.addRow(QLabel("Макс множитель:"), self.max_multiplier_input)
        # --- НОВАЯ НАСТРОЙКА ТОЧНОСТИ ---
        # Точность округления
        self.precision_input = QSpinBox()
        self.precision_input.setRange(0, 10)
        self.precision_input.setValue(2)
        self.precision_input.setToolTip("Количество знаков после запятой для отображения чисел")
        main_settings_layout.addRow(QLabel("Точность округления:"), self.precision_input)
        # --- КОНЕЦ НОВОЙ НАСТРОЙКИ ---
        main_settings_group.setLayout(main_settings_layout)
        layout.addWidget(main_settings_group)
        # Кнопки действий
        buttons_layout = QHBoxLayout()
        buttons_layout.addStretch()
        self.save_settings_button = QPushButton(APP_CONFIG['TEXT_LABELS']['save_settings'])
        self.save_settings_button.clicked.connect(self.save_settings)
        self.save_settings_button.setStyleSheet(f"""
            QPushButton {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                padding: 10px 20px;
                font-weight: bold;
                color: {APP_CONFIG['COLOR_TEXT']};
                min-width: 150px;
            }}
            QPushButton:hover {{
                background-color: {APP_CONFIG['COLOR_POSITIVE']};
                border: 1px solid {APP_CONFIG['COLOR_SECONDARY']};
            }}
        """)
        self.save_settings_button.setToolTip("Сохранить текущие настройки")
        buttons_layout.addWidget(self.save_settings_button)
        self.reset_settings_button = QPushButton(APP_CONFIG['TEXT_LABELS']['reset_settings'])
        self.reset_settings_button.clicked.connect(self.restore_default_settings)
        self.reset_settings_button.setStyleSheet(f"""
            QPushButton {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                padding: 10px 20px;
                font-weight: bold;
                color: {APP_CONFIG['COLOR_TEXT']};
                min-width: 150px;
            }}
            QPushButton:hover {{
                background-color: {APP_CONFIG['COLOR_WARNING']};
                border: 1px solid {APP_CONFIG['COLOR_SECONDARY']};
            }}
        """)
        self.reset_settings_button.setToolTip("Сбросить все настройки к значениям по умолчанию")
        buttons_layout.addWidget(self.reset_settings_button)
        buttons_layout.addStretch()
        layout.addLayout(buttons_layout)
        layout.addStretch()
        return tab
    def create_testing_tab_interface(self):
        tab = QWidget()
        layout = QVBoxLayout(tab)
        layout.setSpacing(15)
        # Заголовок
        header_label = QLabel("Тестирование стратегии")
        header_label.setStyleSheet(f"color: {APP_CONFIG['COLOR_SECONDARY']}; font-size: 16pt; font-weight: bold; padding: 10px;")
        header_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        header_label.setToolTip("Запуск и управление симуляцией игры Crash")
        layout.addWidget(header_label)
        # Панель управления тестированием
        control_group = QGroupBox("Управление тестированием")
        control_layout = QFormLayout()
        control_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight)
        control_layout.setHorizontalSpacing(20)
        control_layout.setVerticalSpacing(10)
        control_group.setToolTip("Параметры и управление процессом тестирования")
        self.num_rounds_input = QSpinBox()
        self.num_rounds_input.setRange(1, 100000000)
        self.num_rounds_input.setValue(1000000)
        self.num_rounds_input.setGroupSeparatorShown(True)
        self.num_rounds_input.setToolTip("Общее количество раундов для симуляции")
        control_layout.addRow(QLabel("Количество раундов:"), self.num_rounds_input)
        self.update_interval_input = QSpinBox()
        self.update_interval_input.setRange(0, 100000000)
        self.update_interval_input.setValue(0)
        self.update_interval_input.setGroupSeparatorShown(True)
        self.update_interval_input.setToolTip("Интервал обновления интерфейса в раундах (0 = только в конце)")
        control_layout.addRow(QLabel("Интервал обновления интерфейса:"), self.update_interval_input)
        # Кнопки
        buttons_layout = QHBoxLayout()
        buttons_layout.addStretch()
        self.start_testing_button = QPushButton("Начать тестирование")
        self.start_testing_button.clicked.connect(self.start_testing)
        self.start_testing_button.setStyleSheet(f"""
            QPushButton {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                padding: 10px 20px;
                font-weight: bold;
                color: {APP_CONFIG['COLOR_TEXT']};
                min-width: 180px;
            }}
            QPushButton:hover {{
                background-color: {APP_CONFIG['COLOR_POSITIVE']};
                border: 1px solid {APP_CONFIG['COLOR_SECONDARY']};
            }}
        """)
        self.start_testing_button.setToolTip("Запустить симуляцию игры с текущими настройками")
        buttons_layout.addWidget(self.start_testing_button)
        self.stop_testing_button = QPushButton("Завершить тестирование")
        self.stop_testing_button.clicked.connect(self.stop_testing)
        self.stop_testing_button.setEnabled(False)
        self.stop_testing_button.setStyleSheet(f"""
            QPushButton {{
                background-color: {APP_CONFIG['COLOR_SURFACE']};
                border: 1px solid {APP_CONFIG['COLOR_BORDER']};
                border-radius: 5px;
                padding: 10px 20px;
                font-weight: bold;
                color: {APP_CONFIG['COLOR_TEXT']};
                min-width: 180px;
            }}
            QPushButton:hover {{
                background-color: {APP_CONFIG['COLOR_NEGATIVE']};
                border: 1px solid {APP_CONFIG['COLOR_SECONDARY']};
            }}
            QPushButton:disabled {{
                background-color: {APP_CONFIG['COLOR_SURFACE_LIGHT']};
                color: #888888;
                border: 1px solid #555555;
            }}
        """)
        self.stop_testing_button.setToolTip("Остановить текущую симуляцию")
        buttons_layout.addWidget(self.stop_testing_button)
        buttons_layout.addStretch()
        control_layout.addRow(buttons_layout)
        control_group.setLayout(control_layout)
        layout.addWidget(control_group)
        # Панель статистики тестирования
        self.testing_stats_group = QGroupBox("Статистика тестирования")
        stats_layout = QFormLayout()
        stats_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight)
        stats_layout.setHorizontalSpacing(20)
        stats_layout.setVerticalSpacing(8)
        self.testing_stats_group.setToolTip("Текущие результаты симуляции")
        # Статус
        self.lbl_testing_status = QLabel("Ожидание запуска...")
        self.lbl_testing_status.setStyleSheet(f"color: {APP_CONFIG['COLOR_INFO']}; font-weight: bold;")
        self.lbl_testing_status.setToolTip("Текущий статус тестирования")
        stats_layout.addRow(QLabel("Статус:"), self.lbl_testing_status)
        # Прогресс
        self.lbl_testing_rounds = QLabel("0 / 0")
        self.lbl_testing_rounds.setStyleSheet(f"color: {APP_CONFIG['COLOR_SECONDARY']}; font-weight: bold;")
        self.lbl_testing_rounds.setToolTip("Прогресс выполнения тестирования")
        stats_layout.addRow(QLabel("Прогресс:"), self.lbl_testing_rounds)
        # Профит
        self.lbl_testing_profit = QLabel("0.00")
        self.lbl_testing_profit.setStyleSheet(f"color: {APP_CONFIG['COLOR_INFO']}; font-weight: bold;")
        self.lbl_testing_profit.setToolTip("Текущий профит (доход/убыток)")
        stats_layout.addRow(QLabel("Профит:"), self.lbl_testing_profit)
        # ROI
        self.lbl_testing_roi = QLabel("0.00%")
        self.lbl_testing_roi.setStyleSheet(f"color: {APP_CONFIG['COLOR_INFO']}; font-weight: bold;")
        self.lbl_testing_roi.setToolTip("Возврат на инвестиции (ROI) в процентах")
        stats_layout.addRow(QLabel("ROI:"), self.lbl_testing_roi)
        # Винрейт
        self.lbl_testing_winrate = QLabel("0.00%")
        self.lbl_testing_winrate.setStyleSheet(f"color: {APP_CONFIG['COLOR_INFO']}; font-weight: bold;")
        self.lbl_testing_winrate.setToolTip("Процент выигранных раундов")
        stats_layout.addRow(QLabel("Винрейт:"), self.lbl_testing_winrate)
        # --- ИЗМЕНЕНИЯ НАЧАЛИСЬ ЗДЕСЬ ---
        # Победы
        self.lbl_testing_wins = QLabel("0")
        self.lbl_testing_wins.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold;")
        self.lbl_testing_wins.setToolTip("Количество выигранных раундов")
        stats_layout.addRow(QLabel("Победы:"), self.lbl_testing_wins)
        # Поражения
        self.lbl_testing_losses = QLabel("0")
        self.lbl_testing_losses.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")
        self.lbl_testing_losses.setToolTip("Количество проигранных раундов")
        stats_layout.addRow(QLabel("Поражения:"), self.lbl_testing_losses)
        # Макс. серия побед
        self.lbl_testing_max_win_streak = QLabel("0")
        self.lbl_testing_max_win_streak.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold;")
        self.lbl_testing_max_win_streak.setToolTip("Максимальная серия побед подряд за всё время тестирования")
        stats_layout.addRow(QLabel("Макс. серия побед (за всё время):"), self.lbl_testing_max_win_streak)
        # Макс. серия поражений
        self.lbl_testing_max_loss_streak = QLabel("0")
        self.lbl_testing_max_loss_streak.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")
        self.lbl_testing_max_loss_streak.setToolTip("Максимальная серия поражений подряд за всё время тестирования")
        stats_layout.addRow(QLabel("Макс. серия поражений (за всё время):"), self.lbl_testing_max_loss_streak)
        # --- ИЗМЕНЕНИЯ ЗАКОНЧИЛИСЬ ---
        self.testing_stats_group.setLayout(stats_layout)
        layout.addWidget(self.testing_stats_group)
        # График тестирования
        self.testing_graph_panel = self.build_testing_graph_panel()
        layout.addWidget(self.testing_graph_panel)
        layout.addStretch()
        return tab
    def create_history_tables_tab_interface(self):
        """Создает новую вкладку с таблицами истории"""
        tab = QWidget()
        layout = QVBoxLayout(tab)
        layout.setSpacing(15)
        # Заголовок
        header_label = QLabel("Таблицы истории")
        header_label.setStyleSheet(f"color: {APP_CONFIG['COLOR_SECONDARY']}; font-size: 16pt; font-weight: bold; padding: 10px;")
        header_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        header_label.setToolTip("Подробная история крашей и действий")
        layout.addWidget(header_label)
        # Таблицы истории
        tables_layout = QHBoxLayout()
        # Таблица истории крашей
        self.crash_history_table = CrashHistoryTable()
        self.crash_history_table.setToolTip("Таблица с последними 100 значениями краша")
        tables_layout.addWidget(self.crash_history_table)
        # Таблица истории действий
        self.actions_history_table = ActionsHistoryTable()
        self.actions_history_table.setToolTip("Таблица с последними 100 записями о действиях")
        tables_layout.addWidget(self.actions_history_table)
        layout.addLayout(tables_layout)
        layout.addStretch()
        return tab
    def build_testing_graph_panel(self):
        """Создает панель с графиком тестирования."""
        panel = QGroupBox("График профита (Тестирование)")
        layout = QVBoxLayout(panel)
        layout.setContentsMargins(10, 10, 10, 10)
        panel.setToolTip("График динамики изменения профита по ходу тестирования")
        self.testing_graph_figure = Figure(figsize=(8, 4), dpi=100)
        self.testing_graph_canvas = FigureCanvas(self.testing_graph_figure)
        self.testing_graph_ax = self.testing_graph_figure.add_subplot(111)
        self.testing_graph_ax.set_title("Профит по раундам", color=APP_CONFIG['COLOR_TEXT'], fontsize=12)
        self.testing_graph_ax.set_xlabel("Раунд", color=APP_CONFIG['COLOR_TEXT'])
        self.testing_graph_ax.set_ylabel("Профит", color=APP_CONFIG['COLOR_TEXT'])
        self.testing_graph_ax.grid(True, color=APP_CONFIG['COLOR_BORDER'])
        self.testing_graph_ax.set_facecolor(APP_CONFIG['COLOR_SURFACE'])
        self.testing_graph_figure.patch.set_facecolor(APP_CONFIG['COLOR_BACKGROUND'])
        self.testing_graph_ax.tick_params(colors=APP_CONFIG['COLOR_TEXT'])
        self.testing_graph_ax.spines['bottom'].set_color(APP_CONFIG['COLOR_BORDER'])
        self.testing_graph_ax.spines['top'].set_color(APP_CONFIG['COLOR_BORDER'])
        self.testing_graph_ax.spines['left'].set_color(APP_CONFIG['COLOR_BORDER'])
        self.testing_graph_ax.spines['right'].set_color(APP_CONFIG['COLOR_BORDER'])
        self.testing_graph_ax.title.set_color(APP_CONFIG['COLOR_TEXT'])
        self.testing_graph_ax.xaxis.label.set_color(APP_CONFIG['COLOR_TEXT'])
        self.testing_graph_ax.yaxis.label.set_color(APP_CONFIG['COLOR_TEXT'])
        layout.addWidget(self.testing_graph_canvas)
        return panel
    def start_testing(self):
        try:
            if hasattr(self, 'testing_worker') and self.testing_worker.isRunning():
                QMessageBox.warning(self, "Предупреждение", "Тестирование уже запущено.")
                return
            # Считываем настройки
            num_rounds = self.num_rounds_input.value()
            update_interval = self.update_interval_input.value()
            min_bet = self.min_bet_input.value()
            max_bet = self.max_bet_input.value()
            min_multiplier = self.min_multiplier_input.value()
            max_multiplier = self.max_multiplier_input.value()
            initial_balance = self.initial_balance_input.value()
            # --- СЧИТЫВАЕМ НОВУЮ НАСТРОЙКУ ---
            precision = self.precision_input.value()
            # --- КОНЕЦ СЧИТЫВАНИЯ НОВОЙ НАСТРОЙКИ ---
            # Обновляем UI
            self.start_testing_button.setEnabled(False)
            self.stop_testing_button.setEnabled(True)
            self.lbl_testing_status.setText("Тестирование...")
            self.lbl_testing_status.setStyleSheet(f"color: {APP_CONFIG['COLOR_WARNING']}; font-weight: bold;")
            self.lbl_testing_rounds.setText(f"0 / {num_rounds:,}")
            # Создаем и запускаем поток тестирования с новым параметром точности
            self.testing_worker = TestingWorker(
                num_rounds, min_bet, max_bet, min_multiplier, max_multiplier,
                initial_balance, update_interval, precision # Передаем точность
            )
            self.testing_worker.batch_finished.connect(self.on_testing_batch_finished)
            self.testing_worker.testing_finished.connect(self.on_testing_finished)
            self.testing_worker.testing_stopped.connect(self.on_testing_stopped)
            self.testing_worker.error_occurred.connect(self.on_testing_error)
            self.testing_worker.plot_data_ready.connect(self.update_testing_graph)
            self.testing_worker.start()
        except Exception as e:
            QMessageBox.critical(self, "Ошибка", f"Произошла ошибка при запуске тестирования: {str(e)}")
    def stop_testing(self):
        if hasattr(self, 'testing_worker') and self.testing_worker.isRunning():
            self.testing_worker.stop()
            self.lbl_testing_status.setText("Остановка...")
            self.lbl_testing_status.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")
    def on_testing_batch_finished(self, batch_stats: dict):
        """Вызывается по завершении батча раундов."""
        try:
            # --- ПОЛУЧАЕМ ТОЧНОСТЬ ИЗ НАСТРОЕК ---
            precision = self.precision_input.value()
            format_str_profit = f"+.{precision}f"
            format_str_roi = f".{precision}f"
            format_str_winrate = f".{precision}f"
            # --- КОНЕЦ ПОЛУЧЕНИЯ ТОЧНОСТИ ---
            # Обновляем текстовую статистику
            self.lbl_testing_rounds.setText(f"{batch_stats['processed_rounds']:,} / {batch_stats['total_rounds']:,}")
            # Профит
            profit_value = batch_stats['current_profit']
            self.lbl_testing_profit.setText(f"{profit_value:{format_str_profit}}")
            if profit_value >= 0:
                self.lbl_testing_profit.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold; font-size: 11pt;")
            else:
                self.lbl_testing_profit.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold; font-size: 11pt;")
            # ROI
            roi_value = batch_stats['current_roi']
            self.lbl_testing_roi.setText(f"{roi_value:{format_str_roi}}%")
            if roi_value >= 0:
                self.lbl_testing_roi.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold;")
            else:
                self.lbl_testing_roi.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")
            # Винрейт
            winrate_value = batch_stats['current_winrate']
            self.lbl_testing_winrate.setText(f"{winrate_value:{format_str_winrate}}%")
            if winrate_value >= 50:
                self.lbl_testing_winrate.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold;")
            else:
                self.lbl_testing_winrate.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")
            # Победы/Поражения
            self.lbl_testing_wins.setText(str(batch_stats['total_wins']))
            self.lbl_testing_losses.setText(str(batch_stats['total_losses']))
            # Серии
            self.lbl_testing_max_win_streak.setText(str(batch_stats['max_win_streak']))
            self.lbl_testing_max_loss_streak.setText(str(batch_stats['max_loss_streak']))
            # Обновляем таблицы (теперь они находятся на другой вкладке) с новой точностью
            self.crash_history_table.set_precision(precision)
            self.actions_history_table.set_precision(precision)
            self.crash_history_table.update_history(batch_stats['crash_history'])
            self.actions_history_table.update_history(batch_stats['actions_history'])
            # Обновляем график
            if len(batch_stats['plot_rounds']) > 1000:
                rounds_to_send = batch_stats['plot_rounds'][-1000:]
                profits_to_send = batch_stats['plot_profits'][-1000:]
            else:
                rounds_to_send = batch_stats['plot_rounds']
                profits_to_send = batch_stats['plot_profits']
            self.update_testing_graph(rounds_to_send, profits_to_send)
        except Exception as e:
            pass
    def update_testing_graph(self, rounds_list: list, profit_list: list):
        """Вызывается для обновления графика."""
        try:
            # Обновляем график
            self.testing_graph_ax.clear()
            if rounds_list and profit_list:
                self.testing_graph_ax.plot(rounds_list, profit_list,
                                         color=APP_CONFIG['COLOR_SECONDARY'], linewidth=1.5)
                # Заливка под графиком
                self.testing_graph_ax.fill_between(rounds_list, profit_list, alpha=0.3, color=APP_CONFIG['COLOR_SECONDARY'])
            self.testing_graph_ax.set_title("Профит по раундам", color=APP_CONFIG['COLOR_TEXT'], fontsize=12)
            self.testing_graph_ax.set_xlabel("Раунд", color=APP_CONFIG['COLOR_TEXT'])
            self.testing_graph_ax.set_ylabel("Профит", color=APP_CONFIG['COLOR_TEXT'])
            self.testing_graph_ax.grid(True, color=APP_CONFIG['COLOR_BORDER'])
            self.testing_graph_canvas.draw()
        except Exception as e:
            pass
    def on_testing_finished(self, final_stats: dict):
        """Вызывается по завершении тестирования."""
        try:
            # --- ПОЛУЧАЕМ ТОЧНОСТЬ ИЗ НАСТРОЕК ---
            precision = self.precision_input.value()
            format_str_profit = f"+.{precision}f"
            format_str_roi = f".{precision}f"
            format_str_winrate = f".{precision}f"
            # --- КОНЕЦ ПОЛУЧЕНИЯ ТОЧНОСТИ ---
            self.start_testing_button.setEnabled(True)
            self.stop_testing_button.setEnabled(False)
            self.lbl_testing_status.setText("Завершено")
            self.lbl_testing_status.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold;")
            # Обновляем всю статистику
            # Профит
            profit_value = final_stats['total_profit']
            self.lbl_testing_profit.setText(f"{profit_value:{format_str_profit}}")
            if profit_value >= 0:
                self.lbl_testing_profit.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold; font-size: 12pt;")
            else:
                self.lbl_testing_profit.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold; font-size: 12pt;")
            # ROI
            roi_value = final_stats['roi']
            self.lbl_testing_roi.setText(f"{roi_value:{format_str_roi}}%")
            if roi_value >= 0:
                self.lbl_testing_roi.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold; font-size: 11pt;")
            else:
                self.lbl_testing_roi.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold; font-size: 11pt;")
            # Винрейт
            winrate_value = final_stats['winrate']
            self.lbl_testing_winrate.setText(f"{winrate_value:{format_str_winrate}}%")
            if winrate_value >= 50:
                self.lbl_testing_winrate.setStyleSheet(f"color: {APP_CONFIG['COLOR_POSITIVE']}; font-weight: bold; font-size: 11pt;")
            else:
                self.lbl_testing_winrate.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold; font-size: 11pt;")
            self.lbl_testing_wins.setText(str(final_stats['total_wins']))
            self.lbl_testing_losses.setText(str(final_stats['total_losses']))
            self.lbl_testing_max_win_streak.setText(str(final_stats['max_win_streak']))
            self.lbl_testing_max_loss_streak.setText(str(final_stats['max_loss_streak']))
            # Обновляем таблицы и график (таблицы теперь на другой вкладке) с новой точностью
            self.crash_history_table.set_precision(precision)
            self.actions_history_table.set_precision(precision)
            self.crash_history_table.update_history(final_stats['crash_history'])
            self.actions_history_table.update_history(final_stats['actions_history'])
            self.update_testing_graph(final_stats['plot_rounds'], final_stats['plot_profits'])
            QMessageBox.information(self, "Тестирование завершено",
                                  f"Тестирование {final_stats['total_rounds']:,} раундов завершено.\n"
                                  f"Итоговый профит: {final_stats['total_profit']:{format_str_profit}}\n"
                                  f"ROI: {final_stats['roi']:{format_str_roi}}%\n"
                                  f"Винрейт: {final_stats['winrate']:{format_str_winrate}}%")
        except Exception as e:
            pass
    def on_testing_stopped(self):
        """Вызывается при остановке тестирования пользователем."""
        self.start_testing_button.setEnabled(True)
        self.stop_testing_button.setEnabled(False)
        self.lbl_testing_status.setText("Остановлено")
        self.lbl_testing_status.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")
        QMessageBox.information(self, "Тестирование остановлено", "Тестирование было остановлено пользователем.")
    def on_testing_error(self, error_msg: str):
        """Вызывается при ошибке в процессе тестирования."""
        self.start_testing_button.setEnabled(True)
        self.stop_testing_button.setEnabled(False)
        self.lbl_testing_status.setText("Ошибка")
        self.lbl_testing_status.setStyleSheet(f"color: {APP_CONFIG['COLOR_NEGATIVE']}; font-weight: bold;")
        QMessageBox.critical(self, "Ошибка тестирования", f"Произошла ошибка во время тестирования:\n{error_msg}")
    def save_settings(self):
        QMessageBox.information(self, "Настройки", "Настройки сохранены.")
        self.statusBar().showMessage("Настройки сохранены", 3000)
    def restore_default_settings(self):
        reply = QMessageBox.question(self, 'Сброс настроек', 'Вы уверены, что хотите сбросить все настройки?',
                                    QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
                                    QMessageBox.StandardButton.No)
        if reply == QMessageBox.StandardButton.Yes:
            try:
                # Сброс основных настроек
                self.initial_balance_input.setValue(0)
                self.min_bet_input.setValue(100)
                self.max_bet_input.setValue(100)
                self.min_multiplier_input.setValue(1.11)
                self.max_multiplier_input.setValue(1.11)
                # --- СБРОС НОВОЙ НАСТРОЙКИ ---
                self.precision_input.setValue(2)
                # --- КОНЕЦ СБРОСА НОВОЙ НАСТРОЙКИ ---
                self.statusBar().showMessage("Настройки сброшены", 3000)
            except Exception as e:
                QMessageBox.critical(self, "Ошибка", f"Произошла ошибка при сбросе настроек: {str(e)}")
if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

 

 

 

2025-08-16_01-17-44.png

2025-08-16_01-18-11.png

2025-08-16_01-23-09.png

2025-08-16_01-25-17.png


Изменено пользователем yellyex

moonfangtopich написал 29.08.2019 в 14:57:
У вас недостаточно широкий кругозор, пацаны

Я странствия этого еблана видел в покерных топанах, а потом в таверне - это один из самых безумных людей на форуме. Я искренне надеялся, что его зов о помощи останется незамеченным, но нет, нашелся доброволец и вот уже три страницы мы пожинаем плоды

Поделиться сообщением


Ссылка на сообщение

попроси его исправлять баг, пока не починит

че ты как не вайб кодер

scarppy понравилось это

Торжество разума в том, чтобы уживаться с теми, у кого этого разума нет. Вольтер.
Чтобы хорошо высыпаться, нужно спать 8 часов в день. И еще столько же ночью.

Поделиться сообщением


Ссылка на сообщение
Kant написал 1 минуту назад:

попроси его исправлять баг, пока не починит

че ты как не вайб кодер

Так он мне впаривает, что всё нормально с его точки зрения. Ошибку он не видит. Даже мне впарил, что 90.09% это не винрейт, а лузрейт. Пришлось ему самому объяснить, что он ошибся в рассчётах.

Сейчас попробую нейроннку запилить, чтобы он нашёл прибыльную страту.


moonfangtopich написал 29.08.2019 в 14:57:
У вас недостаточно широкий кругозор, пацаны

Я странствия этого еблана видел в покерных топанах, а потом в таверне - это один из самых безумных людей на форуме. Я искренне надеялся, что его зов о помощи останется незамеченным, но нет, нашелся доброволец и вот уже три страницы мы пожинаем плоды

Поделиться сообщением


Ссылка на сообщение
(изменено)
  Показать содержимое

202479712_.png.91ce4af401e94bf59a187dc465335dad.png

Пиздец.
Тестаните ваши платные, может они правда лучше чем та бесплатная которой я пользуюсь. 

Изменено пользователем Ritsu twit

 

 

doc137399519_225147917?hash=61dbe6de1d450a42e7&dl=3361c54861ae5a77cb&wnd=1

Поделиться сообщением


Ссылка на сообщение
(изменено)
Ritsu twit написал 17 минут назад:
  Показать содержимое

202479712_.png.91ce4af401e94bf59a187dc465335dad.png

Пиздец.
Тестаните ваши платные, может они правда лучше чем та бесплатная которой я пользуюсь. 

 

gpt-5 +
4.1 +
Claude 3.7 (think) +
Claude 3.7 +

Дальше влом


Изменено пользователем Arzanis
Ritsu twit понравилось это

 

DB

59221730.png


Я - гений, ёпта

bfe7003be27e8e81ce6a7d2d8192e9ae.jpg


22


msg-93176-0-72842500-1438846470_thumb.jpg

Поделиться сообщением


Ссылка на сообщение

чел твое казино заскамят через 10 минут после того, как ты его на хостинг выложишь

вайбкодер бля

 


VyMEtE8XtOI.jpg

  лучшая цитата финта+жизненная

XbkBCDXetHY.jpg

Поделиться сообщением


Ссылка на сообщение

Присоединяйтесь к обсуждению

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

Гость
Ответить в тему...

×   Вставлено в виде отформатированного текста.   Восстановить форматирование

  Разрешено не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отобразить как ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставить изображения напрямую. Загрузите или вставьте изображения по ссылке.

Загрузка...

×
×
  • Создать...