diff --git a/src/StockBench/gui/application.py b/src/StockBench/gui/application.py index 2616ba1a..829dfb5d 100644 --- a/src/StockBench/gui/application.py +++ b/src/StockBench/gui/application.py @@ -1,38 +1,37 @@ from PyQt6.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QTabWidget, QLabel from PyQt6 import QtGui +from StockBench.gui.config.tabs.compare.compare_config_tab import CompareConfigTab +from StockBench.gui.config.tabs.folder.folder_config_tab import FolderConfigTab +from StockBench.gui.config.tabs.singular.singular_config_tab import SingularConfigTab from StockBench.gui.palette.palette import Palette -from StockBench.gui.config.tabs.singular_config_tab import SingularConfigTab -from StockBench.gui.config.tabs.multi_config_tab import MultiConfigTab -from StockBench.gui.config.tabs.compare_config_tab import CompareConfigTab -from StockBench.gui.config.tabs.folder_config_tab import FolderConfigTab +from StockBench.gui.config.tabs.multi.multi_config_tab import MultiConfigTab from StockBench.gui.configuration import AppConfiguration from StockBench.controllers.controller_factory import StockBenchControllerFactory class ConfigMainWindow(QMainWindow): - WIDTH = 400 - HEIGHT = 680 + WIDTH = 650 + HEIGHT = 600 def __init__(self, splash, config: AppConfiguration): super().__init__() self.splash = splash - # get an instance of the controller for the application to use (reference passed to all results windows) self.__stockbench_controller = StockBenchControllerFactory.get_controller_instance() - # main window styling (do it first to prevent window shifting) - self.setWindowIcon(QtGui.QIcon(Palette.CANDLE_ICON)) + # main window styling (done first to prevent window shifting) + self.setWindowIcon(QtGui.QIcon(Palette.CANDLE_ICON_FILEPATH)) self.setWindowTitle('Configuration') - self.setFixedSize(self.WIDTH, self.HEIGHT) + # SEE BOTTOM FOR CONFIG WINDOW SIZING!!! self.layout = QVBoxLayout() self.tab_widget = QTabWidget() - self.tab_widget.addTab(SingularConfigTab(self.__stockbench_controller), "Single") - self.tab_widget.addTab(MultiConfigTab(self.__stockbench_controller), "Multi") - self.tab_widget.addTab(CompareConfigTab(self.__stockbench_controller), "Compare") - self.tab_widget.addTab(FolderConfigTab(self.__stockbench_controller), "Folder") + self.tab_widget.addTab(SingularConfigTab(self.__update_geometry, self.__stockbench_controller), "Single") + self.tab_widget.addTab(MultiConfigTab(self.__update_geometry, self.__stockbench_controller), "Multi") + self.tab_widget.addTab(CompareConfigTab(self.__update_geometry, self.__stockbench_controller), "Compare") + self.tab_widget.addTab(FolderConfigTab(self.__update_geometry, self.__stockbench_controller), "Folder") self.tab_widget.setStyleSheet(Palette.TAB_WIDGET_STYLESHEET) self.layout.addWidget(self.tab_widget) @@ -45,9 +44,25 @@ def __init__(self, splash, config: AppConfiguration): widget.setStyleSheet(Palette.WINDOW_STYLESHEET) widget.setLayout(self.layout) - # Set the central widget of the Window. Widget will expand - # to take up all the space in the window by default. self.setCentralWidget(widget) - # close the splash window self.splash.close() + + # CONFIG WINDOW SIZING + # we want the window to shrink to the size of the widgets but also be non-resizable + # after the tabs are added, the following block shrinks the window to fit of the biggest tab and disables + # resizing + self.__update_geometry() + + def __update_geometry(self): + """Updates the geometry of the main config window while maintaining the in-ability for manual resize. + + Use 1: Used to set the initial size of the main config window after tabs are added to the layout. + Use 2: Callback for when the error message label is added to the layout, preventing other widget squishing by + resizing the layout to fit all contained widgets. + """ + self.updateGeometry() + self.adjustSize() + size = self.sizeHint() + self.setMinimumSize(size) + self.setMaximumSize(size) diff --git a/src/StockBench/gui/config/components/cached_folder_selector.py b/src/StockBench/gui/config/components/cached_folder_selector.py index 35950ecd..044247a3 100644 --- a/src/StockBench/gui/config/components/cached_folder_selector.py +++ b/src/StockBench/gui/config/components/cached_folder_selector.py @@ -14,7 +14,7 @@ def __init__(self): self.layout = QHBoxLayout() self.folderpath_box = QLabel() - self.folderpath_box.setStyleSheet(Palette.TEXT_BOX_STYLESHEET) + self.folderpath_box.setStyleSheet(Palette.FILEPATH_BOX_STYLESHEET) self.layout.addWidget(self.folderpath_box) self.apply_cached_folderpath() @@ -22,6 +22,7 @@ def __init__(self): self.select_folder_btn.setText('Select Folder') self.select_folder_btn.clicked.connect(self.on_select_folder_btn_clicked) # noqa self.select_folder_btn.setStyleSheet(Palette.SECONDARY_BTN) + self.select_folder_btn.setFixedWidth(90) self.layout.addWidget(self.select_folder_btn) self.setLayout(self.layout) diff --git a/src/StockBench/gui/config/components/strategy_selection.py b/src/StockBench/gui/config/components/strategy_selection.py index 9c69535c..df9e5667 100644 --- a/src/StockBench/gui/config/components/strategy_selection.py +++ b/src/StockBench/gui/config/components/strategy_selection.py @@ -17,7 +17,8 @@ def __init__(self, cache_key=None): self.layout = QHBoxLayout() self.filepath_box = QLabel() - self.filepath_box.setStyleSheet(Palette.INPUT_BOX_STYLESHEET) + self.filepath_box.setStyleSheet(Palette.FILEPATH_BOX_STYLESHEET) + self.filepath_box.setFixedHeight(25) self.layout.addWidget(self.filepath_box) self.apply_cached_strategy_filepath(cache_key) @@ -25,6 +26,7 @@ def __init__(self, cache_key=None): self.select_file_btn.setText('Select File') self.select_file_btn.clicked.connect(self.on_select_file_btn_clicked) # noqa self.select_file_btn.setStyleSheet(Palette.SECONDARY_BTN) + self.select_file_btn.setFixedWidth(85) self.layout.addWidget(self.select_file_btn) self.setLayout(self.layout) diff --git a/src/StockBench/gui/config/tabs/base/config_tab.py b/src/StockBench/gui/config/tabs/base/config_tab.py index 2a28fbae..b1543089 100644 --- a/src/StockBench/gui/config/tabs/base/config_tab.py +++ b/src/StockBench/gui/config/tabs/base/config_tab.py @@ -5,8 +5,7 @@ from functools import wraps from typing import Callable -from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel, QLineEdit, QPushButton, QRadioButton, QComboBox -from PyQt6.QtGui import QDoubleValidator +from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton from PyQt6.QtCore import QPoint from StockBench.controllers.stockbench_controller import StockBenchController @@ -37,6 +36,9 @@ def wrapper(self, *args, **kwargs): return original_fxn(self, *args, **kwargs) except MessageBoxCaptureException as e: self.error_message_box.setText(str(e)) + self.layout.addWidget(self.error_message_box) + # update config window geometry using callback + self.update_geometry() return wrapper @@ -47,8 +49,9 @@ class ConfigTab(QWidget): DEFAULT_CACHE_KEY = 'cached_strategy_filepath' - def __init__(self, stockbench_controller: StockBenchController): + def __init__(self, update_geometry: Callable, stockbench_controller: StockBenchController): super().__init__() + self.update_geometry = update_geometry self._stockbench_controller = stockbench_controller # windows launched from a class need to be attributes or else they will be closed when the function @@ -64,97 +67,20 @@ def __init__(self, stockbench_controller: StockBenchController): self.results_depth = Simulator.CHARTS_AND_DATA # ========================= Shared Components ================================ - # define the layout self.layout = QVBoxLayout() - # simulation length label - self.simulation_length_label = QLabel() - self.simulation_length_label.setText('Simulation Length:') - self.simulation_length_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) - # simulation length input - self.simulation_length_cbox = QComboBox() - self.simulation_length_cbox.addItem('1 Year') - self.simulation_length_cbox.addItem('2 Year') - self.simulation_length_cbox.addItem('5 Year') - # set simulation length default to 1 year (must set attribute as well) - self.simulation_length_cbox.setCurrentIndex(0) - self.simulation_length = SECONDS_1_YEAR - self.simulation_length_cbox.setStyleSheet(Palette.COMBOBOX_STYLESHEET) - self.simulation_length_cbox.currentIndexChanged.connect(self.on_simulation_length_cbox_index_changed) # noqa - - # initial balance label - self.initial_balance_label = QLabel() - self.initial_balance_label.setText('Initial Balance:') - self.initial_balance_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) - # initial balance input - self.initial_balance_tbox = QLineEdit() - self.initial_balance_tbox.setText('1000.0') - self.onlyFloat = QDoubleValidator() - self.initial_balance_tbox.setValidator(self.onlyFloat) - self.initial_balance_tbox.setStyleSheet(Palette.LINE_EDIT_STYLESHEET) - - # logging label - self.logging_label = QLabel() - self.logging_label.setText('Logging:') - self.logging_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) - # logging button - self.logging_btn = QPushButton() - self.logging_btn.setCheckable(True) - self.logging_btn.setText(self.OFF) - self.logging_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) - self.logging_btn.clicked.connect(self.on_logging_btn_clicked) # noqa - - # reporting label - self.reporting_label = QLabel() - self.reporting_label.setText('Reporting:') - self.reporting_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) - # reporting button - self.reporting_btn = QPushButton() - self.reporting_btn.setCheckable(True) - self.reporting_btn.setText(self.OFF) - self.reporting_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) - self.reporting_btn.clicked.connect(self.on_reporting_btn_clicked) # noqa - - # unique chart saving - self.unique_chart_save_label = QLabel() - self.unique_chart_save_label.setText('Save Unique Charts:') - self.unique_chart_save_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) - - self.unique_chart_save_btn = QPushButton() - self.unique_chart_save_btn.setCheckable(True) - self.unique_chart_save_btn.setText(self.OFF) - self.unique_chart_save_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) - self.unique_chart_save_btn.clicked.connect(self.on_chart_saving_btn_clicked) # noqa - - # results depth label - self.results_depth_label = QLabel() - self.results_depth_label.setText('Results Depth:') - self.results_depth_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) - # results depth radio button - self.data_and_charts_radio_btn = QRadioButton("Data and Charts") - self.data_and_charts_radio_btn.toggled.connect(self.data_and_charts_btn_selected) # noqa - self.data_and_charts_radio_btn.setStyleSheet(Palette.RADIO_BTN_STYLESHEET) - self.data_and_charts_radio_btn.toggle() # set data and charts as default - # results depth radio button - self.data_only_radio_btn = QRadioButton("Data Only") - self.data_only_radio_btn.toggled.connect(self.data_only_btn_selected) # noqa - self.data_only_radio_btn.setStyleSheet(Palette.RADIO_BTN_STYLESHEET) - - # run button self.run_btn = QPushButton() self.run_btn.setFixedSize(60, 30) self.run_btn.setText('RUN') self.run_btn.clicked.connect(self.on_run_btn_clicked) # noqa self.run_btn.setStyleSheet(Palette.RUN_BTN_STYLESHEET) - # error message box self.error_message_box = QLabel() self.error_message_box.setStyleSheet(Palette.ERROR_LABEL_STYLESHEET) @CaptureConfigErrors def on_strategy_studio_btn_clicked(self, filepath: str): """ - Decorator: The CaptureErrors decorator allows custom exceptions to be caught and logged to the error message box instead of crashing. It also allows us to functionalize the filepath validation without the need for @@ -179,35 +105,41 @@ def on_simulation_length_cbox_index_changed(self, index): elif index == 2: self.simulation_length = SECONDS_5_YEAR - def on_logging_btn_clicked(self): - if self.logging_btn.isChecked(): + def on_logging_btn_clicked(self, button: QPushButton): + """Handles logging button toggle. Button reference is passed so we can read/update the button + at this level despite it being buried under layers of QFrames.""" + if button.isChecked(): self.simulation_logging = True - self.logging_btn.setText(self.ON) - self.logging_btn.setStyleSheet(Palette.TOGGLE_BTN_ENABLED_STYLESHEET) + button.setText(self.ON) + button.setStyleSheet(Palette.TOGGLE_BTN_ENABLED_STYLESHEET) else: self.simulation_logging = False - self.logging_btn.setText(self.OFF) - self.logging_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) + button.setText(self.OFF) + button.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) - def on_reporting_btn_clicked(self): - if self.reporting_btn.isChecked(): + def on_reporting_btn_clicked(self, button: QPushButton): + """Handles reporting button toggle. Button reference is passed so we can read/update the button + at this level despite it being buried under layers of QFrames.""" + if button.isChecked(): self.simulation_reporting = True - self.reporting_btn.setText(self.ON) - self.reporting_btn.setStyleSheet(Palette.TOGGLE_BTN_ENABLED_STYLESHEET) + button.setText(self.ON) + button.setStyleSheet(Palette.TOGGLE_BTN_ENABLED_STYLESHEET) else: self.simulation_reporting = False - self.reporting_btn.setText(self.OFF) - self.reporting_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) + button.setText(self.OFF) + button.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) - def on_chart_saving_btn_clicked(self): - if self.unique_chart_save_btn.isChecked(): + def on_chart_saving_btn_clicked(self, button: QPushButton): + """Handles chart saving button toggle. Button reference is passed so we can read/update the button + at this level despite it being buried under layers of QFrames.""" + if button.isChecked(): self.simulation_unique_chart_saving = True - self.unique_chart_save_btn.setText(self.ON) - self.unique_chart_save_btn.setStyleSheet(Palette.TOGGLE_BTN_ENABLED_STYLESHEET) + button.setText(self.ON) + button.setStyleSheet(Palette.TOGGLE_BTN_ENABLED_STYLESHEET) else: self.simulation_unique_chart_saving = False - self.unique_chart_save_btn.setText(self.OFF) - self.unique_chart_save_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) + button.setText(self.OFF) + button.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) def data_and_charts_btn_selected(self, selected): if selected: @@ -233,7 +165,6 @@ def load_strategy(self, filepath: str, cache_key=None, cache_value=None): except Exception as e: raise MessageBoxCaptureException(f'Uncaught error parsing strategy file: {e}') - # cache the strategy filepath (create if it does not already exist) self.cache_strategy_filepath(filepath, cache_key, cache_value) # inject the unix equivalent dates from the combobox to the dict @@ -244,23 +175,19 @@ def load_strategy(self, filepath: str, cache_key=None, cache_value=None): return strategy def cache_strategy_filepath(self, strategy_filepath, cache_key=None, cache_value=None): - # cache the strategy filepath (create if it does not already exist) - + """Caches the strategy filepath if it does not already exist.""" key = self.DEFAULT_CACHE_KEY if cache_key: key = cache_key - # get the existing cache data with open(CACHE_FILE_FILEPATH, 'r') as file: data = json.load(file) - # add the filepath to the cache data if cache_value: data[key] = cache_value else: data[key] = strategy_filepath - # write the cache data with open(CACHE_FILE_FILEPATH, 'w+') as file: json.dump(data, file) diff --git a/src/StockBench/gui/config/tabs/compare/__init__.py b/src/StockBench/gui/config/tabs/compare/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/StockBench/gui/config/tabs/compare_config_tab.py b/src/StockBench/gui/config/tabs/compare/compare_config_tab.py similarity index 66% rename from src/StockBench/gui/config/tabs/compare_config_tab.py rename to src/StockBench/gui/config/tabs/compare/compare_config_tab.py index 95ff6076..5e4624de 100644 --- a/src/StockBench/gui/config/tabs/compare_config_tab.py +++ b/src/StockBench/gui/config/tabs/compare/compare_config_tab.py @@ -1,11 +1,15 @@ -from PyQt6.QtWidgets import QLabel, QPushButton, QLineEdit +from typing import Callable + +from PyQt6.QtWidgets import QLabel, QPushButton from PyQt6.QtCore import Qt from StockBench.controllers.stockbench_controller import StockBenchController from StockBench.gui.config.tabs.base.config_tab import ConfigTab, MessageBoxCaptureException, CaptureConfigErrors +from StockBench.gui.config.tabs.compare.components.grid_config_frame import GridConfigFrame from StockBench.gui.results.compare.compare_results_window import CompareResultsWindow from StockBench.gui.palette.palette import Palette from StockBench.gui.config.components.strategy_selection import StrategySelection +from StockBench.models.constants.general_constants import SECONDS_1_YEAR class CompareConfigTab(ConfigTab): @@ -13,24 +17,22 @@ class CompareConfigTab(ConfigTab): STRATEGY_2_CACHE_KEY = 'cached_h2h_strategy_2_filepath' - def __init__(self, stockbench_controller: StockBenchController): - super().__init__(stockbench_controller) - # add shared_components to the layout + def __init__(self, update_geometry: Callable, stockbench_controller: StockBenchController): + super().__init__(update_geometry, stockbench_controller) label = QLabel() label.setText('Strategy 1:') label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) self.layout.addWidget(label) self.strategy_1_selection_box = StrategySelection(self.STRATEGY_1_CACHE_KEY) - self.strategy_1_selection_box.setStyleSheet(Palette.INPUT_BOX_STYLESHEET) self.layout.addWidget(self.strategy_1_selection_box) self.strategy_1_studio_btn = QPushButton() self.strategy_1_studio_btn.setText('Strategy Studio') self.strategy_1_studio_btn.clicked.connect(lambda: self.on_strategy_studio_btn_clicked( # noqa self.strategy_1_selection_box.filepath_box.text())) - self.strategy_1_studio_btn.setStyleSheet(Palette.SECONDARY_BTN) - self.layout.addWidget(self.strategy_1_studio_btn) + self.strategy_1_studio_btn.setStyleSheet(Palette.STRATEGY_STUDIO_BTN) + self.layout.addWidget(self.strategy_1_studio_btn, alignment=Qt.AlignmentFlag.AlignRight) label = QLabel() label.setText('Strategy 2:') @@ -38,53 +40,26 @@ def __init__(self, stockbench_controller: StockBenchController): self.layout.addWidget(label) self.strategy_2_selection_box = StrategySelection(self.STRATEGY_2_CACHE_KEY) - self.strategy_2_selection_box.setStyleSheet(Palette.INPUT_BOX_STYLESHEET) self.layout.addWidget(self.strategy_2_selection_box) self.strategy_2_studio_btn = QPushButton() self.strategy_2_studio_btn.setText('Strategy Studio') self.strategy_2_studio_btn.clicked.connect(lambda: self.on_strategy_studio_btn_clicked( # noqa self.strategy_2_selection_box.filepath_box.text())) - self.strategy_2_studio_btn.setStyleSheet(Palette.SECONDARY_BTN) - self.layout.addWidget(self.strategy_2_studio_btn) - - self.layout.addWidget(self.simulation_length_label) - - self.layout.addWidget(self.simulation_length_cbox) - - label = QLabel() - label.setText('Simulation Symbols:') - label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) - self.layout.addWidget(label) - - self.symbol_tbox = QLineEdit() - self.symbol_tbox.setText("MSFT, AAPL") - self.symbol_tbox.setStyleSheet(Palette.LINE_EDIT_STYLESHEET) - self.layout.addWidget(self.symbol_tbox) - - self.layout.addWidget(self.initial_balance_label) - - self.layout.addWidget(self.initial_balance_tbox) - - self.layout.addWidget(self.logging_label) - - self.layout.addWidget(self.logging_btn) - - self.layout.addWidget(self.reporting_label) - - self.layout.addWidget(self.reporting_btn) - - self.layout.addWidget(self.results_depth_label) - - self.layout.addWidget(self.data_and_charts_radio_btn) + self.strategy_2_studio_btn.setStyleSheet(Palette.STRATEGY_STUDIO_BTN) + self.layout.addWidget(self.strategy_2_studio_btn, alignment=Qt.AlignmentFlag.AlignRight) - self.layout.addWidget(self.data_only_radio_btn) + self.simulation_length = SECONDS_1_YEAR + self.grid_config_frame = GridConfigFrame(self.on_simulation_length_cbox_index_changed, + self.on_logging_btn_clicked, self.on_reporting_btn_clicked, + self.on_chart_saving_btn_clicked, self.data_and_charts_btn_selected, + self.data_only_btn_selected) + self.layout.addWidget(self.grid_config_frame) self.layout.addWidget(self.run_btn, alignment=Qt.AlignmentFlag.AlignRight) - self.layout.addWidget(self.error_message_box) + self.layout.addStretch() - # add the layout to the widget self.setLayout(self.layout) @CaptureConfigErrors @@ -105,11 +80,11 @@ def on_run_btn_clicked(self, clicked_signal: bool): strategy2 = self.load_strategy(self.strategy_2_selection_box.filepath_box.text(), self.STRATEGY_2_CACHE_KEY) # gather other data from UI shared_components - raw_simulation_symbols = self.symbol_tbox.text().split(',') + raw_simulation_symbols = self.grid_config_frame.left_frame.symbol_tbox.text().split(',') simulation_symbols = [] for symbol in raw_simulation_symbols: simulation_symbols.append(symbol.upper().strip()) - simulation_balance = float(self.initial_balance_tbox.text()) + simulation_balance = float(self.grid_config_frame.left_frame.initial_balance_tbox.text()) if simulation_balance <= 0: raise MessageBoxCaptureException('Initial account balance must be a positive number!') diff --git a/src/StockBench/gui/config/tabs/compare/components/__init__.py b/src/StockBench/gui/config/tabs/compare/components/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/StockBench/gui/config/tabs/compare/components/grid_config_frame.py b/src/StockBench/gui/config/tabs/compare/components/grid_config_frame.py new file mode 100644 index 00000000..7fd0ba56 --- /dev/null +++ b/src/StockBench/gui/config/tabs/compare/components/grid_config_frame.py @@ -0,0 +1,25 @@ +from typing import Callable + +from PyQt6.QtWidgets import QFrame, QGridLayout + +from StockBench.gui.config.tabs.compare.components.grid_config_left_frame import GridConfigLeftFrame +from StockBench.gui.config.tabs.compare.components.grid_config_right_frame import GridConfigRightFrame + + +class GridConfigFrame(QFrame): + def __init__(self, on_simulation_length_cbox_index_changed: Callable, on_logging_btn_clicked: Callable, + on_reporting_btn_clicked: Callable, on_chart_saving_btn_clicked: Callable, + data_and_charts_btn_selected: Callable, data_only_btn_selected: Callable): + super().__init__() + + self.layout = QGridLayout() + + self.left_frame = GridConfigLeftFrame(on_simulation_length_cbox_index_changed) + self.right_frame = GridConfigRightFrame(on_logging_btn_clicked, on_reporting_btn_clicked, + on_chart_saving_btn_clicked, data_and_charts_btn_selected, + data_only_btn_selected) + + self.layout.addWidget(self.left_frame, 0, 0) + self.layout.addWidget(self.right_frame, 0, 1) + + self.setLayout(self.layout) diff --git a/src/StockBench/gui/config/tabs/compare/components/grid_config_left_frame.py b/src/StockBench/gui/config/tabs/compare/components/grid_config_left_frame.py new file mode 100644 index 00000000..2ad54019 --- /dev/null +++ b/src/StockBench/gui/config/tabs/compare/components/grid_config_left_frame.py @@ -0,0 +1,63 @@ +from typing import Callable + +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QDoubleValidator +from PyQt6.QtWidgets import QFrame, QVBoxLayout, QLabel, QComboBox, QLineEdit + +from StockBench.gui.palette.palette import Palette + + +class GridConfigLeftFrame(QFrame): + FRAME_WIDTH = 300 + + INPUT_WIDTH = 240 + + def __init__(self, on_simulation_length_cbox_index_changed: Callable): + super().__init__() + self.setFixedWidth(self.FRAME_WIDTH) + + self.layout = QVBoxLayout() + + self.simulation_length_label = QLabel() + self.simulation_length_label.setText('Simulation Length:') + self.simulation_length_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.simulation_length_label) + + self.simulation_length_cbox = QComboBox() + self.simulation_length_cbox.addItem('1 Year') + self.simulation_length_cbox.addItem('2 Year') + self.simulation_length_cbox.addItem('5 Year') + # set simulation length default to 1 year (must set attribute as well) + self.simulation_length_cbox.setCurrentIndex(0) + self.simulation_length_cbox.setStyleSheet(Palette.COMBOBOX_STYLESHEET) + self.simulation_length_cbox.currentIndexChanged.connect(on_simulation_length_cbox_index_changed) # noqa + self.simulation_length_cbox.setFixedWidth(self.INPUT_WIDTH) + self.layout.addWidget(self.simulation_length_cbox, alignment=Qt.AlignmentFlag.AlignCenter) + + label = QLabel() + label.setText('Simulation Symbols:') + label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(label) + + self.symbol_tbox = QLineEdit() + self.symbol_tbox.setText("MSFT, AAPL") + self.symbol_tbox.setStyleSheet(Palette.LINE_EDIT_STYLESHEET) + self.symbol_tbox.setFixedWidth(self.INPUT_WIDTH) + self.layout.addWidget(self.symbol_tbox, alignment=Qt.AlignmentFlag.AlignCenter) + + self.initial_balance_label = QLabel() + self.initial_balance_label.setText('Initial Balance:') + self.initial_balance_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.initial_balance_label) + + self.initial_balance_tbox = QLineEdit() + self.initial_balance_tbox.setText('1000.0') + self.onlyFloat = QDoubleValidator() + self.initial_balance_tbox.setValidator(self.onlyFloat) + self.initial_balance_tbox.setStyleSheet(Palette.LINE_EDIT_STYLESHEET) + self.initial_balance_tbox.setFixedWidth(self.INPUT_WIDTH) + self.layout.addWidget(self.initial_balance_tbox, alignment=Qt.AlignmentFlag.AlignCenter) + + self.layout.addStretch() + + self.setLayout(self.layout) diff --git a/src/StockBench/gui/config/tabs/compare/components/grid_config_right_frame.py b/src/StockBench/gui/config/tabs/compare/components/grid_config_right_frame.py new file mode 100644 index 00000000..07f0c6ce --- /dev/null +++ b/src/StockBench/gui/config/tabs/compare/components/grid_config_right_frame.py @@ -0,0 +1,86 @@ +from typing import Callable + +from PyQt6.QtWidgets import QFrame +from PyQt6.QtWidgets import QVBoxLayout, QLabel, QPushButton, QRadioButton + +from StockBench.gui.palette.palette import Palette + + +class GridConfigRightFrame(QFrame): + FRAME_WIDTH = 300 + + ON = 'ON' + OFF = 'OFF' + + FRAME_STYLESHEET = """ + #gridConfigRightFrame { + border-left: 1px solid grey; + padding-left: 25px; + } + """ + + def __init__(self, on_logging_btn_clicked: Callable, on_reporting_btn_clicked: Callable, + on_chart_saving_btn_clicked: Callable, data_and_charts_btn_selected: Callable, + data_only_btn_selected: Callable) -> None: + super().__init__() + self.setFixedWidth(self.FRAME_WIDTH) + self.setObjectName("gridConfigRightFrame") # apply styles based on id (must inherit from QFrame) + self.setStyleSheet(self.FRAME_STYLESHEET) + + self.layout = QVBoxLayout() + + self.logging_label = QLabel() + self.logging_label.setText('Logging:') + self.logging_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.logging_label) + + self.logging_btn = QPushButton() + self.logging_btn.setCheckable(True) + self.logging_btn.setText(self.OFF) + self.logging_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) + self.logging_btn.clicked.connect(lambda: on_logging_btn_clicked(self.logging_btn)) # noqa + self.layout.addWidget(self.logging_btn) + + self.reporting_label = QLabel() + self.reporting_label.setText('Reporting:') + self.reporting_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.reporting_label) + + self.reporting_btn = QPushButton() + self.reporting_btn.setCheckable(True) + self.reporting_btn.setText(self.OFF) + self.reporting_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) + self.reporting_btn.clicked.connect(lambda: on_reporting_btn_clicked(self.reporting_btn)) # noqa + self.layout.addWidget(self.reporting_btn) + + self.unique_chart_save_label = QLabel() + self.unique_chart_save_label.setText('Save Unique Charts:') + self.unique_chart_save_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.unique_chart_save_label) + + self.unique_chart_save_btn = QPushButton() + self.unique_chart_save_btn.setCheckable(True) + self.unique_chart_save_btn.setText(self.OFF) + self.unique_chart_save_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) + self.unique_chart_save_btn.clicked.connect(lambda: on_chart_saving_btn_clicked(self.unique_chart_save_btn)) # noqa + self.layout.addWidget(self.unique_chart_save_btn) + + self.results_depth_label = QLabel() + self.results_depth_label.setText('Results Depth:') + self.results_depth_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.results_depth_label) + + self.data_and_charts_radio_btn = QRadioButton("Data and Charts") + self.data_and_charts_radio_btn.toggled.connect(data_and_charts_btn_selected) # noqa + self.data_and_charts_radio_btn.setStyleSheet(Palette.RADIO_BTN_STYLESHEET) + self.data_and_charts_radio_btn.toggle() # set data and charts as default + self.layout.addWidget(self.data_and_charts_radio_btn) + + self.data_only_radio_btn = QRadioButton("Data Only") + self.data_only_radio_btn.toggled.connect(data_only_btn_selected) # noqa + self.data_only_radio_btn.setStyleSheet(Palette.RADIO_BTN_STYLESHEET) + self.layout.addWidget(self.data_only_radio_btn) + + self.layout.addStretch() + + self.setLayout(self.layout) diff --git a/src/StockBench/gui/config/tabs/folder/__init__.py b/src/StockBench/gui/config/tabs/folder/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/StockBench/gui/config/tabs/folder/components/__init__.py b/src/StockBench/gui/config/tabs/folder/components/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/StockBench/gui/config/tabs/folder/components/grid_config_frame.py b/src/StockBench/gui/config/tabs/folder/components/grid_config_frame.py new file mode 100644 index 00000000..154eeb8f --- /dev/null +++ b/src/StockBench/gui/config/tabs/folder/components/grid_config_frame.py @@ -0,0 +1,22 @@ +from typing import Callable + +from PyQt6.QtWidgets import QFrame, QGridLayout + +from StockBench.gui.config.tabs.folder.components.grid_config_left_frame import GridConfigLeftFrame +from StockBench.gui.config.tabs.folder.components.grid_config_right_frame import GridConfigRightFrame + + +class GridConfigFrame(QFrame): + def __init__(self, on_simulation_length_cbox_index_changed: Callable, on_logging_btn_clicked: Callable, + on_chart_saving_btn_clicked: Callable): + super().__init__() + + self.layout = QGridLayout() + + self.left_frame = GridConfigLeftFrame(on_simulation_length_cbox_index_changed) + self.right_frame = GridConfigRightFrame(on_logging_btn_clicked, on_chart_saving_btn_clicked) + + self.layout.addWidget(self.left_frame, 0, 0) + self.layout.addWidget(self.right_frame, 0, 1) + + self.setLayout(self.layout) diff --git a/src/StockBench/gui/config/tabs/folder/components/grid_config_left_frame.py b/src/StockBench/gui/config/tabs/folder/components/grid_config_left_frame.py new file mode 100644 index 00000000..2ad54019 --- /dev/null +++ b/src/StockBench/gui/config/tabs/folder/components/grid_config_left_frame.py @@ -0,0 +1,63 @@ +from typing import Callable + +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QDoubleValidator +from PyQt6.QtWidgets import QFrame, QVBoxLayout, QLabel, QComboBox, QLineEdit + +from StockBench.gui.palette.palette import Palette + + +class GridConfigLeftFrame(QFrame): + FRAME_WIDTH = 300 + + INPUT_WIDTH = 240 + + def __init__(self, on_simulation_length_cbox_index_changed: Callable): + super().__init__() + self.setFixedWidth(self.FRAME_WIDTH) + + self.layout = QVBoxLayout() + + self.simulation_length_label = QLabel() + self.simulation_length_label.setText('Simulation Length:') + self.simulation_length_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.simulation_length_label) + + self.simulation_length_cbox = QComboBox() + self.simulation_length_cbox.addItem('1 Year') + self.simulation_length_cbox.addItem('2 Year') + self.simulation_length_cbox.addItem('5 Year') + # set simulation length default to 1 year (must set attribute as well) + self.simulation_length_cbox.setCurrentIndex(0) + self.simulation_length_cbox.setStyleSheet(Palette.COMBOBOX_STYLESHEET) + self.simulation_length_cbox.currentIndexChanged.connect(on_simulation_length_cbox_index_changed) # noqa + self.simulation_length_cbox.setFixedWidth(self.INPUT_WIDTH) + self.layout.addWidget(self.simulation_length_cbox, alignment=Qt.AlignmentFlag.AlignCenter) + + label = QLabel() + label.setText('Simulation Symbols:') + label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(label) + + self.symbol_tbox = QLineEdit() + self.symbol_tbox.setText("MSFT, AAPL") + self.symbol_tbox.setStyleSheet(Palette.LINE_EDIT_STYLESHEET) + self.symbol_tbox.setFixedWidth(self.INPUT_WIDTH) + self.layout.addWidget(self.symbol_tbox, alignment=Qt.AlignmentFlag.AlignCenter) + + self.initial_balance_label = QLabel() + self.initial_balance_label.setText('Initial Balance:') + self.initial_balance_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.initial_balance_label) + + self.initial_balance_tbox = QLineEdit() + self.initial_balance_tbox.setText('1000.0') + self.onlyFloat = QDoubleValidator() + self.initial_balance_tbox.setValidator(self.onlyFloat) + self.initial_balance_tbox.setStyleSheet(Palette.LINE_EDIT_STYLESHEET) + self.initial_balance_tbox.setFixedWidth(self.INPUT_WIDTH) + self.layout.addWidget(self.initial_balance_tbox, alignment=Qt.AlignmentFlag.AlignCenter) + + self.layout.addStretch() + + self.setLayout(self.layout) diff --git a/src/StockBench/gui/config/tabs/folder/components/grid_config_right_frame.py b/src/StockBench/gui/config/tabs/folder/components/grid_config_right_frame.py new file mode 100644 index 00000000..b0af15db --- /dev/null +++ b/src/StockBench/gui/config/tabs/folder/components/grid_config_right_frame.py @@ -0,0 +1,56 @@ +from typing import Callable + +from PyQt6.QtWidgets import QFrame +from PyQt6.QtWidgets import QVBoxLayout, QLabel, QPushButton, QRadioButton + +from StockBench.gui.palette.palette import Palette + + +class GridConfigRightFrame(QFrame): + FRAME_WIDTH = 300 + + ON = 'ON' + OFF = 'OFF' + + FRAME_STYLESHEET = """ + #gridConfigRightFrame { + border-left: 1px solid grey; + padding-left: 25px; + } + """ + + def __init__(self, on_logging_btn_clicked: Callable, on_chart_saving_btn_clicked: Callable) -> None: + super().__init__() + self.setFixedWidth(self.FRAME_WIDTH) + self.setObjectName("gridConfigRightFrame") # apply styles based on id (must inherit from QFrame) + self.setStyleSheet(self.FRAME_STYLESHEET) + + self.layout = QVBoxLayout() + + self.logging_label = QLabel() + self.logging_label.setText('Logging:') + self.logging_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.logging_label) + + self.logging_btn = QPushButton() + self.logging_btn.setCheckable(True) + self.logging_btn.setText(self.OFF) + self.logging_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) + self.logging_btn.clicked.connect(lambda: on_logging_btn_clicked(self.logging_btn)) # noqa + self.layout.addWidget(self.logging_btn) + + self.unique_chart_save_label = QLabel() + self.unique_chart_save_label.setText('Save Unique Charts:') + self.unique_chart_save_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.unique_chart_save_label) + + self.unique_chart_save_btn = QPushButton() + self.unique_chart_save_btn.setCheckable(True) + self.unique_chart_save_btn.setText(self.OFF) + self.unique_chart_save_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) + self.unique_chart_save_btn.clicked.connect(lambda: on_chart_saving_btn_clicked(self.unique_chart_save_btn)) # noqa + self.layout.addWidget(self.unique_chart_save_btn) + + self.layout.addStretch() + + self.setLayout(self.layout) diff --git a/src/StockBench/gui/config/tabs/folder_config_tab.py b/src/StockBench/gui/config/tabs/folder/folder_config_tab.py similarity index 66% rename from src/StockBench/gui/config/tabs/folder_config_tab.py rename to src/StockBench/gui/config/tabs/folder/folder_config_tab.py index 37919414..ce84a4e8 100644 --- a/src/StockBench/gui/config/tabs/folder_config_tab.py +++ b/src/StockBench/gui/config/tabs/folder/folder_config_tab.py @@ -1,21 +1,23 @@ import os +from typing import Callable -from PyQt6.QtWidgets import QLabel, QLineEdit +from PyQt6.QtWidgets import QLabel from PyQt6.QtCore import Qt from StockBench.controllers.stockbench_controller import StockBenchController from StockBench.gui.config.tabs.base.config_tab import ConfigTab, MessageBoxCaptureException, CaptureConfigErrors +from StockBench.gui.config.tabs.folder.components.grid_config_frame import GridConfigFrame from StockBench.gui.palette.palette import Palette from StockBench.gui.results.folder.folder_results_window import FolderResultsWindow from StockBench.gui.config.components.cached_folder_selector import CachedFolderSelector +from StockBench.models.constants.general_constants import SECONDS_1_YEAR class FolderConfigTab(ConfigTab): FOLDER_CACHE_KEY = 'cached_folderpath' - def __init__(self, stockbench_controller: StockBenchController): - super().__init__(stockbench_controller) - # add shared_components to the layout + def __init__(self, update_geometry: Callable, stockbench_controller: StockBenchController): + super().__init__(update_geometry, stockbench_controller) label = QLabel() label.setText('Folder:') label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) @@ -25,35 +27,14 @@ def __init__(self, stockbench_controller: StockBenchController): self.folder_selection.setStyleSheet(Palette.SECONDARY_BTN) self.layout.addWidget(self.folder_selection) - self.layout.addWidget(self.simulation_length_label) - - self.layout.addWidget(self.simulation_length_cbox) - - label = QLabel() - label.setText('Simulation Symbols:') - label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) - self.layout.addWidget(label) - - self.symbol_tbox = QLineEdit() - self.symbol_tbox.setText("MSFT, AAPL") - self.symbol_tbox.setStyleSheet(Palette.LINE_EDIT_STYLESHEET) - self.layout.addWidget(self.symbol_tbox) - - self.layout.addWidget(self.initial_balance_label) - - self.layout.addWidget(self.initial_balance_tbox) - - self.layout.addWidget(self.logging_label) - - self.layout.addWidget(self.logging_btn) - - self.layout.addWidget(self.unique_chart_save_label) - - self.layout.addWidget(self.unique_chart_save_btn) + self.simulation_length = SECONDS_1_YEAR + self.grid_config_frame = GridConfigFrame(self.on_simulation_length_cbox_index_changed, + self.on_logging_btn_clicked, self.on_chart_saving_btn_clicked) + self.layout.addWidget(self.grid_config_frame) self.layout.addWidget(self.run_btn, alignment=Qt.AlignmentFlag.AlignRight) - self.layout.addWidget(self.error_message_box) + self.layout.addStretch() self.setLayout(self.layout) @@ -70,15 +51,13 @@ def on_run_btn_clicked(self, clicked_signal: bool): instead of crashing. It also allows us to functionalize the filepath validation without the need for try blocks or return values. """ - # extract the folder path from the input folderpath = self.folder_selection.folderpath_box.text() - # gather other data from UI shared_components - raw_simulation_symbols = self.symbol_tbox.text().split(',') + raw_simulation_symbols = self.grid_config_frame.left_frame.symbol_tbox.text().split(',') simulation_symbols = [] for symbol in raw_simulation_symbols: simulation_symbols.append(symbol.upper().strip()) - simulation_balance = float(self.initial_balance_tbox.text()) + simulation_balance = float(self.grid_config_frame.left_frame.initial_balance_tbox.text()) if simulation_balance <= 0: raise MessageBoxCaptureException('Initial account balance must be a positive number!') @@ -89,7 +68,6 @@ def on_run_btn_clicked(self, clicked_signal: bool): strategy = self.load_strategy(filepath, self.FOLDER_CACHE_KEY, folderpath) strategies.append(strategy) - # create a new simulations results window self.simulation_result_window = FolderResultsWindow( self._stockbench_controller, strategies, diff --git a/src/StockBench/gui/config/tabs/multi/__init__.py b/src/StockBench/gui/config/tabs/multi/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/StockBench/gui/config/tabs/multi/components/__init__.py b/src/StockBench/gui/config/tabs/multi/components/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/StockBench/gui/config/tabs/multi/components/grid_config_frame.py b/src/StockBench/gui/config/tabs/multi/components/grid_config_frame.py new file mode 100644 index 00000000..dc1c86c9 --- /dev/null +++ b/src/StockBench/gui/config/tabs/multi/components/grid_config_frame.py @@ -0,0 +1,25 @@ +from typing import Callable + +from PyQt6.QtWidgets import QFrame, QGridLayout + +from StockBench.gui.config.tabs.multi.components.grid_config_left_frame import GridConfigLeftFrame +from StockBench.gui.config.tabs.multi.components.grid_config_right_frame import GridConfigRightFrame + + +class GridConfigFrame(QFrame): + def __init__(self, on_simulation_length_cbox_index_changed: Callable, on_logging_btn_clicked: Callable, + on_reporting_btn_clicked: Callable, on_chart_saving_btn_clicked: Callable, + data_and_charts_btn_selected: Callable, data_only_btn_selected: Callable): + super().__init__() + + self.layout = QGridLayout() + + self.left_frame = GridConfigLeftFrame(on_simulation_length_cbox_index_changed) + self.right_frame = GridConfigRightFrame(on_logging_btn_clicked, on_reporting_btn_clicked, + on_chart_saving_btn_clicked, data_and_charts_btn_selected, + data_only_btn_selected) + + self.layout.addWidget(self.left_frame, 0, 0) + self.layout.addWidget(self.right_frame, 0, 1) + + self.setLayout(self.layout) diff --git a/src/StockBench/gui/config/tabs/multi/components/grid_config_left_frame.py b/src/StockBench/gui/config/tabs/multi/components/grid_config_left_frame.py new file mode 100644 index 00000000..2ad54019 --- /dev/null +++ b/src/StockBench/gui/config/tabs/multi/components/grid_config_left_frame.py @@ -0,0 +1,63 @@ +from typing import Callable + +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QDoubleValidator +from PyQt6.QtWidgets import QFrame, QVBoxLayout, QLabel, QComboBox, QLineEdit + +from StockBench.gui.palette.palette import Palette + + +class GridConfigLeftFrame(QFrame): + FRAME_WIDTH = 300 + + INPUT_WIDTH = 240 + + def __init__(self, on_simulation_length_cbox_index_changed: Callable): + super().__init__() + self.setFixedWidth(self.FRAME_WIDTH) + + self.layout = QVBoxLayout() + + self.simulation_length_label = QLabel() + self.simulation_length_label.setText('Simulation Length:') + self.simulation_length_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.simulation_length_label) + + self.simulation_length_cbox = QComboBox() + self.simulation_length_cbox.addItem('1 Year') + self.simulation_length_cbox.addItem('2 Year') + self.simulation_length_cbox.addItem('5 Year') + # set simulation length default to 1 year (must set attribute as well) + self.simulation_length_cbox.setCurrentIndex(0) + self.simulation_length_cbox.setStyleSheet(Palette.COMBOBOX_STYLESHEET) + self.simulation_length_cbox.currentIndexChanged.connect(on_simulation_length_cbox_index_changed) # noqa + self.simulation_length_cbox.setFixedWidth(self.INPUT_WIDTH) + self.layout.addWidget(self.simulation_length_cbox, alignment=Qt.AlignmentFlag.AlignCenter) + + label = QLabel() + label.setText('Simulation Symbols:') + label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(label) + + self.symbol_tbox = QLineEdit() + self.symbol_tbox.setText("MSFT, AAPL") + self.symbol_tbox.setStyleSheet(Palette.LINE_EDIT_STYLESHEET) + self.symbol_tbox.setFixedWidth(self.INPUT_WIDTH) + self.layout.addWidget(self.symbol_tbox, alignment=Qt.AlignmentFlag.AlignCenter) + + self.initial_balance_label = QLabel() + self.initial_balance_label.setText('Initial Balance:') + self.initial_balance_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.initial_balance_label) + + self.initial_balance_tbox = QLineEdit() + self.initial_balance_tbox.setText('1000.0') + self.onlyFloat = QDoubleValidator() + self.initial_balance_tbox.setValidator(self.onlyFloat) + self.initial_balance_tbox.setStyleSheet(Palette.LINE_EDIT_STYLESHEET) + self.initial_balance_tbox.setFixedWidth(self.INPUT_WIDTH) + self.layout.addWidget(self.initial_balance_tbox, alignment=Qt.AlignmentFlag.AlignCenter) + + self.layout.addStretch() + + self.setLayout(self.layout) diff --git a/src/StockBench/gui/config/tabs/multi/components/grid_config_right_frame.py b/src/StockBench/gui/config/tabs/multi/components/grid_config_right_frame.py new file mode 100644 index 00000000..a940e62c --- /dev/null +++ b/src/StockBench/gui/config/tabs/multi/components/grid_config_right_frame.py @@ -0,0 +1,87 @@ +from typing import Callable + +from PyQt6.QtWidgets import QFrame +from PyQt6.QtWidgets import QVBoxLayout, QLabel, QPushButton, QRadioButton + +from StockBench.gui.palette.palette import Palette + + +class GridConfigRightFrame(QFrame): + FRAME_WIDTH = 300 + + ON = 'ON' + OFF = 'OFF' + + FRAME_STYLESHEET = """ + #gridConfigRightFrame { + border-left: 1px solid grey; + padding-left: 25px; + } + """ + + def __init__(self, on_logging_btn_clicked: Callable, on_reporting_btn_clicked: Callable, + on_chart_saving_btn_clicked: Callable, data_and_charts_btn_selected: Callable, + data_only_btn_selected: Callable) -> None: + super().__init__() + + self.setFixedWidth(self.FRAME_WIDTH) + self.setObjectName("gridConfigRightFrame") # apply styles based on id (must inherit from QFrame) + self.setStyleSheet(self.FRAME_STYLESHEET) + + self.layout = QVBoxLayout() + + self.logging_label = QLabel() + self.logging_label.setText('Logging:') + self.logging_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.logging_label) + + self.logging_btn = QPushButton() + self.logging_btn.setCheckable(True) + self.logging_btn.setText(self.OFF) + self.logging_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) + self.logging_btn.clicked.connect(lambda: on_logging_btn_clicked(self.logging_btn)) # noqa + self.layout.addWidget(self.logging_btn) + + self.reporting_label = QLabel() + self.reporting_label.setText('Reporting:') + self.reporting_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.reporting_label) + + self.reporting_btn = QPushButton() + self.reporting_btn.setCheckable(True) + self.reporting_btn.setText(self.OFF) + self.reporting_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) + self.reporting_btn.clicked.connect(lambda: on_reporting_btn_clicked(self.reporting_btn)) # noqa + self.layout.addWidget(self.reporting_btn) + + self.unique_chart_save_label = QLabel() + self.unique_chart_save_label.setText('Save Unique Charts:') + self.unique_chart_save_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.unique_chart_save_label) + + self.unique_chart_save_btn = QPushButton() + self.unique_chart_save_btn.setCheckable(True) + self.unique_chart_save_btn.setText(self.OFF) + self.unique_chart_save_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) + self.unique_chart_save_btn.clicked.connect(lambda: on_chart_saving_btn_clicked(self.unique_chart_save_btn)) # noqa + self.layout.addWidget(self.unique_chart_save_btn) + + self.results_depth_label = QLabel() + self.results_depth_label.setText('Results Depth:') + self.results_depth_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.results_depth_label) + + self.data_and_charts_radio_btn = QRadioButton("Data and Charts") + self.data_and_charts_radio_btn.toggled.connect(data_and_charts_btn_selected) # noqa + self.data_and_charts_radio_btn.setStyleSheet(Palette.RADIO_BTN_STYLESHEET) + self.data_and_charts_radio_btn.toggle() # set data and charts as default + self.layout.addWidget(self.data_and_charts_radio_btn) + + self.data_only_radio_btn = QRadioButton("Data Only") + self.data_only_radio_btn.toggled.connect(data_only_btn_selected) # noqa + self.data_only_radio_btn.setStyleSheet(Palette.RADIO_BTN_STYLESHEET) + self.layout.addWidget(self.data_only_radio_btn) + + self.layout.addStretch() + + self.setLayout(self.layout) diff --git a/src/StockBench/gui/config/tabs/multi_config_tab.py b/src/StockBench/gui/config/tabs/multi/multi_config_tab.py similarity index 62% rename from src/StockBench/gui/config/tabs/multi_config_tab.py rename to src/StockBench/gui/config/tabs/multi/multi_config_tab.py index 0caca076..6bfec311 100644 --- a/src/StockBench/gui/config/tabs/multi_config_tab.py +++ b/src/StockBench/gui/config/tabs/multi/multi_config_tab.py @@ -1,72 +1,50 @@ -from PyQt6.QtWidgets import QLabel, QPushButton, QLineEdit +from typing import Callable + +from PyQt6.QtWidgets import QLabel, QPushButton, QFrame from PyQt6.QtCore import Qt from StockBench.controllers.stockbench_controller import StockBenchController from StockBench.gui.config.tabs.base.config_tab import ConfigTab, MessageBoxCaptureException, CaptureConfigErrors +from StockBench.gui.config.tabs.multi.components.grid_config_frame import GridConfigFrame from StockBench.gui.results.multi.multi_results_window import MultiResultsWindow from StockBench.gui.palette.palette import Palette from StockBench.gui.config.components.strategy_selection import StrategySelection +from StockBench.models.constants.general_constants import SECONDS_1_YEAR class MultiConfigTab(ConfigTab): - def __init__(self, stockbench_controller: StockBenchController): - super().__init__(stockbench_controller) - # add shared_components to the layout + def __init__(self, update_geometry: Callable, stockbench_controller: StockBenchController): + super().__init__(update_geometry, stockbench_controller) label = QLabel() label.setText('Strategy:') label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) self.layout.addWidget(label) self.strategy_selection_box = StrategySelection() - self.strategy_selection_box.setStyleSheet(Palette.INPUT_BOX_STYLESHEET) self.layout.addWidget(self.strategy_selection_box) self.strategy_studio_btn = QPushButton() self.strategy_studio_btn.setText('Strategy Studio') self.strategy_studio_btn.clicked.connect(lambda: self.on_strategy_studio_btn_clicked( # noqa self.strategy_selection_box.filepath_box.text())) - self.strategy_studio_btn.setStyleSheet(Palette.SECONDARY_BTN) - self.layout.addWidget(self.strategy_studio_btn) - - self.layout.addWidget(self.simulation_length_label) - - self.layout.addWidget(self.simulation_length_cbox) - - label = QLabel() - label.setText('Simulation Symbols:') - label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) - self.layout.addWidget(label) - - self.symbol_tbox = QLineEdit() - self.symbol_tbox.setText("MSFT, AAPL") - self.symbol_tbox.setStyleSheet(Palette.LINE_EDIT_STYLESHEET) - self.layout.addWidget(self.symbol_tbox) - - self.layout.addWidget(self.initial_balance_label) - - self.layout.addWidget(self.initial_balance_tbox) - - self.layout.addWidget(self.logging_label) - - self.layout.addWidget(self.logging_btn) - - self.layout.addWidget(self.reporting_label) - - self.layout.addWidget(self.reporting_btn) - - self.layout.addWidget(self.unique_chart_save_label) - - self.layout.addWidget(self.unique_chart_save_btn) - - self.layout.addWidget(self.results_depth_label) + self.strategy_studio_btn.setStyleSheet(Palette.STRATEGY_STUDIO_BTN) + self.layout.addWidget(self.strategy_studio_btn, alignment=Qt.AlignmentFlag.AlignCenter) - self.layout.addWidget(self.data_and_charts_radio_btn) + # spacer to even out multi layout + self.spacer = QFrame() + self.spacer.setFixedHeight(15) + self.layout.addWidget(self.spacer) - self.layout.addWidget(self.data_only_radio_btn) + self.simulation_length = SECONDS_1_YEAR + self.grid_config_frame = GridConfigFrame(self.on_simulation_length_cbox_index_changed, + self.on_logging_btn_clicked, self.on_reporting_btn_clicked, + self.on_chart_saving_btn_clicked, self.data_and_charts_btn_selected, + self.data_only_btn_selected) + self.layout.addWidget(self.grid_config_frame) self.layout.addWidget(self.run_btn, alignment=Qt.AlignmentFlag.AlignRight) - self.layout.addWidget(self.error_message_box) + self.layout.addStretch() self.setLayout(self.layout) @@ -87,11 +65,11 @@ def on_run_btn_clicked(self, clicked_signal: bool): strategy = self.load_strategy(self.strategy_selection_box.filepath_box.text()) # gather other data from UI shared_components - raw_simulation_symbols = self.symbol_tbox.text().split(',') + raw_simulation_symbols = self.grid_config_frame.left_frame.symbol_tbox.text().split(',') simulation_symbols = [] for symbol in raw_simulation_symbols: simulation_symbols.append(symbol.upper().strip()) - simulation_balance = float(self.initial_balance_tbox.text()) + simulation_balance = float(self.grid_config_frame.left_frame.initial_balance_tbox.text()) if simulation_balance <= 0: raise MessageBoxCaptureException('Initial account balance must be a positive number!') diff --git a/src/StockBench/gui/config/tabs/singular/__init__.py b/src/StockBench/gui/config/tabs/singular/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/StockBench/gui/config/tabs/singular/components/__init__.py b/src/StockBench/gui/config/tabs/singular/components/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/StockBench/gui/config/tabs/singular/components/grid_config_frame.py b/src/StockBench/gui/config/tabs/singular/components/grid_config_frame.py new file mode 100644 index 00000000..e8512cf3 --- /dev/null +++ b/src/StockBench/gui/config/tabs/singular/components/grid_config_frame.py @@ -0,0 +1,26 @@ +from typing import Callable + +from PyQt6.QtWidgets import QFrame, QGridLayout + +from StockBench.gui.config.tabs.singular.components.grid_config_left_frame import GridConfigLeftFrame +from StockBench.gui.config.tabs.singular.components.grid_config_right_frame import GridConfigRightFrame + + +class GridConfigFrame(QFrame): + def __init__(self, on_simulation_length_cbox_index_changed: Callable, on_logging_btn_clicked: Callable, + on_reporting_btn_clicked: Callable, on_chart_saving_btn_clicked: Callable, + on_show_volume_btn_clicked: Callable, data_and_charts_btn_selected: Callable, + data_only_btn_selected: Callable): + super().__init__() + + self.layout = QGridLayout() + + self.left_frame = GridConfigLeftFrame(on_simulation_length_cbox_index_changed) + self.right_frame = GridConfigRightFrame(on_logging_btn_clicked, on_reporting_btn_clicked, + on_chart_saving_btn_clicked, on_show_volume_btn_clicked, + data_and_charts_btn_selected, data_only_btn_selected) + + self.layout.addWidget(self.left_frame, 0, 0) + self.layout.addWidget(self.right_frame, 0, 1) + + self.setLayout(self.layout) diff --git a/src/StockBench/gui/config/tabs/singular/components/grid_config_left_frame.py b/src/StockBench/gui/config/tabs/singular/components/grid_config_left_frame.py new file mode 100644 index 00000000..9f0178fd --- /dev/null +++ b/src/StockBench/gui/config/tabs/singular/components/grid_config_left_frame.py @@ -0,0 +1,63 @@ +from typing import Callable + +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QDoubleValidator +from PyQt6.QtWidgets import QFrame, QVBoxLayout, QLabel, QComboBox, QLineEdit + +from StockBench.gui.palette.palette import Palette + + +class GridConfigLeftFrame(QFrame): + FRAME_WIDTH = 300 + + INPUT_WIDTH = 240 + + def __init__(self, on_simulation_length_cbox_index_changed: Callable): + super().__init__() + self.setFixedWidth(self.FRAME_WIDTH) + + self.layout = QVBoxLayout() + + self.simulation_length_label = QLabel() + self.simulation_length_label.setText('Simulation Length:') + self.simulation_length_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.simulation_length_label) + + self.simulation_length_cbox = QComboBox() + self.simulation_length_cbox.addItem('1 Year') + self.simulation_length_cbox.addItem('2 Year') + self.simulation_length_cbox.addItem('5 Year') + # set simulation length default to 1 year (must set attribute as well in application) + self.simulation_length_cbox.setCurrentIndex(0) + self.simulation_length_cbox.setStyleSheet(Palette.COMBOBOX_STYLESHEET) + self.simulation_length_cbox.currentIndexChanged.connect(on_simulation_length_cbox_index_changed) # noqa + self.simulation_length_cbox.setFixedWidth(self.INPUT_WIDTH) + self.layout.addWidget(self.simulation_length_cbox, alignment=Qt.AlignmentFlag.AlignCenter) + + label = QLabel() + label.setText('Simulation Symbol:') + label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(label) + + self.symbol_tbox = QLineEdit() + self.symbol_tbox.setText("MSFT") + self.symbol_tbox.setStyleSheet(Palette.LINE_EDIT_STYLESHEET) + self.symbol_tbox.setFixedWidth(self.INPUT_WIDTH) + self.layout.addWidget(self.symbol_tbox, alignment=Qt.AlignmentFlag.AlignCenter) + + self.initial_balance_label = QLabel() + self.initial_balance_label.setText('Initial Balance:') + self.initial_balance_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.initial_balance_label) + + self.initial_balance_tbox = QLineEdit() + self.initial_balance_tbox.setText('1000.0') + self.onlyFloat = QDoubleValidator() + self.initial_balance_tbox.setValidator(self.onlyFloat) + self.initial_balance_tbox.setStyleSheet(Palette.LINE_EDIT_STYLESHEET) + self.initial_balance_tbox.setFixedWidth(self.INPUT_WIDTH) + self.layout.addWidget(self.initial_balance_tbox, alignment=Qt.AlignmentFlag.AlignCenter) + + self.layout.addStretch() + + self.setLayout(self.layout) diff --git a/src/StockBench/gui/config/tabs/singular/components/grid_config_right_frame.py b/src/StockBench/gui/config/tabs/singular/components/grid_config_right_frame.py new file mode 100644 index 00000000..ce1b983a --- /dev/null +++ b/src/StockBench/gui/config/tabs/singular/components/grid_config_right_frame.py @@ -0,0 +1,100 @@ +from typing import Callable + +from PyQt6.QtWidgets import QFrame +from PyQt6.QtWidgets import QVBoxLayout, QLabel, QPushButton, QRadioButton + +from StockBench.gui.palette.palette import Palette + + +class GridConfigRightFrame(QFrame): + FRAME_WIDTH = 300 + + ON = 'ON' + OFF = 'OFF' + + FRAME_STYLESHEET = """ + #gridConfigRightFrame { + border-left: 1px solid grey; + padding-left: 25px; + } + """ + + def __init__(self, on_logging_btn_clicked: Callable, on_reporting_btn_clicked: Callable, + on_chart_saving_btn_clicked: Callable, on_show_volume_btn_clicked: Callable, + data_and_charts_btn_selected: Callable, data_only_btn_selected: Callable): + super().__init__() + + self.setFixedWidth(self.FRAME_WIDTH) + self.setObjectName("gridConfigRightFrame") # apply styles based on id (must inherit from QFrame) + self.setStyleSheet(self.FRAME_STYLESHEET) + + self.layout = QVBoxLayout() + + self.logging_label = QLabel() + self.logging_label.setText('Logging:') + self.logging_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.logging_label) + + self.logging_btn = QPushButton() + self.logging_btn.setCheckable(True) + self.logging_btn.setText(self.OFF) + self.logging_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) + self.logging_btn.clicked.connect(lambda: on_logging_btn_clicked(self.logging_btn)) # noqa + self.layout.addWidget(self.logging_btn) + + self.reporting_label = QLabel() + self.reporting_label.setText('Reporting:') + self.reporting_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.reporting_label) + + self.reporting_btn = QPushButton() + self.reporting_btn.setCheckable(True) + self.reporting_btn.setText(self.OFF) + self.reporting_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) + self.reporting_btn.clicked.connect(lambda: on_reporting_btn_clicked(self.reporting_btn)) # noqa + self.layout.addWidget(self.reporting_btn) + + self.unique_chart_save_label = QLabel() + self.unique_chart_save_label.setText('Save Unique Charts:') + self.unique_chart_save_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.unique_chart_save_label) + + self.unique_chart_save_btn = QPushButton() + self.unique_chart_save_btn.setCheckable(True) + self.unique_chart_save_btn.setText(self.OFF) + self.unique_chart_save_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) + self.unique_chart_save_btn.clicked.connect(lambda: on_chart_saving_btn_clicked(self.unique_chart_save_btn)) # noqa + self.layout.addWidget(self.unique_chart_save_btn) + + self.show_volume_label = QLabel() + self.show_volume_label.setText('Show Volume:') + self.show_volume_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.show_volume_label) + + self.show_volume_btn = QPushButton() + self.show_volume_btn.setCheckable(True) + self.show_volume_btn.setChecked(True) + self.show_volume_btn.setText(self.ON) + self.show_volume_btn.setStyleSheet(Palette.TOGGLE_BTN_ENABLED_STYLESHEET) + self.show_volume_btn.clicked.connect(lambda: on_show_volume_btn_clicked(self.show_volume_btn)) # noqa + self.layout.addWidget(self.show_volume_btn) + + self.results_depth_label = QLabel() + self.results_depth_label.setText('Results Depth:') + self.results_depth_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) + self.layout.addWidget(self.results_depth_label) + + self.data_and_charts_radio_btn = QRadioButton("Data and Charts") + self.data_and_charts_radio_btn.toggled.connect(data_and_charts_btn_selected) # noqa + self.data_and_charts_radio_btn.setStyleSheet(Palette.RADIO_BTN_STYLESHEET) + self.data_and_charts_radio_btn.toggle() # set data and charts as default + self.layout.addWidget(self.data_and_charts_radio_btn) + + self.data_only_radio_btn = QRadioButton("Data Only") + self.data_only_radio_btn.toggled.connect(data_only_btn_selected) # noqa + self.data_only_radio_btn.setStyleSheet(Palette.RADIO_BTN_STYLESHEET) + self.layout.addWidget(self.data_only_radio_btn) + + self.layout.addStretch() + + self.setLayout(self.layout) diff --git a/src/StockBench/gui/config/tabs/singular_config_tab.py b/src/StockBench/gui/config/tabs/singular/singular_config_tab.py similarity index 50% rename from src/StockBench/gui/config/tabs/singular_config_tab.py rename to src/StockBench/gui/config/tabs/singular/singular_config_tab.py index 7fe83407..8b994a21 100644 --- a/src/StockBench/gui/config/tabs/singular_config_tab.py +++ b/src/StockBench/gui/config/tabs/singular/singular_config_tab.py @@ -1,102 +1,66 @@ -from PyQt6.QtWidgets import QLabel, QPushButton, QLineEdit +from typing import Callable + +from PyQt6.QtWidgets import QLabel, QPushButton, QFrame from PyQt6.QtCore import Qt from StockBench.controllers.stockbench_controller import StockBenchController from StockBench.gui.config.tabs.base.config_tab import ConfigTab, MessageBoxCaptureException, CaptureConfigErrors +from StockBench.gui.config.tabs.singular.components.grid_config_frame import GridConfigFrame from StockBench.gui.results.singular.singular_results_window import SingularResultsWindow from StockBench.gui.palette.palette import Palette from StockBench.gui.config.components.strategy_selection import StrategySelection +from StockBench.models.constants.general_constants import SECONDS_1_YEAR class SingularConfigTab(ConfigTab): - def __init__(self, stockbench_controller: StockBenchController): - super().__init__(stockbench_controller) + def __init__(self, update_geometry: Callable, stockbench_controller: StockBenchController): + super().__init__(update_geometry, stockbench_controller) self.show_volume = True - # add shared_components to the layout label = QLabel() label.setText('Strategy:') label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) self.layout.addWidget(label) self.strategy_selection_box = StrategySelection() - self.strategy_selection_box.setStyleSheet(Palette.INPUT_BOX_STYLESHEET) self.layout.addWidget(self.strategy_selection_box) self.strategy_studio_btn = QPushButton() self.strategy_studio_btn.setText('Strategy Studio') self.strategy_studio_btn.clicked.connect(lambda: self.on_strategy_studio_btn_clicked( # noqa self.strategy_selection_box.filepath_box.text())) - self.strategy_studio_btn.setStyleSheet(Palette.SECONDARY_BTN) - self.layout.addWidget(self.strategy_studio_btn) - - self.layout.addWidget(self.simulation_length_label) - - self.layout.addWidget(self.simulation_length_cbox) - - label = QLabel() - label.setText('Simulation Symbol:') - label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) - self.layout.addWidget(label) - - self.symbol_tbox = QLineEdit() - self.symbol_tbox.setText("MSFT") - self.symbol_tbox.setStyleSheet(Palette.LINE_EDIT_STYLESHEET) - self.layout.addWidget(self.symbol_tbox) - - self.layout.addWidget(self.initial_balance_label) - - self.layout.addWidget(self.initial_balance_tbox) - - self.layout.addWidget(self.logging_label) - - self.layout.addWidget(self.logging_btn) - - self.layout.addWidget(self.reporting_label) - - self.layout.addWidget(self.reporting_btn) - - # update the label to remove plural - self.unique_chart_save_label.setText('Save Unique Chart:') - self.layout.addWidget(self.unique_chart_save_label) - - self.layout.addWidget(self.unique_chart_save_btn) - - self.show_volume_label = QLabel() - self.show_volume_label.setText('Show Volume:') - self.show_volume_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) - self.layout.addWidget(self.show_volume_label) - - self.show_volume_btn = QPushButton() - self.show_volume_btn.setCheckable(True) - self.show_volume_btn.setChecked(True) - self.show_volume_btn.setText(self.ON) - self.show_volume_btn.setStyleSheet(Palette.TOGGLE_BTN_ENABLED_STYLESHEET) - self.show_volume_btn.clicked.connect(self.on_show_volume_btn_clicked) # noqa - self.layout.addWidget(self.show_volume_btn) - - self.layout.addWidget(self.results_depth_label) - - self.layout.addWidget(self.data_and_charts_radio_btn) - - self.layout.addWidget(self.data_only_radio_btn) + self.strategy_studio_btn.setStyleSheet(Palette.STRATEGY_STUDIO_BTN) + self.layout.addWidget(self.strategy_studio_btn, alignment=Qt.AlignmentFlag.AlignCenter) + + # spacer to even out singular layout + self.spacer = QFrame() + self.spacer.setFixedHeight(15) + self.layout.addWidget(self.spacer) + + self.simulation_length = SECONDS_1_YEAR + self.grid_config_frame = GridConfigFrame(self.on_simulation_length_cbox_index_changed, + self.on_logging_btn_clicked, self.on_reporting_btn_clicked, + self.on_show_volume_btn_clicked, + self.on_chart_saving_btn_clicked, self.data_and_charts_btn_selected, + self.data_only_btn_selected) + self.layout.addWidget(self.grid_config_frame) self.layout.addWidget(self.run_btn, alignment=Qt.AlignmentFlag.AlignRight) - self.layout.addWidget(self.error_message_box) + self.layout.addStretch() self.setLayout(self.layout) - def on_show_volume_btn_clicked(self): - if self.show_volume_btn.isChecked(): + def on_show_volume_btn_clicked(self, button: QPushButton): + if button.isChecked(): self.show_volume = True - self.show_volume_btn.setText(self.ON) - self.show_volume_btn.setStyleSheet(Palette.TOGGLE_BTN_ENABLED_STYLESHEET) + button.setText(self.ON) + button.setStyleSheet(Palette.TOGGLE_BTN_ENABLED_STYLESHEET) else: self.show_volume = False - self.show_volume_btn.setText(self.OFF) - self.show_volume_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) + button.setText(self.OFF) + button.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET) @CaptureConfigErrors def on_run_btn_clicked(self, clicked_signal: bool): @@ -115,8 +79,8 @@ def on_run_btn_clicked(self, clicked_signal: bool): strategy = self.load_strategy(self.strategy_selection_box.filepath_box.text()) # gather other data from UI shared_components - simulation_symbol = self.symbol_tbox.text().upper().strip() - simulation_balance = float(self.initial_balance_tbox.text()) + simulation_symbol = self.grid_config_frame.left_frame.symbol_tbox.text().upper().strip() + simulation_balance = float(self.grid_config_frame.left_frame.initial_balance_tbox.text()) if simulation_balance <= 0: raise MessageBoxCaptureException('Initial account balance must be a positive number!') diff --git a/src/StockBench/gui/palette/palette.py b/src/StockBench/gui/palette/palette.py index e19f69f2..8a38928e 100644 --- a/src/StockBench/gui/palette/palette.py +++ b/src/StockBench/gui/palette/palette.py @@ -9,117 +9,186 @@ class Palette: classes to share common style code. """ # ============================= Icons ======================================== - CANDLE_ICON = os.path.join('resources', 'images', 'candle.ico') + CANDLE_ICON_FILEPATH = os.path.join('resources', 'images', 'candle.ico') # ============================= Stylesheets ================================== WINDOW_STYLESHEET = """background-color:#202124;""" PROGRESS_BAR_STYLESHEET = """ - QProgressBar{ - border-radius: 2px; - } - - QProgressBar::chunk{ - border-radius: 2px; - background-color: #7532a8; - }""" + QProgressBar{ + border-radius: 2px; + } + + QProgressBar::chunk{ + border-radius: 2px; + background-color: #7532a8; + } + """ TAB_WIDGET_STYLESHEET = """ - QTabWidget::pane{ - background-color: #202124; - border: 0; - } - QTabBar::tab:selected { - color: #ffffff; - background-color: #42444a; - } - QTabBar::tab:!selected { - color: #ffffff; - background-color: #323338; - }""" + QTabWidget::pane{ + background-color: #202124; + border: 0; + } + QTabBar::tab:selected { + color: #ffffff; + background-color: #42444a; + } + QTabBar::tab:!selected { + color: #ffffff; + background-color: #323338; + } + """ HINT_LABEL_STYLESHEET = """color: #707070; font-size:12px;""" - INPUT_LABEL_STYLESHEET = """color: #FFF;""" + INPUT_LABEL_STYLESHEET = """ + color: #FFF; + font-size: 13px; + """ - INPUT_BOX_STYLESHEET = """background-color: #303134;color:#FFF;border-width:0px;border-radius:10px; - height:25px;""" + FILEPATH_BOX_STYLESHEET = """ + color: #8c8c8c; + background-color: #303134; + border-width: 0px; + border-radius: 10px; + padding: 5px; + text-indent: 4px; + """ - TEXT_BOX_STYLESHEET = """background-color: #303134;color:#FFF;border-width:0px;border-radius:10px;height:25px; - text-indent:3px;""" + INPUT_BOX_STYLESHEET = """ + color: #FFF; + background-color: #303134; + border-width: 0px; + border-radius: 10px; + padding: 5px; + text-indent: 4px; + """ + + TEXT_BOX_STYLESHEET = """ + background-color: #303134; + color: #FFF; + border-width: 0px; + border-radius: 10px; + height: 25px; + text-indent: 4px; + """ - COMBOBOX_STYLESHEET = """background-color: #303134;color:#FFF;border-width:0px;border-radius:10px;height:25px; - text-indent:3px;""" + COMBOBOX_STYLESHEET = """ + color:#FFF; + background-color: #303134; + font-size: 15px; + border-width: 0px; + border-radius: 10px; + padding-left: 5px; + height: 30px; + """ - RADIO_BTN_STYLESHEET = """color: #fff; margin-left: 15px;""" + RADIO_BTN_STYLESHEET = """ + color: #fff; + margin-left: 15px; + font-size: 15px; + + """ - LINE_EDIT_STYLESHEET = """background-color:#303134;color:#FFF;border-width:0px;border-radius:10px;height:25px; - text-indent:5px;""" + LINE_EDIT_STYLESHEET = """ + color: #FFF; + background-color:#303134; + font-size: 15px; + border-width: 0px; + border-radius: 10px; + padding-left: 5px; + height: 30px; + """ SIDEBAR_HEADER_STYLESHEET = """max-height:45px; color:#FFF;font-size:20px;font-weight:bold;""" SIDEBAR_OUTPUT_BOX_STYLESHEET = """color: #fff; background-color: #303136; border-radius: 8px;border: 0px; padding: 5px;""" + STRATEGY_STUDIO_BTN = """ + QPushButton + { + color: #FFF; + background-color: #303134; + font-size: 14px; + border-width: 0px; + border-radius: 10px; + height: 25px; + width: 200px; + margin-left: 10px; + } + QPushButton:hover + { + background-color: #3f4145; + } + """ + SECONDARY_BTN = """ - QPushButton - { - color: #FFF; - background-color: #303134; - border-width:0px; - border-radius:10px; - height:25px; - } - QPushButton:hover - { - background-color: #3f4145; - } + QPushButton + { + color: #FFF; + background-color: #303134; + font-size: 14px; + border-width: 0px; + border-radius: 10px; + height: 25px; + } + QPushButton:hover + { + background-color: #3f4145; + } """ TOGGLE_BTN_ENABLED_STYLESHEET = """ - QPushButton - { - background-color:#04ba5f; - margin-left:auto; - margin-right:auto; - width:40%; - height:25px; - border-radius:10px; - } - QPushButton:hover - { - background-color: #4ab577; - } - """ + QPushButton + { + background-color: #04ba5f; + margin-left: auto; + margin-right: auto; + border-radius: 10px; + font-size: 15px; + width: 40%; + height: 25px; + } + QPushButton:hover + { + background-color: #4ab577; + } + """ TOGGLE_BTN_DISABLED_STYLESHEET = """ - QPushButton - { - background-color: #303134; - margin-left: auto; - margin-right:auto; - width: 40%; - height:25px; - border-radius: 10px; - } - QPushButton:hover - { - background-color: #3f4145; - } - """ + QPushButton + { + background-color: #303134; + margin-left: auto; + margin-right: auto; + border-radius: 10px; + font-size: 15px; + width: 40%; + height: 25px; + } + QPushButton:hover + { + background-color: #3f4145; + } + """ RUN_BTN_STYLESHEET = """ - QPushButton - { - background-color: #04ba5f; - color: #FFF; - border-radius: 10px; - } - QPushButton:hover - { - background-color: #4ab577; - } - """ - - ERROR_LABEL_STYLESHEET = """color:#dc143c;""" + QPushButton + { + background-color: #04ba5f; + color: #FFF; + border-radius: 10px; + } + QPushButton:hover + { + background-color: #4ab577; + } + """ + + ERROR_LABEL_STYLESHEET = """ + color: #dc143c; + font-size: 15px; + """ diff --git a/src/StockBench/gui/results/base/overview_sidebar.py b/src/StockBench/gui/results/base/overview_sidebar.py index b35abf63..1458a77d 100644 --- a/src/StockBench/gui/results/base/overview_sidebar.py +++ b/src/StockBench/gui/results/base/overview_sidebar.py @@ -124,7 +124,7 @@ def _copy_to_clipboard(text: str): def _show_message_box(title: str, message: str): # show a message box indicating the file was saved msgbox = QMessageBox() - msgbox.setWindowIcon(QtGui.QIcon(Palette.CANDLE_ICON)) + msgbox.setWindowIcon(QtGui.QIcon(Palette.CANDLE_ICON_FILEPATH)) msgbox.setText(message) msgbox.setWindowTitle(title) msgbox.exec() diff --git a/src/StockBench/gui/results/base/results_window.py b/src/StockBench/gui/results/base/results_window.py index 393d14f8..a842fca8 100644 --- a/src/StockBench/gui/results/base/results_window.py +++ b/src/StockBench/gui/results/base/results_window.py @@ -55,7 +55,7 @@ def __init__(self, stockbench_controller: StockBenchController, strategy, initia self.overview_tab = None self.setWindowTitle('Simulation Results') - self.setWindowIcon(QtGui.QIcon(Palette.CANDLE_ICON)) + self.setWindowIcon(QtGui.QIcon(Palette.CANDLE_ICON_FILEPATH)) self.setStyleSheet(Palette.WINDOW_STYLESHEET) # define layout type diff --git a/src/StockBench/gui/studio/strategy_studio.py b/src/StockBench/gui/studio/strategy_studio.py index aa075bb6..789b812d 100644 --- a/src/StockBench/gui/studio/strategy_studio.py +++ b/src/StockBench/gui/studio/strategy_studio.py @@ -18,7 +18,7 @@ def __init__(self, filepath, config_pos, config_width): self.filepath = filepath # header - self.setWindowIcon(QtGui.QIcon(Palette.CANDLE_ICON)) + self.setWindowIcon(QtGui.QIcon(Palette.CANDLE_ICON_FILEPATH)) self.setWindowTitle('Strategy Studio') # layout type @@ -93,7 +93,7 @@ def __save_json_file(self, filepath): @staticmethod def _show_message_box(title: str, message: str): msgbox = QMessageBox() - msgbox.setWindowIcon(QtGui.QIcon(Palette.CANDLE_ICON)) + msgbox.setWindowIcon(QtGui.QIcon(Palette.CANDLE_ICON_FILEPATH)) msgbox.setText(message) msgbox.setWindowTitle(title) msgbox.exec()