Split notebook page (tab) out as seperate class from Settings Dialog.

This commit is contained in:
Jamie Cash
2021-02-17 18:24:48 +00:00
parent 2349a362e8
commit 862a55d649
5 changed files with 497 additions and 386 deletions
+50 -231
View File
@@ -1,7 +1,7 @@
import wx
import wx.grid
from mt5_correlation.correlation import Correlation
from mt5_correlation.config import Config
from mt5_correlation.config import Config, SettingsDialog
from datetime import datetime, timedelta
import pytz
import pandas as pd
@@ -180,9 +180,6 @@ class MonitorFrame(wx.Frame):
utc_to = datetime.now(tz=timezone)
utc_from = utc_to - timedelta(days=self.config.get('calculate.from.days'))
# Set timeframe
timeframe = self.config.get('calculate.timeframe')
# Calculate
self.SetStatusText("Calculating coefficients.")
self.cor.calculate(date_from=utc_from, date_to=utc_to,
@@ -252,12 +249,12 @@ class MonitorFrame(wx.Frame):
self.monitor_toggle.SetLabelText("On")
self.SetStatusText("Monitoring for changes to coefficients.")
# Calculate correlations fro last 10 mins
# From and to dates for calculations.
timezone = pytz.timezone("Etc/UTC")
utc_to = datetime.now(tz=timezone)
utc_from = utc_to - timedelta(minutes=self.config.get('monitor.from.minutes'))
self.timer.Start(10000)
self.timer.Start(self.config.get('monitor.interval')*1000)
self.cor.start_monitor(interval=self.config.get('monitor.interval'), date_from=utc_from, date_to=utc_to,
min_prices=self.config.get('monitor.min_prices'),
max_set_size_diff_pct=self.config.get('monitor.max_set_size_diff_pct'),
@@ -282,10 +279,53 @@ class MonitorFrame(wx.Frame):
# Reload relevant parts of app
# TODO: Reload relevant parts of app once settings have changed. Stop and restart monitoring,
# reload logger, filter data, refresh data window.
self.log.debug("Settings updated. Reloading logger.")
log_config = Config().get('logging')
logging.config.dictConfig(log_config)
settings_dialog.Destroy()
restart_monitor_timer = False
restart_gui_timer = False
reload_correlations = False
reload_logger = False
for setting in settings_dialog.changed_settings:
# If any 'monitor.' settings except 'monitor.divergence_threshold have changed then restart
# monitoring timer with new settings.
# If 'monitor.interval has changed then restart gui timer.
# If 'monitor.monitoring_threshold' has changed, then refresh correlation data.
# If any 'logging.' settings have changed, then reload logger config.
if setting.startswith('monitor. ') and setting != 'monitor.divergence_threshold':
restart_monitor_timer = True
if setting == 'monitor.interval':
restart_gui_timer = True
if setting == 'monitor.monitoring_threshold':
reload_correlations = True
if setting.startswith('logging.'):
reload_logger = True
# Now perform the actions
if restart_monitor_timer:
self.log.debug("Settings updated. Reloading monitoring timer.")
self.cor.stop_monitor()
# From and to dates for calculations.
timezone = pytz.timezone("Etc/UTC")
utc_to = datetime.now(tz=timezone)
utc_from = utc_to - timedelta(minutes=self.config.get('monitor.from.minutes'))
self.cor.start_monitor(interval=self.config.get('monitor.interval'), date_from=utc_from, date_to=utc_to,
min_prices=self.config.get('monitor.min_prices'),
max_set_size_diff_pct=self.config.get('monitor.max_set_size_diff_pct'),
overlap_pct=self.config.get('monitor.overlap_pct'),
max_p_value=self.config.get('monitor.max_p_value'))
if restart_gui_timer:
self.log.debug("Settings updated. Restarting gui timer.")
self.timer.Stop()
self.timer.Start(self.config.get('monitor.interval') * 1000)
if reload_correlations:
self.log.debug("Settings updated. Updating monitoring threshold and reloading grid.")
self.cor.monitoring_threshold = self.config.get("monitor.monitoring_threshold")
self.refresh_grid(event)
if reload_logger:
self.log.debug("Settings updated. Reloading logger.")
log_config = Config().get('logging')
logging.config.dictConfig(log_config)
def on_close(self, event):
"""
@@ -355,224 +395,3 @@ class DataTable(wx.grid.GridTableBase):
attr.SetBackgroundColour(wx.WHITE)
return attr
class SettingsDialog(wx.Dialog):
# Store any settings that have changed
changed_settings = {}
def __init__(self, *args, **kwargs):
# Super Constructor
wx.Dialog.__init__(self, *args, **kwargs)
self.SetTitle("Settings")
# Create logger and get config
self.log = logging.getLogger(__name__)
# Get settings
self.__settings = Config()
# Dict of changes. Will commit only on ok
self.__changes = {}
# We want 2 vertical sections, the tabbed notebook and the buttons. The buttons sizer will have 2 horizontal
# sections, one for each button.
# -------------------------
# |Tabbed notebook |
# | |
# | |
# | |
# | |
# |-----------------------|
# |ok | cancel |
# -------------------------
main_sizer = wx.BoxSizer(wx.VERTICAL) # Notebook panel
button_sizer = wx.BoxSizer(wx.HORIZONTAL) # Button sizer
# Notebook
self.__notebook = wx.Notebook(self, wx.ID_ANY) # The notebook
# A tab for each root node in config. We will store the tabs components in lists which can be accessed by the
# index returned from notebook.GetSelectedItem()
root_nodes = self.__settings.get_root_nodes()
self.__tabs = []
self.__trees = []
self.__tab_sizers = []
self.__value_sizers = []
self.__value_boxes = [] # We need to store these as they will all be bound to a change event
# self.roots = []
for node in root_nodes:
# Create new tab
self.__tabs.append(wx.Panel(self.__notebook, wx.ID_ANY))
self.__tab_sizers.append(wx.BoxSizer(wx.HORIZONTAL))
self.__tabs[-1].SetSizer(self.__tab_sizers[-1])
# Get settings items for tab / node
node_settings = self.__settings.get(node)
# Create tree control
self.__trees.append(wx.TreeCtrl(self.__tabs[-1], wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize))
# Add root to tree and use item data to store settings path
root = self.__trees[-1].AddRoot(node)
self.__trees[-1].SetItemData(root, node)
# Add items to root
self.__trees[-1] = self.__build_tree(self.__trees[-1], root, node_settings)
# expand tree and add it to the sizer
self.__trees[-1].Expand(root)
self.__tab_sizers[-1].Add(self.__trees[-1], 1, wx.ALL | wx.EXPAND, 1)
# Add the value sizer for settings values. This is only used for spacing, as it will be overwritten when
# tree items are selected.
self.__value_sizers.append(wx.FlexGridSizer(rows=1, cols=2, vgap=2, hgap=2))
self.__tab_sizers[-1].Add(self.__value_sizers[-1], 1, wx.ALL | wx.EXPAND, 1)
label = wx.StaticText(self.__tabs[-1], wx.ID_ANY, " ".ljust(50), style=wx.ALIGN_LEFT)
self.__value_sizers[-1].Add(label)
# Add tab to notebook
self.__notebook.AddPage(self.__tabs[-1], node)
# Buttons
button_ok = wx.Button(self, label="Update")
button_cancel = wx.Button(self, label="Cancel")
button_sizer.Add(button_ok, 0, wx.ALL, 1)
button_sizer.Add(button_cancel, 0, wx.ALL, 1)
# Add notebook and button sizer to main sizer and set main sizer for window
main_sizer.Add(self.__notebook, 1, wx.ALL | wx.EXPAND, 5)
main_sizer.Add(button_sizer)
self.SetSizer(main_sizer)
# Bind buttons, notebook page select and tree control select item
button_ok.Bind(wx.EVT_BUTTON, self.__on_ok)
button_cancel.Bind(wx.EVT_BUTTON, self.__on_cancel)
self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.__on_page_select)
self.Bind(wx.EVT_TREE_SEL_CHANGED, self.__on_tree_select)
# Call on_page_select to select the first page
self.__on_page_select(event=None)
def __build_tree(self, tree, node, settings):
"""
Recursive function to build the tree from the node, using the settings
:param tree: The tree to build
:param node: The tree view node
:param settings: The settings dict for the node.
:return: The built tree
"""
for setting in settings:
# Get value. If dict, add the node and recursively call this function again.
value = settings[setting]
if type(value) is dict:
# Add the node and set its settings path
node_id = tree.AppendItem(node, setting)
current_settings_path = tree.GetItemData(node)
tree.SetItemData(node_id, f"{current_settings_path}.{setting}")
# Recurse
tree = self.__build_tree(tree, node_id, value)
return tree
def __populate_settings_values(self, setting_path, index):
"""
Populates the settings in the value sizer for a settings path.
:param setting_path:
:param index: The tab index containing the value_sizer to populate
:return:
"""
# Get the setting values
settings = self.__settings.get(setting_path)
# Get the value sizer, clear it and set its rows
value_sizer = self.__value_sizers[index]
value_sizer.Clear(True)
value_sizer.SetRows(len(settings))
# Display every value that is a leaf (not dict)
for setting in settings:
value = settings[setting]
if type(value) is not dict:
# Add a label and value text box
label = wx.StaticText(self.__tabs[index], wx.ID_ANY, setting, style=wx.ALIGN_LEFT)
self.__value_boxes.append(wx.TextCtrl(self.__tabs[index], wx.ID_ANY, f"{value}", style=wx.ALIGN_LEFT))
value_sizer.AddMany([(label, 0, wx.EXPAND), (self.__value_boxes[-1], 0, wx.EXPAND)])
# Bind to text change. We need to generate a handler as this will have a parameter.
self.__value_boxes[-1].Bind(wx.EVT_TEXT,
self.__get_on_change_evt_handler(setting_path=f'{setting_path}.{setting}'))
value_sizer.Layout()
def __on_page_select(self, event):
# Populate value sizer for the selected item, If no item is selected, populate for root.
index = self.__notebook.GetSelection()
selected_item = self.__trees[index].GetSelection()
if selected_item.ID is None:
root_node = self.__trees[index].GetRootItem()
setting_path = self.__trees[index].GetItemData(root_node)
else:
setting_path = self.__trees[index].GetItemData(selected_item)
self.__populate_settings_values(setting_path, index)
def __on_tree_select(self, event):
# Get Selected item and check that it is a tree item
tree_item = event.GetItem()
if not tree_item.IsOk():
return
# Get the index of the current tab
index = self.__notebook.GetSelection()
# Get the setting path from item data
setting_path = self.__trees[index].GetItemData(tree_item)
# Populate value_sizer
self.__populate_settings_values(setting_path, index)
def __on_cancel(self, e):
# Clear changed settings and close
self.changed_settings = {}
self.EndModal(wx.ID_CANCEL)
self.Destroy()
def __on_ok(self, e):
# Update settings and save
delkeys = []
for setting in self.changed_settings:
# Get the current and new setting
orig_value = self.__settings.get(setting)
new_value = self.changed_settings[setting]
# If they are the same, discard from changes. We will use a list of items to delete (delkeys) as we cant
# delete whilst iterating. If they are different, update settings.
if orig_value == new_value:
delkeys.append(setting)
else:
# We need to retain data type. New values will all be string as they were retrieved from textctl.
# Get the data type of the original and cast new to it.
new_value = type(orig_value)(new_value)
self.__settings.set(setting, new_value)
# Now delete the items that were the same from changed_settings. changed_settings may be used by settings
# dialog caller.
for key in delkeys:
del(self.changed_settings[key])
# Save the settings and close dialog
self.__settings.save()
self.EndModal(wx.ID_OK)
self.Destroy()
def __get_on_change_evt_handler(self, setting_path):
def on_value_changed(event):
self.changed_settings[setting_path] = event.String
self.log.debug(f"Value changed for {setting_path}.")
return on_value_changed