Visual improvements to diverged symbols chart and added log file and help file windows.

This commit is contained in:
Jamie Cash
2021-06-02 16:03:30 +01:00
parent 66cdf47406
commit 1656c6a4d2
6 changed files with 185 additions and 59 deletions
+3 -1
View File
@@ -1,3 +1,5 @@
import os
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
HELP_FILE = fr"{ROOT_DIR}\README.md"
LOG_FILE = fr"{ROOT_DIR}\debug.log"
+25 -8
View File
@@ -1,15 +1,14 @@
import abc
import importlib
import logging
import sys
import pytz
import wx
import wx.lib.inspection as ins
import wxconfig as cfg
from datetime import datetime, timedelta
import mt5_correlation.gui as gui
from mt5_correlation import correlation as cor
@@ -84,6 +83,14 @@ class CorrelationMDIFrame(wx.MDIParentFrame):
"View diverged symbols."))
self.menubar.Append(menu_view, "&View")
# Help menu and items
help_menu = wx.Menu()
self.Bind(wx.EVT_MENU, self.__on_view_log, help_menu.Append(wx.ID_ANY, "View Log", "Show application log."))
self.Bind(wx.EVT_MENU, self.__on_view_help, help_menu.Append(wx.ID_ANY, "Help",
"Show application usage instructions."))
self.menubar.Append(help_menu, "&Help")
# Set menu bar
self.SetMenuBar(self.menubar)
@@ -262,6 +269,16 @@ class CorrelationMDIFrame(wx.MDIParentFrame):
frame_class='MDIChildDivergedSymbols',
raise_if_open=True)
def __on_view_log(self, evt):
FrameManager.open_frame(parent=self, frame_module='mt5_correlation.gui.mdi_child_log',
frame_class='MDIChildLog',
raise_if_open=True)
def __on_view_help(self, evt):
FrameManager.open_frame(parent=self, frame_module='mt5_correlation.gui.mdi_child_help',
frame_class='MDIChildHelp',
raise_if_open=True)
def __refresh(self):
"""
Refresh all open child frames
@@ -272,11 +289,12 @@ class CorrelationMDIFrame(wx.MDIParentFrame):
for child in children:
if isinstance(child, CorrelationMDIChild):
child.refresh()
elif isinstance(child, wx.StatusBar):
elif isinstance(child, wx.StatusBar) or isinstance(child, ins.InspectionFrame):
# Ignore
pass
else:
raise Exception(f"MDI Child for application must implement CorrelationMDIChild.")
raise Exception(f"MDI Child for application must implement CorrelationMDIChild. MDI Child is "
f"{type(child)}.")
class CorrelationMDIChild(wx.MDIChildFrame):
@@ -305,9 +323,9 @@ class FrameManager:
:param frame_module: A string specifying the module containing the frame class to open or raise
:param frame_class: A string specifying the frame class to open or raise
:param raise_if_open: Whether the frame should raise rather than open if an instance is already open.
:param kwargs: A dict of parameters to pass to frame constructor. These will also be checked in raise_if_open to
determine uniqueness (i.e. If a frame of the same class is already open but its params are different, then
the frame will be opened again with the new params instead of being raised.)
:param kwargs: A dict of parameters to pass to frame constructor. These will also be checked in raise_if_open
to determine uniqueness (i.e. If a frame of the same class is already open but its params are different,
then the frame will be opened again with the new params instead of being raised.)
:return:
"""
@@ -337,4 +355,3 @@ class FrameManager:
clazz(parent=parent, **kwargs).Show(True)
else:
opened_instance.Raise()
+72 -50
View File
@@ -3,7 +3,6 @@ import matplotlib.dates
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import wx
import wxconfig as cfg
import wx.lib.scrolledpanel as scrolled
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
@@ -23,10 +22,12 @@ class MDIChildDivergedGraph(mdi.CorrelationMDIChild):
__tick_fmt_date = matplotlib.dates.DateFormatter('%d-%b')
__tick_fmt_time = matplotlib.dates.DateFormatter('%H:%M:%S')
# Fig, axes and canvas
__fig = None
__axs = None
# Panel
__panel = None
# Graph Canvas and Figure
__canvas = None
__fig = None
# Colors for graph lines
__colours = ['red', 'green', 'blue', 'pink', 'purple', 'black', 'yellow', 'lightblue']
@@ -42,40 +43,18 @@ class MDIChildDivergedGraph(mdi.CorrelationMDIChild):
# Store the symbol
self.symbol = kwargs['symbol']
# We will freeze this frame and thaw once constructed to avoid flicker.
self.Freeze()
# Draw the empty graph. We will populate with data in refresh.
# Create fig and 1 axes.
self.__fig, self.__axs = plt.subplots(1)
# Set title
self.__axs.set_title(f"Price Data for {self.symbol} vs Previously Correlated Symbols")
# Set Y Labels and tick colours for symbol. This will be set for other symbols on plot
for i in range(0, 2):
self.__axs.set_ylabel(f"{self.symbol}", color=self.__colours[0], labelpad=10)
self.__axs.tick_params(axis='y', labelcolor=self.__colours[0])
# Hack to stop xaxis dropping outside of window
self.__axs.set_xlabel(" ", labelpad=10)
# Layout with padding between charts
self.__fig.tight_layout(pad=0.5)
# Create panel and sizer. This will provide scrollbar
panel = scrolled.ScrolledPanel(self, wx.ID_ANY)
# Create panel and sizer
self.__panel = scrolled.ScrolledPanel(self, wx.ID_ANY)
sizer = wx.BoxSizer()
panel.SetSizer(sizer)
self.__panel.SetSizer(sizer)
# Add fig to canvas and canvas to sizer. Thaw window to update
self.__canvas = FigureCanvas(panel, wx.ID_ANY, self.__fig)
sizer.Add(self.__canvas, 1, wx.ALL | wx.EXPAND)
self.Thaw()
# Create figure and canvas. Add canvas to sizer
self.__fig = plt.Figure()
self.__canvas = FigureCanvas(self.__panel, wx.ID_ANY, self.__fig)
self.__panel.GetSizer().Add(self.__canvas, 1, wx.ALL | wx.EXPAND)
# Setup scrolling
panel.SetupScrolling()
self.__panel.SetupScrolling()
# Refresh to show content
self.refresh()
@@ -100,7 +79,8 @@ class MDIChildDivergedGraph(mdi.CorrelationMDIChild):
# Get all symbols and remove the base one. We will need to ensure that this is first in the list
other_symbols = list(filtered_data['Symbol 1'].append(filtered_data['Symbol 2']).drop_duplicates())
other_symbols.remove(self.symbol)
if self.symbol in other_symbols:
other_symbols.remove(self.symbol)
# Get the tick data for base symbols and other symbols
symbol_tick_data = self.GetMDIParent().cor.get_ticks(self.symbol, cache_only=True)
@@ -109,29 +89,71 @@ class MDIChildDivergedGraph(mdi.CorrelationMDIChild):
tick_data = self.GetMDIParent().cor.get_ticks(symbol, cache_only=True)
other_tick_data.append(tick_data)
# Plot tick data for the base symbol
self.__axs.plot(symbol_tick_data['time'], symbol_tick_data['ask'], color=self.__colours[0], label=self.symbol)
# We will clear the figure and create new axis on every refresh as the shape will change on refresh if symbols
# change
self.__fig.clear()
axs = self.__fig.add_subplot(111) # Axis to fill canvas
# Plot for the other symbols on new axes
# Set title
axs.set_title(f"Price Data for {self.symbol}:{other_symbols}".replace("'", ""))
# Set Y Labels and tick colours for symbol. This will be set for other symbols on plot
for i in range(0, 2):
axs.set_ylabel(f"{self.symbol}", color=self.__colours[0], labelpad=10)
axs.tick_params(axis='y', labelcolor=self.__colours[0])
# Store the lines for the legend
lines = []
# Plot tick data for the base symbol. Store line and label.
line = axs.plot(symbol_tick_data['time'], symbol_tick_data['ask'], color=self.__colours[0],
label=self.symbol)[0]
lines.append(line)
# Plot for the other symbols on new axes.
spinepos = 0 # Position of spine. Starting at 2nd plot, will be increased for every plot.
for i in range(0, len(other_tick_data)):
new_ax = self.__axs.twinx()
new_ax.plot(other_tick_data[i]['time'], other_tick_data[i]['ask'], label=other_symbols[i],
color=self.__colours[i+1])
new_ax.set_ylabel(f"{other_symbols[i]}", color=self.__colours[i+1], labelpad=10)
new_ax.tick_params(axis='y', labelcolor=self.__colours[i+1])
other_axis = axs.twinx()
# Move spine if we are not on first plot
if i > 0:
spinepos += 1.15
other_axis.spines['right'].set_position(("axes", spinepos))
# The frame is off, so line of detached spine is invisible. Activate frame and then make patch and
# spines invisible
other_axis.set_frame_on(True)
other_axis.patch.set_visible(False)
for sp in other_axis.spines.values():
sp.set_visible(False)
# Show the right spline
other_axis.spines['right'].set_visible(True)
# Plot. We will use a lighter line for the other symbols. Store line.
line = other_axis.plot(other_tick_data[i]['time'], other_tick_data[i]['ask'], color=self.__colours[i + 1],
label=other_symbols[i], linestyle='solid', linewidth=0.5)[0]
lines.append(line)
# Set y label, label colour and tick colour
other_axis.set_ylabel(f"{other_symbols[i]}", color=self.__colours[i + 1], labelpad=10)
other_axis.tick_params(axis='y', labelcolor=self.__colours[i + 1])
# Ticks, labels and formats. Fixing xticks with FixedLocator but also using MaxNLocator to avoid
# cramped x-labels
if len(symbol_tick_data['time']) > 0:
self.__axs.xaxis.set_major_locator(mticker.MaxNLocator(10))
ticks_loc = self.__axs.get_xticks().tolist()
self.__axs.xaxis.set_major_locator(mticker.FixedLocator(ticks_loc))
self.__axs.set_xticklabels(ticks_loc)
self.__axs.xaxis.set_major_formatter(self.__tick_fmt_time)
plt.setp(self.__axs.xaxis.get_majorticklabels(), rotation=45)
axs.xaxis.set_major_locator(mticker.MaxNLocator(10))
ticks_loc = axs.get_xticks().tolist()
axs.xaxis.set_major_locator(mticker.FixedLocator(ticks_loc))
axs.set_xticklabels(ticks_loc)
axs.xaxis.set_major_formatter(self.__tick_fmt_time)
plt.setp(axs.xaxis.get_majorticklabels(), rotation=45)
# Legend
#self.__axs.legend([self.symbol, ] + other_symbols)
# axs.legend(lines, [line.get_label() for line in lines], loc=0)
# Tight layout
self.__fig.tight_layout()
# Redraw canvas
self.__canvas.draw()
+41
View File
@@ -0,0 +1,41 @@
import definitions
import markdown
import wx
import wx.html
import mt5_correlation.gui.mdi as mdi
class MDIChildHelp(mdi.CorrelationMDIChild):
"""
Shows the README.md file
"""
def __init__(self, parent):
# Super
mdi.CorrelationMDIChild.__init__(self, parent=parent, id=wx.ID_ANY, pos=wx.DefaultPosition, title="Help",
size=wx.Size(width=800, height=-1),
style=wx.DEFAULT_FRAME_STYLE)
# Panel and sizer for help file
panel = wx.Panel(self, wx.ID_ANY)
sizer = wx.BoxSizer()
panel.SetSizer(sizer)
# HtmlWindow
html_widget = wx.html.HtmlWindow(parent=panel, id=wx.ID_ANY, style=wx.html.HW_SCROLLBAR_AUTO | wx.html.HW_NO_SELECTION)
sizer.Add(html_widget, 1, wx.ALL | wx.EXPAND)
# Load the help file, convert markdown to HTML and display. The markdown library doesnt understand the shell
# format so we will remove.
markdown_text = open(definitions.HELP_FILE).read()
markdown_text = markdown_text.replace("```shell", "```")
html = markdown.markdown(markdown_text)
html_widget.SetPage(html)
def refresh(self):
"""
Nothing to do on refresh. Help file doesnt change during outside of development.
:return:
"""
pass
+43
View File
@@ -0,0 +1,43 @@
import definitions
import wx
import wx.html
import mt5_correlation.gui.mdi as mdi
class MDIChildLog(mdi.CorrelationMDIChild):
"""
Shows the debug.log file
"""
__log_window = None # Widget to display log file in
def __init__(self, parent):
# Super
mdi.CorrelationMDIChild.__init__(self, parent=parent, id=wx.ID_ANY, pos=wx.DefaultPosition, title="Log",
size=wx.Size(width=800, height=200),
style=wx.DEFAULT_FRAME_STYLE)
# Panel and sizer for help file
panel = wx.Panel(self, wx.ID_ANY)
sizer = wx.BoxSizer()
panel.SetSizer(sizer)
# Log file window
self.__log_window = wx.TextCtrl(parent=panel, id=wx.ID_ANY, style=wx.HSCROLL | wx.TE_MULTILINE | wx.TE_READONLY)
sizer.Add(self.__log_window, 1, wx.ALL | wx.EXPAND)
# Refresh to populate
self.refresh()
def refresh(self):
"""
Refresh the log file
:return:
"""
# Load the help file
self.__log_window.LoadFile(definitions.LOG_FILE)
# Scroll to bottom
self.__log_window.SetInsertionPoint(-1)
+1
View File
@@ -4,6 +4,7 @@ MetaTrader5==5.0.34
pytz==2021.1
scipy==1.6.0
logging==0.4.9.6
markdown==3.3.4
wxpython==4.1.1
mock==4.0.3
numpy==1.20.0