From 8aed138baadf9ff80c8bcf61a6bd4e8318c3244c Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Thu, 29 Jan 2026 21:42:08 -0500 Subject: [PATCH 01/41] created components for multi grid --- .../gui/config/tabs/multi/__init__.py | 0 .../config/tabs/multi/components/__init__.py | 0 .../multi/components/grid_config_frame.py | 25 ++++++ .../components/grid_config_left_frame.py | 58 +++++++++++++ .../components/grid_config_right_frame.py | 84 +++++++++++++++++++ 5 files changed, 167 insertions(+) create mode 100644 src/StockBench/gui/config/tabs/multi/__init__.py create mode 100644 src/StockBench/gui/config/tabs/multi/components/__init__.py create mode 100644 src/StockBench/gui/config/tabs/multi/components/grid_config_frame.py create mode 100644 src/StockBench/gui/config/tabs/multi/components/grid_config_left_frame.py create mode 100644 src/StockBench/gui/config/tabs/multi/components/grid_config_right_frame.py 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..4616eb68 --- /dev/null +++ b/src/StockBench/gui/config/tabs/multi/components/grid_config_left_frame.py @@ -0,0 +1,58 @@ +from typing import Callable + +from PyQt6.QtGui import QDoubleValidator +from PyQt6.QtWidgets import QFrame, QVBoxLayout, QLabel, QComboBox, QLineEdit + +from StockBench.gui.palette.palette import Palette + + +class GridConfigLeftFrame(QFrame): + def __init__(self, on_simulation_length_cbox_index_changed: Callable): + super().__init__() + + self.setFixedWidth(300) + + 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) + self.layout.addWidget(self.simulation_length_label) + + # 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_cbox.setStyleSheet(Palette.COMBOBOX_STYLESHEET) + self.simulation_length_cbox.currentIndexChanged.connect(on_simulation_length_cbox_index_changed) # noqa + 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.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.layout.addWidget(self.initial_balance_tbox) + + 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..2f5dee0c --- /dev/null +++ b/src/StockBench/gui/config/tabs/multi/components/grid_config_right_frame.py @@ -0,0 +1,84 @@ +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): + ON = 'ON' + OFF = 'OFF' + + FRAME_STYLESHEET = """ + #gridConfigRightFrame { + border-left: 1px solid grey; + } + """ + + 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(300) + 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) From 8434f320d4acb0f2d59b6df1358ac214871d4fee Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Thu, 29 Jan 2026 21:42:56 -0500 Subject: [PATCH 02/41] removed shared components from base config_tab --- .../gui/config/tabs/base/config_tab.py | 118 ++++-------------- 1 file changed, 24 insertions(+), 94 deletions(-) diff --git a/src/StockBench/gui/config/tabs/base/config_tab.py b/src/StockBench/gui/config/tabs/base/config_tab.py index 2a28fbae..44c3359b 100644 --- a/src/StockBench/gui/config/tabs/base/config_tab.py +++ b/src/StockBench/gui/config/tabs/base/config_tab.py @@ -64,90 +64,14 @@ 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) @@ -179,35 +103,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: From 3f44663c039479594b5d930980d06d7bafd72ee1 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Thu, 29 Jan 2026 21:43:21 -0500 Subject: [PATCH 03/41] implemented grid layout for multi config --- .../tabs/{ => multi}/multi_config_tab.py | 57 +++++-------------- src/StockBench/gui/palette/palette.py | 42 ++++++++++++-- 2 files changed, 51 insertions(+), 48 deletions(-) rename src/StockBench/gui/config/tabs/{ => multi}/multi_config_tab.py (65%) 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 65% 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..c956a941 100644 --- a/src/StockBench/gui/config/tabs/multi_config_tab.py +++ b/src/StockBench/gui/config/tabs/multi/multi_config_tab.py @@ -1,71 +1,44 @@ -from PyQt6.QtWidgets import QLabel, QPushButton, QLineEdit +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.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 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.strategy_selection_box.filepath_box.text())) + self.strategy_studio_btn.setStyleSheet(Palette.STRATEGY_STUDIO_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.layout.addWidget(self.data_and_charts_radio_btn) - - 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.addStretch() + self.layout.addWidget(self.error_message_box) self.setLayout(self.layout) @@ -87,11 +60,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/palette/palette.py b/src/StockBench/gui/palette/palette.py index e19f69f2..f60ee437 100644 --- a/src/StockBench/gui/palette/palette.py +++ b/src/StockBench/gui/palette/palette.py @@ -43,25 +43,55 @@ class Palette: INPUT_LABEL_STYLESHEET = """color: #FFF;""" - INPUT_BOX_STYLESHEET = """background-color: #303134;color:#FFF;border-width:0px;border-radius:10px; - height:25px;""" + INPUT_BOX_STYLESHEET = """ + color:#FFF; + background-color: #303134; + border-width:0px; + border-radius:10px; + padding: 5px; + """ TEXT_BOX_STYLESHEET = """background-color: #303134;color:#FFF;border-width:0px;border-radius:10px;height:25px; text-indent:3px;""" - 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; + border-width:0px; + border-radius:10px; + height:25px; + text-indent:3px;""" RADIO_BTN_STYLESHEET = """color: #fff; margin-left: 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; + border-width:0px; + border-radius:10px; + height:25px; + text-indent:5px;""" 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; + border-width:0px; + border-radius:10px; + height:25px; + margin-left: 100px; + margin-right: 100px; + } + QPushButton:hover + { + background-color: #3f4145; + }""" + SECONDARY_BTN = """ QPushButton { From c6648c74b688f4eb82be681929486ca68736a880 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Thu, 29 Jan 2026 21:43:59 -0500 Subject: [PATCH 04/41] added size constraints to strategy selection components to better allocate space --- src/StockBench/gui/config/components/strategy_selection.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/StockBench/gui/config/components/strategy_selection.py b/src/StockBench/gui/config/components/strategy_selection.py index 9c69535c..f29de7cd 100644 --- a/src/StockBench/gui/config/components/strategy_selection.py +++ b/src/StockBench/gui/config/components/strategy_selection.py @@ -18,6 +18,7 @@ def __init__(self, cache_key=None): self.filepath_box = QLabel() self.filepath_box.setStyleSheet(Palette.INPUT_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) From ad9b9f169e27c13b1643dec074ed57a18293f328 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Thu, 29 Jan 2026 21:48:07 -0500 Subject: [PATCH 05/41] fixed indentation --- src/StockBench/gui/config/tabs/multi/multi_config_tab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StockBench/gui/config/tabs/multi/multi_config_tab.py b/src/StockBench/gui/config/tabs/multi/multi_config_tab.py index c956a941..1f72ba82 100644 --- a/src/StockBench/gui/config/tabs/multi/multi_config_tab.py +++ b/src/StockBench/gui/config/tabs/multi/multi_config_tab.py @@ -24,7 +24,7 @@ def __init__(self, stockbench_controller: StockBenchController): 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_selection_box.filepath_box.text())) self.strategy_studio_btn.setStyleSheet(Palette.STRATEGY_STUDIO_BTN) self.layout.addWidget(self.strategy_studio_btn) From f44155d136ea93a9c725e60efde4aab6436c976a Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Thu, 29 Jan 2026 22:39:48 -0500 Subject: [PATCH 06/41] created singular config components --- .../gui/config/tabs/singular/__init__.py | 0 .../tabs/singular/components/__init__.py | 0 .../singular/components/grid_config_frame.py | 26 +++++ .../components/grid_config_left_frame.py | 64 ++++++++++++ .../components/grid_config_right_frame.py | 97 +++++++++++++++++++ 5 files changed, 187 insertions(+) create mode 100644 src/StockBench/gui/config/tabs/singular/__init__.py create mode 100644 src/StockBench/gui/config/tabs/singular/components/__init__.py create mode 100644 src/StockBench/gui/config/tabs/singular/components/grid_config_frame.py create mode 100644 src/StockBench/gui/config/tabs/singular/components/grid_config_left_frame.py create mode 100644 src/StockBench/gui/config/tabs/singular/components/grid_config_right_frame.py 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..087569da --- /dev/null +++ b/src/StockBench/gui/config/tabs/singular/components/grid_config_left_frame.py @@ -0,0 +1,64 @@ +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): + INPUT_WIDTH = 240 + + def __init__(self, on_simulation_length_cbox_index_changed: Callable): + super().__init__() + + self.setFixedWidth(300) + + 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) + self.layout.addWidget(self.simulation_length_label) + + # 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_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") + 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..a5d85c70 --- /dev/null +++ b/src/StockBench/gui/config/tabs/singular/components/grid_config_right_frame.py @@ -0,0 +1,97 @@ +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): + ON = 'ON' + OFF = 'OFF' + + FRAME_STYLESHEET = """ + #gridConfigRightFrame { + border-left: 1px solid grey; + } + """ + + 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(300) + 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) From c182f70d45307ff337a67bff1e31f47d50502199 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Thu, 29 Jan 2026 22:41:05 -0500 Subject: [PATCH 07/41] implemented singular grid config --- .../{ => singular}/singular_config_tab.py | 83 +++++-------------- src/StockBench/gui/palette/palette.py | 13 ++- 2 files changed, 31 insertions(+), 65 deletions(-) rename src/StockBench/gui/config/tabs/{ => singular}/singular_config_tab.py (54%) 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 54% 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..1a105512 100644 --- a/src/StockBench/gui/config/tabs/singular_config_tab.py +++ b/src/StockBench/gui/config/tabs/singular/singular_config_tab.py @@ -1,11 +1,13 @@ -from PyQt6.QtWidgets import QLabel, QPushButton, QLineEdit +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.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): @@ -14,89 +16,46 @@ def __init__(self, stockbench_controller: StockBenchController): 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.strategy_studio_btn.setStyleSheet(Palette.STRATEGY_STUDIO_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.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.addStretch() + self.layout.addWidget(self.error_message_box) 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 +74,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 f60ee437..6d6cdff9 100644 --- a/src/StockBench/gui/palette/palette.py +++ b/src/StockBench/gui/palette/palette.py @@ -49,10 +49,17 @@ class Palette: 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;""" + TEXT_BOX_STYLESHEET = """ + background-color: #303134; + color:#FFF; + border-width:0px; + border-radius:10px; + height:25px; + text-indent:4px; + """ COMBOBOX_STYLESHEET = """ color:#FFF; @@ -60,7 +67,7 @@ class Palette: border-width:0px; border-radius:10px; height:25px; - text-indent:3px;""" + text-indent:4px;""" RADIO_BTN_STYLESHEET = """color: #fff; margin-left: 15px;""" From 259f14db2022055d28a14ec6262086fb05dff624 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Thu, 29 Jan 2026 22:44:45 -0500 Subject: [PATCH 08/41] added singular-style input indents to multi --- .../tabs/multi/components/grid_config_left_frame.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) 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 index 4616eb68..80981510 100644 --- 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 @@ -1,5 +1,6 @@ from typing import Callable +from PyQt6.QtCore import Qt from PyQt6.QtGui import QDoubleValidator from PyQt6.QtWidgets import QFrame, QVBoxLayout, QLabel, QComboBox, QLineEdit @@ -7,6 +8,8 @@ class GridConfigLeftFrame(QFrame): + INPUT_WIDTH = 240 + def __init__(self, on_simulation_length_cbox_index_changed: Callable): super().__init__() @@ -29,7 +32,8 @@ def __init__(self, on_simulation_length_cbox_index_changed: Callable): 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.layout.addWidget(self.simulation_length_cbox) + 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:') @@ -39,7 +43,8 @@ def __init__(self, on_simulation_length_cbox_index_changed: Callable): 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.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:') @@ -51,7 +56,8 @@ def __init__(self, on_simulation_length_cbox_index_changed: Callable): self.onlyFloat = QDoubleValidator() self.initial_balance_tbox.setValidator(self.onlyFloat) self.initial_balance_tbox.setStyleSheet(Palette.LINE_EDIT_STYLESHEET) - self.layout.addWidget(self.initial_balance_tbox) + self.initial_balance_tbox.setFixedWidth(self.INPUT_WIDTH) + self.layout.addWidget(self.initial_balance_tbox, alignment=Qt.AlignmentFlag.AlignCenter) self.layout.addStretch() From e52147fc5807dce970c7ef84daa631a0e413f010 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 16:50:31 -0500 Subject: [PATCH 09/41] created components for compare config --- .../gui/config/tabs/compare/__init__.py | 0 .../tabs/compare/components/__init__.py | 0 .../compare/components/grid_config_frame.py | 25 ++++++ .../components/grid_config_left_frame.py | 64 ++++++++++++++ .../components/grid_config_right_frame.py | 84 +++++++++++++++++++ 5 files changed, 173 insertions(+) create mode 100644 src/StockBench/gui/config/tabs/compare/__init__.py create mode 100644 src/StockBench/gui/config/tabs/compare/components/__init__.py create mode 100644 src/StockBench/gui/config/tabs/compare/components/grid_config_frame.py create mode 100644 src/StockBench/gui/config/tabs/compare/components/grid_config_left_frame.py create mode 100644 src/StockBench/gui/config/tabs/compare/components/grid_config_right_frame.py 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/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..80981510 --- /dev/null +++ b/src/StockBench/gui/config/tabs/compare/components/grid_config_left_frame.py @@ -0,0 +1,64 @@ +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): + INPUT_WIDTH = 240 + + def __init__(self, on_simulation_length_cbox_index_changed: Callable): + super().__init__() + + self.setFixedWidth(300) + + 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) + self.layout.addWidget(self.simulation_length_label) + + # 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_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..2f5dee0c --- /dev/null +++ b/src/StockBench/gui/config/tabs/compare/components/grid_config_right_frame.py @@ -0,0 +1,84 @@ +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): + ON = 'ON' + OFF = 'OFF' + + FRAME_STYLESHEET = """ + #gridConfigRightFrame { + border-left: 1px solid grey; + } + """ + + 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(300) + 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) From 178b1afe7697cfa5a9d46c15e31ec51e61a7b979 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 16:50:53 -0500 Subject: [PATCH 10/41] implemented compare config --- .../tabs/{ => compare}/compare_config_tab.py | 55 +++++-------------- src/StockBench/gui/palette/palette.py | 9 +++ 2 files changed, 23 insertions(+), 41 deletions(-) rename src/StockBench/gui/config/tabs/{ => compare}/compare_config_tab.py (70%) 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 70% 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..414a44b0 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,13 @@ -from PyQt6.QtWidgets import QLabel, QPushButton, QLineEdit +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): @@ -15,21 +17,19 @@ class CompareConfigTab(ConfigTab): def __init__(self, stockbench_controller: StockBenchController): super().__init__(stockbench_controller) - # add shared_components to the layout 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.strategy_1_studio_btn.setStyleSheet(Palette.STRATEGY_STUDIO_BTN) self.layout.addWidget(self.strategy_1_studio_btn) label = QLabel() @@ -38,53 +38,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.strategy_2_studio_btn.setStyleSheet(Palette.STRATEGY_STUDIO_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.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 +78,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/palette/palette.py b/src/StockBench/gui/palette/palette.py index 6d6cdff9..48206f11 100644 --- a/src/StockBench/gui/palette/palette.py +++ b/src/StockBench/gui/palette/palette.py @@ -43,6 +43,15 @@ class Palette: INPUT_LABEL_STYLESHEET = """color: #FFF;""" + FILEPATH_BOX_STYLESHEET = """ + color: #8c8c8c; + background-color: #303134; + border-width:0px; + border-radius:10px; + padding: 5px; + text-indent:4px; + """ + INPUT_BOX_STYLESHEET = """ color:#FFF; background-color: #303134; From bf7c4c1e6f9d392ed5ce75fb042a9e5350febe2d Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 16:51:29 -0500 Subject: [PATCH 11/41] removed error message widget from initial layout to save space --- src/StockBench/gui/config/tabs/base/config_tab.py | 1 + src/StockBench/gui/config/tabs/multi/multi_config_tab.py | 2 -- src/StockBench/gui/config/tabs/singular/singular_config_tab.py | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/StockBench/gui/config/tabs/base/config_tab.py b/src/StockBench/gui/config/tabs/base/config_tab.py index 44c3359b..607ad96d 100644 --- a/src/StockBench/gui/config/tabs/base/config_tab.py +++ b/src/StockBench/gui/config/tabs/base/config_tab.py @@ -37,6 +37,7 @@ 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) return wrapper diff --git a/src/StockBench/gui/config/tabs/multi/multi_config_tab.py b/src/StockBench/gui/config/tabs/multi/multi_config_tab.py index 1f72ba82..1ecf0582 100644 --- a/src/StockBench/gui/config/tabs/multi/multi_config_tab.py +++ b/src/StockBench/gui/config/tabs/multi/multi_config_tab.py @@ -39,8 +39,6 @@ def __init__(self, stockbench_controller: StockBenchController): self.layout.addStretch() - self.layout.addWidget(self.error_message_box) - self.setLayout(self.layout) @CaptureConfigErrors diff --git a/src/StockBench/gui/config/tabs/singular/singular_config_tab.py b/src/StockBench/gui/config/tabs/singular/singular_config_tab.py index 1a105512..75e85518 100644 --- a/src/StockBench/gui/config/tabs/singular/singular_config_tab.py +++ b/src/StockBench/gui/config/tabs/singular/singular_config_tab.py @@ -43,8 +43,6 @@ def __init__(self, stockbench_controller: StockBenchController): self.layout.addStretch() - self.layout.addWidget(self.error_message_box) - self.setLayout(self.layout) def on_show_volume_btn_clicked(self, button: QPushButton): From 00f2167ca3ab4ddf04a5dc3e7164bbc4a1abbd53 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 16:51:43 -0500 Subject: [PATCH 12/41] darkened filepath text to look non-clickable --- src/StockBench/gui/config/components/strategy_selection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StockBench/gui/config/components/strategy_selection.py b/src/StockBench/gui/config/components/strategy_selection.py index f29de7cd..df9e5667 100644 --- a/src/StockBench/gui/config/components/strategy_selection.py +++ b/src/StockBench/gui/config/components/strategy_selection.py @@ -17,7 +17,7 @@ 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) From 15cf7c175b5a39a9975ef441b34556e80097bcf9 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 17:07:45 -0500 Subject: [PATCH 13/41] cleaned up imports in base config --- src/StockBench/gui/config/tabs/base/config_tab.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/StockBench/gui/config/tabs/base/config_tab.py b/src/StockBench/gui/config/tabs/base/config_tab.py index 607ad96d..8cece89c 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 From 0c950a8566a51ad1c64f60868f132ba833d87fbf Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 17:09:55 -0500 Subject: [PATCH 14/41] fixed spacing --- src/StockBench/gui/config/tabs/base/config_tab.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/StockBench/gui/config/tabs/base/config_tab.py b/src/StockBench/gui/config/tabs/base/config_tab.py index 8cece89c..4d58816f 100644 --- a/src/StockBench/gui/config/tabs/base/config_tab.py +++ b/src/StockBench/gui/config/tabs/base/config_tab.py @@ -78,7 +78,6 @@ def __init__(self, stockbench_controller: StockBenchController): @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 From 0c7ecfd6235d095da1afe5d9cb6daa0ad4340b56 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 17:14:11 -0500 Subject: [PATCH 15/41] created components for folder config --- .../gui/config/tabs/folder/__init__.py | 0 .../config/tabs/folder/components/__init__.py | 0 .../folder/components/grid_config_frame.py | 25 ++++++ .../components/grid_config_left_frame.py | 64 ++++++++++++++ .../components/grid_config_right_frame.py | 84 +++++++++++++++++++ 5 files changed, 173 insertions(+) create mode 100644 src/StockBench/gui/config/tabs/folder/__init__.py create mode 100644 src/StockBench/gui/config/tabs/folder/components/__init__.py create mode 100644 src/StockBench/gui/config/tabs/folder/components/grid_config_frame.py create mode 100644 src/StockBench/gui/config/tabs/folder/components/grid_config_left_frame.py create mode 100644 src/StockBench/gui/config/tabs/folder/components/grid_config_right_frame.py 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..c1b8647e --- /dev/null +++ b/src/StockBench/gui/config/tabs/folder/components/grid_config_frame.py @@ -0,0 +1,25 @@ +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_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/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..80981510 --- /dev/null +++ b/src/StockBench/gui/config/tabs/folder/components/grid_config_left_frame.py @@ -0,0 +1,64 @@ +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): + INPUT_WIDTH = 240 + + def __init__(self, on_simulation_length_cbox_index_changed: Callable): + super().__init__() + + self.setFixedWidth(300) + + 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) + self.layout.addWidget(self.simulation_length_label) + + # 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_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..2f5dee0c --- /dev/null +++ b/src/StockBench/gui/config/tabs/folder/components/grid_config_right_frame.py @@ -0,0 +1,84 @@ +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): + ON = 'ON' + OFF = 'OFF' + + FRAME_STYLESHEET = """ + #gridConfigRightFrame { + border-left: 1px solid grey; + } + """ + + 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(300) + 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) From 79fd722a20a8f241924b4c8fa83e16b1fcac49ab Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 17:14:22 -0500 Subject: [PATCH 16/41] implemented folder config --- .../tabs/{ => folder}/folder_config_tab.py | 41 ++++++------------- 1 file changed, 12 insertions(+), 29 deletions(-) rename src/StockBench/gui/config/tabs/{ => folder}/folder_config_tab.py (74%) 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 74% 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..b74365b5 100644 --- a/src/StockBench/gui/config/tabs/folder_config_tab.py +++ b/src/StockBench/gui/config/tabs/folder/folder_config_tab.py @@ -1,13 +1,15 @@ import os -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): @@ -25,35 +27,16 @@ 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_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) @@ -74,11 +57,11 @@ def on_run_btn_clicked(self, clicked_signal: bool): 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!') From ccd039b52b964b8ca6a11868e445c5149e9df9f0 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 17:14:36 -0500 Subject: [PATCH 17/41] implemented all configs --- src/StockBench/gui/application.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/StockBench/gui/application.py b/src/StockBench/gui/application.py index 2616ba1a..b67771e3 100644 --- a/src/StockBench/gui/application.py +++ b/src/StockBench/gui/application.py @@ -1,18 +1,18 @@ 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__() @@ -24,11 +24,14 @@ def __init__(self, splash, config: AppConfiguration): # main window styling (do it first to prevent window shifting) self.setWindowIcon(QtGui.QIcon(Palette.CANDLE_ICON)) self.setWindowTitle('Configuration') - self.setFixedSize(self.WIDTH, self.HEIGHT) + # FIXME - decide on fixed or variable height + self.setFixedWidth(self.WIDTH) + # self.setFixedSize(self.WIDTH, self.HEIGHT) self.layout = QVBoxLayout() self.tab_widget = QTabWidget() + # FIXME: DEBUG comments 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") From 6d9a92c213bc05ca9998b5cdca19c92b97a6f967 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 17:16:22 -0500 Subject: [PATCH 18/41] removed comments --- src/StockBench/gui/config/tabs/folder/folder_config_tab.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/StockBench/gui/config/tabs/folder/folder_config_tab.py b/src/StockBench/gui/config/tabs/folder/folder_config_tab.py index b74365b5..17a9d6b1 100644 --- a/src/StockBench/gui/config/tabs/folder/folder_config_tab.py +++ b/src/StockBench/gui/config/tabs/folder/folder_config_tab.py @@ -17,7 +17,6 @@ class FolderConfigTab(ConfigTab): def __init__(self, stockbench_controller: StockBenchController): super().__init__(stockbench_controller) - # add shared_components to the layout label = QLabel() label.setText('Folder:') label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) @@ -53,10 +52,8 @@ 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.grid_config_frame.left_frame.symbol_tbox.text().split(',') simulation_symbols = [] for symbol in raw_simulation_symbols: From e1ab03c23d6d82136f99487810620a3a84c6e58c Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 17:18:43 -0500 Subject: [PATCH 19/41] removed config elements from folder --- .../folder/components/grid_config_frame.py | 7 ++-- .../components/grid_config_right_frame.py | 32 +------------------ .../config/tabs/folder/folder_config_tab.py | 4 +-- 3 files changed, 4 insertions(+), 39 deletions(-) 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 index c1b8647e..154eeb8f 100644 --- a/src/StockBench/gui/config/tabs/folder/components/grid_config_frame.py +++ b/src/StockBench/gui/config/tabs/folder/components/grid_config_frame.py @@ -8,16 +8,13 @@ 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): + 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_reporting_btn_clicked, - on_chart_saving_btn_clicked, data_and_charts_btn_selected, - data_only_btn_selected) + 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) 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 index 2f5dee0c..438357ec 100644 --- 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 @@ -16,9 +16,7 @@ class GridConfigRightFrame(QFrame): } """ - 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: + def __init__(self, on_logging_btn_clicked: Callable, on_chart_saving_btn_clicked: Callable) -> None: super().__init__() self.setFixedWidth(300) @@ -39,18 +37,6 @@ def __init__(self, on_logging_btn_clicked: Callable, on_reporting_btn_clicked: C 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) @@ -63,22 +49,6 @@ def __init__(self, on_logging_btn_clicked: Callable, on_reporting_btn_clicked: C 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/folder_config_tab.py b/src/StockBench/gui/config/tabs/folder/folder_config_tab.py index 17a9d6b1..cb8ef0bb 100644 --- a/src/StockBench/gui/config/tabs/folder/folder_config_tab.py +++ b/src/StockBench/gui/config/tabs/folder/folder_config_tab.py @@ -28,9 +28,7 @@ def __init__(self, stockbench_controller: StockBenchController): 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.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) From a0de4df890f8c3dbaf5f7e185a6240fad761f3bc Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 17:19:10 -0500 Subject: [PATCH 20/41] removed comment --- src/StockBench/gui/config/tabs/folder/folder_config_tab.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/StockBench/gui/config/tabs/folder/folder_config_tab.py b/src/StockBench/gui/config/tabs/folder/folder_config_tab.py index cb8ef0bb..82c589cf 100644 --- a/src/StockBench/gui/config/tabs/folder/folder_config_tab.py +++ b/src/StockBench/gui/config/tabs/folder/folder_config_tab.py @@ -67,7 +67,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, From c42894e1ac274bb299194d7b08814ff906bfb344 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 17:20:55 -0500 Subject: [PATCH 21/41] removed comments --- src/StockBench/gui/config/tabs/base/config_tab.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/StockBench/gui/config/tabs/base/config_tab.py b/src/StockBench/gui/config/tabs/base/config_tab.py index 4d58816f..55c38a44 100644 --- a/src/StockBench/gui/config/tabs/base/config_tab.py +++ b/src/StockBench/gui/config/tabs/base/config_tab.py @@ -162,7 +162,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 @@ -173,23 +172,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) From 21a1c72a8c6452660e4aea4a0095054c648b325e Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 17:21:38 -0500 Subject: [PATCH 22/41] removed comments --- src/StockBench/gui/application.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/StockBench/gui/application.py b/src/StockBench/gui/application.py index b67771e3..0be84738 100644 --- a/src/StockBench/gui/application.py +++ b/src/StockBench/gui/application.py @@ -31,7 +31,6 @@ def __init__(self, splash, config: AppConfiguration): self.layout = QVBoxLayout() self.tab_widget = QTabWidget() - # FIXME: DEBUG comments 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") @@ -48,9 +47,7 @@ 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. + # 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() From 3d0ff48e7820d2ba59cb4803ac337be527252629 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 17:28:38 -0500 Subject: [PATCH 23/41] config window shrinks to fit biggest tab and also is not resizable --- src/StockBench/gui/application.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/StockBench/gui/application.py b/src/StockBench/gui/application.py index 0be84738..5e16d65a 100644 --- a/src/StockBench/gui/application.py +++ b/src/StockBench/gui/application.py @@ -24,9 +24,7 @@ def __init__(self, splash, config: AppConfiguration): # main window styling (do it first to prevent window shifting) self.setWindowIcon(QtGui.QIcon(Palette.CANDLE_ICON)) self.setWindowTitle('Configuration') - # FIXME - decide on fixed or variable height - self.setFixedWidth(self.WIDTH) - # self.setFixedSize(self.WIDTH, self.HEIGHT) + # SEE BOTTOM FOR CONFIG WINDOW SIZING!!! self.layout = QVBoxLayout() @@ -51,3 +49,12 @@ def __init__(self, splash, config: AppConfiguration): self.setCentralWidget(widget) 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.updateGeometry() + size = self.sizeHint() + self.setMinimumSize(size) + self.setMaximumSize(size) From 0df90e70ac745d1c71f0b65f61578c2cedc9b15e Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 17:52:57 -0500 Subject: [PATCH 24/41] folder selector now matches style of file selector --- src/StockBench/gui/config/components/cached_folder_selector.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/StockBench/gui/config/components/cached_folder_selector.py b/src/StockBench/gui/config/components/cached_folder_selector.py index 35950ecd..e08af276 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(85) self.layout.addWidget(self.select_folder_btn) self.setLayout(self.layout) From 523759ca9a682d1c881c050d5c3e17b9209cc7c2 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 17:53:26 -0500 Subject: [PATCH 25/41] added spacers to singular and multi config to better use available space --- src/StockBench/gui/config/tabs/multi/multi_config_tab.py | 7 ++++++- .../gui/config/tabs/singular/singular_config_tab.py | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/StockBench/gui/config/tabs/multi/multi_config_tab.py b/src/StockBench/gui/config/tabs/multi/multi_config_tab.py index 1ecf0582..417eac59 100644 --- a/src/StockBench/gui/config/tabs/multi/multi_config_tab.py +++ b/src/StockBench/gui/config/tabs/multi/multi_config_tab.py @@ -1,4 +1,4 @@ -from PyQt6.QtWidgets import QLabel, QPushButton +from PyQt6.QtWidgets import QLabel, QPushButton, QFrame from PyQt6.QtCore import Qt from StockBench.controllers.stockbench_controller import StockBenchController @@ -28,6 +28,11 @@ def __init__(self, stockbench_controller: StockBenchController): self.strategy_studio_btn.setStyleSheet(Palette.STRATEGY_STUDIO_BTN) self.layout.addWidget(self.strategy_studio_btn) + # spacer to even out multi 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, diff --git a/src/StockBench/gui/config/tabs/singular/singular_config_tab.py b/src/StockBench/gui/config/tabs/singular/singular_config_tab.py index 75e85518..1b99beec 100644 --- a/src/StockBench/gui/config/tabs/singular/singular_config_tab.py +++ b/src/StockBench/gui/config/tabs/singular/singular_config_tab.py @@ -1,4 +1,4 @@ -from PyQt6.QtWidgets import QLabel, QPushButton +from PyQt6.QtWidgets import QLabel, QPushButton, QFrame from PyQt6.QtCore import Qt from StockBench.controllers.stockbench_controller import StockBenchController @@ -31,6 +31,11 @@ def __init__(self, stockbench_controller: StockBenchController): self.strategy_studio_btn.setStyleSheet(Palette.STRATEGY_STUDIO_BTN) self.layout.addWidget(self.strategy_studio_btn) + # 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, From 72e5adc217591d04be31290870bd6daa156c97df Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 17:57:41 -0500 Subject: [PATCH 26/41] added left side padding to push right side config controls away from center line (border) which is more symmetrical --- .../config/tabs/compare/components/grid_config_right_frame.py | 1 + .../gui/config/tabs/folder/components/grid_config_right_frame.py | 1 + .../gui/config/tabs/multi/components/grid_config_right_frame.py | 1 + .../config/tabs/singular/components/grid_config_right_frame.py | 1 + 4 files changed, 4 insertions(+) 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 index 2f5dee0c..77c62526 100644 --- 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 @@ -13,6 +13,7 @@ class GridConfigRightFrame(QFrame): FRAME_STYLESHEET = """ #gridConfigRightFrame { border-left: 1px solid grey; + padding-left: 25px; } """ 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 index 438357ec..dc11ade4 100644 --- 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 @@ -13,6 +13,7 @@ class GridConfigRightFrame(QFrame): FRAME_STYLESHEET = """ #gridConfigRightFrame { border-left: 1px solid grey; + padding-left: 25px; } """ 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 index 2f5dee0c..77c62526 100644 --- 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 @@ -13,6 +13,7 @@ class GridConfigRightFrame(QFrame): FRAME_STYLESHEET = """ #gridConfigRightFrame { border-left: 1px solid grey; + padding-left: 25px; } """ 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 index a5d85c70..dd2aaa6a 100644 --- 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 @@ -13,6 +13,7 @@ class GridConfigRightFrame(QFrame): FRAME_STYLESHEET = """ #gridConfigRightFrame { border-left: 1px solid grey; + padding-left: 25px; } """ From d5e7eeea0afd25a528fa9a7046b0794018826133 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 17:58:33 -0500 Subject: [PATCH 27/41] updated comments --- .../config/tabs/singular/components/grid_config_left_frame.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 index 087569da..104b13c4 100644 --- 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 @@ -17,18 +17,16 @@ def __init__(self, on_simulation_length_cbox_index_changed: Callable): 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) self.layout.addWidget(self.simulation_length_label) - # 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) + # 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 From a33b72fdecbb0251948e1d8f7545b84a4b7b5acc Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 20:00:59 -0500 Subject: [PATCH 28/41] added resize function to allow error message add to layout to not mangle other widgets --- src/StockBench/gui/application.py | 16 +++++++++++----- .../gui/config/tabs/base/config_tab.py | 5 ++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/StockBench/gui/application.py b/src/StockBench/gui/application.py index 5e16d65a..2e09f973 100644 --- a/src/StockBench/gui/application.py +++ b/src/StockBench/gui/application.py @@ -22,17 +22,17 @@ def __init__(self, splash, config: AppConfiguration): self.__stockbench_controller = StockBenchControllerFactory.get_controller_instance() # main window styling (do it first to prevent window shifting) - self.setWindowIcon(QtGui.QIcon(Palette.CANDLE_ICON)) + self.setWindowIcon(QtGui.QIcon(Palette.CANDLE_ICON_FILEPATH)) self.setWindowTitle('Configuration') # 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) @@ -54,7 +54,13 @@ def __init__(self, splash, config: AppConfiguration): # 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): + + # FIXME: need to invalidate the layout and force it to recalculate self.updateGeometry() + self.adjustSize() size = self.sizeHint() self.setMinimumSize(size) self.setMaximumSize(size) diff --git a/src/StockBench/gui/config/tabs/base/config_tab.py b/src/StockBench/gui/config/tabs/base/config_tab.py index 55c38a44..b1543089 100644 --- a/src/StockBench/gui/config/tabs/base/config_tab.py +++ b/src/StockBench/gui/config/tabs/base/config_tab.py @@ -37,6 +37,8 @@ def wrapper(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 From fa0ce181f8ef0b4031ddeebe863d9ddfb16bba8a Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 20:01:28 -0500 Subject: [PATCH 29/41] increased select folder button size --- src/StockBench/gui/config/components/cached_folder_selector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StockBench/gui/config/components/cached_folder_selector.py b/src/StockBench/gui/config/components/cached_folder_selector.py index e08af276..044247a3 100644 --- a/src/StockBench/gui/config/components/cached_folder_selector.py +++ b/src/StockBench/gui/config/components/cached_folder_selector.py @@ -22,7 +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(85) + self.select_folder_btn.setFixedWidth(90) self.layout.addWidget(self.select_folder_btn) self.setLayout(self.layout) From aaa8d3d2aa0a397dd1f038f22a3a54b9228b112b Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 20:01:57 -0500 Subject: [PATCH 30/41] updated alignments --- .../gui/config/tabs/compare/compare_config_tab.py | 10 ++++++---- .../gui/config/tabs/folder/folder_config_tab.py | 5 +++-- .../gui/config/tabs/multi/multi_config_tab.py | 8 +++++--- .../gui/config/tabs/singular/singular_config_tab.py | 8 +++++--- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/StockBench/gui/config/tabs/compare/compare_config_tab.py b/src/StockBench/gui/config/tabs/compare/compare_config_tab.py index 414a44b0..5e4624de 100644 --- a/src/StockBench/gui/config/tabs/compare/compare_config_tab.py +++ b/src/StockBench/gui/config/tabs/compare/compare_config_tab.py @@ -1,3 +1,5 @@ +from typing import Callable + from PyQt6.QtWidgets import QLabel, QPushButton from PyQt6.QtCore import Qt @@ -15,8 +17,8 @@ class CompareConfigTab(ConfigTab): STRATEGY_2_CACHE_KEY = 'cached_h2h_strategy_2_filepath' - 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) label = QLabel() label.setText('Strategy 1:') label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) @@ -30,7 +32,7 @@ def __init__(self, stockbench_controller: StockBenchController): 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.STRATEGY_STUDIO_BTN) - self.layout.addWidget(self.strategy_1_studio_btn) + self.layout.addWidget(self.strategy_1_studio_btn, alignment=Qt.AlignmentFlag.AlignRight) label = QLabel() label.setText('Strategy 2:') @@ -45,7 +47,7 @@ def __init__(self, stockbench_controller: StockBenchController): 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.STRATEGY_STUDIO_BTN) - self.layout.addWidget(self.strategy_2_studio_btn) + self.layout.addWidget(self.strategy_2_studio_btn, alignment=Qt.AlignmentFlag.AlignRight) self.simulation_length = SECONDS_1_YEAR self.grid_config_frame = GridConfigFrame(self.on_simulation_length_cbox_index_changed, diff --git a/src/StockBench/gui/config/tabs/folder/folder_config_tab.py b/src/StockBench/gui/config/tabs/folder/folder_config_tab.py index 82c589cf..ce84a4e8 100644 --- a/src/StockBench/gui/config/tabs/folder/folder_config_tab.py +++ b/src/StockBench/gui/config/tabs/folder/folder_config_tab.py @@ -1,4 +1,5 @@ import os +from typing import Callable from PyQt6.QtWidgets import QLabel from PyQt6.QtCore import Qt @@ -15,8 +16,8 @@ class FolderConfigTab(ConfigTab): FOLDER_CACHE_KEY = 'cached_folderpath' - 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) label = QLabel() label.setText('Folder:') label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) diff --git a/src/StockBench/gui/config/tabs/multi/multi_config_tab.py b/src/StockBench/gui/config/tabs/multi/multi_config_tab.py index 417eac59..6bfec311 100644 --- a/src/StockBench/gui/config/tabs/multi/multi_config_tab.py +++ b/src/StockBench/gui/config/tabs/multi/multi_config_tab.py @@ -1,3 +1,5 @@ +from typing import Callable + from PyQt6.QtWidgets import QLabel, QPushButton, QFrame from PyQt6.QtCore import Qt @@ -11,8 +13,8 @@ class MultiConfigTab(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) label = QLabel() label.setText('Strategy:') label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) @@ -26,7 +28,7 @@ def __init__(self, stockbench_controller: StockBenchController): 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.STRATEGY_STUDIO_BTN) - self.layout.addWidget(self.strategy_studio_btn) + self.layout.addWidget(self.strategy_studio_btn, alignment=Qt.AlignmentFlag.AlignCenter) # spacer to even out multi layout self.spacer = QFrame() diff --git a/src/StockBench/gui/config/tabs/singular/singular_config_tab.py b/src/StockBench/gui/config/tabs/singular/singular_config_tab.py index 1b99beec..8b994a21 100644 --- a/src/StockBench/gui/config/tabs/singular/singular_config_tab.py +++ b/src/StockBench/gui/config/tabs/singular/singular_config_tab.py @@ -1,3 +1,5 @@ +from typing import Callable + from PyQt6.QtWidgets import QLabel, QPushButton, QFrame from PyQt6.QtCore import Qt @@ -11,8 +13,8 @@ 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 @@ -29,7 +31,7 @@ def __init__(self, stockbench_controller: StockBenchController): 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.STRATEGY_STUDIO_BTN) - self.layout.addWidget(self.strategy_studio_btn) + self.layout.addWidget(self.strategy_studio_btn, alignment=Qt.AlignmentFlag.AlignCenter) # spacer to even out singular layout self.spacer = QFrame() From f6bc37d6f3a4be5c8b269d01b7c04b7fc43155b0 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 20:02:09 -0500 Subject: [PATCH 31/41] updated palette styles --- src/StockBench/gui/palette/palette.py | 245 ++++++++++++++------------ 1 file changed, 134 insertions(+), 111 deletions(-) diff --git a/src/StockBench/gui/palette/palette.py b/src/StockBench/gui/palette/palette.py index 48206f11..8a38928e 100644 --- a/src/StockBench/gui/palette/palette.py +++ b/src/StockBench/gui/palette/palette.py @@ -9,163 +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; + """ FILEPATH_BOX_STYLESHEET = """ color: #8c8c8c; background-color: #303134; - border-width:0px; - border-radius:10px; + border-width: 0px; + border-radius: 10px; padding: 5px; - text-indent:4px; + text-indent: 4px; """ INPUT_BOX_STYLESHEET = """ - color:#FFF; + color: #FFF; background-color: #303134; - border-width:0px; - border-radius:10px; + border-width: 0px; + border-radius: 10px; padding: 5px; - text-indent:4px; + text-indent: 4px; """ TEXT_BOX_STYLESHEET = """ background-color: #303134; - color:#FFF; - border-width:0px; - border-radius:10px; - height:25px; - text-indent:4px; + color: #FFF; + border-width: 0px; + border-radius: 10px; + height: 25px; + text-indent: 4px; """ COMBOBOX_STYLESHEET = """ color:#FFF; background-color: #303134; - border-width:0px; - border-radius:10px; - height:25px; - text-indent:4px;""" + 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 = """ - color:#FFF; + color: #FFF; background-color:#303134; - border-width:0px; - border-radius:10px; - height:25px; - text-indent:5px;""" + 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; - border-width:0px; - border-radius:10px; - height:25px; - margin-left: 100px; - margin-right: 100px; - } - QPushButton:hover - { - background-color: #3f4145; - }""" + 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; + """ From 9e0721c3026f2601bb51b6eefae810030f1b109e Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 20:02:30 -0500 Subject: [PATCH 32/41] renamed filepath variable --- src/StockBench/gui/results/base/overview_sidebar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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() From e323c0af955c5da7206d5e9c4e23b29b7103e153 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 20:02:42 -0500 Subject: [PATCH 33/41] renamed filepath variable --- src/StockBench/gui/results/base/results_window.py | 2 +- src/StockBench/gui/studio/strategy_studio.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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() From 50008d6bfa315723b05eb49a535534a715525dcd Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 20:03:00 -0500 Subject: [PATCH 34/41] removed plural label for singular --- .../config/tabs/singular/components/grid_config_left_frame.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 104b13c4..9967631d 100644 --- 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 @@ -34,7 +34,7 @@ def __init__(self, on_simulation_length_cbox_index_changed: Callable): self.layout.addWidget(self.simulation_length_cbox, alignment=Qt.AlignmentFlag.AlignCenter) label = QLabel() - label.setText('Simulation Symbols:') + label.setText('Simulation Symbol:') label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET) self.layout.addWidget(label) From 9287783a374e51ca0dcae42f2cd0a97d243018b0 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 20:07:13 -0500 Subject: [PATCH 35/41] converted grid frame width to constant --- .../config/tabs/compare/components/grid_config_left_frame.py | 5 +++-- .../tabs/compare/components/grid_config_right_frame.py | 5 +++-- .../config/tabs/folder/components/grid_config_left_frame.py | 5 +++-- .../config/tabs/folder/components/grid_config_right_frame.py | 5 +++-- .../config/tabs/multi/components/grid_config_left_frame.py | 5 +++-- .../config/tabs/multi/components/grid_config_right_frame.py | 4 +++- .../tabs/singular/components/grid_config_left_frame.py | 5 +++-- .../tabs/singular/components/grid_config_right_frame.py | 4 +++- 8 files changed, 24 insertions(+), 14 deletions(-) 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 index 80981510..1d843240 100644 --- 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 @@ -8,12 +8,13 @@ class GridConfigLeftFrame(QFrame): + FRAME_WIDTH = 300 + INPUT_WIDTH = 240 def __init__(self, on_simulation_length_cbox_index_changed: Callable): super().__init__() - - self.setFixedWidth(300) + self.setFixedWidth(self.FRAME_WIDTH) self.layout = QVBoxLayout() 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 index 77c62526..07f0c6ce 100644 --- 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 @@ -7,6 +7,8 @@ class GridConfigRightFrame(QFrame): + FRAME_WIDTH = 300 + ON = 'ON' OFF = 'OFF' @@ -21,8 +23,7 @@ def __init__(self, on_logging_btn_clicked: Callable, on_reporting_btn_clicked: C on_chart_saving_btn_clicked: Callable, data_and_charts_btn_selected: Callable, data_only_btn_selected: Callable) -> None: super().__init__() - - self.setFixedWidth(300) + self.setFixedWidth(self.FRAME_WIDTH) self.setObjectName("gridConfigRightFrame") # apply styles based on id (must inherit from QFrame) self.setStyleSheet(self.FRAME_STYLESHEET) 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 index 80981510..1d843240 100644 --- 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 @@ -8,12 +8,13 @@ class GridConfigLeftFrame(QFrame): + FRAME_WIDTH = 300 + INPUT_WIDTH = 240 def __init__(self, on_simulation_length_cbox_index_changed: Callable): super().__init__() - - self.setFixedWidth(300) + self.setFixedWidth(self.FRAME_WIDTH) self.layout = QVBoxLayout() 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 index dc11ade4..b0af15db 100644 --- 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 @@ -7,6 +7,8 @@ class GridConfigRightFrame(QFrame): + FRAME_WIDTH = 300 + ON = 'ON' OFF = 'OFF' @@ -19,8 +21,7 @@ class GridConfigRightFrame(QFrame): def __init__(self, on_logging_btn_clicked: Callable, on_chart_saving_btn_clicked: Callable) -> None: super().__init__() - - self.setFixedWidth(300) + self.setFixedWidth(self.FRAME_WIDTH) self.setObjectName("gridConfigRightFrame") # apply styles based on id (must inherit from QFrame) self.setStyleSheet(self.FRAME_STYLESHEET) 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 index 80981510..3512c7db 100644 --- 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 @@ -8,12 +8,13 @@ class GridConfigLeftFrame(QFrame): + FRAME_WIDTH = 300 + INPUT_WIDTH = 240 def __init__(self, on_simulation_length_cbox_index_changed: Callable): super().__init__() - - self.setFixedWidth(300) + self.setFixedWidth(self.INPUT_WIDTH) self.layout = QVBoxLayout() 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 index 77c62526..a940e62c 100644 --- 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 @@ -7,6 +7,8 @@ class GridConfigRightFrame(QFrame): + FRAME_WIDTH = 300 + ON = 'ON' OFF = 'OFF' @@ -22,7 +24,7 @@ def __init__(self, on_logging_btn_clicked: Callable, on_reporting_btn_clicked: C data_only_btn_selected: Callable) -> None: super().__init__() - self.setFixedWidth(300) + self.setFixedWidth(self.FRAME_WIDTH) self.setObjectName("gridConfigRightFrame") # apply styles based on id (must inherit from QFrame) self.setStyleSheet(self.FRAME_STYLESHEET) 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 index 9967631d..9f0178fd 100644 --- 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 @@ -8,12 +8,13 @@ class GridConfigLeftFrame(QFrame): + FRAME_WIDTH = 300 + INPUT_WIDTH = 240 def __init__(self, on_simulation_length_cbox_index_changed: Callable): super().__init__() - - self.setFixedWidth(300) + self.setFixedWidth(self.FRAME_WIDTH) self.layout = QVBoxLayout() 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 index dd2aaa6a..ce1b983a 100644 --- 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 @@ -7,6 +7,8 @@ class GridConfigRightFrame(QFrame): + FRAME_WIDTH = 300 + ON = 'ON' OFF = 'OFF' @@ -22,7 +24,7 @@ def __init__(self, on_logging_btn_clicked: Callable, on_reporting_btn_clicked: C data_and_charts_btn_selected: Callable, data_only_btn_selected: Callable): super().__init__() - self.setFixedWidth(300) + self.setFixedWidth(self.FRAME_WIDTH) self.setObjectName("gridConfigRightFrame") # apply styles based on id (must inherit from QFrame) self.setStyleSheet(self.FRAME_STYLESHEET) From 1104072c328f978d3125c0a79145148d12e7fc88 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 20:09:01 -0500 Subject: [PATCH 36/41] removed comments --- .../config/tabs/compare/components/grid_config_left_frame.py | 2 -- .../gui/config/tabs/folder/components/grid_config_left_frame.py | 2 -- .../gui/config/tabs/multi/components/grid_config_left_frame.py | 2 -- 3 files changed, 6 deletions(-) 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 index 1d843240..2ad54019 100644 --- 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 @@ -18,13 +18,11 @@ def __init__(self, on_simulation_length_cbox_index_changed: Callable): 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) self.layout.addWidget(self.simulation_length_label) - # simulation length input self.simulation_length_cbox = QComboBox() self.simulation_length_cbox.addItem('1 Year') self.simulation_length_cbox.addItem('2 Year') 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 index 1d843240..2ad54019 100644 --- 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 @@ -18,13 +18,11 @@ def __init__(self, on_simulation_length_cbox_index_changed: Callable): 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) self.layout.addWidget(self.simulation_length_label) - # simulation length input self.simulation_length_cbox = QComboBox() self.simulation_length_cbox.addItem('1 Year') self.simulation_length_cbox.addItem('2 Year') 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 index 3512c7db..2a1ef447 100644 --- 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 @@ -18,13 +18,11 @@ def __init__(self, on_simulation_length_cbox_index_changed: Callable): 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) self.layout.addWidget(self.simulation_length_label) - # simulation length input self.simulation_length_cbox = QComboBox() self.simulation_length_cbox.addItem('1 Year') self.simulation_length_cbox.addItem('2 Year') From 89a99aec2eed0106ea065a27644e204942416704 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 20:33:02 -0500 Subject: [PATCH 37/41] removed comment --- src/StockBench/gui/application.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/StockBench/gui/application.py b/src/StockBench/gui/application.py index 2e09f973..4a540864 100644 --- a/src/StockBench/gui/application.py +++ b/src/StockBench/gui/application.py @@ -45,7 +45,6 @@ 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) self.splash.close() @@ -57,8 +56,7 @@ def __init__(self, splash, config: AppConfiguration): self.__update_geometry() def __update_geometry(self): - - # FIXME: need to invalidate the layout and force it to recalculate + """Updates the geometry of the main config window while maintaining the in-ability for manual resize.""" self.updateGeometry() self.adjustSize() size = self.sizeHint() From d8699ac18f856b8b5e5f543268e25fd85bb30e97 Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 20:38:11 -0500 Subject: [PATCH 38/41] fixed issue where grid frame size used wrong constant --- .../gui/config/tabs/multi/components/grid_config_left_frame.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 2a1ef447..2ad54019 100644 --- 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 @@ -14,7 +14,7 @@ class GridConfigLeftFrame(QFrame): def __init__(self, on_simulation_length_cbox_index_changed: Callable): super().__init__() - self.setFixedWidth(self.INPUT_WIDTH) + self.setFixedWidth(self.FRAME_WIDTH) self.layout = QVBoxLayout() From e4080cca068abfd24ba0d989b85cd5200508fa6b Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 20:49:52 -0500 Subject: [PATCH 39/41] updated docstring --- src/StockBench/gui/application.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/StockBench/gui/application.py b/src/StockBench/gui/application.py index 4a540864..19cc8bb5 100644 --- a/src/StockBench/gui/application.py +++ b/src/StockBench/gui/application.py @@ -56,7 +56,12 @@ def __init__(self, splash, config: AppConfiguration): self.__update_geometry() def __update_geometry(self): - """Updates the geometry of the main config window while maintaining the in-ability for manual resize.""" + """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() From 8c7c3f73befafe4e73cb81cf521d29c622f4d05b Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 20:50:14 -0500 Subject: [PATCH 40/41] removed comment --- src/StockBench/gui/application.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/StockBench/gui/application.py b/src/StockBench/gui/application.py index 19cc8bb5..c8fb6d5c 100644 --- a/src/StockBench/gui/application.py +++ b/src/StockBench/gui/application.py @@ -18,7 +18,6 @@ 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) From 1d80cd5b4b2bd27346312db1d09ecc345ffa33db Mon Sep 17 00:00:00 2001 From: Jason O'Connell Date: Fri, 30 Jan 2026 20:50:30 -0500 Subject: [PATCH 41/41] removed comment --- src/StockBench/gui/application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StockBench/gui/application.py b/src/StockBench/gui/application.py index c8fb6d5c..829dfb5d 100644 --- a/src/StockBench/gui/application.py +++ b/src/StockBench/gui/application.py @@ -20,7 +20,7 @@ def __init__(self, splash, config: AppConfiguration): self.__stockbench_controller = StockBenchControllerFactory.get_controller_instance() - # main window styling (do it first to prevent window shifting) + # main window styling (done first to prevent window shifting) self.setWindowIcon(QtGui.QIcon(Palette.CANDLE_ICON_FILEPATH)) self.setWindowTitle('Configuration') # SEE BOTTOM FOR CONFIG WINDOW SIZING!!!