Layout and save window pos

This commit is contained in:
Jamie Cash
2021-03-03 13:47:19 +00:00
parent c85ff8f79e
commit 81f5fa6c07
4 changed files with 125 additions and 70 deletions
+18 -8
View File
@@ -95,6 +95,9 @@ class SettingsDialog(wx.Dialog):
box for every value.
"""
# Settings
__settings = None # Will set in init.
# Store any settings that have changed
changed_settings = {}
@@ -170,14 +173,19 @@ class SettingsDialog(wx.Dialog):
orig_value = self.__settings.get(setting)
new_value = self.changed_settings[setting]
# 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. Note boolean needs to be handled differently as it
# doesn't cast directly.
if isinstance(orig_value, bool):
new_value = new_value.lower() in ['true', '1', 'yes', 't']
else:
new_value = type(orig_value)(new_value)
# 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
@@ -378,12 +386,12 @@ class SettingsValuePanel(wx.ScrolledWindow):
# Store the parent frame and get the settings for this node.
self.__parent_frame = parent_frame
settings = Config().get(node)
self.__settings = Config().get(node)
leaf_settings = {}
for setting in settings:
if type(settings[setting]) is not dict:
leaf_settings[setting] = settings[setting]
for setting in self.__settings:
if type(self.__settings[setting]) is not dict:
leaf_settings[setting] = self.__settings[setting]
# Add the value sizer for settings values.
self.__value_sizer = wx.FlexGridSizer(rows=len(leaf_settings), cols=2, vgap=2, hgap=2)
@@ -424,7 +432,9 @@ class SettingsValuePanel(wx.ScrolledWindow):
"""
def on_value_changed(event):
old_val = self.__settings.get(setting_path)
self.__parent_frame.changed_settings[setting_path] = event.String
self.__log.debug(f"Value changed for {setting_path}.")
self.__log.debug(f"Value changed from {old_val} to {self.__parent_frame.changed_settings[setting_path]} "
f"for {setting_path}.")
return on_value_changed
+14 -4
View File
@@ -8,6 +8,8 @@ import threading
import pytz
from scipy.stats.stats import pearsonr
import pickle
import inspect
import sys
from mt5_correlation.mt5 import MT5
@@ -28,6 +30,9 @@ class Correlation:
__monitoring = False
__monitoring_params = {}
# First run of scheduler
__first_run = True
# The price data used to calculate the correlations
__price_data = None
@@ -391,7 +396,14 @@ class Correlation:
'max_p_value': max_p_value, "cache_time": cache_time, 'autosave': autosave,
'filename': filename}
self.__scheduler.enter(delay=interval, priority=1, action=self.__monitor, kwargs=params)
self.__scheduler.run()
# Log the stack. Debug stack overflow
self.__log.debug(f"Current stack size: {len(inspect.stack())} Recursion limit: {sys.getrecursionlimit()}")
# Run
if self.__first_run:
self.__first_run = False
self.__scheduler.run()
def __update_coefficient(self, symbol1, symbol2, from_mins, min_prices=100, max_set_size_diff_pct=90,
overlap_pct=90, max_p_value=0.05, cache_time=10):
@@ -460,7 +472,7 @@ class Correlation:
return coefficient
def __update_all_coefficients(self, from_mins, min_prices=100, max_set_size_diff_pct=90, overlap_pct=90,
max_p_value=0.05, cache_time=10, autosave=False):
max_p_value=0.05, cache_time=10):
"""
Updates the coefficient for all symbol pairs in that meet the min_coefficient threshold. Symbol pairs that meet
the threshold can be accessed through the filtered_coefficient_data property.
@@ -474,8 +486,6 @@ class Correlation:
:param max_p_value: The maximum p value for the correlation to be meaningful
:param cache_time: Tick data is cached so that we can check coefficients for multiple symbol pairs and reuse
the tick data. Number of seconds to cache tick data for before it becomes stale.
:param autosave: Whether to autosave after every monitor run. If there is no filename set then will create one
named autosave.cpd
:return: correlation coefficient, or None if coefficient could not be calculated.
"""
+86 -57
View File
@@ -18,10 +18,10 @@ matplotlib.use('WXAgg')
class MonitorFrame(wx.Frame):
cor = None
rows = 0 # Need to track as we need to notify grid if row count changes.
__cor = None
__rows = 0 # Need to track as we need to notify grid if row count changes.
__opened_filename = None # So we can save to same file as we opened
config = None # The applications config
__config = None # The applications config
__selected_correlation = [] # List of Symbol 1 & Symbol 2
@@ -38,15 +38,20 @@ class MonitorFrame(wx.Frame):
def __init__(self):
# Super
wx.Frame.__init__(self, parent=None, id=wx.ID_ANY, title="Divergence Monitor")
wx.Frame.__init__(self, parent=None, id=wx.ID_ANY, title="Divergence Monitor",
pos=wx.Point(x=Config().get('window.x'),
y=Config().get('window.y')),
size=wx.Size(width=Config().get('window.width'),
height=Config().get('window.height')),
style=Config().get('window.style'))
# Create logger and get config
self.__log = logging.getLogger(__name__)
self.config = Config()
self.__config = Config()
# Create correlation instance to maintain state of calculated coefficients. Set min coefficient from config
self.cor = Correlation()
self.cor.monitoring_threshold = self.config.get("monitor.monitoring_threshold")
self.__cor = Correlation()
self.__cor.monitoring_threshold = self.__config.get("monitor.monitoring_threshold")
# Status bar
self.statusbar = self.CreateStatusBar(1)
@@ -99,7 +104,7 @@ class MonitorFrame(wx.Frame):
# Create the correlations grid. This is a data table using pandas dataframe for underlying data. Add the
# correlations_grid to the correlations sizer.
self.table = DataTable(self.cor.filtered_coefficient_data)
self.table = DataTable(self.__cor.filtered_coefficient_data)
self.grid_correlations = wx.grid.Grid(panel, wx.ID_ANY)
self.grid_correlations.SetTable(self.table, takeOwnership=True)
self.grid_correlations.EnableEditing(False)
@@ -129,8 +134,7 @@ class MonitorFrame(wx.Frame):
self.__main_sizer.Add(correlations_sizer, 0, wx.ALL | wx.EXPAND, 1)
self.__main_sizer.Add(self.__graph, 1, wx.ALL | wx.EXPAND, 1)
# Size the window.
self.SetSize((800, 500))
# Layout the window.
self.Layout()
# Set up timer to refresh grid
@@ -167,7 +171,7 @@ class MonitorFrame(wx.Frame):
self.__opened_filename = fileDialog.GetPath()
self.SetStatusText(f"Loading file {self.__opened_filename}.")
self.cor.load(self.__opened_filename)
self.__cor.load(self.__opened_filename)
# Refresh data in grid
self.refresh_grid()
@@ -180,7 +184,7 @@ class MonitorFrame(wx.Frame):
if self.__opened_filename is None:
self.save_file_as(event)
else:
self.cor.save(self.__opened_filename)
self.__cor.save(self.__opened_filename)
self.SetStatusText(f"File saved as {self.__opened_filename}")
@@ -194,7 +198,7 @@ class MonitorFrame(wx.Frame):
self.SetStatusText(f"Saving file as {self.__opened_filename}")
self.__opened_filename = fileDialog.GetPath()
self.cor.save(self.__opened_filename)
self.__cor.save(self.__opened_filename)
self.SetStatusText(f"File saved as {self.__opened_filename}")
@@ -202,22 +206,23 @@ class MonitorFrame(wx.Frame):
# set time zone to UTC to avoid local offset issues, and get from and to dates (a week ago to today)
timezone = pytz.timezone("Etc/UTC")
utc_to = datetime.now(tz=timezone)
utc_from = utc_to - timedelta(days=self.config.get('calculate.from.days'))
utc_from = utc_to - timedelta(days=self.__config.get('calculate.from.days'))
# Calculate
self.SetStatusText("Calculating coefficients.")
self.cor.calculate(date_from=utc_from, date_to=utc_to,
timeframe=self.config.get('calculate.timeframe'),
min_prices=self.config.get('calculate.min_prices'),
max_set_size_diff_pct=self.config.get('calculate.max_set_size_diff_pct'),
overlap_pct=self.config.get('calculate.overlap_pct'),
max_p_value=self.config.get('calculate.max_p_value'))
self.__cor.calculate(date_from=utc_from, date_to=utc_to,
timeframe=self.__config.get('calculate.timeframe'),
min_prices=self.__config.get('calculate.min_prices'),
max_set_size_diff_pct=self.__config.get('calculate.max_set_size_diff_pct'),
overlap_pct=self.__config.get('calculate.overlap_pct'),
max_p_value=self.__config.get('calculate.max_p_value'))
self.SetStatusText("")
# Show calculated data
self.refresh_grid()
def quit(self, event):
# Close
self.Close()
def refresh_grid(self):
@@ -228,7 +233,7 @@ class MonitorFrame(wx.Frame):
self.__log.debug(f"Refreshing grid. Timer running: {self.timer.IsRunning()}")
# Update data
self.table.data = self.cor.coefficient_data.copy()
self.table.data = self.__cor.coefficient_data.copy()
# Format
self.table.data.loc[:, 'Base Coefficient'] = self.table.data['Base Coefficient'].map('{:.5f}'.format)
@@ -244,16 +249,16 @@ class MonitorFrame(wx.Frame):
self.grid_correlations.BeginBatch()
# Check if num rows in dataframe has changed, and send appropriate APPEND or DELETE messages
cur_rows = len(self.cor.filtered_coefficient_data.index)
if cur_rows < self.rows:
cur_rows = len(self.__cor.filtered_coefficient_data.index)
if cur_rows < self.__rows:
# Data has been deleted. Send message
msg = wx.grid.GridTableMessage(self.table, wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED,
self.rows - cur_rows, self.rows - cur_rows)
self.__rows - cur_rows, self.__rows - cur_rows)
self.grid_correlations.ProcessTableMessage(msg)
elif cur_rows > self.rows:
elif cur_rows > self.__rows:
# Data has been added. Send message
msg = wx.grid.GridTableMessage(self.table, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED,
cur_rows - self.rows) # how many
cur_rows - self.__rows) # how many
self.grid_correlations.ProcessTableMessage(msg)
self.grid_correlations.EndBatch()
@@ -263,7 +268,7 @@ class MonitorFrame(wx.Frame):
self.grid_correlations.ProcessTableMessage(msg)
# Update row count
self.rows = cur_rows
self.__rows = cur_rows
def monitor(self, event):
# Check state of toggle button. If on, then start monitoring, else stop
@@ -273,27 +278,27 @@ class MonitorFrame(wx.Frame):
self.monitor_toggle.SetLabelText("On")
self.SetStatusText("Monitoring for changes to coefficients.")
self.timer.Start(self.config.get('monitor.interval')*1000)
self.timer.Start(self.__config.get('monitor.interval')*1000)
# Autosave filename
filename = self.__opened_filename if self.__opened_filename is not None else 'autosave.cpd'
self.cor.start_monitor(interval=self.config.get('monitor.interval'),
from_mins=self.config.get('monitor.from.minutes'),
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'),
cache_time=self.config.get('monitor.tick_cache_time'),
autosave=self.config.get('monitor.autosave'),
filename=filename)
self.__cor.start_monitor(interval=self.__config.get('monitor.interval'),
from_mins=self.__config.get('monitor.from.minutes'),
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'),
cache_time=self.__config.get('monitor.tick_cache_time'),
autosave=self.__config.get('monitor.autosave'),
filename=filename)
else:
self.__log.info("Stopping monitoring.")
self.monitor_toggle.SetBackgroundColour(wx.RED)
self.monitor_toggle.SetLabelText("Off")
self.SetStatusText("Monitoring stopped.")
self.timer.Stop()
self.cor.stop_monitor()
self.__cor.stop_monitor()
def open_settings(self, event):
"""
@@ -327,25 +332,25 @@ class MonitorFrame(wx.Frame):
# Now perform the actions
if restart_monitor_timer:
self.__log.info("Settings updated. Reloading monitoring timer.")
self.cor.stop_monitor()
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'),
from_mins=self.config.get('monitor.from.minutes'),
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'))
utc_from = utc_to - timedelta(minutes=self.__config.get('monitor.from.minutes'))
self.__cor.start_monitor(interval=self.__config.get('monitor.interval'),
from_mins=self.__config.get('monitor.from.minutes'),
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.info("Settings updated. Restarting gui timer.")
self.timer.Stop()
self.timer.Start(self.config.get('monitor.interval') * 1000)
self.timer.Start(self.__config.get('monitor.interval') * 1000)
if reload_correlations:
self.__log.info("Settings updated. Updating monitoring threshold and reloading grid.")
self.cor.monitoring_threshold = self.config.get("monitor.monitoring_threshold")
self.__cor.monitoring_threshold = self.__config.get("monitor.monitoring_threshold")
self.refresh_grid()
if reload_logger:
@@ -359,10 +364,22 @@ class MonitorFrame(wx.Frame):
:param event:
:return:
"""
if self.__opened_filename is not None:
self.cor.save(self.__opened_filename)
# Save pos and size
x, y = self.GetPosition()
width, height = self.GetSize()
self.__config.set('window.x', x)
self.__config.set('window.y', y)
self.__config.set('window.width', width)
self.__config.set('window.height', height)
self.cor.stop_monitor()
# Style
style = self.GetWindowStyle()
self.__config.set('window.style', style)
self.__config.save()
# Stop monitoring
self.__cor.stop_monitor()
# Kill graph as it seems to be stopping script from ending
self.__graph = None
@@ -393,11 +410,11 @@ class MonitorFrame(wx.Frame):
"""
# Get the price data for the base coefficient calculation, tick data to calculate last coefficient and and the
# coefficient history data
symbol_1_price_data = self.cor.get_price_data(symbol1)
symbol_2_price_data = self.cor.get_price_data(symbol2)
symbol_1_ticks = self.cor.get_ticks(symbol1, cache_only=True)
symbol_2_ticks = self.cor.get_ticks(symbol2, cache_only=True)
history_data = self.cor.get_coefficient_history(symbol1, symbol2)
symbol_1_price_data = self.__cor.get_price_data(symbol1)
symbol_2_price_data = self.__cor.get_price_data(symbol2)
symbol_1_ticks = self.__cor.get_ticks(symbol1, cache_only=True)
symbol_2_ticks = self.__cor.get_ticks(symbol2, cache_only=True)
history_data = self.__cor.get_coefficient_history(symbol1, symbol2)
times = history_data['UTC Date To']
coefficients = history_data['Coefficient']
@@ -479,6 +496,10 @@ class DataTable(wx.grid.GridTableBase):
class GraphPanel(wx.Panel):
def __init__(self, parent):
"""
A panel to show the graphs
:param parent: The parent panel
"""
# Super
wx.Panel.__init__(self, parent)
@@ -540,6 +561,7 @@ class GraphPanel(wx.Panel):
mtick_rot = [0, 45, 0, 45, 45]
xdata = [prices[0]['time'], prices[1]['time'], ticks[0]['time'], ticks[1]['time'], times]
ydata = [prices[0]['close'], prices[1]['close'], ticks[0]['ask'], ticks[1]['ask'], coefficients]
types = ['plot', 'plot', 'plot', 'plot', 'scatter']
# Draw 5 charts
for index in range(0, len(self.__axes)):
@@ -561,8 +583,15 @@ class GraphPanel(wx.Panel):
self.__axes[index].xaxis.set_major_formatter(mtick_fmts[index])
plt.setp(self.__axes[index].xaxis.get_majorticklabels(), rotation=mtick_rot[index])
# Remove typ and right boarders
self.__axes[index].spines["top"].set_visible(False)
self.__axes[index].spines["right"].set_visible(False)
# Plot
self.__axes[index].plot(xdata[index], ydata[index])
if types[index] == 'plot':
self.__axes[index].plot(xdata[index], ydata[index])
elif types[index] == 'scatter':
self.__axes[index].scatter(xdata[index], ydata[index], s=1)
# Layout with padding between charts
self.__fig.tight_layout(pad=0.5)