Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
8aed138
created components for multi grid
jocon15 Jan 30, 2026
8434f32
removed shared components from base config_tab
jocon15 Jan 30, 2026
3f44663
implemented grid layout for multi config
jocon15 Jan 30, 2026
c6648c7
added size constraints to strategy selection components to better all…
jocon15 Jan 30, 2026
ad9b9f1
fixed indentation
jocon15 Jan 30, 2026
f44155d
created singular config components
jocon15 Jan 30, 2026
c182f70
implemented singular grid config
jocon15 Jan 30, 2026
259f14d
added singular-style input indents to multi
jocon15 Jan 30, 2026
e52147f
created components for compare config
jocon15 Jan 30, 2026
178b1af
implemented compare config
jocon15 Jan 30, 2026
bf7c4c1
removed error message widget from initial layout to save space
jocon15 Jan 30, 2026
00f2167
darkened filepath text to look non-clickable
jocon15 Jan 30, 2026
15cf7c1
cleaned up imports in base config
jocon15 Jan 30, 2026
0c950a8
fixed spacing
jocon15 Jan 30, 2026
0c7ecfd
created components for folder config
jocon15 Jan 30, 2026
79fd722
implemented folder config
jocon15 Jan 30, 2026
ccd039b
implemented all configs
jocon15 Jan 30, 2026
6d9a92c
removed comments
jocon15 Jan 30, 2026
e1ab03c
removed config elements from folder
jocon15 Jan 30, 2026
a0de4df
removed comment
jocon15 Jan 30, 2026
c42894e
removed comments
jocon15 Jan 30, 2026
21a1c72
removed comments
jocon15 Jan 30, 2026
3d0ff48
config window shrinks to fit biggest tab and also is not resizable
jocon15 Jan 30, 2026
0df90e7
folder selector now matches style of file selector
jocon15 Jan 30, 2026
523759c
added spacers to singular and multi config to better use available space
jocon15 Jan 30, 2026
72e5adc
added left side padding to push right side config controls away from …
jocon15 Jan 30, 2026
d5e7eee
updated comments
jocon15 Jan 30, 2026
a33b72f
added resize function to allow error message add to layout to not man…
jocon15 Jan 31, 2026
fa0ce18
increased select folder button size
jocon15 Jan 31, 2026
aaa8d3d
updated alignments
jocon15 Jan 31, 2026
f6bc37d
updated palette styles
jocon15 Jan 31, 2026
9e0721c
renamed filepath variable
jocon15 Jan 31, 2026
e323c0a
renamed filepath variable
jocon15 Jan 31, 2026
50008d6
removed plural label for singular
jocon15 Jan 31, 2026
9287783
converted grid frame width to constant
jocon15 Jan 31, 2026
1104072
removed comments
jocon15 Jan 31, 2026
89a99ae
removed comment
jocon15 Jan 31, 2026
d8699ac
fixed issue where grid frame size used wrong constant
jocon15 Jan 31, 2026
e4080cc
updated docstring
jocon15 Jan 31, 2026
8c7c3f7
removed comment
jocon15 Jan 31, 2026
1d80cd5
removed comment
jocon15 Jan 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 32 additions & 17 deletions src/StockBench/gui/application.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,37 @@
from PyQt6.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QTabWidget, QLabel
from PyQt6 import QtGui

from StockBench.gui.config.tabs.compare.compare_config_tab import CompareConfigTab
from StockBench.gui.config.tabs.folder.folder_config_tab import FolderConfigTab
from StockBench.gui.config.tabs.singular.singular_config_tab import SingularConfigTab
from StockBench.gui.palette.palette import Palette
from StockBench.gui.config.tabs.singular_config_tab import SingularConfigTab
from StockBench.gui.config.tabs.multi_config_tab import MultiConfigTab
from StockBench.gui.config.tabs.compare_config_tab import CompareConfigTab
from StockBench.gui.config.tabs.folder_config_tab import FolderConfigTab
from StockBench.gui.config.tabs.multi.multi_config_tab import MultiConfigTab
from StockBench.gui.configuration import AppConfiguration
from StockBench.controllers.controller_factory import StockBenchControllerFactory


class ConfigMainWindow(QMainWindow):
WIDTH = 400
HEIGHT = 680
WIDTH = 650
HEIGHT = 600

def __init__(self, splash, config: AppConfiguration):
super().__init__()
self.splash = splash

# get an instance of the controller for the application to use (reference passed to all results windows)
self.__stockbench_controller = StockBenchControllerFactory.get_controller_instance()

# main window styling (do it first to prevent window shifting)
self.setWindowIcon(QtGui.QIcon(Palette.CANDLE_ICON))
# main window styling (done first to prevent window shifting)
self.setWindowIcon(QtGui.QIcon(Palette.CANDLE_ICON_FILEPATH))
self.setWindowTitle('Configuration')
self.setFixedSize(self.WIDTH, self.HEIGHT)
# SEE BOTTOM FOR CONFIG WINDOW SIZING!!!

self.layout = QVBoxLayout()

self.tab_widget = QTabWidget()
self.tab_widget.addTab(SingularConfigTab(self.__stockbench_controller), "Single")
self.tab_widget.addTab(MultiConfigTab(self.__stockbench_controller), "Multi")
self.tab_widget.addTab(CompareConfigTab(self.__stockbench_controller), "Compare")
self.tab_widget.addTab(FolderConfigTab(self.__stockbench_controller), "Folder")
self.tab_widget.addTab(SingularConfigTab(self.__update_geometry, self.__stockbench_controller), "Single")
self.tab_widget.addTab(MultiConfigTab(self.__update_geometry, self.__stockbench_controller), "Multi")
self.tab_widget.addTab(CompareConfigTab(self.__update_geometry, self.__stockbench_controller), "Compare")
self.tab_widget.addTab(FolderConfigTab(self.__update_geometry, self.__stockbench_controller), "Folder")
self.tab_widget.setStyleSheet(Palette.TAB_WIDGET_STYLESHEET)
self.layout.addWidget(self.tab_widget)

Expand All @@ -45,9 +44,25 @@ def __init__(self, splash, config: AppConfiguration):
widget.setStyleSheet(Palette.WINDOW_STYLESHEET)
widget.setLayout(self.layout)

# Set the central widget of the Window. Widget will expand
# to take up all the space in the window by default.
self.setCentralWidget(widget)

# close the splash window
self.splash.close()

# CONFIG WINDOW SIZING
# we want the window to shrink to the size of the widgets but also be non-resizable
# after the tabs are added, the following block shrinks the window to fit of the biggest tab and disables
# resizing
self.__update_geometry()

def __update_geometry(self):
"""Updates the geometry of the main config window while maintaining the in-ability for manual resize.

Use 1: Used to set the initial size of the main config window after tabs are added to the layout.
Use 2: Callback for when the error message label is added to the layout, preventing other widget squishing by
resizing the layout to fit all contained widgets.
"""
self.updateGeometry()
self.adjustSize()
size = self.sizeHint()
self.setMinimumSize(size)
self.setMaximumSize(size)
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ 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()

self.select_folder_btn = QPushButton()
self.select_folder_btn.setText('Select Folder')
self.select_folder_btn.clicked.connect(self.on_select_folder_btn_clicked) # noqa
self.select_folder_btn.setStyleSheet(Palette.SECONDARY_BTN)
self.select_folder_btn.setFixedWidth(90)
self.layout.addWidget(self.select_folder_btn)

self.setLayout(self.layout)
Expand Down
4 changes: 3 additions & 1 deletion src/StockBench/gui/config/components/strategy_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@ 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)

self.select_file_btn = QPushButton()
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)
Expand Down
135 changes: 31 additions & 104 deletions src/StockBench/gui/config/tabs/base/config_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -37,6 +36,9 @@ def wrapper(self, *args, **kwargs):
return original_fxn(self, *args, **kwargs)
except MessageBoxCaptureException as e:
self.error_message_box.setText(str(e))
self.layout.addWidget(self.error_message_box)
# update config window geometry using callback
self.update_geometry()
return wrapper


Expand All @@ -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
Expand All @@ -64,97 +67,20 @@ def __init__(self, stockbench_controller: StockBenchController):
self.results_depth = Simulator.CHARTS_AND_DATA

# ========================= Shared Components ================================
# define the layout
self.layout = QVBoxLayout()

# simulation length label
self.simulation_length_label = QLabel()
self.simulation_length_label.setText('Simulation Length:')
self.simulation_length_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET)
# simulation length input
self.simulation_length_cbox = QComboBox()
self.simulation_length_cbox.addItem('1 Year')
self.simulation_length_cbox.addItem('2 Year')
self.simulation_length_cbox.addItem('5 Year')
# set simulation length default to 1 year (must set attribute as well)
self.simulation_length_cbox.setCurrentIndex(0)
self.simulation_length = SECONDS_1_YEAR
self.simulation_length_cbox.setStyleSheet(Palette.COMBOBOX_STYLESHEET)
self.simulation_length_cbox.currentIndexChanged.connect(self.on_simulation_length_cbox_index_changed) # noqa

# initial balance label
self.initial_balance_label = QLabel()
self.initial_balance_label.setText('Initial Balance:')
self.initial_balance_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET)
# initial balance input
self.initial_balance_tbox = QLineEdit()
self.initial_balance_tbox.setText('1000.0')
self.onlyFloat = QDoubleValidator()
self.initial_balance_tbox.setValidator(self.onlyFloat)
self.initial_balance_tbox.setStyleSheet(Palette.LINE_EDIT_STYLESHEET)

# logging label
self.logging_label = QLabel()
self.logging_label.setText('Logging:')
self.logging_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET)
# logging button
self.logging_btn = QPushButton()
self.logging_btn.setCheckable(True)
self.logging_btn.setText(self.OFF)
self.logging_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET)
self.logging_btn.clicked.connect(self.on_logging_btn_clicked) # noqa

# reporting label
self.reporting_label = QLabel()
self.reporting_label.setText('Reporting:')
self.reporting_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET)
# reporting button
self.reporting_btn = QPushButton()
self.reporting_btn.setCheckable(True)
self.reporting_btn.setText(self.OFF)
self.reporting_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET)
self.reporting_btn.clicked.connect(self.on_reporting_btn_clicked) # noqa

# unique chart saving
self.unique_chart_save_label = QLabel()
self.unique_chart_save_label.setText('Save Unique Charts:')
self.unique_chart_save_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET)

self.unique_chart_save_btn = QPushButton()
self.unique_chart_save_btn.setCheckable(True)
self.unique_chart_save_btn.setText(self.OFF)
self.unique_chart_save_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET)
self.unique_chart_save_btn.clicked.connect(self.on_chart_saving_btn_clicked) # noqa

# results depth label
self.results_depth_label = QLabel()
self.results_depth_label.setText('Results Depth:')
self.results_depth_label.setStyleSheet(Palette.INPUT_LABEL_STYLESHEET)
# results depth radio button
self.data_and_charts_radio_btn = QRadioButton("Data and Charts")
self.data_and_charts_radio_btn.toggled.connect(self.data_and_charts_btn_selected) # noqa
self.data_and_charts_radio_btn.setStyleSheet(Palette.RADIO_BTN_STYLESHEET)
self.data_and_charts_radio_btn.toggle() # set data and charts as default
# results depth radio button
self.data_only_radio_btn = QRadioButton("Data Only")
self.data_only_radio_btn.toggled.connect(self.data_only_btn_selected) # noqa
self.data_only_radio_btn.setStyleSheet(Palette.RADIO_BTN_STYLESHEET)

# run button
self.run_btn = QPushButton()
self.run_btn.setFixedSize(60, 30)
self.run_btn.setText('RUN')
self.run_btn.clicked.connect(self.on_run_btn_clicked) # noqa
self.run_btn.setStyleSheet(Palette.RUN_BTN_STYLESHEET)

# error message box
self.error_message_box = QLabel()
self.error_message_box.setStyleSheet(Palette.ERROR_LABEL_STYLESHEET)

@CaptureConfigErrors
def on_strategy_studio_btn_clicked(self, filepath: str):
"""

Decorator:
The CaptureErrors decorator allows custom exceptions to be caught and logged to the error message box
instead of crashing. It also allows us to functionalize the filepath validation without the need for
Expand All @@ -179,35 +105,41 @@ def on_simulation_length_cbox_index_changed(self, index):
elif index == 2:
self.simulation_length = SECONDS_5_YEAR

def on_logging_btn_clicked(self):
if self.logging_btn.isChecked():
def on_logging_btn_clicked(self, button: QPushButton):
"""Handles logging button toggle. Button reference is passed so we can read/update the button
at this level despite it being buried under layers of QFrames."""
if button.isChecked():
self.simulation_logging = True
self.logging_btn.setText(self.ON)
self.logging_btn.setStyleSheet(Palette.TOGGLE_BTN_ENABLED_STYLESHEET)
button.setText(self.ON)
button.setStyleSheet(Palette.TOGGLE_BTN_ENABLED_STYLESHEET)
else:
self.simulation_logging = False
self.logging_btn.setText(self.OFF)
self.logging_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET)
button.setText(self.OFF)
button.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET)

def on_reporting_btn_clicked(self):
if self.reporting_btn.isChecked():
def on_reporting_btn_clicked(self, button: QPushButton):
"""Handles reporting button toggle. Button reference is passed so we can read/update the button
at this level despite it being buried under layers of QFrames."""
if button.isChecked():
self.simulation_reporting = True
self.reporting_btn.setText(self.ON)
self.reporting_btn.setStyleSheet(Palette.TOGGLE_BTN_ENABLED_STYLESHEET)
button.setText(self.ON)
button.setStyleSheet(Palette.TOGGLE_BTN_ENABLED_STYLESHEET)
else:
self.simulation_reporting = False
self.reporting_btn.setText(self.OFF)
self.reporting_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET)
button.setText(self.OFF)
button.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET)

def on_chart_saving_btn_clicked(self):
if self.unique_chart_save_btn.isChecked():
def on_chart_saving_btn_clicked(self, button: QPushButton):
"""Handles chart saving button toggle. Button reference is passed so we can read/update the button
at this level despite it being buried under layers of QFrames."""
if button.isChecked():
self.simulation_unique_chart_saving = True
self.unique_chart_save_btn.setText(self.ON)
self.unique_chart_save_btn.setStyleSheet(Palette.TOGGLE_BTN_ENABLED_STYLESHEET)
button.setText(self.ON)
button.setStyleSheet(Palette.TOGGLE_BTN_ENABLED_STYLESHEET)
else:
self.simulation_unique_chart_saving = False
self.unique_chart_save_btn.setText(self.OFF)
self.unique_chart_save_btn.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET)
button.setText(self.OFF)
button.setStyleSheet(Palette.TOGGLE_BTN_DISABLED_STYLESHEET)

def data_and_charts_btn_selected(self, selected):
if selected:
Expand All @@ -233,7 +165,6 @@ def load_strategy(self, filepath: str, cache_key=None, cache_value=None):
except Exception as e:
raise MessageBoxCaptureException(f'Uncaught error parsing strategy file: {e}')

# cache the strategy filepath (create if it does not already exist)
self.cache_strategy_filepath(filepath, cache_key, cache_value)

# inject the unix equivalent dates from the combobox to the dict
Expand All @@ -244,23 +175,19 @@ def load_strategy(self, filepath: str, cache_key=None, cache_value=None):
return strategy

def cache_strategy_filepath(self, strategy_filepath, cache_key=None, cache_value=None):
# cache the strategy filepath (create if it does not already exist)

"""Caches the strategy filepath if it does not already exist."""
key = self.DEFAULT_CACHE_KEY
if cache_key:
key = cache_key

# get the existing cache data
with open(CACHE_FILE_FILEPATH, 'r') as file:
data = json.load(file)

# add the filepath to the cache data
if cache_value:
data[key] = cache_value
else:
data[key] = strategy_filepath

# write the cache data
with open(CACHE_FILE_FILEPATH, 'w+') as file:
json.dump(data, file)

Expand Down
Empty file.
Loading