title

GMOコインにFX取引が追加され、APIも使えるようになりました。 GMOコインが扱うFXの特徴は、なんといっても1通貨単位から取引できることでしょう。(APIを利用する場合の最小取引単位は1000通貨でしたが…)既に、Selenium での自動取引は運転していますが、APIが使えるようになったのでUpdateしていこうと思います。まずはAPIラッパーを準備します。

参考にした仮想通貨のAPIラッパー

GMOコインの仮想通貨取引は以前からAPIが使えてラッパーもいくつか紹介されていますが、FX用のものが見当たらないのでドースーさんの仮想通貨用のラッパーを参考にしてFX用のラッパーを作成しました。

note(ノート)
【GMOコイン】APIラッパー【python】|ドースー
こんにちは。ドースー(@dosu0217)です。 自分のBotでも利用しているGMOコインで自動売買などをするためのAPIラッパーを公開します。 GMOコインのAPIドキュメントはこちら APIドキュメント| GMOコイン GMOコインが提供するAPIのドキュメントページです。認証不要のPublic APIと、APIキーによる認証が必要なPri api.coin.z.com なお、このソースコードはこちらのニッケルメッキ先生が公開しているbitFlyer APIのラッパークラスの記事をとても参考にしています。 とても勉強になるソースですのでよかったら見
dummy

注意点

API経由だと、残念ながら1通貨単位の発注ができないです。最小単位が1000になってます。あと、API経由での発注ですと手数料がかかります。これらのことから、発注は元々やっていたSeleniumでの発注のままにしたいと思っています。
ということで、発注関係はテストしてないので、ミスがあるかもしれません。バグがあったら教えてくださると嬉しいです。

ソースコード

GMOCoinFX_API_Wrapper.py

# gmocoinfx_api_wrapper.py

import aiohttp
import asyncio
import async_timeout
import json
from aiohttp import WSMsgType
import traceback
import time
from datetime import datetime
import hmac
import hashlib
import urllib
from secrets import token_hex

class GMOCoinFX():

    # 定数
    TIMEOUT = 3600               # タイムアウト
    EXTEND_TOKEN_TIME = 3000     # アクセストークン延長までの時間
    URLS = {'public': 'https://forex-api.coin.z.com/public',
            'private': 'https://forex-api.coin.z.com/private',
            'publicWS': 'wss://forex-api.coin.z.com/ws/public/v1',
            'privateWS': 'wss://forex-api.coin.z.com/ws/private/v1',
            }    

    # 変数
    api_key = ''
    api_secret = ''

    session = None          # セッション保持
    requests = []           # リクエストパラメータ
    token = ''              # Private Websocket API用トークン

    # ------------------------------------------------ #
    # init
    # ------------------------------------------------ #
    def __init__(self, api_key, api_secret):
        # APIキー・SECRETをセット
        self.api_key = api_key
        self.api_secret = api_secret
       
    # ------------------------------------------------ #
    # async request for rest api
    # ------------------------------------------------ #
    def set_request(self, method, access_modifiers, target_path, params):
        if access_modifiers == 'public':
            url = ''.join([self.URLS['public'], target_path])
            if method == 'GET':
                headers = ''
                self.requests.append({'method': method,
                                        'access_modifiers': access_modifiers,
                                        'target_path': target_path, 'url': url,
                                        'params': params, 'headers':{}})

            if method == 'POST':
                headers = {'Content-Type': 'application/json'}
                self.requests.append({'method': method,
                                        'access_modifiers': access_modifiers,
                                        'target_path': target_path, 'url': url,
                                        'params': params, 'headers':headers})

        if access_modifiers == 'private':
            url = ''.join([self.URLS['private'], target_path])
            path = target_path

            timestamp = '{0}000'.format(int(time.mktime(datetime.now().timetuple())))
            if method == 'GET':
                text = ''.join([timestamp, method, path,])
                sign = self.get_sign(text)
                headers = self.set_headers_for_private(timestamp=timestamp,
                                                        sign=sign)

                self.requests.append({'url': url,
                                        'method': method,
                                        'headers': headers,
                                        'params': params,
                                        })

            if method == 'POST':
                post_data = json.dumps(params)

                text = ''.join([timestamp, method, path, post_data])
                sign = self.get_sign(text)
                headers = self.set_headers_for_private(timestamp=timestamp,
                                                        sign=sign)

                self.requests.append({'url': url,
                                        'method': method,
                                        'headers': headers,
                                        'params': post_data,
                                        })

            if method == 'PUT':
                post_data = json.dumps(params)
                
                text = ''.join([timestamp, method, path])
                sign = self.get_sign(text)
                headers = self.set_headers_for_private(timestamp=timestamp,
                                                        sign=sign)
                self.requests.append({'url': url,
                                        'method': method,
                                        'headers': headers,
                                        'params': post_data,
                                        })

    def set_headers_for_private(self, timestamp, sign):
        headers = {'API-KEY': self.api_key,
                    'API-TIMESTAMP': timestamp,
                    'API-SIGN': sign}
        return headers

    def get_sign(self, text):
        sign = hmac.new(bytes(self.api_secret.encode('ascii')),
                        bytes(text.encode('ascii')), hashlib.sha256).hexdigest()
        return sign

    async def fetch(self, request):
        status = 0
        content = []

        async with async_timeout.timeout(self.TIMEOUT):
            try:
                if self.session is None:
                    self.session = await aiohttp.ClientSession().__aenter__()
                if request['method'] == 'GET':
                    async with self.session.get(url=request['url'],
                                                params=request['params'],
                                                headers=request['headers']) as response:
                        status = response.status
                        content = await response.read()
                        if status != 200:
                            # エラーのログ出力など必要な場合
                            pass

                elif request['method'] == 'POST':
                    async with self.session.post(url=request['url'],
                                                data=request['params'],
                                                headers=request['headers']) as response:
                        status = response.status
                        content = await response.read()
                        if status != 200:
                            # エラーのログ出力など必要な場合
                            pass

                elif request['method'] == 'PUT':
                    async with self.session.put(url=request['url'],
                                                data=request['params'],
                                                headers=request['headers']) as response:
                        status = response.status
                        content = await response.read()
                        if status != 200:
                            # エラーのログ出力など必要な場合
                            pass

                if len(content) == 0:
                    result = []

                else:
                    try:
                        result = json.loads(content.decode('utf-8'))
                    except Exception as e:
                        traceback.print_exc()

                return result

            except Exception as e:
                # セッション終了
                if self.session is not None:
                    await self.session.__aexit__(None, None, None)
                    await asyncio.sleep(0)
                    self.session = None

                traceback.print_exc()

    async def send(self):
        promises = [self.fetch(req) for req in self.requests]
        self.requests.clear()
        return await asyncio.gather(*promises)

    # ------------------------------------------------ #
    # public api
    # ------------------------------------------------ #
    # 取引所ステータス
    # 取引所の稼動状態を取得します。
    def status(self):
        params = {}
        self.set_request(method='GET', access_modifiers='public',
                        target_path='/v1/status', params=params)
                        
    # 最新レート
    # 全銘柄の最新レートを取得します。
    def ticker(self):
        params = {}
        self.set_request(method='GET', access_modifiers='public',
                        target_path='/v1/ticker', params=params)

    # KLine情報の取得
    # 指定した銘柄の四本値を取得します。
    def klines(self,symbol,date,priceType='BID',interval='1min'):
        params = {'symbol': symbol,
                    'date'  : date,
                    'priceType' : priceType,
                    'interval'  : interval
                }
        self.set_request(method='GET', access_modifiers='public',
                        target_path='/v1/klines', params=params)

    # 取引ルール
    # 取引ルールを取得します。
    def symbols(self):
        params = {}
        self.set_request(method='GET', access_modifiers='public',
                        target_path='/v1/symbols', params=params)

    # ------------------------------------------------ #
    # private api
    # ------------------------------------------------ #
    # 資産残高を取得
    # 資産残高を取得します。
    def assets(self):
        params = {}
        self.set_request(method='GET', access_modifiers='private',
                        target_path='/v1/account/assets', params=params)

    # 注文情報取得
    # 指定した注文IDの注文情報を取得します。 
    # rootOrderId orderId いずれか1つが必須です。2つ同時には設定できません。
    def orders(self, rootOrderId='',orderId=''):
        params = {}
        if len(rootOrderId) > 0:
            params['rootOrderId'] = rootOrderId    
        elif len(orderId) > 0:
            params['orderId'] = orderId
        self.set_request(method='GET', access_modifiers='private',
                        target_path='/v1/orders', params=params)

    # 有効注文一覧
    # 有効注文一覧を取得します。
    # prevID,count は指定しない(銘柄別、100件以上の注文する場合に要修正)
    def activeOrders(self, symbol=''):
        params = {}
        if len(symbol) > 0:
            params['symbol'] = symbol
        self.set_request(method='GET', access_modifiers='private',
                        target_path='/v1/activeOrders', params=params)

    # 約定情報取得
    # 約定情報を取得します。
    # orderId executionId いずれか1つが必須です。2つ同時には設定できません。
    def executions(self, orderId='', executionId=''):
        params = {}
        if len(orderId) > 0:
            params['orderId'] = orderId
        elif len(executionId) > 0:
            params['executionId'] = executionId    
        self.set_request(method='GET', access_modifiers='private',
                        target_path='/v1/executions', params=params)

    # 最新の約定一覧
    # 最新約定一覧を取得します。
    # 直近1日分から最新100件の約定情報を返します。
    # count は指定せず。100(最大値)
    def latestExecutions(self, symbol, count=''):
        params = {'symbol': symbol}
        if len(count) > 0:
            params['count'] = count
        self.set_request(method='GET', access_modifiers='private',
                        target_path='/v1/latestExecutions', params=params)

    # 建玉一覧を取得
    # 有効建玉一覧を取得します。
    # countは100が最大で101以上だとエラー
    # positionID順で取得できるので、最後のPositionIdをprevIdに入れて
    # 続きを呼びなおせる。全建玉の一覧取得には、分けて呼び出す必要あり
    def openPositions(self, symbol='',prevId='',count=''):
        params = {}
        if len(symbol) > 0:
            params['symbol'] = symbol
        if len(prevId) > 0:
            params['prevId'] = prevId
        if len(count) > 0:
            params['count'] = count
        self.set_request(method='GET', access_modifiers='private',
                        target_path='/v1/openPositions', params=params)

    # 建玉サマリーを取得
    # 建玉サマリーを取得します。
    # 指定した銘柄の建玉サマリーを売買区分(買/売)ごとに取得できます。
    # symbolパラメータ指定無しの場合は、保有している全銘柄の建玉サマリーを
    # 売買区分(買/売)ごとに取得します。
    def positionSummary(self, symbol=''):
        params = {}
        if len(symbol) > 0:
            params['symbol'] = symbol
        self.set_request(method='GET', access_modifiers='private',
                        target_path='/v1/positionSummary', params=params)

    # Speed注文を出す
    # lowerBound: 成立下限価格、SELLの場合指定可能
    # upperBound: 成立上限価格、BUY の場合指定可能
    # isHedgeable: 両建てなし(default) True で両建てあり
    def speedOrder(self, symbol, side, size, 
            clientOrderId ='', lowerBound ='', upperBound='',
            isHedgeable = False):

        params = {'symbol': symbol,
                    'side': side,
                    'size': size
                }
        if len(clientOrderId) > 0:
            params['clientOrderId'] = clientOrderId
        if len(lowerBound) > 0:
            params['lowerBound'] = lowerBound
        if len(upperBound) > 0:
            params['upperBound'] = upperBound
        if isHedgeable:
            params['isHedgeable'] = True
        self.set_request(method='POST', access_modifiers='private',
                        target_path='/v1/speedOrder', params=params)

    # 新規注文を出す
    # executionType: MARKET LIMIT STOP OCO
    # LIMIT/OCO: limitPrice 必須 STOP/OCO: stopPrice 必須
    # lowerBound: 成立下限価格、MARKET,SELLの場合指定可能
    # upperBound: 成立上限価格、MARKET,BUY の場合指定可能
    def order(self, symbol, side, size, executionType,
            clientOrderId ='', limitPrice ='', stopPrice ='',
            lowerBound ='', upperBound=''):

        params = {'symbol': symbol,
                    'side': side,
                    'size': size,
                    'executionType': executionType,
                }
        if len(clientOrderId) > 0:
            params['clientOrderId'] = clientOrderId
        if len(limitPrice) > 0:
            params['limitPrice'] = limitPrice
        if len(stopPrice) > 0:
            params['stopPrice'] = stopPrice
        if len(lowerBound) > 0:
            params['lowerBound'] = lowerBound
        if len(upperBound) > 0:
            params['upperBound'] = upperBound
        self.set_request(method='POST', access_modifiers='private',
                        target_path='/v1/order', params=params)

    # IFD注文を出す
    # firstSide : BUY/SELL 2次注文はこれの逆
    # firstExecutionType: LIMIT/STOP
    # secondExecutionType: LIMIT/STOP
    def ifdOrder(self, symbol,
            firstSide,
            firstExecutionType, firstSize, firstPrice,
            secondExecutionType, secondSize, secondPrice,
            clientOrderId =''):

        params = {'symbol': symbol,
                  'firstSide': firstSide,
                  'firstExecutionType': firstExecutionType,
                  'firstSize': firstSize,
                  'firstPrice': firstPrice,
                  'secondExecutionType': secondExecutionType,
                  'secondSize': secondSize,
                  'secondPrice': secondPrice
                }
        if len(clientOrderId) > 0:
            params['clientOrderId'] = clientOrderId
        self.set_request(method='POST', access_modifiers='private',
                        target_path='/v1/ifdOrder', params=params)

    # IFDOCO注文を出す
    # firstSide : BUY/SELL 2次注文はこれの逆
    # firstExecutionType: LIMIT/STOP
    # secondExecutionType: LIMIT/STOP
    def ifoOrder(self, symbol,
            firstSide,
            firstExecutionType, firstSize, firstPrice,
            secondSize, secondLimitPrice, secondStopPrice,
            clientOrderId =''):

        params = {'symbol': symbol,
                  'firstSide': firstSide,
                  'firstExecutionType': firstExecutionType,
                  'firstSize': firstSize,
                  'firstPrice': firstPrice,
                  'secondSize': secondSize,
                  'secondLimitPrice': secondLimitPrice,
                  'secondStopPrice': secondStopPrice
                }
        if len(clientOrderId) > 0:
            params['clientOrderId'] = clientOrderId
        self.set_request(method='POST', access_modifiers='private',
                        target_path='/v1/ifoOrder', params=params)

    # 注文変更
    # 注文変更をします。
    # orderId clientOrderIdいずれか1つが必須です。2つ同時には設定できません。
    def changeOrder(self, price, orderId='', clientOrderId=''):
        params = {'price': price}
        if len(orderId) > 0:
            params['orderId'] = orderId
        if len(clientOrderId) > 0:
            params['clientOrderId'] = clientOrderId
        self.set_request(method='POST', access_modifiers='private',
                        target_path='/v1/changeOrder', params=params)

    # OCO注文変更
    # OCO注文変更をします。
    # rootOrderId clientOrderIdいずれか1つが必須です。2つ同時には設定できません。
    # limitPrice stopPrice 両方もしくはどちらか1つが必須です。
    def changeOcoOrder(self, price, rootOrderId='', clientOrderId='',
                       limitPrice = '', stopPrice =''):
        params = {'price': price}
        if len(rootOrderId) > 0:
            params['rootOrderId'] = rootOrderId
        if len(clientOrderId) > 0:
            params['clientOrderId'] = clientOrderId
        if len(limitPrice) > 0:
            params['limitPrice'] = limitPrice
        if len(stopPrice) > 0:
            params['stopPrice'] = stopPrice

        self.set_request(method='POST', access_modifiers='private',
                        target_path='/v1/changeOcoOrder', params=params)

    # IFD注文変更
    # IFD注文変更をします。
    # rootOrderId clientOrderIdいずれか1つが必須です。2つ同時には設定できません。
    # firstPrice secondPrice 両方もしくはどちらか1つが必須です。
    def changeIfdOrder(self, rootOrderId='', clientOrderId='',
                       firstPrice = '', secondPrice =''):
        params = {}
        if len(rootOrderId) > 0:
            params['rootOrderId'] = rootOrderId
        if len(clientOrderId) > 0:
            params['clientOrderId'] = clientOrderId
        if len(firstPrice) > 0:
            params['firstPrice'] = firstPrice
        if len(secondPrice) > 0:
            params['secondPrice'] = secondPrice

        self.set_request(method='POST', access_modifiers='private',
                        target_path='/v1/changeIfdOrder', params=params)

    # IFDOCO注文変更
    # IFDOCO注文変更をします。
    # rootOrderId clientOrderIdいずれか1つが必須です。2つ同時には設定できません。
    # firstPrice secondLimitPrice secondStopPrice のうち全てもしくはいずれか1つが必須です。
    def changeIfoOrder(self, rootOrderId='', clientOrderId='',
                       firstPrice = '', secondLimitPrice ='', secondStopPrice =''):
        params = {}
        if len(rootOrderId) > 0:
            params['rootOrderId'] = rootOrderId
        if len(clientOrderId) > 0:
            params['clientOrderId'] = clientOrderId
        if len(firstPrice) > 0:
            params['firstPrice'] = firstPrice
        if len(secondLimitPrice) > 0:
            params['secondLimitPrice'] = secondLimitPrice
        if len(secondStopPrice) > 0:
            params['secondStopPrice'] = secondStopPrice

        self.set_request(method='POST', access_modifiers='private',
                        target_path='/v1/changeIfoOrder', params=params)

    # 注文の複数キャンセル
    # 複数の注文を取消をします。
    # rootOrderId clientOrderIdいずれか1つが必須です。2つ同時には設定できません。
    # 最大10件まで注文を取消することができます。
    def cancelOrders(self, rootOrderIds='', clientOrderIds=''):
        params = {}
        if len(rootOrderIds) > 0:
            params['rootOrderIds'] = rootOrderIds
        if len(clientOrderIds) > 0:
            params['clientOrderIds'] = clientOrderIds
        self.set_request(method='POST', access_modifiers='private',
                        target_path='/v1/cancelOrders', params=params)

    # 注文の一括キャンセル
    # 一括で注文(通常注文、特殊注文いずれも)を取消します。
    # 取消対象検索後に、対象注文を全て取消します。
    # 1次注文が約定していないIFD、IFDOCOケースでは1次注文もしくは
    # 2次注文にsideとsettleTypeの両方が一致するものが1つでもあれば注文全体を取消します。
    # 1次注文が約定しているIFD、IFDOCOケースでは
    # 2次注文にsideとsettleTypeの両方が一致するものがあれば2次注文を取消します。
    def cancelBulkOrder(self, symbols, side='', settleType=''):
        params = {'symbols':symbols}
        if len(side) > 0:
            params['side'] = side
        if len(settleType) > 0:
            params['settleType'] = settleType
        self.set_request(method='POST', access_modifiers='private',
                        target_path='/v1/cancelBulkOrder', params=params)

    # 決済注文
    # 決済注文をします。
    # size settlePositionいずれか1つが必須です。2つ同時には設定できません。
    # Side: BUY/SELL executionType: MARKET LIMIT STOP OCO
    # size: 注文数量 建玉指定なし※size settlePositionいずれかのみ指定可能
    # limitPrice: LIMIT OCO の場合は必須
    # stopPrice: STOP OCO の場合は必須
    # lowerBound:成立下限価格※executionType:MARKET side: SELLの場合に指定可能
    # upperBound:成立上限価格※executionType:MARKET side: BUYの場合に指定可能
    # settlePosition: 複数建玉 複数指定可能 ※size settlePositionいずれかのみ指定可能
    #                 ※最大10件まで指定可能
    #     "settlePosition": [
    #       {
    #        "positionId": 1000342, 建玉ID
    #        "size": "10000"        注文数量
    #       }
    #      ]
    def closeOrder(self, symbol, side, executionType,
                   clientOrderId='',size ='',
                   limitPrice='', stopPrice='',
                   lowerBound='', upperBound='',
                   settlePosition=''):
        params = {'symbol': symbol,
                  'side': side,
                  'executionType': executionType
                }
        if len(clientOrderId) > 0:
            params['clientOrderId'] = clientOrderId
        if len(size) > 0:
            params['size'] = size
        if len(limitPrice) > 0:
            params['limitPrice'] = limitPrice
        if len(stopPrice) > 0:
            params['stopPrice'] = stopPrice
        if len(lowerBound) > 0:
            params['lowerBound'] = lowerBound
        if len(upperBound) > 0:
            params['upperBound'] = upperBound
        if len(settlePosition) > 0:
            params['settlePosition'] = settlePosition

        self.set_request(method='POST', access_modifiers='private',
                        target_path='/v1/closeOrder', params=params)

    # ---------------------------------------- #
    # Private WebSocket API 
    # ---------------------------------------- #
    # アクセストークンを取得
    # Private WebSocket API用のアクセストークンを取得します。
    def post_ws_auth(self):
        params = {}

        self.set_request(method='POST', access_modifiers='private',
                        target_path='/v1/ws-auth', params=params)
                        
    # アクセストークンを延長
    # Private WebSocket API用のアクセストークンを延長します。
    def put_ws_auth(self, token):
        params = {'token': token}
        
        self.set_request(method='PUT', access_modifiers='private',
                        target_path='/v1/ws-auth', params=params)

    # アクセストークンを削除
    # Private WebSocket API用のアクセストークンを削除します。
    def delete_ws_auth(self, token):
        params = {'token': token}
        
        self.set_request(method='DELETE', access_modifiers='private',
                        target_path='/v1/ws-auth', params=params)

    # ------------------------------------------------ #
    # WebSocket
    # ------------------------------------------------ #
    # Public WebSocket
    async def public_ws_run(self, callback, channels):
        # 変数
        end_point_public = self.URLS['publicWS']

        while True:
            try:
                async with aiohttp.ClientSession() as session:
                    # Public WebSocket
                    async with session.ws_connect(end_point_public,
                                                    receive_timeout=self.TIMEOUT) as client:

                        if len(channels) > 0:
                            await self.subscribe(client, channels)

                        async for response in client:
                            if response.type != WSMsgType.TEXT:
                                print('response:' + str(response))
                                break
                            elif 'error' in response[1]:
                                print(response[1])
                                break
                            else:
                                data = json.loads(response[1])
                                await self.handler(callback, data)

            except Exception as e:
                print(e)
                print(traceback.format_exc().strip())
                await asyncio.sleep(10)


    # Private WebSocket
    async def private_ws_run(self, callback, channels):

        while True:
            try:
                async with aiohttp.ClientSession() as session:

                    # トークンの取得
                    if self.token == '':
                        self.post_ws_auth()
                        response = await self.send()
                        self.token = response[0]['data']
            
                    # 変数
                    end_point_private = ''.join([self.URLS['privateWS'], '/', self.token])

                    # Private WebSocket
                    async with session.ws_connect(end_point_private,
                                                    receive_timeout=self.TIMEOUT) as client:

                        if len(channels) > 0:
                            await self.subscribe(client, channels)

                        async for response in client:
                            if response.type != WSMsgType.TEXT:
                                print('response:' + str(response))
                                break
                            elif 'error' in response[1]:
                                print(response[1])
                                break
                            else:
                                data = json.loads(response[1])
                                await self.handler(callback, data)

            except Exception as e:
                print(e)
                print(traceback.format_exc().strip())
                await asyncio.sleep(10)
                
                if self.token != '':
                    self.token = ''            


    # 購読
    # PUBLIC_CHANNELS  : [{'channel':'ticker','symbol':'USD_JPY'},
    #                     {'channel':'ticker','symbol':'EUR_JPY'}  ... ]
    #
    # PRIVATE_CHANNELS : [{'channel':'executionEvents'},
    #                     {'channel':'orderEvents'},
    #                     {'channel':'positionEvents'},
    #                     {'channel':'positionSummaryEvents','option':'PERIODIC'}]
    #
    async def subscribe(self, client, channels):
        for channel in channels:
            print(channel)
            if channel['channel'] == 'ticker':
                params = {'command':'subscribe', 'channel':'ticker', 'symbol': channel['symbol']}

            elif channel['channel'] == 'executionEvents':
                params = {'command':'subscribe', 'channel':'executionEvents'}

            elif channel['channel'] == "orderEvents":
                params = {'command':'subscribe', 'channel':'orderEvents'}

            elif channel['channel'] == "positionEvents":
                params = {'command':'subscribe', 'channel':'positionEvents'}

            elif channel['channel'] == "positionSummaryEvents":
                if channel['option'] == 'PERIODIC':
                    params = {'command':'subscribe', 'channel':'positionSummaryEvents', 'option':'PERIODIC'}
                else:
                    params = {'command':'subscribe', 'channel':'positionSummaryEvents'}

            await asyncio.wait([client.send_str(json.dumps(params))])
            print('---- %s connect ----' %(channel))
            await asyncio.sleep(2)


    # トークンの延長
    async def extend_token(self):
        while True:
            try:
                await asyncio.sleep(self.EXTEND_TOKEN_TIME)
                if self.token != '':
                    # トークンの延長
                    self.put_ws_auth(self.token)
                    response = await self.send()

            except Exception as e:
                print(e)
                print(traceback.format_exc().strip())

    # UTILS
    # コールバック、ハンドラー
    async def handler(self, func, *args):
        return await func(*args)

利用サンプル

import asyncio
from gmocoinfx_api_wrapper import GMOCoinFX

class Sample():

   # ---------------------------------------- #
   # init
   # ---------------------------------------- #
   def __init__(self, api_key, api_secret):
       self.gmocoinfx = GMOCoinFX(api_key=api_key, api_secret=api_secret)
       
       # タスクの設定およびイベントループの開始
       public_channels = [{'channel':'ticker','symbol':'USD_JPY'},
                          {'channel':'ticker','symbol':'EUR_JPY'},
                          {'channel':'ticker','symbol':'GBP_JPY'},
                          {'channel':'ticker','symbol':'AUD_JPY'}
                         ]
    
       private_channels = [{'channel':'executionEvents'},
                           {'channel':'orderEvents'},
                           {'channel':'positionEvents'},
                           {'channel':'positionSummaryEvents','option':'PERIODIC'}
                          ]

       loop = asyncio.get_event_loop()
       tasks = [
                   self.gmocoinfx.private_ws_run(self.realtime,private_channels),
                   self.gmocoinfx.public_ws_run(self.realtime,public_channels),
                   self.gmocoinfx.extend_token(),
                   self.run()
               ]
               
       loop.run_until_complete(asyncio.wait(tasks))
      
   # ---------------------------------------- #
   # bot main
   # ---------------------------------------- #
   async def run(self):
        while(True):
           await self.main(5)
           await asyncio.sleep(0)
           
   async def main(self, interval):
       # main処理
       
       # 取引所ステータスを取得 
       self.gmocoinfx.status()
       response = await self.gmocoinfx.send()
       print("================ status ===============")
       print(response[0])        
    
       # 最新レートを取得        
       self.gmocoinfx.ticker()
       response = await self.gmocoinfx.send()
       print("================ ticker ===============")
       print(response[0])        

       # 資産残高を取得        
       self.gmocoinfx.assets()
       response = await self.gmocoinfx.send()
       print("================ assets ===============")
       print(response[0])        
       
       await asyncio.sleep(interval)

    # リアルタイムデータの受信
   async def realtime(self, data):
       # ここにWebSocketから配信されるデータが落ちてきますので
       # 適宜加工して利用してみてください。
       print(data)

       await asyncio.sleep(0)

# --------------------------------------- #
# main
# --------------------------------------- #
if __name__ == '__main__':
    api_key=""
    api_secret=""  
    Sample(api_key=api_key, api_secret=api_secret)