iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 14
2

安裝所需套件

使用 pip 安裝

# Win32
pip install pywin32
pip install ctypes comtypes

# 日期處理
pip install python-dateutil

# 計算文字寬度
pip install wcwidth

下載元大期貨行情 API 並安裝
http://easywin.yuantafutures.com.tw/api/download.html
下載導引

安裝元大期貨行情 API

  1. 解壓縮 ZIP 檔案。
  2. 將 QAPI 目錄複製至 C:\Yuanta\QAPI。
  3. 以系統管理員權限執行 C:\Yuanta\QAPI\install_ytocx.bat 以安裝 COM 元件。
  4. 以系統管理員權限啟動 PowerShell 並執行以下命令,如回應 True 代表安裝成功。
New-PSDrive -PSProvider registry -Root HKEY_CLASSES_ROOT -Name HKCR | Out-Null; (Get-ItemProperty -Path "HKCR:\TypeLib\{350F911B-4F62-42AF-BCD5-83B431A4BBDF}\1.0" -Name "(default)")."(default)" -eq "YuantaQuote ActiveX Control module"

命令列執行結果

串接元大期貨行情 API

透過 ATL 串接封裝元大期貨行情 API 元件為 YuantaQuoteAXCtrl,並登入服務接收訊息。

import re

import ctypes
import comtypes
import comtypes.client
import datetime
import dateutil.relativedelta
import loguru
import wcwidth
import wx

class YuantaQuoteAXCtrl:
    def __init__(self, parent):
        # 父層元件必須是頂層視窗,才能正常接收事件
        self.parent = parent

        # 準備呼叫 ATL API 時所需參數
        container = ctypes.POINTER(comtypes.IUnknown)()
        control = ctypes.POINTER(comtypes.IUnknown)()
        guid = comtypes.GUID()
        sink = ctypes.POINTER(comtypes.IUnknown)()

        # 建立 ActiveX 控制元件實體
        ctypes.windll.atl.AtlAxCreateControlEx(
            'YUANTAQUOTE.YuantaQuoteCtrl.1',
            self.parent.Handle,
            None,
            ctypes.byref(container),
            ctypes.byref(control),
            ctypes.byref(guid),
            sink
        )
        # 取得 ActiveX 控制元件實體
        self.ctrl = comtypes.client.GetBestInterface(control)
        # 綁定 ActiveX 控制元件事件
        self.sink = comtypes.client.GetEvents(self.ctrl, self)

        # 連線資訊
        self.Host = None
        self.Port = None
        self.Username = None
        self.Password = None

    # 設定連線資訊
    def Config(self, host, port, username, password):
        self.Host = host
        self.Port = port
        self.Username = username
        self.Password = password

    # 登入元大服務
    def Logon(self):
        # 登入日盤主機
        self.ctrl.SetMktLogon(
            self.Username,
            self.Password,
            self.Host,
            self.Port,
            1,
            0
        )

    # ActiveX 控制元件事件
    def OnGetBreakResume(self,
        symbol,
        breakTime,
        resumeTime,
        reqType):
        pass

    # ActiveX 控制元件事件
    def OnGetDelayClose(self,
        symbol,
        delayClose,
        reqType):
        pass

    # ActiveX 控制元件事件
    def OnGetDelayOpen(self,
        symbol,
        delayOpen,
        reqType):
        pass

    # ActiveX 控制元件事件
    def OnGetFutStatus(self,
        symbol,
        functionCode,
        breakTime,
        startTime,
        reopenTime,
        reqType):
        pass

    # ActiveX 控制元件事件
    def OnGetLimitChange(self,
        symbol,
        functionCode,
        statusTime,
        level,
        expandType,
        reqType):
        pass

    # ActiveX 控制元件事件
    def OnGetMktAll(self,
        symbol,
        refPri,
        openPri,
        highPri,
        lowPri,
        upPri,
        dnPri,
        matchTime,
        matchPri,
        matchQty,
        tolMatchQty,
        bestBuyQty,
        bestBuyPri,
        bestSellQty,
        bestSellPri,
        fdbPri,
        fdbQty,
        fdsPri,
        fdsQty,
        reqType):
        pass

    # ActiveX 控制元件事件
    def OnGetMktData(self,
        priType,
        symbol,
        qty,
        pri,
        reqType):
        pass

    # ActiveX 控制元件事件
    def OnGetMktQuote(self,
        symbol,
        disClosure,
        duration,
        reqType):
        pass

    # ActiveX 控制元件事件
    # 連線行為造成的狀態改變會從這個事件通知
    def OnMktStatusChange(self,
        status,
        msg,
        reqType):
        # 取出訊息開頭可能存在的連線狀態代碼
        code = ''
        find = re.match(r'^(?P<code>\d).+$', msg)
        if find:
            code = msg[0]
            msg = msg[1:]
        # 去除訊息結尾可能存在的驚嘆號
        if msg.endswith('!'):
            msg = msg[:-1]
        # 計算文字寬度並補上結尾空白
        msg = msg + ' ' * (50 - wcwidth.wcswidth(msg))
        # 產出欄位值
        clX = f' {datetime.datetime.now():%H:%M:%S.%f} '
        cl01 = f' {reqType: >7} '
        cl02 = f' {status: >6} '
        cl03 = f' {code: >4} '
        cl04 = f' {msg} '

        loguru.logger.info(
            f'OnMktStatusChange\n' +
            f'--------------------------------------------------------------------------------------------------\n' +
            f'|                 | reqType | status | code | msg                                                |\n' +
            f'--------------------------------------------------------------------------------------------------\n' +
            f'|{            clX}|{   cl01}|{  cl02}|{cl03}|{cl04                                              }|\n' +
            f'--------------------------------------------------------------------------------------------------\n'
        )

    # ActiveX 控制元件事件
    # 登入後不定期會接收到元大服務主機發送的時戳,供客戶端進行校時使用
    def OnGetTimePack(self,
        strTradeType,
        strTime,
        reqType):
        clX = f' {datetime.datetime.now():%H:%M:%S.%f} '
        cl01 = f' {reqType: >7} '
        cl02 = f' {strTime[:2]}:{strTime[2:4]}:{strTime[4:6]}.{strTime[6:]} '
        cl03 = f' {strTradeType: >12} '
        loguru.logger.info(
            f'OnGetTimePack\n' +
            f'--------------------------------------------------------------\n' +
            f'|                 | reqType |         strTime | strTradeType |\n' +
            f'--------------------------------------------------------------\n' +
            f'|{            clX}|{   cl01}|{           cl02}|{        cl03}|\n' +
            f'--------------------------------------------------------------\n'
        )

    # ActiveX 控制元件事件
    def OnGetTradeStatus(self,
        symbol,
        tradeStatus,
        reqType):
        pass

    # ActiveX 控制元件事件
    def OnRegError(self,
        symbol,
        updMode,
        errCode,
        reqType):
        pass

    # ActiveX 控制元件事件
    def OnTickRangeDataError(self,
        symbol,
        errCode,
        reqType):
        pass

    # ActiveX 控制元件事件
    def OnTickRegError(self,
        symbol,
        mode,
        errCode,
        reqType):
        pass

def main():
    app = wx.App()

    frame = wx.Frame(
        parent=None,
        id=wx.ID_ANY,
        title='Yuanta.Quote'
    )
    frame.Hide()

    # 加入封裝後的元大期貨 API 報價元件
    quote = YuantaQuoteAXCtrl(frame)
    # 設定連線資訊
    quote.Config(
        host='<Host>',
        port='<Port>',
        username='<Username>',
        password='<Password>'
    )
    # 登入元大服務
    quote.Logon()

    app.MainLoop()

if __name__ == '__main__':
    loguru.logger.add(
        f'{datetime.date.today():%Y%m%d}.log',
        rotation='1 day',
        retention='7 days',
        level='DEBUG'
    )
    main()

執行結果


團隊系列文:

CSScoke - 金魚都能懂的這個網頁畫面怎麼切 - 金魚都能懂了你還怕學不會嗎
Clarence - LINE bot 好好玩 30 天玩轉 LINE API
Hina Hina - 陣列大亂鬥
King Tzeng - IoT沒那麼難!新手用JavaScript入門做自己的玩具
Vita Ora - 好 Js 不學嗎 !? JavaScript 入門中的入門。
TaTaMo - 用Python開發的網頁不能放到Github上?Lektor說可以!!


上一篇
Day-13 視窗程式:使用 wxPython
下一篇
Day-15 券商串接:串接元大期貨行情 API(二)
系列文
Python 程式交易 30 天新手入門30
0
penut85420
iT邦新手 5 級 ‧ 2019-10-01 14:35:16

研究了兩年終於發現這片綠洲
請收下我的膝蓋QwQ

阿斬 iT邦新手 5 級 ‧ 2019-10-01 21:50:21 檢舉

歡迎交流,我的 LINE 是 erinus

0
pony5412
iT邦新手 5 級 ‧ 2020-02-05 10:56:39

不好意思,請教一下,我按照範例執行,一直出現

raise AttributeError(name)
AttributeError: SetMktLogon

有可能是什麼原因造成,感謝。

阿斬 iT邦新手 5 級 ‧ 2020-02-09 22:37:58 檢舉

請問是否有按照流程註冊元大期貨 API 元件?

pony5412 iT邦新手 5 級 ‧ 2020-02-11 10:41:35 檢舉

有,執行

New-PSDrive -PSProvider registry -Root HKEY_CLASSES_ROOT -Name HKCR | Out-Null; (Get-ItemProperty -Path "HKCR:\TypeLib\{350F911B-4F62-42AF-BCD5-83B431A4BBDF}\1.0" -Name "(default)")."(default)" -eq "YuantaQuote ActiveX Control module"

結果是True

阿斬 iT邦新手 5 級 ‧ 2020-04-21 18:53:34 檢舉

Python 要裝 x86 版本喔

0
skyksl066
iT邦新手 5 級 ‧ 2020-09-20 11:16:56

想請問一個問題:

系統windows 7 x86
python 3.8.5 x86
我在串接元大另外一套OneAPI的時候出現這個錯誤:

Traceback (most recent call last):
  File "C:\Users\trevor\Desktop\HSUNJEN\YuantaOneOrder.py", line 9, in <module>
    objYuantaOneAPI.Login(System.String('98875005091'), System.String('1234'))
System.NullReferenceException: 並未將物件參考設定為物件的執行個體。
   於 YuantaOneAPI.YuantaOneAPITrader.Login(String Account, String Password)

原始碼:

import clr
import System
path = "c://Yuanta//ONE//YuantaOneAPI.dll"
clr.AddReference(path)

from YuantaOneAPI import YuantaOneAPITrader
objYuantaOneAPI = YuantaOneAPITrader()
objYuantaOneAPI.Open()
objYuantaOneAPI.Login(System.String('98875005091'), System.String('1234'))

因為在網路上找不到相關資料,又剛好看到版主這篇文章,所以就想請教一下版主要怎麼處理,感恩。

skyksl066 iT邦新手 5 級 ‧ 2020-09-21 14:00:29 檢舉

已解決,open後sleep1秒就可以登入了

我要留言

立即登入留言