Added diverged symbols chart

This commit is contained in:
Jamie Cash
2021-06-01 17:45:11 +01:00
parent b947b928e7
commit 66cdf47406
5 changed files with 224 additions and 41 deletions
+57 -24
View File
@@ -1,11 +1,15 @@
import abc
import importlib
import logging
import sys
import pytz
import wx
import wxconfig as cfg
from datetime import datetime, timedelta
import mt5_correlation.gui as gui
from mt5_correlation import correlation as cor
@@ -249,32 +253,14 @@ class CorrelationMDIFrame(wx.MDIParentFrame):
self.SetStatusText(f"Status updated at {self.cor.get_last_calculation():%d-%b %H:%M:%S}.", 1)
def __on_view_status(self, evt):
from mt5_correlation.gui.mdi_child_status import MDIChildStatus
# Only open if not already open. If already open then raise to top.
opened_instance = None
for child in self.GetChildren():
if isinstance(child, MDIChildStatus):
opened_instance = child
if opened_instance is None:
MDIChildStatus(parent=self).Show(True)
else:
opened_instance.Raise()
FrameManager.open_frame(parent=self, frame_module='mt5_correlation.gui.mdi_child_status',
frame_class='MDIChildStatus',
raise_if_open=True)
def __on_view_diverged(self, evt):
from mt5_correlation.gui.mdi_child_diverged_symbols import MDIChildDivergedSymbols
# Only open if not already open. If already open then raise to top.
opened_instance = None
for child in self.GetChildren():
if isinstance(child, MDIChildDivergedSymbols):
opened_instance = child
if opened_instance is None:
MDIChildDivergedSymbols(parent=self).Show(True)
else:
opened_instance.Raise()
FrameManager.open_frame(parent=self, frame_module='mt5_correlation.gui.mdi_child_diverged_symbols',
frame_class='MDIChildDivergedSymbols',
raise_if_open=True)
def __refresh(self):
"""
@@ -305,3 +291,50 @@ class CorrelationMDIChild(wx.MDIChildFrame):
:return:
"""
raise NotImplementedError
class FrameManager:
"""
Manages the opening and raising of MDIChild frames
"""
@staticmethod
def open_frame(parent, frame_module, frame_class, raise_if_open=True, **kwargs):
"""
Opens the frame specified by the frame class
:param parent: The MDIParentFrame to open the child frame into
: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.)
:return:
"""
# Load the module and class
module = importlib.import_module(frame_module)
clazz = getattr(module, frame_class)
# Do we have an opened instance
opened_instance = None
for child in parent.GetChildren():
if isinstance(child, clazz):
# do the args match
match = True
for key in kwargs:
if kwargs[key] != getattr(child, key):
match = False
# Only open existing instance if args matched
if match:
opened_instance = child
# If we dont have an opened instance or raise_on_open is False then open new frame, otherwise raise it
if opened_instance is None or raise_if_open is False:
if len(kwargs) == 0:
clazz(parent=parent).Show(True)
else:
clazz(parent=parent, **kwargs).Show(True)
else:
opened_instance.Raise()
@@ -30,16 +30,16 @@ class MDIChildCorrelationGraph(mdi.CorrelationMDIChild):
__axs = None
__canvas = None
def __init__(self, parent, symbol1, symbol2):
def __init__(self, parent, **kwargs):
# Super
wx.MDIChildFrame.__init__(self, parent=parent, id=wx.ID_ANY,
title=f"Correlation Status for {symbol1}:{symbol2}")
title=f"Correlation Status for {kwargs['symbols'][0]}:{kwargs['symbols'][1]}")
# Create logger
self.__log = logging.getLogger(__name__)
# Store the symbols
self.symbols = [symbol1, symbol2]
self.symbols = kwargs['symbols']
# We will freeze this frame and thaw once constructed to avoid flicker.
self.Freeze()
@@ -56,6 +56,9 @@ class MDIChildDivergedSymbols(mdi.CorrelationMDIChild):
self.__grid.SetMinSize((220, 100))
sizer.Add(self.__grid, 1, wx.ALL | wx.EXPAND)
# Bind row doubleclick
self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_doubleckick_row, self.__grid)
# Refresh to populate
self.refresh()
@@ -94,6 +97,21 @@ class MDIChildDivergedSymbols(mdi.CorrelationMDIChild):
# Update row count
self.__rows = cur_rows
def __on_doubleckick_row(self, evt):
"""
Open the graphs when a row is doubleclicked.
:param evt:
:return:
"""
row = evt.GetRow()
symbol = self.__grid.GetCellValue(row, COLUMN_SYMBOL)
mdi.FrameManager.open_frame(parent=self.GetMDIParent(),
frame_module='mt5_correlation.gui.mdi_child_divergedgraph',
frame_class='MDIChildDivergedGraph',
raise_if_open=True,
symbol=symbol)
class _DataTable(wx.grid.GridTableBase):
"""
@@ -0,0 +1,141 @@
import logging
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
import mt5_correlation.gui.mdi as mdi
from mt5_correlation import correlation as cor
class MDIChildDivergedGraph(mdi.CorrelationMDIChild):
"""
Shows the graphs for the specified correlation
"""
symbol = None # Symbol to chart divergence for. Public as we use to check if window for the symbol is already open.
# Date formats for graphs
__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
__canvas = None
# Colors for graph lines
__colours = ['red', 'green', 'blue', 'pink', 'purple', 'black', 'yellow', 'lightblue']
def __init__(self, parent, **kwargs):
# Super
wx.MDIChildFrame.__init__(self, parent=parent, id=wx.ID_ANY,
title=f"Divergence Graph for {kwargs['symbol']}")
# Create logger
self.__log = logging.getLogger(__name__)
# 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)
sizer = wx.BoxSizer()
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()
# Setup scrolling
panel.SetupScrolling()
# Refresh to show content
self.refresh()
def refresh(self):
"""
Refresh the graph
:return:
"""
# Get the symbols that this one has diverged against.
data = self.GetMDIParent().cor.filtered_coefficient_data
filtered_data = data.loc[(
(data['Status'] == cor.STATUS_DIVERGED) |
(data['Status'] == cor.STATUS_DIVERGING) |
(data['Status'] == cor.STATUS_CONVERGING)
) &
(
(data['Symbol 1'] == self.symbol) |
(data['Symbol 2'] == self.symbol)
)]
# 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)
# Get the tick data for base symbols and other symbols
symbol_tick_data = self.GetMDIParent().cor.get_ticks(self.symbol, cache_only=True)
other_tick_data = []
for symbol in other_symbols:
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)
# Plot for the other symbols on new axes
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])
# 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)
# Legend
#self.__axs.legend([self.symbol, ] + other_symbols)
# Redraw canvas
self.__canvas.draw()
def __del__(self):
# Close all plots
plt.close('all')
+5 -14
View File
@@ -125,20 +125,11 @@ class MDIChildStatus(mdi.CorrelationMDIChild):
symbol1 = self.__grid.GetCellValue(row, COLUMN_SYMBOL1)
symbol2 = self.__grid.GetCellValue(row, COLUMN_SYMBOL2)
from mt5_correlation.gui.mdi_child_correlationgraph import MDIChildCorrelationGraph
# Check if already open
instance = None
for child in self.GetMDIParent().GetChildren():
if isinstance(child, MDIChildCorrelationGraph):
if child.symbols[0] == symbol1 and child.symbols[1] == symbol2:
instance = child
# If already open, raise to top. Otherwise open
if instance is None:
MDIChildCorrelationGraph(parent=self.GetMDIParent(), symbol1=symbol1, symbol2=symbol2).Show(True)
else:
instance.Raise()
mdi.FrameManager.open_frame(parent=self.GetMDIParent(),
frame_module='mt5_correlation.gui.mdi_child_correlationgraph',
frame_class='MDIChildCorrelationGraph',
raise_if_open=True,
symbols=[symbol1, symbol2])
class _DataTable(wx.grid.GridTableBase):