iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 16
1
Software Development

Python 程式交易 30 天新手入門系列 第 16

Day-16 券商串接:串接元大期貨行情 API(三)

安裝所需套件

使用 pip 安裝

# ZeroMQ
pip install zmq

行情訊號轉發

透過 ZeroMQ 的 Pub-Sub 模式實現交易與策略模組分離。

quote.py

import json
import os
import re
import sys

# 停用 loguru 的 stdout 輸出
os.environ['LOGURU_AUTOINIT'] = 'False'

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

class YuantaQuoteAXCtrl:
    def __init__(self, parent):
        self.parent = parent

        # 建立一個 ZeroMQ Publisher 用以讓其他程式註冊接收行情訊號
        self.pub = None
        try:
            loguru.logger.info(f'ZMQ Publisher is starting...\n')
            self.pub = zmq.Context().socket(zmq.PUB)
            self.pub.bind('tcp://*:10000')
            loguru.logger.success(f'ZMQ Publisher is started.\n')
        except zmq.error.ZMQError as e:
            loguru.logger.error(f'ZMQ Publisher is stopped ({e}).\n')

        container = ctypes.POINTER(comtypes.IUnknown)()
        control = ctypes.POINTER(comtypes.IUnknown)()
        guid = comtypes.GUID()
        sink = ctypes.POINTER(comtypes.IUnknown)()

        ctypes.windll.atl.AtlAxCreateControlEx(
            'YUANTAQUOTE.YuantaQuoteCtrl.1',
            self.parent.Handle,
            None,
            ctypes.byref(container),
            ctypes.byref(control),
            ctypes.byref(guid),
            sink
        )
        self.ctrl = comtypes.client.GetBestInterface(control)
        self.sink = comtypes.client.GetEvents(self.ctrl, self)

        self.Host = None
        self.Port = None
        self.Username = None
        self.Password = None

        # 儲存繪圖點位
        self.Series = []

    def Config(self, host, port, username, password):
        self.Host = host
        self.Port = port
        self.Username = username
        self.Password = password

    def XXF(self):
        today = datetime.date.today()
        day = datetime.date.today().replace(day=1)
        while day.weekday() != 2:
            day = day + datetime.timedelta(days=1)
        day = day + dateutil.relativedelta.relativedelta(days=14)
        if day < today:
            day = day + dateutil.relativedelta.relativedelta(months=1)
        codes = [
            'A',
            'B',
            'C',
            'D',
            'E',
            'F',
            'G',
            'H',
            'I',
            'J',
            'K',
            'L'
        ]
        y = day.year % 10
        m = codes[day.month - 1]
        return f'{m}{y}'

    def TXF(self):
        return f'TXF{self.XXF()}'

    def Logon(self):
        self.ctrl.SetMktLogon(
            self.Username,
            self.Password,
            self.Host,
            self.Port,
            1,
            0
        )

    # 省略未使用事件函數

    def OnGetMktAll(self,
        symbol,
        refPri,
        openPri,
        highPri,
        lowPri,
        upPri,
        dnPri,
        matchTime,
        matchPri,
        matchQty,
        tolMatchQty,
        bestBuyQty,
        bestBuyPri,
        bestSellQty,
        bestSellPri,
        fdbPri,
        fdbQty,
        fdsPri,
        fdsQty,
        reqType):
        # 如果 ZeroMQ Publisher 存在,那就將行情訊號發送至 Yuanta.Quote 這個 Topic
        if self.pub is not None:
            topic = 'Yuanta.Quote'
            payload = json.dumps({
                'symbol': symbol,
                'refPri': refPri,
                'openPri': openPri,
                'highPri': highPri,
                'lowPri': lowPri,
                'upPri': upPri,
                'dnPri': dnPri,
                'matchTime': matchTime,
                'matchPri': matchPri,
                'matchQty': matchQty,
                'tolMatchQty': tolMatchQty,
                'bestBuyQty': bestBuyQty,
                'bestBuyPri': bestBuyPri,
                'bestSellQty': bestSellQty,
                'bestSellPri': bestSellPri,
                'fdbPri': fdbPri,
                'fdbQty': fdbQty,
                'fdsPri': fdsPri,
                'fdsQty': fdsQty,
                'reqType': reqType
            })
            self.pub.send_multipart([
                topic.encode('utf-8'),
                payload.encode('utf-8')
            ])

        self.Series.append(float(matchPri))
        if len(self.Series) > 165:
            self.Series = self.Series[1:]

        now = datetime.datetime.now()
        clX = f' {now:%H:%M:%S.%f} '
        cl01 = f' {symbol: >6} '
        cl02 = f' {reqType: >4} '
        cl03 = f' {refPri: >10} '
        cl04 = f' {openPri: >10} '
        cl05 = f' {highPri: >10} '
        cl06 = f' {lowPri: >10} '
        cl07 = f' {upPri: >10} '
        cl08 = f' {dnPri: >10} '
        cl09 = f' {matchTime: >15} '
        if matchTime:
            cl09 = f' {matchTime[:2]}:{matchTime[2:4]}:{matchTime[4:6]}.{matchTime[6:]} '
        cl10 = f' {matchPri: >10} '
        cl11 = f' {matchQty: >8} '
        cl12 = f' {tolMatchQty: >8} '

        date = f'{datetime.date.today():%Y-%m-%d}'
        title = '                                                                                        MatchPrice                                                                              '
        chart = ''
        if len(set(self.Series)) > 1:
            chart = asciichartpy.plot(self.Series, {
                'width': 116,
                'height': 30
            }) 
        sys.stdout.write(
            '\033[2J' +
            f'          元大期貨                                                                                                                                                    {  date  }\n' +
            f'          ----------------------------------------------------------------------------------------------------------------------------------------------------------------------\n' +
            f'          |        接收時間 |   代碼 | 盤別 |     參考價 |     開盤價 |     最高價 |     最低價 |     漲停價 |     跌停價 |        成交時間 |   成交價位 | 成交數量 | 總成交量 |\n' +
            f'          ----------------------------------------------------------------------------------------------------------------------------------------------------------------------\n' +
            f'          |{           cl00}|{  cl01}|{cl02}|{      cl03}|{      cl04}|{      cl05}|{      cl06}|{      cl07}|{      cl08}|{           cl09}|{      cl10}|{    cl11}|{    cl12}|\n' +
            f'          ----------------------------------------------------------------------------------------------------------------------------------------------------------------------\n' +
            f'\n' +
            f'{title}\n' +
            f'\n' +
            f'{chart}\n'
        )

    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'
        )

        if status != 2:
            return

        code = self.TXF()
        loguru.logger.info(f'OnMktStatusChange: AddMktReg(code={code}, 4, reqType={reqType}, 0)\n')
        result = self.ctrl.AddMktReg(code, 4, reqType, 0)
        loguru.logger.info(f'OnMktStatusChange: AddMktReg(code={code}, 4, reqType={reqType}, 0) => {result}\n')

    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'
        )

    # 省略未使用事件函數

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

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

    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()

signal.py

import datetime
import os

import loguru
import zmq

def main():
    # 建立一個 ZeroMQ Subscriber 用以註冊接收行情訊號
    context = zmq.Context()
    socket = context.socket(zmq.SUB)
    socket.connect('tcp://localhost:10000')
    socket.setsockopt(zmq.SUBSCRIBE, b'Yuanta.Quote')
    loguru.logger.debug(f'main: receiving...')
    while True:
        message = socket.recv()
        message = message.decode('utf-8')
        loguru.logger.debug(f'main: receive message{os.linesep}{message}')
        if message == 'Yuanta.Quote':
            continue
        # 進行策略判斷與下單行為

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-15 券商串接:串接元大期貨行情 API(二)
下一篇
Day-17 計算指標:從 Tick 換算分 K 與分 MA
系列文
Python 程式交易 30 天新手入門30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
cfchenmoto
iT邦新手 5 級 ‧ 2020-07-29 16:08:39

您好

感謝您的技術文章分享
已成功根據您的步驟取得tick資料
後續想在 signal.py 內的
#進行策略判斷與下單行為
嘗試取得 message 的 matchPri 及 matchQty 進行運算
請問我該往哪個方向涉略???
目前因為隨便寫進一行像是 a=1
程式便無法執行而閃退

再次感謝您的分享

我要留言

立即登入留言