GMOコイン FX API ラッパー
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)
コメント
0 件のコメント :
コメントを投稿