matplotlib/mplfinance

How to display a mplfinance chart in a Qt window using PyQt6

Closed this issue · 5 comments

I would like to display a mplfinance chart in a Qt window using PyQt6.
How do I attach a mplfinance chart in a Qt layout? Or any other way to get it in a Qt window?

Here is a simple example.
See below the example for comments:

import pandas as pd
import mplfinance as mpf
import sys

# immediately after import matplotlib,
# tell matplotlib to use the `QtAgg` backend:
import matplotlib
matplotlib.use('QtAgg')

from PyQt6 import QtCore, QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg

#=====================================================================#

class MplCanvas(FigureCanvasQTAgg):
    def __init__(self, figure):
        super(MplCanvas, self).__init__(figure)


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        # Create an mplfinance plot, AND be sure to set
        # `returnfig=True` to get the figure from mplfinance:

        infile = 'yahoofinance-SPY-20200901-20210113.csv'
        df = pd.read_csv(infile, index_col=0, parse_dates=True).iloc[0:90]
        fig, axes = mpf.plot(df,type='candle',volume=True,mav=(10,20),figscale=1.5,returnfig=True)

        # now create an MplCanvas object from the figure
        # set it as the CentralWidget in this MainWindow
        # and then call .show to display:
        sc = MplCanvas(fig)
        self.setCentralWidget(sc)
        self.show()


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec()

Comments:

  1. You can get the input file here: https://github.com/matplotlib/mplfinance/blob/master/examples/data/yahoofinance-SPY-20200901-20210113.csv
    Just click the "download raw file" button on the right side
    (or click the "copy raw file" button, also on the right side, and paste into your own file).

  2. Once you have the input file, you should be able to put the above example into a .py file and run it with python.

  3. I honestly know almost nothing about PyQt. To create the above example, I did the following:
    a. Web search "PyQt6 basics" and determined need to run pip install PyQt6 and pip install pyqt6-tools to install PyQt6.
    b. Then web searched "displaying matplotlib plots in PyQt6" and found (among other things) this useful page:
    https://www.pythonguis.com/tutorials/pyqt6-plotting-matplotlib/

    c. Based only on the "A Simple Example" near the top of the above pythonguis page, and some knowlege of mplfinance, I then wrote the above simple mplfinance in pyqt6 example.

  4. Given the above, I'd recommend reading the rest of the above pythonguis page
    AND this mplfinance page, and links therein, about accessing mplfinance Figure and Axes objects.

hth

Thank you so much Daniel. That is really good code to learn.
Congratulation to mplfinance. Super library.
Will get back to you after some learning how it worked.

Hi Daniel,

your example works great. In which GUI do you use your library? Or just Jupiter or CoLab?

Now my next problem:
When I put 2 windows on the canvas the mplfinace part does nor show the stock chart any more, just an empty canvas with grid. Both windows work with moving and all parts of the toolbar.

Here my code:

import sys, os
from PyQt6.QtWidgets import (QApplication, QWidget, QMainWindow, QVBoxLayout)

import matplotlib
matplotlib.use('QtAgg')
import pandas as pd
from matplotlib.backends.backend_qtagg import (FigureCanvasQTAgg,
        NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import yfinance as yf
import mplfinance as mpf

class MplCanvas(FigureCanvasQTAgg):
    def __init__(self, parent=None, width=10, height=8, dpi=100):
        fig=Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        super().__init__(fig)

class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        sc1 = MplCanvas(self, width=5, height=4, dpi=100)
        df = pd.DataFrame([
            [0,10,2.876],
            [5,15,6],
            [2,20,5],
            [15,25,25],
            [4,10,0],
            ],
            columns=["A","B","C"])
        df.plot(ax=sc1.axes)
        ticker_symbol = 'AAPL'
        header = ticker_symbol + ' Candlestick Chart'
        s_d = yf.download(ticker_symbol,start='2023-09-01',end='2024-10-01')
        fig = mpf.plot(s_d, type='candle', style='charles', title=header,returnfig=True)
        sc2 = MplCanvas(fig)

        toolbar = NavigationToolbar(sc1, self)
        toolbar2 = NavigationToolbar(sc2, self)
        layout = QVBoxLayout()
        layout.addWidget(toolbar2)
        layout.addWidget(sc2)
        layout.addWidget(toolbar)
        layout.addWidget(sc1)
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)
        self.show()
        print(s_d)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

@OneManShow0815
I don't know when I will have time again to play with the code, but by just a quick glance at your code, I see you are calling .plot() directly on the DataFrame, which means that you are giving Pandas the responsibility to manage the plot.

I do not know what Pandas will do that may or may not interfere with how mplfinance is using matplotlib. It may be fine/ok to call df.plot() however you may try also calling sc1.axes.plot() and passing in data from your data frame. That is, don't let Pandas do the plotting for you, but do it directly yourself with axes.plot(). See if that does anything different.

Also, I just now noticed this, and this is definitely a problem, maybe the problem: It appears you have redefined MplCanvas differently than I have. In my case, I pass the Figure object from mplfinance.plot() into the MplCanvas constructor. But your constructor creates it's own Figure object. You will need to either be consistent with how MplCanvas works, or alternatively create two MplCanvas versions. (I recommend consistency).

@DanielGoldfarb
Great help from you. It works!! Thanks so much.
To change my MplCanvas was one thing, consistency is the key, 2nd: your hint to df.plot() was super. First you create a subplot and the pass the plot into the figure of the subplot with (ax=ax):

        fig1, ax = plt.subplots()
        df.plot(ax=ax)
        sc1 = MplCanvas(fig1)

Just the Qt window cuts some title text and some axes txt off. Need to figure that out:

import sys, os
from PyQt6.QtWidgets import (QApplication, QWidget, QMainWindow, QVBoxLayout)
import matplotlib
matplotlib.use('QtAgg')
import pandas as pd
from matplotlib.backends.backend_qtagg import (FigureCanvasQTAgg,
        NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import yfinance as yf
import mplfinance as mpf

class MplCanvas(FigureCanvasQTAgg):
    def __init__(self, fig):
        super(MplCanvas,self).__init__(fig)

class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        #Pandas data
        df = pd.DataFrame([
            [0,10,2.876],
            [5,15,6],
            [2,20,5],
            [15,25,25],
            [4,10,0],
            ],
            columns=["A","B","C"])

        #Pandas plot
        fig1, ax = plt.subplots()
        df.plot(ax=ax)
        sc1 = MplCanvas(fig1)

        # finance data
        ticker_symbol = 'AAPL'
        header = ticker_symbol + ' Candlestick Chart'
        s_d = yf.download(ticker_symbol,start='2023-09-01',end='2024-10-01')
        fig2, axes = mpf.plot(s_d, type='candle', style='charles', title=header,returnfig=True)
        sc2 = MplCanvas(fig2)

        #PyQt6 setup
        toolbar = NavigationToolbar(sc1, self)
        toolbar2 = NavigationToolbar(sc2, self)
        layout = QVBoxLayout()
        layout.addWidget(toolbar2)
        layout.addWidget(sc2)
        layout.addWidget(toolbar)
        layout.addWidget(sc1)
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)
        self.show()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

The output:
ScreenShot1