import sys
from PyQt5.QtWidgets import (
    QApplication, QWidget, QLabel, QPushButton, QVBoxLayout,
    QComboBox, QHBoxLayout, QLineEdit, QDoubleSpinBox, QMessageBox, QSpinBox
)
from PyQt5.QtCore import QThread, pyqtSignal, QTimer
import DataFromBB

class DataUpdater(QThread):
    data_updated = pyqtSignal(object)

    def run(self):
        """Метод, который будет выполняться в фоне."""
        while True:
            option_data = DataFromBB.process_tickers()  # Получение данных
            self.data_updated.emit(option_data)  # Отправка обновленных данных
            self.sleep(10)  # Пауза на 1 секунду

class PositionWidget(QWidget):
    def __init__(self, remove_callback, option_data):
        """
        remove_callback — функция, вызываемая для удаления этой позиции из родительского виджета.
        """
        super().__init__()
        # Инициализация параметров позиции
        self.position_name = "Позиция 1"   # Выбор из списка 4 позиций
        self.leverage = 1                  # Кредитное плечо
        self.expiration = "1 неделя"       # Срок действия
        self.strike_price = 100            # Страйк-цена
        self.option_type = "Call"          # Тип (применяется для позиции 4)
        self.direction = "Купить"          # Направление
        self.demand_size = 100             # Размер спроса
        self.demand_price = 10             # Цена спроса
        self.order_price = 10              # Цена ордера/IV (пересчитывается)
        self.quantity = 150                # Количество
        self.offer_size = 150              # Размер предложения
        self.offer_price = 12              # Цена предложения/IV

        self.remove_callback = remove_callback
        self.option_data = option_data  # Хранит общий option_data

        # -------------------------------
        # 1. Наименование позиции
        self.position_name_combo = QComboBox(self)
        self.position_name_combo.addItems(["Позиция 1", "Позиция 2", "Позиция 3", "Позиция 4"])
        self.position_name_combo.setCurrentText(self.position_name)
        self.position_name_combo.currentTextChanged.connect(self.update_position_name)
        layout_name = QVBoxLayout()
        layout_name.addWidget(QLabel("Наименование"))
        layout_name.addWidget(self.position_name_combo)

        # -------------------------------
        # 2. Кредитное плечо
        self.leverage_input = QSpinBox(self)
        self.leverage_input.setRange(1, 100)
        self.leverage_input.setValue(self.leverage)
        self.leverage_input.valueChanged.connect(self.update_leverage)
        self.leverage_label = QLabel("——", self)
        self.leverage_label.hide()  # По умолчанию скрыта
        layout_leverage = QVBoxLayout()
        layout_leverage.addWidget(QLabel("Кред. плечо"))
        layout_leverage.addWidget(self.leverage_input)
        layout_leverage.addWidget(self.leverage_label)

        # -------------------------------
        # 3. Срок действия
        self.expiration_combo = QComboBox(self)
        self.expiration_combo.addItems(["1 неделя", "1 месяц", "3 месяца"])
        self.expiration_combo.setCurrentText(self.expiration)
        self.expiration_combo.currentTextChanged.connect(self.update_expiration)
        self.expiration_label = QLabel("——", self)
        self.expiration_label.hide()
        layout_expiration = QVBoxLayout()
        layout_expiration.addWidget(QLabel("Срок действия"))
        layout_expiration.addWidget(self.expiration_combo)
        layout_expiration.addWidget(self.expiration_label)

        # -------------------------------
        # 4. Страйк-цена
        self.strike_price_combo = QComboBox(self)
        self.strike_price_combo.addItems([str(x) for x in range(90, 111, 5)])
        self.strike_price_combo.setCurrentText(str(self.strike_price))
        self.strike_price_combo.currentTextChanged.connect(self.update_strike_price)
        self.strike_price_display = QLabel("——", self)
        self.strike_price_display.hide()
        layout_strike = QVBoxLayout()
        layout_strike.addWidget(QLabel("Страйк-цена"))
        layout_strike.addWidget(self.strike_price_combo)
        layout_strike.addWidget(self.strike_price_display)

        # -------------------------------
        # 5. Тип
        self.option_type_combo = QComboBox(self)
        self.option_type_combo.addItems(["Call", "Put"])
        self.option_type_combo.setCurrentText(self.option_type)
        self.option_type_combo.currentTextChanged.connect(self.update_option_type)
        self.option_type_label = QLabel("——", self)
        self.option_type_label.hide()
        layout_option_type = QVBoxLayout()
        layout_option_type.addWidget(QLabel("Тип"))
        layout_option_type.addWidget(self.option_type_combo)
        layout_option_type.addWidget(self.option_type_label)

        # -------------------------------
        # 6. Направление
        self.direction_combo = QComboBox(self)
        self.direction_combo.addItems(["Купить", "Продать"])
        self.direction_combo.setCurrentText(self.direction)
        self.direction_combo.currentTextChanged.connect(self.update_direction)
        layout_direction = QVBoxLayout()
        layout_direction.addWidget(QLabel("Направление"))
        layout_direction.addWidget(self.direction_combo)

        # 7. Размер спроса
        self.demand_size_input = QDoubleSpinBox(self)
        self.demand_size_input.setRange(0.01, 1000.0)
        self.demand_size_input.setValue(self.demand_size)
        self.demand_size_input.valueChanged.connect(self.update_demand_size)
        layout_demand_size = QVBoxLayout()
        layout_demand_size.addWidget(QLabel("Размер спроса"))
        layout_demand_size.addWidget(self.demand_size_input)

        # 8. Цена спроса
        self.demand_price_input = QLineEdit(str(self.demand_price), self)
        self.demand_price_input.textChanged.connect(self.update_demand_price)
        layout_demand_price = QVBoxLayout()
        layout_demand_price.addWidget(QLabel("Цена спроса"))
        layout_demand_price.addWidget(self.demand_price_input)

        # 9. Цена ордера/IV
        self.order_price_label = QLabel(f"Цена ордера: {self.order_price}", self)
        layout_order_price = QVBoxLayout()
        layout_order_price.addWidget(QLabel("Цена ордера"))
        layout_order_price.addWidget(self.order_price_label)

        # 10. Количество
        self.quantity_input = QDoubleSpinBox(self)
        self.quantity_input.setRange(0.01, 1000.0)
        self.quantity_input.setValue(self.quantity)
        self.quantity_input.valueChanged.connect(self.update_quantity)
        layout_quantity = QVBoxLayout()
        layout_quantity.addWidget(QLabel("Количество"))
        layout_quantity.addWidget(self.quantity_input)

        # 11. Размер предложения
        self.offer_size_input = QDoubleSpinBox(self)
        self.offer_size_input.setRange(0.01, 1000.0)
        self.offer_size_input.setValue(self.offer_size)
        self.offer_size_input.valueChanged.connect(self.update_offer_size)
        layout_offer_size = QVBoxLayout()
        layout_offer_size.addWidget(QLabel("Размер предложения"))
        layout_offer_size.addWidget(self.offer_size_input)

        # 12. Цена предложения/IV
        self.offer_price_input = QLineEdit(str(self.offer_price), self)
        self.offer_price_input.textChanged.connect(self.update_offer_price)
        layout_offer_price = QVBoxLayout()
        layout_offer_price.addWidget(QLabel("Цена предложения"))
        layout_offer_price.addWidget(self.offer_price_input)

        # -------------------------------
        # 13. Кнопка удаления позиции
        self.delete_button = QPushButton("Удалить", self)
        self.delete_button.clicked.connect(self.delete_position)
        layout_delete = QVBoxLayout()
        layout_delete.addWidget(QLabel("Действие"))
        layout_delete.addWidget(self.delete_button)

        # Основной горизонтальный макет для размещения всех групп
        main_layout = QHBoxLayout()
        main_layout.addLayout(layout_name)
        main_layout.addLayout(layout_leverage)
        main_layout.addLayout(layout_expiration)
        main_layout.addLayout(layout_strike)
        main_layout.addLayout(layout_option_type)
        main_layout.addLayout(layout_direction)
        main_layout.addLayout(layout_demand_size)
        main_layout.addLayout(layout_demand_price)
        main_layout.addLayout(layout_order_price)
        main_layout.addLayout(layout_quantity)
        main_layout.addLayout(layout_offer_size)
        main_layout.addLayout(layout_offer_price)
        main_layout.addLayout(layout_delete)

        # Устанавливаем основной макет для виджета позиции (без заголовка)
        self.setLayout(main_layout)

        # Первоначальное обновление отображения в зависимости от выбранной позиции
        self.update_position_name(self.position_name)
        self.update_order_price()  # Обновляем цену ордера согласно начальным параметрам

        self.option_data = DataFromBB.process_tickers()

    def update_option_data(self, option_data):
        """Получение и обновление данных для позиции."""
        self.option_data = option_data
        self.update_strike_price(self.strike_price)
        print(option_data)

    def update_position_name(self, text):
        """Обновление наименования позиции и переключение между интерактивными элементами и метками."""
        self.position_name = text
        if self.position_name == "Позиция 4":
            # Обновляем даты экспирации
            self.expiration_combo.clear()
            self.expiration_combo.addItems(self.option_data.keys())
            # Обновляем Страйк цены
            self.strike_price_combo.show()
            self.strike_price_display.hide()
            # Для позиции 4:
            # Кредитное плечо не применяется
            self.leverage_input.hide()
            self.leverage_label.setText("——")
            self.leverage_label.show()
            # Срок действия применяется
            self.expiration_combo.show()
            self.expiration_label.hide()
            # Страйк-цена и Тип применяются
            self.option_type_combo.show()
            self.option_type_label.hide()
        elif self.position_name in ["Позиция 1", "Позиция 2"]:
            # Для позиций 1 и 2:
            # Кредитное плечо применяется
            self.leverage_input.show()
            self.leverage_label.hide()
            # Срок действия не применяется
            self.expiration_combo.hide()
            self.expiration_label.setText("——")
            self.expiration_label.show()
            # Страйк-цена и Тип не применяются
            self.strike_price_combo.hide()
            self.strike_price_display.setText("——")
            self.strike_price_display.show()
            self.option_type_combo.hide()
            self.option_type_label.setText("——")
            self.option_type_label.show()
        elif self.position_name == "Позиция 3":
            # Для позиции 3:
            # Кредитное плечо применяется
            self.leverage_input.show()
            self.leverage_label.hide()
            # Срок действия применяется
            self.expiration_combo.show()
            self.expiration_label.hide()
            # Страйк-цена и Тип не применяются
            self.strike_price_combo.hide()
            self.strike_price_display.setText("——")
            self.strike_price_display.show()
            self.option_type_combo.hide()
            self.option_type_label.setText("——")
            self.option_type_label.show()

    def update_leverage(self, value):
        self.leverage = value

    def update_expiration(self, text):
        self.expiration = text
        try:
            self.strike_price_combo.clear()
            for i in range(len(self.option_data[self.expiration])):
                self.strike_price_combo.addItems([self.option_data[self.expiration][i]['str_price']])
        except Exception:
            pass

    def update_strike_price(self, text):
        try:
            self.strike_price = int(text)
            if self.position_name == "Позиция 4":
                if self.option_data.get(self.expiration)[self.strike_price_combo.currentIndex()].get('options')[0].get('option_type') == 'Call' and self.option_type == 'Call' or self.option_data.get(self.expiration)[self.strike_price_combo.currentIndex()].get('options')[0].get('option_type') == 'Put' and self.option_type == 'Put':
                    # Если тип Call, устанавливаем параметры
                    self.demand_size = float(self.option_data.get(self.expiration)[self.strike_price_combo.currentIndex()].get('options')[0].get('bid1Size'))
                    self.demand_price = float(self.option_data.get(self.expiration)[self.strike_price_combo.currentIndex()].get('options')[0].get('bid1Price'))
                    self.offer_size = float(self.option_data.get(self.expiration)[self.strike_price_combo.currentIndex()].get('options')[0].get('ask1Size'))
                    self.offer_price = float(self.option_data.get(self.expiration)[self.strike_price_combo.currentIndex()].get('options')[0].get('ask1Price'))
                elif self.option_data.get(self.expiration)[self.strike_price_combo.currentIndex()].get('options')[0].get('option_type') == 'Call' and self.option_type == 'Put' or self.option_data.get(self.expiration)[self.strike_price_combo.currentIndex()].get('options')[0].get('option_type') == 'Put' and self.option_type == 'Call':
                    # Если тип Put, устанавливаем параметры
                    self.demand_size = float(self.option_data.get(self.expiration)[self.strike_price_combo.currentIndex()].get('options')[1].get('bid1Size'))
                    self.demand_price = float(self.option_data.get(self.expiration)[self.strike_price_combo.currentIndex()].get('options')[1].get('bid1Price'))
                    self.offer_size = float(self.option_data.get(self.expiration)[self.strike_price_combo.currentIndex()].get('options')[1].get('ask1Size'))
                    self.offer_price = float(self.option_data.get(self.expiration)[self.strike_price_combo.currentIndex()].get('options')[1].get('ask1Price'))

                self.demand_size_input.setValue(self.demand_size)
                self.demand_price_input.setText(str(self.demand_price))
                self.offer_size_input.setValue(self.offer_size)
                self.offer_price_input.setText(str(self.offer_price))

            self.update_order_price()
            # if self.position_name == "Позиция 1":
            #
            # if self.position_name == "Позиция 2":
            #
            # if self.position_name == "Позиция 3":

        except ValueError:
            pass

    def update_option_type(self, text):
        self.option_type = text
        self.update_strike_price(self.strike_price)

    def update_direction(self, text):
        self.direction = text
        self.update_quantity()     # Пересчитываем количество при изменении направления
        self.update_order_price()  # Пересчитываем цену ордера

    def update_demand_size(self, value):
        self.demand_size = value
        self.update_quantity()  # Пересчитываем количество при изменении размера спроса

    def update_demand_price(self, text):
        try:
            self.demand_price = float(text)
        except ValueError:
            pass
        self.update_order_price()  # Пересчитываем цену ордера при изменении цены спроса

    def update_quantity(self):
        """Обновление количества в зависимости от направления."""
        if self.direction == "Купить":
            self.quantity = self.offer_size
        else:
            self.quantity = self.demand_size
        self.quantity_input.setValue(self.quantity)

    def update_offer_size(self, value):
        self.offer_size = value
        self.update_quantity()  # Пересчитываем количество при изменении размера предложения

    def update_offer_price(self, text):
        try:
            self.offer_price = float(text)
        except ValueError:
            pass
        self.update_order_price()  # Пересчитываем цену ордера при изменении цены предложения

    def update_order_price(self):
        """Пересчет цены ордера в зависимости от направления.
           Если направление 'Купить' – цена ордера равна цене предложения,
           иначе – цена ордера равна цене спроса."""
        if self.direction == "Купить":
            self.order_price = self.offer_price
        else:
            self.order_price = self.demand_price
        self.order_price_label.setText(f"{self.order_price}")

    def delete_position(self):
        """Удаление позиции."""
        self.remove_callback(self)


class App(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Динамическое количество позиций")
        self.setGeometry(100, 100, 1400, 100)

        self.positions = []

        # Создание потока для обновления данных
        self.data_updater = DataUpdater()
        self.data_updater.data_updated.connect(self.update_all_positions)  # Подключаем сигнал к методу
        self.data_updater.start()  # Запуск потока

        # Единая плашка для всех позиций
        header_label = QLabel("Параметры позиции:")
        header_label.setStyleSheet("font-size: 16px; font-weight: bold;")

        # Вертикальный макет для размещения позиций
        self.positions_layout = QVBoxLayout()

        # Основной вертикальный макет с единым заголовком
        self.main_layout = QVBoxLayout()
        self.main_layout.addWidget(header_label)
        self.main_layout.addLayout(self.positions_layout)

        # Кнопка для добавления новой позиции
        self.add_button = QPushButton("Добавить позицию", self)
        self.add_button.clicked.connect(self.add_position)
        add_layout = QHBoxLayout()
        add_layout.addWidget(self.add_button)

        # Добавляем кнопку в основной макет
        self.main_layout.addLayout(add_layout)

        self.setLayout(self.main_layout)

    def add_position(self):
        """Добавление новой позиции в интерфейс, передавая общий option_data."""
        position = PositionWidget(remove_callback=self.remove_position, option_data={})
        self.positions.append(position)
        self.positions_layout.addWidget(position)

    def update_all_positions(self, option_data):
        """Метод для обновления всех позиций."""
        for position in self.positions:
            position.update_option_data(option_data)  # Передаем новые данные каждой позиции

    def remove_position(self, position_widget):
        """Удаление позиции из интерфейса и списка."""
        if position_widget in self.positions:
            self.positions.remove(position_widget)
            self.positions_layout.removeWidget(position_widget)
            position_widget.deleteLater()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = App()
    window.show()
    sys.exit(app.exec_()) 
у меня есть такая программа но теперь функция process_tickers возвращает return options, futures, USDT_perp, USDC_perp