Improved divergence graph.

This commit is contained in:
Jamie Cash
2021-06-04 17:40:44 +01:00
parent 06c980a45a
commit 7d97b78f8c
5 changed files with 125 additions and 92 deletions
+5 -5
View File
@@ -8,7 +8,7 @@ calculate:
overlap_pct: 90
max_p_value: 0.05
monitor:
interval: 20
interval: 10
calculations:
long:
from: 30
@@ -74,10 +74,10 @@ charts:
developer:
inspection: true
window:
x: -8
y: -8
width: 1636
height: 1056
x: -4
y: 1
width: 1626
height: 1043
style: 541072960
settings_window:
x: 354
+4
View File
@@ -93,6 +93,10 @@ monitor:
autosave:
__label: Auto Save
__helptext: Whether to auto save after every monitoring event. If a file was opened or has been saved, then the data will be saved to this file, otherwise the data will be saved to a file named autosave.cpd.
charts:
colormap:
__label: Color Map
__helptext: The matplotlib color pallet to use for plotting graphs. A list of pallets is available at https://matplotlib.org/stable/tutorials/colors/colormaps.html
developer:
inspection:
__label: Inspection
+3 -1
View File
@@ -5,6 +5,7 @@ import logging
import pytz
import wx
import wx.lib.inspection as ins
import wxconfig
import wxconfig as cfg
from datetime import datetime, timedelta
@@ -289,7 +290,8 @@ class CorrelationMDIFrame(wx.MDIParentFrame):
for child in children:
if isinstance(child, CorrelationMDIChild):
child.refresh()
elif isinstance(child, wx.StatusBar) or isinstance(child, ins.InspectionFrame):
elif isinstance(child, wx.StatusBar) or isinstance(child, ins.InspectionFrame) or \
isinstance(child, wxconfig.SettingsDialog):
# Ignore
pass
else:
+112 -85
View File
@@ -1,15 +1,14 @@
import logging
import matplotlib.dates
import matplotlib.pylab as pl
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import numpy as mp
import numpy as np
import wx
import wx.lib.scrolledpanel as scrolled
import wxconfig as cfg
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from mpl_toolkits.axes_grid1 import host_subplot
from mpl_toolkits import axisartist
import mt5_correlation.gui.mdi as mdi
from mt5_correlation import correlation as cor
@@ -22,20 +21,23 @@ class MDIChildDivergedGraph(mdi.CorrelationMDIChild):
symbol = None # Symbol to chart divergence for. Public as we use to check if window for the symbol is already open.
# Logger
__log = None
# Date formats for graphs
__tick_fmt_date = matplotlib.dates.DateFormatter('%d-%b')
__tick_fmt_time = matplotlib.dates.DateFormatter('%H:%M:%S')
# Panel
__panel = None
# Graph Canvas and Figure
# Graph Canvas
__canvas = None
__fig = None
# Colors for graph lines
__colours = matplotlib.cm.get_cmap(cfg.Config().get("charts.colormap")).colors
# We will store the other symbols last plotted. This will save us rebuilding teh figure if teh symbols haven't
# changed.
__other_symbols = None
def __init__(self, parent, **kwargs):
# Super
wx.MDIChildFrame.__init__(self, parent=parent, id=wx.ID_ANY,
@@ -48,17 +50,14 @@ class MDIChildDivergedGraph(mdi.CorrelationMDIChild):
self.symbol = kwargs['symbol']
# Create panel and sizer
self.__panel = scrolled.ScrolledPanel(self, wx.ID_ANY)
panel = scrolled.ScrolledPanel(self, wx.ID_ANY)
sizer = wx.BoxSizer()
self.__panel.SetSizer(sizer)
panel.SetSizer(sizer)
# 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
self.__panel.SetupScrolling()
self.__canvas = FigureCanvas(panel, wx.ID_ANY, plt.figure())
panel.GetSizer().Add(self.__canvas, 1, wx.ALL | wx.EXPAND)
panel.SetupScrolling()
# Refresh to show content
self.refresh()
@@ -69,6 +68,41 @@ class MDIChildDivergedGraph(mdi.CorrelationMDIChild):
:return:
"""
# Get tick data for base symbol
symbol_tick_data = self.GetMDIParent().cor.get_ticks(self.symbol, cache_only=True)
# Get the other symbols and their tick data
other_symbols_data = self.__get_other_symbols_data()
# Delete all axes from the figure. They will need to be recreated as the symbols may be different
for axes in self.__canvas.figure.axes:
axes.remove()
# Plot for all other symbols
num_subplots = 1 if len(other_symbols_data.keys()) == 0 else len(other_symbols_data.keys())
plotnum = 1
axs = [] # Store the axes for sharing x axis
for other_symbol in other_symbols_data:
axs.append(self.__canvas.figure.add_subplot(num_subplots, 1, plotnum))
self.__plot(axes=axs[-1], base_symbol=self.symbol, other_symbol=other_symbol,
base_symbol_data=symbol_tick_data, other_symbol_data=other_symbols_data[other_symbol])
# Next plot
plotnum += 1
# Share x axis of the last axes with all the others
self.__share_xaxis(axs)
# Redraw canvas
self.__canvas.figure.tight_layout(pad=0.5)
self.__canvas.draw()
def __get_other_symbols_data(self):
"""
Gets the data required for the graphs
:param self:
:return: dict of other symbols their tick data
"""
# Get the symbols that this one has diverged against.
data = self.GetMDIParent().cor.filtered_coefficient_data
filtered_data = data.loc[(
@@ -77,90 +111,83 @@ class MDIChildDivergedGraph(mdi.CorrelationMDIChild):
(data['Status'] == cor.STATUS_CONVERGING)
) &
(
(data['Symbol 1'] == self.symbol) |
(data['Symbol 2'] == self.symbol)
)]
(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())
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)
other_tick_data = []
# Get the tick data other symbols and add to dict
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)
other_tick_data[symbol] = tick_data
# 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
return other_tick_data
# Set title
axs.set_title(f"Price Data for {self.symbol}:{other_symbols}".replace("'", ""))
def __plot(self, axes, base_symbol, other_symbol, base_symbol_data, other_symbol_data):
"""
Plots the data on the axes
:param axes: The subplot to plot onto
:param base_symbol
:param other_symbol:
"""
# Create the other axes. Will need an axes for the base symbol data and another for the other symbol data
other_axes = axes.twinx()
# 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])
# Set plot title and axis labels
axes.set_title(f"Tick Data for {base_symbol}:{other_symbol}")
axes.set_ylabel(base_symbol, color=self.__colours[0], labelpad=10)
other_axes.set_ylabel(other_symbol, color=self.__colours[1], labelpad=10)
# Store the lines for the legend
lines = []
# Set the tick and axis colors
self.__set_axes_color(axes, self.__colours[0], 'left')
self.__set_axes_color(other_axes, self.__colours[1], 'right')
# 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 both lines
axes.plot(base_symbol_data['time'], base_symbol_data['ask'], color=self.__colours[0])
other_axes.plot(other_symbol_data['time'], other_symbol_data['ask'], color=self.__colours[1])
# 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)):
other_axis = axs.twinx()
@staticmethod
def __set_axes_color(axes, color, axis_loc='right'):
"""
Set the color for the axes, including axis line, ticks and tick labels
:param axes: The axes to set color for.
:param color: The color to set to
:param axis_loc: The location of the axis, label and ticks. Either left for base symbol or right for others
:return:
"""
# Change the color for the axis line, ticks and tick labels
axes.spines[axis_loc].set_color(color)
axes.tick_params(axis='y', colors=color)
# 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:
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
# axs.legend(lines, [line.get_label() for line in lines], loc=0)
# Tight layout
self.__fig.tight_layout()
# Redraw canvas
self.__canvas.draw()
def __share_xaxis(self, axs):
"""
Share the xaxis of the last axes with all other axes. Remove axis tick labels for all but the last. Format axis
tick labels for the last.
:param axs:
:return:
"""
if len(axs) > 0:
last_ax = axs[-1]
for ax in axs:
# If we are not on the last one, share and hide tick labels. If we are on the last one, format tick
# labels.
if ax != last_ax:
ax.sharex(last_ax)
plt.setp(ax.xaxis.get_majorticklabels(), visible=False)
else:
# Ticks, labels and formats. Fixing xticks with FixedLocator but also using MaxNLocator to avoid
# cramped x-labels
ax.xaxis.set_major_locator(mticker.MaxNLocator(10))
ticks_loc = ax.get_xticks().tolist()
ax.xaxis.set_major_locator(mticker.FixedLocator(ticks_loc))
ax.set_xticklabels(ticks_loc)
ax.xaxis.set_major_formatter(self.__tick_fmt_time)
plt.setp(ax.xaxis.get_majorticklabels(), rotation=45)
def __del__(self):
# Close all plots
+1 -1
View File
@@ -1,5 +1,5 @@
pandas==1.2.1
matplotlib==3.3.4
matplotlib==3.4.2
MetaTrader5==5.0.34
pytz==2021.1
scipy==1.6.0