Added diverged symbols view

This commit is contained in:
Jamie Cash
2021-06-01 14:20:57 +01:00
parent f6d05873d2
commit b947b928e7
4 changed files with 216 additions and 2 deletions
+28
View File
@@ -161,6 +161,34 @@ class Correlation:
return filtered_data
@property
def diverged_symbols(self):
"""
:return: dataframe containing all diverged, diverging or converging symbols and count of number of
divergences for those symbols.
"""
filtered_data = None
if self.coefficient_data is not None:
# Only rows where we have a divergence
filtered_data = self.coefficient_data \
.loc[(self.coefficient_data['Status'] == STATUS_DIVERGED) |
(self.coefficient_data['Status'] == STATUS_DIVERGING) |
(self.coefficient_data['Status'] == STATUS_CONVERGING)]
# We only need the symbols
all_symbols = pd.DataFrame(columns=['Symbol', 'Count'],
data={'Symbol': filtered_data['Symbol 1'].append(filtered_data['Symbol 2']),
'Count': 1})
# Group and count. Reset index so that we have named SYMBOL column.
filtered_data = all_symbols.groupby(by='Symbol').count().reset_index()
# Sort
filtered_data = filtered_data.sort_values('Count', ascending=False)
return filtered_data
def load(self, filename):
"""
Loads calculated coefficients, price data used to calculate them and tick data used during monitoring.
+16
View File
@@ -76,6 +76,8 @@ class CorrelationMDIFrame(wx.MDIParentFrame):
menu_view = wx.Menu()
self.Bind(wx.EVT_MENU, self.__on_view_status, menu_view.Append(wx.ID_ANY, "Status",
"View status of correlations."))
self.Bind(wx.EVT_MENU, self.__on_view_diverged, menu_view.Append(wx.ID_ANY, "Diverged Symbols",
"View diverged symbols."))
self.menubar.Append(menu_view, "&View")
# Set menu bar
@@ -260,6 +262,20 @@ class CorrelationMDIFrame(wx.MDIParentFrame):
else:
opened_instance.Raise()
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()
def __refresh(self):
"""
Refresh all open child frames
@@ -0,0 +1,138 @@
import logging
import pandas as pd
import wx
import wx.grid
import mt5_correlation.gui.mdi as mdi
# Columns for diverged symbols table
COLUMN_INDEX = 0
COLUMN_SYMBOL = 1
COLUMN_NUM_DIVERGENCES = 2
class MDIChildDivergedSymbols(mdi.CorrelationMDIChild):
"""
Shows the status of all correlations that are within the monitoring threshold
"""
# The table and grid containing the symbols that form part of the diverged correlations. Defined at instance level
# to enable refresh.
__table = None
__grid = None
# Number of rows. Required for and updated by refresh method
__rows = 0
__log = None # The logger
def __init__(self, parent):
# Super
wx.MDIChildFrame.__init__(self, parent=parent, id=wx.ID_ANY, title="Diverged Symbols",
size=wx.Size(width=240, height=-1), style=wx.DEFAULT_FRAME_STYLE)
# Create logger
self.__log = logging.getLogger(__name__)
# Panel and sizer for table
panel = wx.Panel(self, wx.ID_ANY)
sizer = wx.BoxSizer(wx.VERTICAL)
panel.SetSizer(sizer)
# Create the grid. This is a data table using pandas dataframe for underlying data. Add the
# grid to the sizer.
self.__table = _DataTable(columns=self.GetMDIParent().cor.diverged_symbols.columns)
self.__grid = wx.grid.Grid(panel, wx.ID_ANY)
self.__grid.SetTable(self.__table, takeOwnership=True)
self.__grid.EnableEditing(False)
self.__grid.EnableDragRowSize(False)
self.__grid.EnableDragColSize(True)
self.__grid.EnableDragGridSize(True)
self.__grid.SetSelectionMode(wx.grid.Grid.SelectRows)
self.__grid.SetRowLabelSize(0)
self.__grid.SetColSize(COLUMN_INDEX, 0) # Index. Hide
self.__grid.SetColSize(COLUMN_SYMBOL, 100) # Symbol
self.__grid.SetColSize(COLUMN_NUM_DIVERGENCES, 100) # Num divergences
self.__grid.SetMinSize((220, 100))
sizer.Add(self.__grid, 1, wx.ALL | wx.EXPAND)
# Refresh to populate
self.refresh()
def refresh(self):
"""
Refreshes grid. Notifies if rows have been added or deleted.
:return:
"""
self.__log.debug(f"Refreshing grid.")
# Update data
self.__table.data = self.GetMDIParent().cor.diverged_symbols.copy()
# Start refresh
self.__grid.BeginBatch()
# Check if num rows in dataframe has changed, and send appropriate APPEND or DELETE messages
cur_rows = len(self.GetMDIParent().cor.diverged_symbols.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.__grid.ProcessTableMessage(msg)
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
self.__grid.ProcessTableMessage(msg)
self.__grid.EndBatch()
# Send updated message
msg = wx.grid.GridTableMessage(self.__table, wx.grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES)
self.__grid.ProcessTableMessage(msg)
# Update row count
self.__rows = cur_rows
class _DataTable(wx.grid.GridTableBase):
"""
A data table that holds data in a pandas dataframe.
"""
data = None # The data for this table. A Pandas DataFrame
def __init__(self, columns):
wx.grid.GridTableBase.__init__(self)
self.headerRows = 1
self.data = pd.DataFrame(columns=columns)
def GetNumberRows(self):
return len(self.data)
def GetNumberCols(self):
return len(self.data.columns) + 1
def GetValue(self, row, col):
if row < self.RowsCount and col < self.ColsCount:
return self.data.index[row] if col == 0 else self.data.iloc[row, col - 1]
else:
raise Exception(f"Trying to access row {row} and col {col} which does not exist.")
def SetValue(self, row, col, value):
self.data.iloc[row, col - 1] = value
def GetColLabelValue(self, col):
if col == 0:
if self.data.index.name is None:
return 'Index'
else:
return self.data.index.name
return str(self.data.columns[col - 1])
def GetTypeName(self, row, col):
return wx.grid.GRID_VALUE_STRING
def GetAttr(self, row, col, prop):
attr = wx.grid.GridCellAttr()
return attr
+34 -2
View File
@@ -1,5 +1,5 @@
import unittest
from unittest.mock import patch
from unittest.mock import patch, PropertyMock
import time
import mt5_correlation.correlation as correlation
import pandas as pd
@@ -121,7 +121,7 @@ class TestCorrelation(unittest.TestCase):
self.assertEqual(cor.coefficient_data.iloc[1, 2], 1, "The correlation for SYMBOL1:SYMBOL5 should be 1.")
self.assertEqual(cor.coefficient_data.iloc[2, 2], 1, "The correlation for SYMBOL4:SYMBOL5 should be 1.")
# Get the price data used to calculate the coefficients fro symbol 1. It should match mock_base_prices.
# Get the price data used to calculate the coefficients for symbol 1. It should match mock_base_prices.
price_data = cor.get_price_data('SYMBOL1')
self.assertTrue(price_data.equals(self.mock_base_prices), "Price data returned post calculation should match "
"mock price data.")
@@ -325,6 +325,38 @@ class TestCorrelation(unittest.TestCase):
# Cleanup. delete the file
os.remove("unittest.cpd")
@patch('mt5_correlation.correlation.Correlation.coefficient_data', new_callable=PropertyMock)
def test_diverged_symbols(self, mock):
"""
Test that diverged_symbols property correctly groups symbols and counts.
:param mock:
:return:
"""
# Correlation class
cor = correlation.Correlation()
# Mock the correlation data. Symbol 1 has diverged 3 times; symbols 2 has diverged twice; symbol 3 has
# diverged once; symbols 4 has diverged twice and symbol 5 has not diverged at all. Use all diverged status'
# (diverged, diverging & converging). Also add a row for a non diverged pair.
mock.return_value = pd.DataFrame(columns=['Symbol 1', 'Symbol 2', 'Status'], data=[
['SYMBOL1', 'SYMBOL2', correlation.STATUS_DIVERGED],
['SYMBOL1', 'SYMBOL3', correlation.STATUS_DIVERGING],
['SYMBOL1', 'SYMBOL4', correlation.STATUS_CONVERGING],
['SYMBOL2', 'SYMBOL4', correlation.STATUS_DIVERGED],
['SYMBOL2', 'SYMBOL3', correlation.STATUS_CORRELATED]])
# Get the diverged_symbols data and check the counts
diverged_symbols = cor.diverged_symbols
self.assertEqual(diverged_symbols.loc[(diverged_symbols['Symbol'] == 'SYMBOL1')]['Count'].iloc[0], 3,
"Symbol 1 has diverged three times.")
self.assertEqual(diverged_symbols.loc[(diverged_symbols['Symbol'] == 'SYMBOL2')]['Count'].iloc[0], 2,
"Symbol 2 has diverged twice.")
self.assertEqual(diverged_symbols.loc[(diverged_symbols['Symbol'] == 'SYMBOL3')]['Count'].iloc[0], 1,
"Symbol 3 has diverged once.")
self.assertEqual(diverged_symbols.loc[(diverged_symbols['Symbol'] == 'SYMBOL4')]['Count'].iloc[0], 2,
"Symbol 4 has diverged twice.")
self.assertFalse('SYMBOL5' in diverged_symbols['Symbol'])
if __name__ == '__main__':
unittest.main()