From 443ad822ad11e521ed9f38368ee21f664ecb87ab Mon Sep 17 00:00:00 2001 From: Jamie Cash Date: Tue, 16 Mar 2021 17:56:54 +0000 Subject: [PATCH] Merged charts to create more space. --- mt5_correlation/gui.py | 179 ++++++++++++++++++++--------------------- 1 file changed, 89 insertions(+), 90 deletions(-) diff --git a/mt5_correlation/gui.py b/mt5_correlation/gui.py index b617c9f..4011ba5 100644 --- a/mt5_correlation/gui.py +++ b/mt5_correlation/gui.py @@ -527,12 +527,8 @@ class GraphPanel(wx.Panel): # Super wx.Panel.__init__(self, parent) - # 3 axis, 2 price data for calculate, 2 price data for last coefficient and coefficient history. - # All will have axis labels and top and right boarders - # removed - self.__fig, self.__axes = plt.subplots(nrows=5, ncols=1) - - # Create the canvas + # Fig & canvas + self.__fig = plt.figure() self.__canvas = FigureCanvas(self, -1, self.__fig) # Date format for x axes @@ -551,8 +547,7 @@ class GraphPanel(wx.Panel): self.__axes = None self.__fig = None - def draw(self, prices=None, ticks=None, history=None, symbols=None, divergence_threshold=None, - monitor_inverse=False): + def draw(self, prices, ticks, history, symbols, divergence_threshold=None, monitor_inverse=False): """ Plot the correlations. :param prices: Price data used to calculate base coefficient. List [Symbol1 Price Data, Symbol 2 Price Data] @@ -582,10 +577,6 @@ class GraphPanel(wx.Panel): times.append(hist['Date To']) coefficients.append(hist['Coefficient']) - # Clear. We will need to redraw - for ax in self.__axes: - ax.clear() - if symbols_selected: # Axis ranges if price_data_available: @@ -600,101 +591,109 @@ class GraphPanel(wx.Panel): 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]}", - f"Coefficient Tick Data for {symbols[0]}", f"Coefficient Tick Data for {symbols[1]}", - 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]] + # First two charts. Data used to calculate base coefficient and data used to calculate latest coefficient. + # Both charts will use 2 plots on a single axis and have different y ranges. + titles = [f"Base Coefficient Price Data for {symbols[0]}:{symbols[1]}", + f"Coefficient Tick Data for {symbols[0]}:{symbols[1]}"] + xlims = [price_chart_date_range, tick_chart_date_range] - # Axis labels - xlabels = [None, None, None, None, None] - ylabels = ['Price', 'Price', 'Price', 'Price', 'Coefficient'] + 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 []]] - # 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] + 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 []]] - # 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 []] + tick_labels = [prices[1]['time'] if price_data_available else [], + ticks[1]['time'] if tick_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 []] + tick_formats = [self.__tick_fmt_date, self.__tick_fmt_time] - # Chart types - types = ['plot', 'plot', 'plot', 'plot', 'scatter'] + # Clear the figure then redraw the 2 charts + self.__fig.clf() + for i in range(0, 2): + # 2 axis. One for each symbol + s1ax = self.__fig.add_subplot(3, 1, i+1) + s2ax = s1ax.twinx() - # Legends - legends = [None, None, None, None, [f"{Config().get('monitor.calculations.long.from')} Minutes", - f"{Config().get('monitor.calculations.medium.from')} Minutes", - f"{Config().get('monitor.calculations.short.from')} Minutes"]] - - # lines - horiz_lines = [None, None, None, None, [divergence_threshold, divergence_threshold * -1 - if divergence_threshold is not None and monitor_inverse else None]] - - # Draw 5 charts - for index in range(0, len(self.__axes)): # Titles and axis labels - self.__axes[index].set_title(titles[index]) - self.__axes[index].set_xlabel(xlabels[index]) - self.__axes[index].set_ylabel(ylabels[index]) + s1ax.set_title(titles[i]) + s1ax.set_ylabel('Price') - # Limits - if xlims[index] is not None: - self.__axes[index].set_xlim(xlims[index]) + # X Limits + s1ax.set_xlim(xlims[i]) - if ylims[index] is not None: - self.__axes[index].set_ylim(ylims[index]) + # Y Labels. Left for symbol1, right for symbol2 + colors = ['green', 'blue'] + s1ax.set_ylabel(f"{symbols[0]}", color=colors[0]) + s2ax.set_ylabel(f"{symbols[1]}", color=colors[1]) - # Remove typ and right boarders - self.__axes[index].spines["top"].set_visible(False) - self.__axes[index].spines["right"].set_visible(False) + # Plot both lines + s1ax.plot(xdata[i][0], ydata[i][0], color=colors[0]) + s2ax.plot(xdata[i][1], ydata[i][1], color=colors[1]) - # Plot. There may be more than one set of data or each chart. Convert single data to list, then loop - xdata_list = xdata[index] if isinstance(xdata[index], list) else [xdata[index], ] - ydata_list = ydata[index] if isinstance(ydata[index], list) else [ydata[index], ] - - for data_index in range(0, len(xdata_list)): - if types[index] == 'plot': - self.__axes[index].plot(xdata_list[data_index], ydata_list[data_index]) - elif types[index] == 'scatter': - self.__axes[index].scatter(xdata_list[data_index], ydata_list[data_index], s=1) + # Y tick colours + s1ax.tick_params(axis='y', labelcolor=colors[0]) + s2ax.tick_params(axis='y', labelcolor=colors[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]) + if len(tick_labels[i]) > 0: + s1ax.xaxis.set_major_locator(mticker.MaxNLocator(10)) + ticks_loc = s1ax.get_xticks().tolist() + s1ax.xaxis.set_major_locator(mticker.FixedLocator(ticks_loc)) + s1ax.set_xticklabels(ticks_loc) + if tick_formats[i] is not None: + s1ax.xaxis.set_major_formatter(tick_formats[i]) + plt.setp(s1ax.xaxis.get_majorticklabels(), rotation=45) else: - self.__axes[index].set_xticklabels([]) + s1ax.set_xticklabels([]) - # Lines - if horiz_lines[index] is not None and isinstance(horiz_lines[index], list): - for line_pos in horiz_lines[index]: - if line_pos is not None: - self.__axes[index].axhline(y=line_pos, color="red", label='_nolegend_', linewidth=1) + # Third chart showing the coefficient history and the divergence threshold lines. + ax = self.__fig.add_subplot(3, 1, 3) - # Legends - if legends[index] is not None: - self.__axes[index].legend(legends[index]) + # Titles and axis labels + ax.set_title(f"Coefficient History for {symbols[0]}:{symbols[1]}") + ax.set_ylabel('Coefficient') + + # Y Limits. Coefficients range from -1 to 1 + ax.set_ylim([-1, 1]) + + # Plot data if we have history data available + if history_data_available: + # Plot. There may be more than one set of data for chart. One for each coefficient date range. Convert + # single data to list, then loop to plot + xdata = times if isinstance(times, list) else [times, ] + ydata = coefficients if isinstance(coefficients, list) else [coefficients, ] + + for i in range(0, len(xdata)): + ax.scatter(xdata[i], ydata[i], s=1) + + # Ticks, labels and formats. Fixing xticks with FixedLocator but also using MaxNLocator to avoid + # cramped x-labels + if len(times[0].array) > 0: + 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) + else: + ax.set_xticklabels([]) + + # Legend + ax.legend([f"{Config().get('monitor.calculations.long.from')} Minutes", + f"{Config().get('monitor.calculations.medium.from')} Minutes", + f"{Config().get('monitor.calculations.short.from')} Minutes"]) + + # Lines showing divergence threshold. 2 if we are monitoring inverse correlations. + if divergence_threshold is not None: + ax.axhline(y=divergence_threshold, color="red", label='_nolegend_', linewidth=1) + if monitor_inverse: + ax.axhline(y=divergence_threshold * -1, color="red", label='_nolegend_', linewidth=1) # Layout with padding between charts self.__fig.tight_layout(pad=0.5)