UI improvements
This commit is contained in:
+2
-2
@@ -63,8 +63,8 @@ developer:
|
||||
window:
|
||||
x: 37
|
||||
y: 42
|
||||
width: 1512
|
||||
height: 975
|
||||
width: 1510
|
||||
height: 956
|
||||
style: 541072960
|
||||
settings_window:
|
||||
x: 354
|
||||
|
||||
@@ -337,6 +337,18 @@ class Correlation:
|
||||
|
||||
return history
|
||||
|
||||
def clear_coefficient_history(self):
|
||||
"""
|
||||
Clears the coefficient history for all symbol pairs
|
||||
:return:
|
||||
"""
|
||||
# Create dataframes for coefficient history.
|
||||
coefficient_history_columns = ['Symbol 1', 'Symbol 2', 'Coefficient', 'Timeframe', 'Date From', 'Date To']
|
||||
self.coefficient_history = pd.DataFrame(columns=coefficient_history_columns)
|
||||
|
||||
# Clear tick data
|
||||
self.__monitor_tick_data = {}
|
||||
|
||||
def get_ticks(self, symbol, date_from=None, date_to=None, cache_time=0, cache_only=False):
|
||||
"""
|
||||
Returns the ticks for the specified symbol. Get's from cache if available and not older than cache_timeframe.
|
||||
@@ -539,13 +551,11 @@ class Correlation:
|
||||
'Timeframe', 'Last Check', 'Last Coefficient']
|
||||
self.coefficient_data = pd.DataFrame(columns=coefficient_data_columns)
|
||||
|
||||
# Create dataframes for coefficient history.
|
||||
coefficient_history_columns = ['Symbol 1', 'Symbol 2', 'Coefficient', 'Timeframe', 'Date From', 'Date To']
|
||||
self.coefficient_history = pd.DataFrame(columns=coefficient_history_columns)
|
||||
# Clear coefficient history
|
||||
self.clear_coefficient_history()
|
||||
|
||||
# Clear price data and tick data
|
||||
# Clear price data
|
||||
self.__price_data = None
|
||||
self.__monitor_tick_data = {}
|
||||
|
||||
def __update_coefficient_data(self, symbol1, symbol2, coefficient, timeframe, date_from, date_to):
|
||||
"""
|
||||
|
||||
+119
-70
@@ -4,6 +4,7 @@ import matplotlib.pyplot as plt
|
||||
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
|
||||
import matplotlib.dates
|
||||
import matplotlib
|
||||
import matplotlib.ticker as mticker
|
||||
|
||||
from mt5_correlation.correlation import Correlation
|
||||
from mt5_correlation.config import Config, SettingsDialog
|
||||
@@ -53,55 +54,44 @@ class MonitorFrame(wx.Frame):
|
||||
self.__cor = Correlation()
|
||||
self.__cor.monitoring_threshold = self.__config.get("monitor.monitoring_threshold")
|
||||
|
||||
# Status bar
|
||||
self.statusbar = self.CreateStatusBar(1)
|
||||
# Status bar. 2 fields, one for monitoring status and one for general status. On open, monitoring status is not
|
||||
# monitoring. SetBackgroundColour will change colour of both. Couldn't find a way to set on single field only.
|
||||
self.__statusbar = self.CreateStatusBar(2)
|
||||
self.__statusbar.SetStatusWidths([100, -1])
|
||||
self.SetStatusText("Not Monitoring", 0)
|
||||
|
||||
# Menu Bar and file menu
|
||||
# Menu Bar
|
||||
self.menubar = wx.MenuBar()
|
||||
file_menu = wx.Menu()
|
||||
|
||||
# File menu items
|
||||
# File menu and items
|
||||
file_menu = wx.Menu()
|
||||
menu_item_open = file_menu.Append(wx.ID_ANY, "Open", "Open correlations file.")
|
||||
menu_item_save = file_menu.Append(wx.ID_ANY, "Save", "Save correlations file.")
|
||||
menu_item_saveas = file_menu.Append(wx.ID_ANY, "Save As", "Save correlations file.")
|
||||
file_menu.AppendSeparator()
|
||||
menu_item_calculate = file_menu.Append(wx.ID_ANY, "Calculate", "Calculate base coefficients.")
|
||||
file_menu.AppendSeparator()
|
||||
menu_item_settings = file_menu.Append(wx.ID_ANY, "Settings", "Change application settings.")
|
||||
file_menu.AppendSeparator()
|
||||
menu_item_exit = file_menu.Append(wx.ID_ANY, "Exit", "Close the application")
|
||||
|
||||
# Add file menu and set menu bar
|
||||
self.menubar.Append(file_menu, "File")
|
||||
|
||||
# Coefficient menu and items
|
||||
coef_menu = wx.Menu()
|
||||
menu_item_calculate = coef_menu.Append(wx.ID_ANY, "Calculate", "Calculate base coefficients.")
|
||||
self.__menu_item_monitor = coef_menu.Append(wx.ID_ANY, "Monitor", "Monitor correlated pairs for changes to "
|
||||
"coefficient.", kind=wx.ITEM_CHECK)
|
||||
coef_menu.AppendSeparator()
|
||||
menu_item_clear = coef_menu.Append(wx.ID_ANY, "Clear", "Clear coefficient and price history.")
|
||||
self.menubar.Append(coef_menu, "Coefficient")
|
||||
|
||||
# Set menu bar
|
||||
self.SetMenuBar(self.menubar)
|
||||
|
||||
# Main window. We want 2 horizontal sections, the grid showing correlations and a graph. In the correlations
|
||||
# section, we want 2 vertical sections, the monitor toggle and the correlations grid. For the toggle we want 2
|
||||
# sections, a label and a toggle.
|
||||
# ---------------------------------------------------------------
|
||||
# |label | toggle | |
|
||||
# |-----------------------| |
|
||||
# | | |
|
||||
# |Correlations Grid | Graphs |
|
||||
# | | |
|
||||
# | | |
|
||||
# | | |
|
||||
# | | |
|
||||
# ----------------------------------------------------------------
|
||||
# Main window. We want 2 horizontal sections, the grid showing correlations and a graph.
|
||||
panel = wx.Panel(self, wx.ID_ANY)
|
||||
toggle_sizer = wx.BoxSizer(wx.HORIZONTAL) # Label and toggle
|
||||
correlations_sizer = wx.BoxSizer(wx.VERTICAL) # Toggle sizer and correlations grid
|
||||
correlations_sizer = wx.BoxSizer(wx.VERTICAL) # Correlations grid
|
||||
self.__main_sizer = wx.BoxSizer(wx.HORIZONTAL) # Correlations sizer and graphs panel
|
||||
panel.SetSizer(self.__main_sizer)
|
||||
|
||||
# Create the label and toggle, populate the toggle sizer and add the toggle sizer to the correlations sizer
|
||||
monitor_toggle_label = wx.StaticText(panel, id=wx.ID_ANY, label="Monitoring")
|
||||
toggle_sizer.Add(monitor_toggle_label, 0, wx.ALL, 1)
|
||||
self.monitor_toggle = wx.ToggleButton(panel, wx.ID_ANY, label="Off")
|
||||
self.monitor_toggle.SetBackgroundColour(wx.RED)
|
||||
toggle_sizer.Add(self.monitor_toggle, 0, wx.ALL, 1)
|
||||
correlations_sizer.Add(toggle_sizer, 0, wx.ALL, 1)
|
||||
|
||||
# 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)
|
||||
@@ -140,9 +130,6 @@ class MonitorFrame(wx.Frame):
|
||||
# Set up timer to refresh grid
|
||||
self.timer = wx.Timer(self)
|
||||
|
||||
# Bind monitor button
|
||||
self.monitor_toggle.Bind(wx.EVT_TOGGLEBUTTON, self.monitor)
|
||||
|
||||
# Bind timer
|
||||
self.Bind(wx.EVT_TIMER, self.__timer_event, self.timer)
|
||||
|
||||
@@ -152,6 +139,8 @@ class MonitorFrame(wx.Frame):
|
||||
self.Bind(wx.EVT_MENU, self.save_file_as, menu_item_saveas)
|
||||
self.Bind(wx.EVT_MENU, self.calculate_coefficients, menu_item_calculate)
|
||||
self.Bind(wx.EVT_MENU, self.open_settings, menu_item_settings)
|
||||
self.Bind(wx.EVT_MENU, self.__monitor, self.__menu_item_monitor)
|
||||
self.Bind(wx.EVT_MENU, self.__clear_history, menu_item_clear)
|
||||
self.Bind(wx.EVT_MENU, self.quit, menu_item_exit)
|
||||
|
||||
# Bind row select
|
||||
@@ -170,23 +159,23 @@ class MonitorFrame(wx.Frame):
|
||||
# Load the file chosen by the user.
|
||||
self.__opened_filename = fileDialog.GetPath()
|
||||
|
||||
self.SetStatusText(f"Loading file {self.__opened_filename}.")
|
||||
self.SetStatusText(f"Loading file {self.__opened_filename}.", 1)
|
||||
self.__cor.load(self.__opened_filename)
|
||||
|
||||
# Refresh data in grid
|
||||
self.refresh_grid()
|
||||
|
||||
self.SetStatusText(f"File {self.__opened_filename} loaded.")
|
||||
self.SetStatusText(f"File {self.__opened_filename} loaded.", 1)
|
||||
|
||||
def save_file(self, event):
|
||||
self.SetStatusText(f"Saving file as {self.__opened_filename}")
|
||||
self.SetStatusText(f"Saving file as {self.__opened_filename}", 1)
|
||||
|
||||
if self.__opened_filename is None:
|
||||
self.save_file_as(event)
|
||||
else:
|
||||
self.__cor.save(self.__opened_filename)
|
||||
|
||||
self.SetStatusText(f"File saved as {self.__opened_filename}")
|
||||
self.SetStatusText(f"File saved as {self.__opened_filename}", 1)
|
||||
|
||||
def save_file_as(self, event):
|
||||
with wx.FileDialog(self, "Save Coefficients file", wildcard="cpd (*.cpd)|*.cpd",
|
||||
@@ -195,12 +184,12 @@ class MonitorFrame(wx.Frame):
|
||||
return # the user changed their mind
|
||||
|
||||
# Save the file and price data file, changing opened filename so next save writes to new file
|
||||
self.SetStatusText(f"Saving file as {self.__opened_filename}")
|
||||
self.SetStatusText(f"Saving file as {self.__opened_filename}", 1)
|
||||
|
||||
self.__opened_filename = fileDialog.GetPath()
|
||||
self.__cor.save(self.__opened_filename)
|
||||
|
||||
self.SetStatusText(f"File saved as {self.__opened_filename}")
|
||||
self.SetStatusText(f"File saved as {self.__opened_filename}", 1)
|
||||
|
||||
def calculate_coefficients(self, event):
|
||||
# set time zone to UTC to avoid local offset issues, and get from and to dates (a week ago to today)
|
||||
@@ -209,14 +198,14 @@ class MonitorFrame(wx.Frame):
|
||||
utc_from = utc_to - timedelta(days=self.__config.get('calculate.from.days'))
|
||||
|
||||
# Calculate
|
||||
self.SetStatusText("Calculating coefficients.")
|
||||
self.SetStatusText("Calculating coefficients.", 1)
|
||||
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("")
|
||||
self.SetStatusText("", 1)
|
||||
|
||||
# Show calculated data
|
||||
self.refresh_grid()
|
||||
@@ -270,13 +259,13 @@ class MonitorFrame(wx.Frame):
|
||||
# Update row count
|
||||
self.__rows = cur_rows
|
||||
|
||||
def monitor(self, event):
|
||||
def __monitor(self, event):
|
||||
# Check state of toggle button. If on, then start monitoring, else stop
|
||||
if self.monitor_toggle.GetValue():
|
||||
self.__log.info("Starting monitoring.")
|
||||
self.monitor_toggle.SetBackgroundColour(wx.GREEN)
|
||||
self.monitor_toggle.SetLabelText("On")
|
||||
self.SetStatusText("Monitoring for changes to coefficients.")
|
||||
if self.__menu_item_monitor.IsChecked():
|
||||
self.__log.info("Starting monitoring for changes to coefficients.")
|
||||
self.SetStatusText("Monitoring", 0)
|
||||
self.__statusbar.SetBackgroundColour('green')
|
||||
self.__statusbar.Refresh()
|
||||
|
||||
self.timer.Start(self.__config.get('monitor.interval')*1000)
|
||||
|
||||
@@ -295,9 +284,9 @@ class MonitorFrame(wx.Frame):
|
||||
filename=filename)
|
||||
else:
|
||||
self.__log.info("Stopping monitoring.")
|
||||
self.monitor_toggle.SetBackgroundColour(wx.RED)
|
||||
self.monitor_toggle.SetLabelText("Off")
|
||||
self.SetStatusText("Monitoring stopped.")
|
||||
self.SetStatusText("Not Monitoring", 0)
|
||||
self.__statusbar.SetBackgroundColour('lightgray')
|
||||
self.__statusbar.Refresh()
|
||||
self.timer.Stop()
|
||||
self.__cor.stop_monitor()
|
||||
|
||||
@@ -445,6 +434,20 @@ class MonitorFrame(wx.Frame):
|
||||
if len(self.__selected_correlation) == 2:
|
||||
self.show_graph(symbol1=self.__selected_correlation[0], symbol2=self.__selected_correlation[1])
|
||||
|
||||
def __clear_history(self, event):
|
||||
"""
|
||||
Clears the calculated coefficient history and associated price data
|
||||
:param event:
|
||||
:return:
|
||||
"""
|
||||
# Clear the history
|
||||
self.__cor.clear_coefficient_history()
|
||||
|
||||
# Reload graph if we have a coefficient selected
|
||||
self.__log.info("History cleared. Reloading graph.")
|
||||
if len(self.__selected_correlation) == 2:
|
||||
self.show_graph(symbol1=self.__selected_correlation[0], symbol2=self.__selected_correlation[1])
|
||||
|
||||
|
||||
class DataTable(wx.grid.GridTableBase):
|
||||
"""
|
||||
@@ -545,29 +548,39 @@ class GraphPanel(wx.Panel):
|
||||
:return:
|
||||
"""
|
||||
|
||||
# Check what data we have available
|
||||
price_data_available = prices is not None and len(prices) == 2 and \
|
||||
prices[0] is not None and prices[1] is not None
|
||||
tick_data_available = ticks is not None and len(ticks) == 2 and ticks[0] is not None and ticks[1] is not None
|
||||
history_data_available = history is not None and len(history) > 0
|
||||
symbols_selected = symbols is not None and len(symbols) == 2
|
||||
|
||||
# Get all plots for history. History can contain multiple plots for different timeframes. They will all be
|
||||
# plotted on the same chart.
|
||||
times = []
|
||||
coefficients = []
|
||||
for hist in history:
|
||||
times.append(hist['Date To'])
|
||||
coefficients.append(hist['Coefficient'])
|
||||
if history_data_available:
|
||||
for hist in history:
|
||||
times.append(hist['Date To'])
|
||||
coefficients.append(hist['Coefficient'])
|
||||
|
||||
# Clear. We will need to redraw
|
||||
for ax in self.__axes:
|
||||
ax.clear()
|
||||
|
||||
if symbols is not None and len(symbols) == 2:
|
||||
if symbols_selected:
|
||||
# Axis ranges
|
||||
default_range = [datetime.now() - timedelta(days=1), datetime.now()]
|
||||
price_chart_date_range = default_range # We need a default here if no data is available
|
||||
if prices is not None and len(prices) == 0 and prices[0] is not None and prices[1] is not None:
|
||||
if price_data_available:
|
||||
price_chart_date_range = [min(min(prices[0]['time']), min(prices[1]['time'])),
|
||||
max(max(prices[0]['time']), max(prices[1]['time']))]
|
||||
tick_chart_date_range = default_range # We need a default here if no data is available
|
||||
if ticks is not None and len(ticks) == 0 and ticks[0] is not None and ticks[1] is not None:
|
||||
else:
|
||||
price_chart_date_range = [datetime.now() - timedelta(days=1), datetime.now()]
|
||||
|
||||
if tick_data_available:
|
||||
tick_chart_date_range = [min(min(ticks[0]['time']), min(ticks[1]['time'])),
|
||||
max(max(ticks[0]['time']), max(ticks[1]['time']))]
|
||||
else:
|
||||
tick_chart_date_range = [datetime.now() - timedelta(days=1/48), datetime.now()]
|
||||
|
||||
# Chart config
|
||||
titles = [f"Base Coefficient Price Data for {symbols[0]}", f"Base Coefficient Price Data for {symbols[1]}",
|
||||
@@ -575,15 +588,40 @@ class GraphPanel(wx.Panel):
|
||||
f"Coefficient History for {symbols[0]}:{symbols[1]}"]
|
||||
xlims = [price_chart_date_range, price_chart_date_range, tick_chart_date_range, tick_chart_date_range, None]
|
||||
ylims = [None, None, None, None, [-1, 1]]
|
||||
|
||||
# Axis labels
|
||||
xlabels = [None, None, None, None, None]
|
||||
ylabels = ['Price', 'Price', 'Price', 'Price', 'Coefficient']
|
||||
tick_labels = [[], prices[1]['time'], [], ticks[1]['time'], times[0]]
|
||||
|
||||
# Tick labels and formats
|
||||
tick_labels = [[],
|
||||
prices[1]['time'] if price_data_available else [],
|
||||
[],
|
||||
ticks[1]['time'] if tick_data_available else [],
|
||||
times[0].array if history_data_available else []]
|
||||
mtick_fmts = [None, self.__tick_fmt_date, None, self.__tick_fmt_time, self.__tick_fmt_time]
|
||||
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]
|
||||
|
||||
# Chart data
|
||||
xdata = [prices[0]['time'] if price_data_available else [],
|
||||
prices[1]['time'] if price_data_available else [],
|
||||
ticks[0]['time'] if tick_data_available else [],
|
||||
ticks[1]['time'] if tick_data_available else [],
|
||||
times if history_data_available else []]
|
||||
|
||||
ydata = [prices[0]['close'] if price_data_available else [],
|
||||
prices[1]['close'] if price_data_available else [],
|
||||
ticks[0]['ask'] if tick_data_available else [],
|
||||
ticks[1]['ask'] if tick_data_available else [],
|
||||
coefficients if history_data_available else []]
|
||||
|
||||
# Chart types
|
||||
types = ['plot', 'plot', 'plot', 'plot', 'scatter']
|
||||
|
||||
# Legends
|
||||
legends = [None, None, None, None, [f"{Config().get('monitor.calculate_from.short.minutes')} Minutes",
|
||||
f"{Config().get('monitor.calculate_from.long.minutes')} Minutes"]]
|
||||
|
||||
# Draw 5 charts
|
||||
for index in range(0, len(self.__axes)):
|
||||
# Titles and axis labels
|
||||
@@ -598,12 +636,6 @@ class GraphPanel(wx.Panel):
|
||||
if ylims[index] is not None:
|
||||
self.__axes[index].set_ylim(ylims[index])
|
||||
|
||||
# Tick labels and formats
|
||||
self.__axes[index].xaxis.set_ticklabels(tick_labels[index])
|
||||
if mtick_fmts[index] is not None:
|
||||
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)
|
||||
@@ -618,6 +650,23 @@ class GraphPanel(wx.Panel):
|
||||
elif types[index] == 'scatter':
|
||||
self.__axes[index].scatter(xdata_list[data_index], ydata_list[data_index], s=1)
|
||||
|
||||
# Ticks, labels and formats. Fixing xticks with FixedLocator but also using MaxNLocator to avoid
|
||||
# cramped x-labels
|
||||
if len(tick_labels[index]) > 0:
|
||||
self.__axes[index].xaxis.set_major_locator(mticker.MaxNLocator(10))
|
||||
ticks_loc = self.__axes[index].get_xticks().tolist()
|
||||
self.__axes[index].xaxis.set_major_locator(mticker.FixedLocator(ticks_loc))
|
||||
self.__axes[index].set_xticklabels(ticks_loc)
|
||||
if mtick_fmts[index] is not None:
|
||||
self.__axes[index].xaxis.set_major_formatter(mtick_fmts[index])
|
||||
plt.setp(self.__axes[index].xaxis.get_majorticklabels(), rotation=mtick_rot[index])
|
||||
else:
|
||||
self.__axes[index].set_xticklabels([])
|
||||
|
||||
# Legends
|
||||
if legends[index] is not None:
|
||||
self.__axes[index].legend(legends[index])
|
||||
|
||||
# Layout with padding between charts
|
||||
self.__fig.tight_layout(pad=0.5)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user