自動売買ソフトにチャート表示を付けるため、PyQtGraphを使いました。リアルタイムグラフはPyQtGraph のほうが、Matplotlabより使い勝手がいいのですが、横軸を時間軸にするのに少してこずったので紹介しておきます。

時間軸データの変換

まず時間軸のデータは、UNIX時間(エポック秒)に変換します。 以下にサンプルです。PandasのDataFrameにデータを入れて、時間データ部分を変換するのですが、最終的にはmapを使う方法を採用しました。astypeでintにしたあとに割り算する方法ですが、最初、//演算でやってたら(当然ですが)、秒以下が切り捨てられてしまってしばらく悩みました。

# time を index にする
df.set_index('time', inplace=True)
# UNIX時間(エポック秒)にする
'''
	他の書き方
        以下の書き方のサンプルだと、秒単位でns単位が切り捨てられる
        self.dt = df.index.astype(np.int64)//10**9
        以下のやり方だと、ns単位
        self.dt = df.index.astype(np.int64)/10**9
'''
self.dt = df.index.map(pd.Timestamp.timestamp)

時間軸の表示

AxisItemから継承して時間軸の表示フォーマットを指定します。拡大、縮小すれば自動的に変わります。

# ----- 時間軸の表示フォーマットを指定 -----
class TimeAxisItem(pg.AxisItem):
    
    def __init__(self, *args, **kwargs):
        super(TimeAxisItem, self).__init__(*args, **kwargs)
    
    def tickStrings(self, values, scale, spacing):
        return [datetime.datetime.fromtimestamp(v).strftime('%m-%d %H:%M:%S') for v in values]


PlotWidgetで時間軸を指定してグラフを作成

PlotWidgetのaxisItemに、先ほどの時間軸表示のクラスを指定してあげます。

# dockarea を準備
dockarea = pgda.DockArea()
self.setCentralWidget(dockarea)
# Chart を準備
setprop = lambda x: (x.showGrid(x=True, y=True),
                     x.showAxis('right'),
                     x.hideAxis('left'))        
plt_chart = pg.PlotWidget(axisItems={'bottom': TimeAxisItem(orientation='bottom')})
setprop(plt_chart)
# dockaarea へ追加
dockarea.addDock(pgda.Dock('chart', widget=plt_chart))



出来栄え

サンプルコード全体

# coding: utf-8
import sys
import datetime
import pandas as pd
from PyQt6.QtWidgets import *
import pyqtgraph as pg 
import pyqtgraph.dockarea as pgda

# ----- Sample Data -----
data =[ 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 0, 105000), 'val_A': 36059.0, 'val_B': 36063.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 0, 364000), 'val_A': 36060.0, 'val_B': 36065.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 0, 364000), 'val_A': 36060.0, 'val_B': 36065.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 0, 670000), 'val_A': 36061.0, 'val_B': 36066.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 1, 17000), 'val_A': 36061.0, 'val_B': 36066.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 1, 270000), 'val_A': 36061.0, 'val_B': 36065.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 1, 624000), 'val_A': 36061.0, 'val_B': 36065.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 1, 927000), 'val_A': 36061.0, 'val_B': 36065.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 2, 226000), 'val_A': 36061.0, 'val_B': 36066.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 2, 530000), 'val_A': 36061.0, 'val_B': 36065.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 2, 530000), 'val_A': 36061.0, 'val_B': 36065.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 3, 71000), 'val_A': 36061.0, 'val_B': 36066.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 3, 370000), 'val_A': 36061.0, 'val_B': 36065.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 3, 370000), 'val_A': 36061.0, 'val_B': 36065.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 3, 370000), 'val_A': 36061.0, 'val_B': 36065.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 3, 873000), 'val_A': 36064.0, 'val_B': 36068.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 4, 166000), 'val_A': 36062.0, 'val_B': 36067.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 4, 166000), 'val_A': 36062.0, 'val_B': 36067.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 4, 467000), 'val_A': 36062.0, 'val_B': 36067.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 4, 467000), 'val_A': 36062.0, 'val_B': 36067.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 4, 467000), 'val_A': 36062.0, 'val_B': 36067.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 5, 76000), 'val_A': 36062.0, 'val_B': 36067.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 5, 374000), 'val_A': 36063.0, 'val_B': 36068.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 5, 374000), 'val_A': 36063.0, 'val_B': 36068.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 5, 668000), 'val_A': 36062.0, 'val_B': 36067.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 5, 974000), 'val_A': 36063.0, 'val_B': 36068.0} , 
 {'time': datetime.datetime(2024, 2, 2, 0, 25, 6, 120000), 'val_A': 36065.0, 'val_B': 36069.0} 
]

# ----- 時間軸の表示フォーマットを指定 -----
class TimeAxisItem(pg.AxisItem):
    
    def __init__(self, *args, **kwargs):
        super(TimeAxisItem, self).__init__(*args, **kwargs)
    
    def tickStrings(self, values, scale, spacing):
        return [datetime.datetime.fromtimestamp(v).strftime('%m-%d %H:%M:%S') for v in values]

# ----- TestChart -----
class TestChart(QMainWindow):
    """
    DockAreaにPlotWidgetを表示する
    """
    def __init__(self):
        super(TestChart, self).__init__()

        # DataFrameにしておく
        df = pd.DataFrame(data)        
        # time を index にする
        df.set_index('time', inplace=True)
        # UNIX時間(エポック秒)にする
        '''
            他の書き方
            以下の書き方のサンプルだと、秒単位でns単位が切り捨てられる
            self.dt = df.index.astype(np.int64)//10**9
            以下のやり方だと、ns単位
            self.dt = df.index.astype(np.int64)/10**9
        '''
        self.dt = df.index.map(pd.Timestamp.timestamp)
        
        # dockarea を準備
        dockarea = pgda.DockArea()
        self.setCentralWidget(dockarea)
        # Chart を準備
        setprop = lambda x: (x.showGrid(x=True, y=True),
                             x.showAxis('right'),
                             x.hideAxis('left'))        
        plt_chart = pg.PlotWidget(axisItems={'bottom': TimeAxisItem(orientation='bottom')})
        setprop(plt_chart)
        # dockaarea へ追加
        dockarea.addDock(pgda.Dock('chart', widget=plt_chart))

        # データを描画
        plt_chart.addItem(pg.PlotDataItem(self.dt, df['val_A'], pen='w'))
        plt_chart.addItem(pg.PlotDataItem(self.dt, df['val_B'], pen='r'))

# ----- MainWindow -----
class TestMainWindow(QMainWindow):
    """
    QMainWindowにチャートを表示する
    """
    def __init__(self):
        super(TestMainWindow, self).__init__()
        self.setGeometry(200, 200, 800, 600)
        self.setCentralWidget(TestChart())
        self.show()
    
def main():    
    app = QApplication(sys.argv)
    test=TestMainWindow()
    sys.exit(app.exec())

if __name__ == '__main__':
    main()