網頁

2019年2月24日 星期日

在 Python 使用者介面(GUI)中顯示台指期五分鐘K線圖

最近在學習 Python 的使用者介面(GUI)設計 ,  先從最基本的 tkinter 下手 , 接下來就是把之前學的 panda , matplotlib 及 K線圖呈現到 Python 的使用者介面中 .


我的目標是一邊學習 Python 程式設計 , 順便打造我的 [台指期即時自動當沖交易系統 第三代] , 首先設計一個視窗主程式單獨放在一隻 Python 程式中 , 其他的功能各自獨立放在個別的 Python 程式中 , 最後再將這些個別的功能集中透過這個視窗主程式來使用及呈現 .

視窗主程式程式碼如下 :

# 匯入使用者介面(GUI)套件
import tkinter as tk
from tkinter import *

# 匯入 AFDTS3 類別及函式
import AFDTS as a, Ticks2K5 as t

# ----------------------------------------------------------------------------

class MainPage(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        container = tk.Frame(self)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.frames = {}
        self.frames["AFDTS"] = a.AFDTS(parent=container, controller=self)
        self.frames["AFDTS"].grid(row=0, column=0, sticky="nsew")
     
        self.frames["TicksToK5"] = t.TicksToK5(parent=container,
                   controller=self)
        self.frames["TicksToK5"].grid(row=0, column=0, sticky="nsew")
                     
        self.show_frame("AFDTS")

    def show_frame(self, page_name):     
        frame = self.frames[page_name]
        frame.tkraise()

# ----------------------------------------------------------------------------
if __name__ == '__main__':
    # 創建根視窗
    afdts3 = MainPage()
    afdts3.iconbitmap('afdts.ico')
    afdts3.geometry('1024x768')
    afdts3.title('台指期自動當沖交易系統 (第三代)')
 
    # 主選單
    mmenu = Menu(afdts3, font=('標楷體'))
 
    # 創建主選單內容
    smenu = Menu(mmenu, tearoff=0) 
    smenu.add_command(label="台指期即時自動當沖交易系統",
                      command=lambda:afdts3.show_frame("AFDTS"))
    smenu.add_separator() 
    smenu.add_command(label="關閉程式", command=afdts3.quit)
    mmenu.add_cascade(label="交易", menu=smenu)

    smenu = Menu(mmenu, tearoff=0)
    smenu.add_command(label="用原始資料畫K線圖",
                      command=lambda:afdts3.show_frame("TicksToK5")) 
    mmenu.add_cascade(label="工具", menu=smenu)
 
    afdts3['menu'] = mmenu
 
    afdts3.mainloop()
# ----------------------------------------------------------------------------

執行結果如下 :



TicksToK5 類別的程式碼如下 :

# 匯入使用者介面(GUI)套件
import tkinter as tk
import tkinter.filedialog as fd
from tkinter import messagebox,colorchooser,font,Button,Frame,Label,Entry
# 匯入資料處理套件
import pandas as pd
import xlrd
# 匯入畫K線圖套件
import mpl_finance as mpf
from mpl_finance import candlestick_ochl
# 匯入畫圖套件
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
from matplotlib import dates as mdates
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk
from matplotlib.gridspec import GridSpec
# 讓 matplotlib 可以顯示中文
plt.rcParams['font.sans-serif'] = ['Microsoft JhengHei']
plt.rcParams['axes.unicode_minus'] = False

# ----------------------------------------------------------------------------

# 用原始資料畫K線圖 的 類別及函式
class TicksToK5(Frame):
 
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.controller = controller
     
        self.createWidgets()
     
    def createWidgets(self):
        self.source_file = tk.StringVar()
     
        self.up_fm = Frame(self, height=68)
        self.up_fm.pack(padx=10, pady=10, fill=tk.X)
     
        self.lb_title = Label(self.up_fm, text="用原始資料畫K線圖",
                              font=('標楷體', 20))
        self.lb_title.place(rely=0.2, relx=0.5, anchor='center')
     
        self.btn_select_file = Button(self.up_fm,
                                      text="選取原始資料檔",
                                      font=('標楷體', 12),
                                      command=lambda:self.chooseFile())
        self.btn_select_file.place(rely=0.8, relx=0.3, anchor='e')
     
        self.ent_file = Entry(self.up_fm, textvariable=self.source_file,
                              width=50, font=('標楷體', 12))
        self.ent_file.place(rely=0.8, relx=0.5, anchor='center')
     
        self.btn_draw_k_line = Button(self.up_fm,
                                      text="畫K線圖",
                                      font=('標楷體', 12),
                                      command=lambda:self.DrawKLine())
        self.btn_draw_k_line.place(rely=0.8, relx=0.7, anchor='w')
     
       
    def chooseFile(self):     
        f_name = fd.askopenfilename()
        self.source_file.set(f_name)
 
    def DrawKLine(self):
        # 從 Excel 讀入 ticks row data
        fileName = self.source_file.get()
        product = fileName[-17:-14]
        title = fileName[-13:-5]
        date_title = title[:4] + '/' + title[4:6] + '/' + title[6:]
     
        if product == 'MXF' :
            date_title = '小型台指期 - ' + date_title
        else :
            date_title = '台指期 - ' + date_title
         
        book = xlrd.open_workbook(fileName)
        sheet = book.sheet_by_index(0)

        # 先開好一個空的 DataFrame
        ticks = pd.DataFrame(columns=['date', 'open', 'close', 'high', 'low',
                              'volume', 'time'])

        for row in range(sheet.nrows):
            timesecond = sheet.cell_value(row,1)
            price = sheet.cell_value(row,2) / 100
            volume = sheet.cell_value(row,3)

            newRow = [[timesecond, price, price, price, price,
                       volume, timesecond]]
            ticks = ticks.append(pd.DataFrame(newRow, columns=['date', 'open',
                                                       'close', 'high',
                                                       'low', 'volume',
                                                       'time'
                                                       ]), ignore_index=True)

     
        ticks['date'] = pd.to_datetime(ticks['date'], unit='s')
        ticks['time'] = pd.to_timedelta(ticks['time'], unit='s')
        ticks.index = ticks['date']
     
        k5 = pd.DataFrame(columns=['date', 'open', 'close', 'high', 'low',
                           'volume', 'time'])
 
        k5 = ticks.resample('5T', closed='left', label='left').apply({
                'open':'first', 'close':'last', 'high':'max',
                'low':'min', 'volume':'sum', 'time':'first'})

        k5.reset_index(inplace=True)

# ------------------------------------------------------------------
     
        # 銷毀前一個 Frame
        try:
            self.down_fm.destroy()
            self.down_fm.pack_forget()
        except :
            print('self.down_fm does not exist !')
     
        # 開一個新的 Frame
        self.down_fm = Frame(self, height=700)
        self.down_fm.pack(padx=10, pady=10, fill=tk.X)

        # ----------------------------------------------------------------------

        k5['date'] = k5['date'].apply(mdates.date2num)
     
# 用 plt.Figure() , 不是 plt.figure()
        fig = plt.Figure(figsize=(15,8))
     
# 用 GridSpec 設定 fig 為 3列 * 1行
        gs = GridSpec(3, 1, figure=fig)

        # ax 會佔用上兩列的空間
        ax = fig.add_subplot(gs.new_subplotspec((0, 0), rowspan=2))
     
        candlestick_ochl(ax, k5.values, width=0.001, colorup='#ff1717',
                 colordown='#53c156', alpha=1.0)

   
        ax.set_title(date_title)
        ax.set_ylabel('指數')
        ax.set_xticks(k5['date'])
        ax.set_xticklabels(k5['time'], rotation=30, ha='right',
                           fontsize='small')

        ax.grid(True)

        # ax2 會佔用最下面那列的空間
        ax2 = fig.add_subplot(gs[-1, :])
     
        mpf.volume_overlay(ax2, k5['open'], k5['close'],
                           k5['volume'], width=0.3,
                           colorup='#ff1717', colordown='#53c156', alpha=1.0)

        ax2.set_ylabel('成交量')
        ax2.set_xticks(range(0, len(k5['date'])))
        ax2.set_xticklabels(k5['time'], rotation=30, ha='right',
                            fontsize='small')
        ax2.set_xlabel('時間')
        ax2.grid(True)
     
        fig.subplots_adjust(bottom=0.2)
        fig.subplots_adjust(hspace=0.4)

# 在 tkinter 的 Frame 中畫K線圖
        canvas = FigureCanvasTkAgg(fig, self.down_fm)
        canvas.get_tk_widget().pack()
        canvas.draw()
        # 顯示K線圖的 Tool Bar
        toolbar = NavigationToolbar2Tk(canvas, self.down_fm)
        toolbar.update()
        canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=True)

執行結果如下 :



沒有留言:

張貼留言