2024-01-30

QThreadPool を使う ~ PySide6

PySide (Qt for Python) は、Qt(キュート)の Python バインディングで、GUI などを構築するためのクロスプラットフォームなライブラリです。Linux/X11, macOS および Microsoft Windows をサポートしています。配布ライセンスは LGPL で公開されています。

以前、本ブログで PySide2 と mplfinance を組み合わせた GUI サンプルを紹介しました [1]。また、PySide6 に GUI ライブラリを更新したサンプルも既に紹介しています [2]

今回は yfinance の API を利用して株価データを取得する部分を、QThreadPool / QRunnable で別スレッドにして読み込むようにしました。データの読み込みにそれほど時間が掛かるわけでもないのですが、スレッドの練習題材にしたくて、ローソク足チャートのサンプルを再び取り上げました。

下記の OS 環境で動作確認をしています。

Fedora Workstation 39 x86_64
Python 3.12.1
PySide6 6.6.1
matplotlib 3.8.2
mplfinance 0.12.10b0
pandas 2.2.0
yfinance 0.2.36

サンプル

サンプルはあまりコンパクトにできなかったので、下記の4つのファイルに分けました。

  • qt_mpl_finance_2.py
    • GUI サンプルのメイン
  • qt_mpl_finance_2_func.py
    • 関数処理
  • qt_mpl_finance_2_sub.py
    • GUI のサブ・コンポーネント
  • qt_mpl_finance_2_thread.py
    • QRunner によるスレッド処理

qt_mpl_finance_2.py は、本サンプルのメイン部分で、下記の GUI を QMainWindow で構成しています。

qt_mpl_finance_2.py の実行例 (1)
qt_mpl_finance_2.py
import pandas as pd
import sys
from PySide6.QtCore import (
QThreadPool,
Qt,
)
from PySide6.QtWidgets import (
QApplication,
QMainWindow,
QWidget,
)
from qt_mpl_finance_2_func import draw_chart
from qt_mpl_finance_2_sub import (
DockNavigator,
MyToolBar,
StockChart,
)
from qt_mpl_finance_2_thread import ThreadWorker
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.threadpool = QThreadPool()
self.init_ui()
self.setWindowTitle('Candlestick chart')
self.resize(800, 600)
def init_ui(self):
toolbar = MyToolBar()
toolbar.tickerEntered.connect(self.on_ticker_entered)
self.addToolBar(toolbar)
chart = StockChart()
self.setCentralWidget(chart)
dock_bottom = DockNavigator(chart)
self.addDockWidget(
Qt.DockWidgetArea.BottomDockWidgetArea,
dock_bottom
)
def on_ticker_entered(self, ticker: str):
worker = ThreadWorker(ticker)
worker.finished.connect(self.on_draw_chart)
self.threadpool.start(worker)
def on_draw_chart(self, ticker: str, df: pd.DataFrame):
chart: QWidget | StockChart = self.centralWidget()
draw_chart(chart, ticker, df)
def main():
app = QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec())
if __name__ == '__main__':
main()

qt_mpl_finance_2_func.py は、yfinance で株価データを読み込む処理と、mplfinance でデータフレームをキャンバス上に描画する処理を関数としてまとめてあります。

qt_mpl_finance_2_func.py
import mplfinance as mpf
import pandas as pd
import yfinance as yf
from qt_mpl_finance_2_sub import StockChart
def draw_chart(chart:StockChart, ticker:str, df:pd.DataFrame):
chart.clearAxes()
chart.ax.set_title(ticker)
mpf.plot(
df,
type='candle',
datetime_format='%m/%d',
mav=(5, 25),
tight_layout=False,
style='yahoo',
ax=chart.ax,
volume=chart.ax2
)
chart.ax.grid()
chart.ax2.grid()
chart.refreshDraw()
def get_trade_info(ticker: str) -> pd.DataFrame:
df = yf.download(ticker, period='3mo')
return df

qt_mpl_finance_2_sub.py は、メインの QMainWindow の GUI を構成する、ツールバー、中心部のチャート用のキャンバス、Matplotlib のナビゲーション・ツールバーを表示する下部のドックを記述しています。

qt_mpl_finance_2_sub.py
from PySide6.QtCore import Signal
from PySide6.QtWidgets import (
QDockWidget,
QLineEdit,
QToolBar,
QWidget,
)
from matplotlib import pyplot as plt
from matplotlib.backends.backend_qtagg import (
FigureCanvasQTAgg as FigureCanvas,
NavigationToolbar2QT as NavigationToolbar,
)
from matplotlib.figure import Figure
class DockNavigator(QDockWidget):
def __init__(self, canvas):
super().__init__()
self.setTitleBarWidget(QWidget(None))
navtoolbar = NavigationToolbar(canvas, self)
self.setWidget(navtoolbar)
class MyToolBar(QToolBar):
tickerEntered = Signal(str)
def __init__(self):
super().__init__()
ent_ticker = QLineEdit()
ent_ticker.setFixedWidth(100)
ent_ticker.returnPressed.connect(self.on_ticker_entered)
self.addWidget(ent_ticker)
def on_ticker_entered(self):
entry: QLineEdit = self.sender()
ticker = entry.text()
self.tickerEntered.emit(ticker)
class StockChart(FigureCanvas):
plt.rcParams['font.family'] = plt.rcParams['font.monospace'][0]
plt.rcParams['font.size'] = 9
def __init__(self):
self.fig = Figure()
super().__init__(self.fig)
grid = plt.GridSpec(3, 1, wspace=0.0, hspace=0.0)
self.ax = self.fig.add_subplot(grid[0:2, 0])
self.ax2 = self.fig.add_subplot(grid[2, 0], sharex=self.ax)
def clearAxes(self):
self.ax.cla()
self.ax2.cla()
def refreshDraw(self):
self.fig.canvas.draw()

qt_mpl_finance_2_thread.py は、yfinance で株価データを読み込む処理を別スレッドで実行する QRunnable クラスを継承した ThreadWorker クラスです。

qt_mpl_finance_2_thread.py
import pandas as pd
from PySide6.QtCore import (
QObject,
QRunnable,
Signal,
)
from qt_mpl_finance_2_func import get_trade_info
class ThreadWorkerSignal(QObject):
finished = Signal(str, pd.DataFrame)
class ThreadWorker(QRunnable, ThreadWorkerSignal):
def __init__(self, ticker: str):
super().__init__()
self.ticker = ticker
def run(self):
df = get_trade_info(self.ticker)
self.finished.emit(self.ticker, df)

QRunnable クラスを継承した ThreadWorker のインスタンスでは、別スレッドで処理を実行させることができますが、そのままでは、処理の終了や結果を Signal を発して親スレッドへ送ることができません。そのため、このサンプルでは、QObject を継承したシグナル専用のクラス ThreadWorkerSignal を作成しておいて、QRunnable と一緒に多重継承して、ThreadWorkerSignal で定義したシグナルを利用できるようにしています。

class ThreadWorkerSignal(QObject):
    finished = Signal(str, pd.DataFrame)
 
 
class ThreadWorker(QRunnable, ThreadWorkerSignal):
    def __init__(self, ticker: str):
        super().__init__()
        self.ticker = ticker
 
    def run(self):
        df = get_trade_info(self.ticker)
        self.finished.emit(self.ticker, df)

この ThreadWorker は、本体 qt_mpl_finance_2.py 側で、下記のメソッド内でインスタンス worker を生成して、QThreadPool のインスタンス self.threadpoolstart メソッドにインスタンス worker を渡します。この start メソッドが実行されると、別スレッドで worker.run() が実行されます。

def on_ticker_entered(self, ticker: str):
    worker = ThreadWorker(ticker)
    worker.finished.connect(self.on_draw_chart)
    self.threadpool.start(worker)

worker の処理が終わると、finished シグナルが発せられるので、これを self.on_draw_chart メソッド(スロット)で処理します。

def on_draw_chart(self, ticker: str, df: pd.DataFrame):
    chart: QWidget | StockChart = self.centralWidget()
    draw_chart(chart, ticker, df)

サンプルの使い方

東証に上場している銘柄であれば、ツールボックスの QLineEdit のインスタンスに、銘柄コードに .T を付加した文字列(例 1301.T)を入力して Enter すると、過去3ヶ月分の日足チャートが出力されます。

qt_mpl_finance_2.py の実行例 (2)

日経平均の指数は ^N255 と入力して Enter します。米国銘柄はアルファベットのシンボルを入力します。

qt_mpl_finance_2.py の実行例 (3)

サンプルということで、存在しないコードを入力したときなどのエラー処理は一切していません。🙇🏻

参考サイト

  1. bitWalk's: ローソク足チャート ~ python/mplfinance [2021-05-02]
  2. bitWalk's: ローソク足チャート ~ PySide6, mplfinance [2023-08-15]
  3. QThreadPool - Qt for Python
  4. QRunnable - Qt for Python
  5. ranaroussi/yfinance: Download market data from Yahoo! Finance's API
  6. matplotlib/mplfinance: Financial Markets Data Visualization using Matplotlib

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ オープンソースへ
にほんブログ村

オープンソース - ブログ村ハッシュタグ
#オープンソース



0 件のコメント: