2023年6月17日 星期六

【Python交易程式開發現場9】使用Qt Designer設計GUI (3) | 順利使用Qt獨特的Model-View架構

終於順利使用Qt獨特的Model-View架構

.
受不了群益API的python範例檔的設計架構,
用Qt的Model-Views架構重新改寫..
把核心和介面切割開來,
介面用UI Designer設計完就沒事了,
不用和程式邏輯混在一起..
.
新版的GUI已經慢慢和群益API接上了,
好像幫家裡接電一樣,
終於電通了..
.
其實寫程式和打電動的感覺很像,
關關難過, 關關過..


Model:


def twLogin(twID, twPW):
    try:
        m_nCode = skC.SKCenterLib_SetLogPath(os.path.split(os.path.realpath(__file__))[0] + "\\CapitalLog_Quote")
        m_nCode = skC.SKCenterLib_Login(twID, twPW)
        if(m_nCode==0):
            #Global_ID["text"] =  twID   # textID: ID帳號
            SetID(twID)  #ctw focus
            print("登入成功")
            #WriteMessage("登入成功",GlobalListInformation)
        else:
            print("登入失敗")
            #WriteMessage(m_nCode,GlobalListInformation)
    except Exception as e:
        messagebox.showerror("error!",e)

def twConnect():
    try:
        m_nCode = skQ.SKQuoteLib_EnterMonitorLONG()
        #SendReturnMessage("Quote", m_nCode, "SKQuoteLib_EnterMonitorLONG",GlobalListInformation)
        return m_nCode
    except Exception as e:
        messagebox.showerror("error!",e)    

def twDisconnect():
    try:
        m_nCode = skQ.SKQuoteLib_LeaveMonitor()
        return m_nCode
        """
        if (m_nCode != 0):
            strMsg = "SKQuoteLib_LeaveMonitor failed!", skC.SKCenterLib_GetReturnCodeMessage(m_nCode)
            WriteMessage(strMsg,GlobalListInformation)
        else:
            SendReturnMessage("Quote", m_nCode, "SKQuoteLib_LeaveMonitor",GlobalListInformation)
        """    
    except Exception as e:
        messagebox.showerror("error!",e)    


ViewController:

class MyMainWindow(QMainWindow, Ui_Form):
    def __init__(self, parent=None):    
        super(MyMainWindow, self).__init__(parent)
        self.setupUi(self)
        self.pushButton.clicked.connect(self.twTest1) #ctw signal
        self.pushButton_2.clicked.connect(self.twTest2) #ctw signal
        self.pushButton_3.clicked.connect(self.twTest3) #ctw signal

    def twTest1(self):  #ctw slot function
        print("1-Login")    
        self.listWidget.addItem("1-Login")
        twLogin(ID, PW)

    def twTest2(self):  #ctw slot function
        print("2-Connect")    
        self.listWidget.addItem("2-Connect")
        self.listWidget.addItem("m_nCode: " + str(twConnect()))

    def twTest3(self):  #ctw slot function
        print("3-DisConnect")    
        self.listWidget.addItem("3-DisConnect")
        self.listWidget.addItem("m_nCode: " + str(twDisconnect()))




2023年6月16日 星期五

【Python交易程式開發現場8】使用Qt Designer設計GUI (2) | 群益API: 錯誤代碼2017之處理

 群益API: m_nCode回報錯誤代碼2017之處理

class MyMainWindow(QMainWindow, Ui_Form):
    def __init__(self, parent=None):    
        super(MyMainWindow, self).__init__(parent)
        self.setupUi(self)
        self.pushButton.clicked.connect(self.twTest) #ctw signal

    def twTest(self):  #ctw slot function
        print("test...")    
        self.listWidget.addItem("add item")
        twLogin("ID: xxxxxx", "PW: xxxxxxxxx")

        global GlobalListInformation,Global_ID
        GlobalListInformation = self.listWidget
        #Global_ID = self.labelID    

發生m_nCode回報2017錯誤(正常連線時, m_nCode回報0)


def twLogin(twID, twPW):
    try:
        m_nCode = skC.SKCenterLib_SetLogPath(os.path.split(os.path.realpath(__file__))[0] + "\\CapitalLog_Quote")
        m_nCode = skC.SKCenterLib_Login(twID, twPW)
        if(m_nCode==0):
            Global_ID["text"] =  twID   # textID: ID帳號
            SetID(twID)  #ctw focus
            print("登入成功")
            WriteMessage("登入成功",GlobalListInformation)
        else:
            WriteMessage(m_nCode,GlobalListInformation)
    except Exception as e:
        messagebox.showerror("error!",e)






因此, 在程式上方加上以下程式碼後, 2017錯誤終於順利解決了.. (sConfirmCode不能用-1, 要用0xFFFF才行)

class SKReplyLibEvent():
   
    def OnReplyMessage(self,bstrUserID, bstrMessages):
        #根據API 手冊,login 前會先檢查這個 callback,
        #要返回 VARIANT_TRUE 給 server,  表示看過公告了,我預設返回值是 0xFFFF
        #sConfirmCode = -1     # 會出現2017錯誤
        sConfirmCode = 0xFFFF  # 改後就一切正常了..
        print('OnReplyMessage 回報正常', bstrMessages) #ctw
        #WriteMessage(bstrMessages,GlobalListInformation)
        return sConfirmCode


# comtypes使用此方式註冊callback
SKReplyEvent = SKReplyLibEvent()
SKReplyLibEventHandler = comtypes.client.GetEvents(skR, SKReplyEvent)


後來再測, 發現真正的問題不在sConfirmCode=-1(也行)
而是後面的WriteMessage()出錯, 才造成OnReplyMessage沒法正常回報, 
進而導致2017錯誤..
.
所以, 只要把WriteMessage()註解掉, 問題就解決了..

class SKReplyLibEvent():
   
    def OnReplyMessage(self,bstrUserID, bstrMessages):
        #根據API 手冊,login 前會先檢查這個 callback,
        #要返回 VARIANT_TRUE 給 server,  表示看過公告了,我預設返回值是 0xFFFF
        sConfirmCode = -1     # 會出現2017錯誤
        #sConfirmCode = 0xFFFF  # 改後就一切正常了..
        print('OnReplyMessage 回報正常', bstrMessages) #ctw
        #WriteMessage(bstrMessages,GlobalListInformation)
        return sConfirmCode



2023年6月15日 星期四

【Python交易程式開發現場7】使用Qt Designer設計GUI (1) | 實現界面與邏輯分離 | Signal/Slot

 1. 使用Qt Designer



2. Convert .ui to .py

將python310\Scripts\下的pyuic6.exe拖拉至cmd視窗

Run: pyuic6.exe -x "XXX.ui" -o "XXX.py"







3. ctw1.ui to ctw1.py後之python檔案內容如下:


# Form implementation generated from reading ui file 'D:\tw-skcom-python-43\all\ctw1.ui'
#
# Created by: PyQt6 UI code generator 6.4.2
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt6 import QtCore, QtGui, QtWidgets


class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(798, 537)
        self.pushButton = QtWidgets.QPushButton(parent=Form)
        self.pushButton.setGeometry(QtCore.QRect(50, 40, 75, 24))
        self.pushButton.setObjectName("pushButton")
        self.pushButton_2 = QtWidgets.QPushButton(parent=Form)
        self.pushButton_2.setGeometry(QtCore.QRect(140, 40, 75, 24))
        self.pushButton_2.setObjectName("pushButton_2")
        self.pushButton_3 = QtWidgets.QPushButton(parent=Form)
        self.pushButton_3.setGeometry(QtCore.QRect(240, 40, 91, 24))
        self.pushButton_3.setObjectName("pushButton_3")
        self.listView = QtWidgets.QListView(parent=Form)
        self.listView.setGeometry(QtCore.QRect(50, 110, 331, 401))
        self.listView.setObjectName("listView")
        self.listWidget = QtWidgets.QListWidget(parent=Form)
        self.listWidget.setGeometry(QtCore.QRect(420, 110, 341, 401))
        self.listWidget.setObjectName("listWidget")

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "Form"))
        self.pushButton.setText(_translate("Form", "1-Login"))
        self.pushButton_2.setText(_translate("Form", "2-Connect"))
        self.pushButton_3.setText(_translate("Form", "3-Disconnect"))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    Form = QtWidgets.QWidget()
    ui = Ui_Form()
    ui.setupUi(Form)
    Form.show()
    sys.exit(app.exec()) # app.exec_()中的底線要拿掉


4. 另加一檔案: call_ctw1.py, 以實現界面與邏輯分離, 檔案內容如下:

import sys  
from PyQt6.QtWidgets import QApplication , QMainWindow
from ctw1 import *

class MyMainWindow(QMainWindow, Ui_Form):
    def __init__(self, parent=None):    
        super(MyMainWindow, self).__init__(parent)
        self.setupUi(self)
           
if __name__=="__main__":  
    app = QApplication(sys.argv)  
    myWin = MyMainWindow()  
    myWin.show()  
    sys.exit(app.exec())  


5. 加signal/slot function:

import sys  
from PyQt6.QtWidgets import QApplication , QMainWindow
from ctw1 import *

class MyMainWindow(QMainWindow, Ui_Form):
    def __init__(self, parent=None):    
        super(MyMainWindow, self).__init__(parent)
        self.setupUi(self)
        self.pushButton.clicked.connect(self.twTest) #ctw signal

    def twTest(self):  #ctw slot function
        print("test...")    
        self.listWidget.addItem("add item")

if __name__=="__main__":  
    app = QApplication(sys.argv)  
    myWin = MyMainWindow()  
    myWin.show()  
    sys.exit(app.exec())  

6. 執行結果: (按四下按鈕1)







2023年6月13日 星期二

【Python交易程式開發現場1】Tick歷史報價之取得 | 資料準備

  【Python交易程式開發現場】Tick報價程式碼 | 資料準備












【Multichart太貴, 自己DIY不就得了】
早上起床, 把Tick圖修一下(圖中sma是用Talib計算出來的)..
其實, 到這個階段, 所有會用到的群益API功能都在掌握之下了..
有了即時Tick, 歷史Tick,
接下來就是將Tick轉成分K, 接著就能產出自訂交易信號,
加上Talib相關訊號碼都已備妥,
接著只要將訊號碼橋接入位即搞定prototype版自動交易程式,
剩下的都是trival work了..
.
以後有時間再用pyside慢慢將程式美容, 拉皮, 加上些互動,
就可完成自己DIY版的Multichart了..









哇哈, 時間格式有夠難搞..
開debug Console,
用顯微除錯模式慢慢解決..
.
原來群益API的時間是用float回傳HHMMSS, 真是有夠奇怪..
正常不是應該回傳字串嗎?
難怪我用strptime,會一直解析不出來時間。。





【Python交易程式開發現場2】1分K(1) | 資料準備 | 群益API的1分K只有昨天以前的60000根歷史資料

原來群益API的1分K只有歷史資料(昨天以前的, 約6萬筆.. 超過一個月的歷史資料, 夠短線交易使用了..),沒有即時的1分K,得自己用Tick組一分K了。。


群益API可以取得從上市櫃開始到今日的所有【日K】歷史報價,有了歷史報價就可以做很多樣回測。

【一分K】歷史報價目前可以取得約 60000 根的資料,且幾秒鐘內就可以取回來


參考資料1:







原來群益API的1分K只有歷史資料(昨天以前的, 約6萬筆.. 超過一個月的歷史資料, 夠短線交易使用了..),沒有即時的1分K,得自己用Tick組一分K了。。




prog-0318

test test1 Written with StackEdit .