知識ゼロでもpythonでアービトラージBOT作成3 ~価格差調査編~

Bot

今回はpythonで各取引所間にどれくらい価格差があり、利益が見込めるのか調査します

第3回にしてやっと本格的にpythonでプログラミングに突入です

番外編で解説した価格差収束型アービトラージでBOT作成を進めていきます

 

pythonの勉強方法

それでは実際にpythonでプログラミングを始めていきます

アビトラBOT第2回目でプログラミングの準備は出来ていますが、まず何をしたらよいのか分からないと思います

私はこちらの「Python-izm」というサイトでpythonの基礎を勉強しました

>>「Python-izm」へ基礎を学びに行く

「入門編」と「基本編」を終了すれば、一通りpythonでプログラミングが出来るレベルに達すると思います

以降はこの「入門編」と「基本編」が一通り終わっている前提で進めていきます

pythonで価格差を調べる

ここからは実際に完成したプログラムの内容を見ながら解説していきます

# In[1]:ライブラリインポート---------------------------

import ccxt
import pandas as pd
from datetime import datetime
from time import sleep

# In[2]:変数設定---------------------------

btcamount = 1.0 #一回の取引に使用するBTCの量
profits_check_spread = 2 #[現在値 > 平均値 × この変数]の場合異常値とする

exchangeA = ccxt.coincheck()
exchangeB = ccxt.liquid()
exchangeC = ccxt.btcbox()

# In[3]:【関数】現在の板で「btcamount」分の枚数購入時の平均価格計算---------------------------

def average_cost(btccount,ita):
    itacount = 0
    btcremaining = btccount
    averagesum = 0.0
    while btcremaining > 0.0:
        if btcremaining >= ita[itacount][1]:
            averagesum += ita[itacount][0] * ita[itacount][1]
            btcremaining -= ita[itacount][1]
        else:
            averagesum += ita[itacount][0] * btcremaining
            btcremaining = 0
        itacount += 1
        if itacount == 20:
            return "NG"
            break
    average = averagesum / btccount
    return average

# In[4]:【関数】価格差異常値チェック---------------------------

def outliers_check(profits_def,aveary_def,protchksp_def):
    chknum = profits_def
    chkave = 0.0
    avesumcnt = 0
    for l in reversed(aveary_def):
        if l != "ERROR":
            chkave += l
            avesumcnt += 1
        if avesumcnt == 5:
            break
    chkave = chkave / avesumcnt
    if chkave == 0.0:
        chkave = profits_def
    if abs(profits_def) > abs(chkave * protchksp_def):
        chknum = "ERROR"
    return chknum

# In[5]:価格差収集---------------------------

csvlistab = []
csvlistac = []
csvlistbc = []
averageSary = {'ab':[],'ac':[],'bc':[]}
averageEary = {'ab':[],'ac':[],'bc':[]}
loopcount = 0

while True:
    now = datetime.now()
    timeval = now.replace(microsecond=0)
    
    ngchkAB = 'true'
    ngchkAC = 'true'
    ngchkBC = 'true'
    
    itaA = exchangeA.fetch_order_book(symbol='BTC/JPY')
    itaB = exchangeB.fetch_order_book(symbol='BTC/JPY')
    itaC = exchangeC.fetch_order_book(symbol='BTC/JPY')
    
    asksA = average_cost(btcamount,itaA['asks'])
    asksB = average_cost(btcamount,itaB['asks'])
    asksC = average_cost(btcamount,itaC['asks'])
    bidsA = average_cost(btcamount,itaA['bids'])
    bidsB = average_cost(btcamount,itaB['bids'])
    bidsC = average_cost(btcamount,itaC['bids'])
    
    if(asksA == "NG" or asksB == "NG" or bidsA == "NG" or bidsB == "NG"):
        ngchkAB = 'false'
    if(asksA == "NG" or asksC == "NG" or bidsA == "NG" or bidsC == "NG"):
        ngchkAC = 'false'  
    if(asksB == "NG" or asksC == "NG" or bidsB == "NG" or bidsC == "NG"):
        ngchkBC = 'false'
        
    if(ngchkAB == 'true'):
        profitsab_start = bidsB - asksA
        profitsab_end = asksB - bidsA
        averageSary['ab'].append(profitsab_start)
        averageEary['ab'].append(profitsab_end)
        
        profitsab_start = outliers_check(profitsab_start,averageSary['ab'],profits_check_spread)
        profitsab_end = outliers_check(profitsab_end,averageEary['ab'],profits_check_spread)
        
        csvlistab.append([timeval,profitsab_start,profitsab_end])
    else:
        csvlistab.append([timeval,'ERROR','ERROR'])
        
    if(ngchkAC == 'true'):
        profitsac_start = bidsC - asksA
        profitsac_end = asksC - bidsA
        averageSary['ac'].append(profitsac_start)
        averageEary['ac'].append(profitsac_end)
        
        profitsac_start = outliers_check(profitsac_start,averageSary['ac'],profits_check_spread)
        profitsac_end = outliers_check(profitsac_end,averageEary['ac'],profits_check_spread)
            
        csvlistac.append([timeval,profitsac_start,profitsac_end])
    else:
        csvlistac.append([timeval,'ERROR','ERROR'])
        
    if(ngchkBC == 'true'):
        profitsbc_start = bidsC - asksB
        profitsbc_end = asksC - bidsB
        averageSary['bc'].append(profitsbc_start)
        averageEary['bc'].append(profitsbc_end)
        
        profitsbc_start = outliers_check(profitsbc_start,averageSary['bc'],profits_check_spread)
        profitsbc_end = outliers_check(profitsbc_end,averageEary['bc'],profits_check_spread)
            
        csvlistbc.append([timeval,profitsbc_start,profitsbc_end])
    else:
        csvlistbc.append([timeval,'ERROR','ERROR'])
        
    loopcount += 1
    print(loopcount)
    if loopcount == 8641:
        break
    sleep(1)
    
#配列にCSVの見出し用の値を追加
dfab = pd.DataFrame(csvlistab,columns=['time','start','end'])
dfac = pd.DataFrame(csvlistac,columns=['time','start','end'])
dfbc = pd.DataFrame(csvlistbc,columns=['time','start','end'])
 
#CSVファイルを出力
dfab.to_csv("profitsAB.csv", index=False)
dfac.to_csv("profitsAC.csv", index=False)
dfbc.to_csv("profitsBC.csv", index=False)

 

ライブラリをインポート

まずは様々な追加機能を使用できるようにライブラリをインポートしなければなりません

「import ○○」で追加機能を使用する準備ができます
「from ○○ import △△」は○○の中の△△という機能だけインポートするという事です

import ccxt
import pandas as pd
from datetime import datetime
from time import sleep
○ ccxt
様々な取引所のデータを取得したり売買を行うことができる
○ pandas
データの統計、グラフ化、データ分析等を簡単に行うことができる
○ datetime
日付や時間に関するデータを取得することができる
○ sleep
一定時間プログラムを止めることができる

 

変数を設定する

btcamount = 1.0 #一回の取引に使用するBTCの量
profits_check_spread = 2  #[現在値 > 平均値 × この変数]の場合異常値とする

exchangeA = ccxt.coincheck()
exchangeB = ccxt.liquid()
exchangeC = ccxt.btcbox()

まずは「btcamout」という変数を例に、変数のメリットを見ていきましょう

「btcamout」には「一回の取引に使用するBTCの量」を入れています。これはプログラム中に何度も使用しています

後でBTCの取引量を変えたいと思ったときも変数に入れていればここの数字だけ変えれば済みます

仮に変数に入れずにその都度「1.0」とプログラミングしていたら全ての箇所の数字を書き変えなければなりません

何度も使用する数字や長いプログラムは変数に入れておくと管理がとても楽です

「profits_check_spread」はプログラム内のコメントの通りです
稀に価格が異常な変動をすることがあるのでその回避策用の変数です

最後の変数「exchangeA,B,C」ですが、「ccxt.○○()」で先程インポートした「ccxt」を使用し各取引所の操作や情報取得ができるようにしています

○○の部分に取得したい取引所の名前を入力することで操作が可能になります
※例「ccxt.coincheck()」「ccxt.liquid()」など

 

「ccxt」対応取引所一覧

少しわき道に逸れて「ccxt」で使用できる取引所一覧を見てみましょう
下記プログラムで対応している取引所一覧を配列で取得することができます

ccxt.exchanges

pythonで実際に見てみたい場合は下記2行を実行します

import ccxt
print(ccxt.exchanges)
2018/11/11 時点の「ccxt」取引所一覧
_1broker_1btcxeacxallcoinanxpro
anybitsbcexbiboxbigonebinance
bit2cbitbankbitbaybitfinexbitfinex2
bitflyerbitforexbithumbbitkkbitlish
bitmarketbitmexbitsanebitsobitstamp
bitstamp1bittrexbitzbl3pbleutrade
braziliexbtcalphabtcboxbtcchinabtcexchange
btcmarketsbtctradeimbtctradeuabtcturkbtcx
bxinthccexcexchbtcchilebit
cobinhoodcoinbasecoinbaseprimecoinbaseprocoincheck
coineggcoinexcoinexchangecoinfalconcoinfloor
coingicoinmarketcapcoinmatecoinnestcoinone
coinsecurecoinspotcointigercoolcoincrypton
cryptopiaderibitdsxethfinexexmo
exxfcoinflowbtcfoxbitfybse
fybsggatecoingateiogdaxgemini
getbtchadaxhitbtchitbtc2huobi
huobicnyhuobiproice3xindependentreserveindodax
itbitjubikrakenkucoinkuna
lakebtclbankliquiliquidlivecoin
lunolykkemercadomixcoinsnegociecoins
novaokcoincnyokcoinusdokex'paymium
poloniexqryptosquadrigacxquoinexrightbtc
southxchangesurbitcointheoceantherocktidebit
tidexuexurdubitvaultorovbtc
virwoxwexxbtceyobityunbi
zaifzb

関数を作成する

今回のプログラムでは2つの関数を作成しています

まず関数を簡単に説明すると、処理の集まりを一文で実行できるものです

具体的な使い方は、勉強方法で紹介したサイト「Python-izm」のこちらに詳しく説明されています

関数は「応用編」になりますが便利なので覚えてしまいましょう

 

関数①「average_cost」

まず1つ目は、先程変数を作成した「btcamout」で指定したBTC枚数分を板の上から順に取得した平均取得価格を調べる関数です

def average_cost(btccount,ita):
    itacount = 0
    btcremaining = btccount
    averagesum = 0.0
    while btcremaining > 0.0:
        if btcremaining >= ita[itacount][1]:
            averagesum += ita[itacount][0] * ita[itacount][1]
            btcremaining -= ita[itacount][1]
        else:
            averagesum += ita[itacount][0] * btcremaining
            btcremaining = 0
        itacount += 1
        if itacount == 20:
            return "NG"
            break
    average = averagesum / btccount
    return average

簡単な説明としましては、「while」で板の上から足していき、指定BTC枚数に達したら平均値を返す。または指定BTC枚数に達する前にループが20回を超えた場合はエラーとして’NG’を返す。というものです

「return」を使うことで結果を返すことができます

※例 1BTC売れた時のこの関数の動きです

19行目で「average_cost」という名前で関数を作っていますが、()の中に「btccount」と「ita」というものがあります

def average_cost(btccount,ita):

これらは引数といって処理は同じだけどこの数値だけ変えて実行したいという場合に使用します

後ほど実際に使用している部分でも解説しますが、この関数は複数の取引所の買い板と売り板の値を「ita」に入れて同じ処理を実行しています

この処理を関数にしていないと、取引所の個数分この部分をプログラムしなければいけないのですごく煩雑になってしまいます

 

関数②「outliers_check」

2つ目は、稀に価格が異常な変動をすることがあるのでその回避策用の関数です

def outliers_check(profits_def,aveary_def,protchksp_def):
    chknum = profits_def
    chkave = 0.0
    avesumcnt = 0
    for l in reversed(aveary_def):
        if l != "ERROR":
            chkave += l
            avesumcnt += 1
        if avesumcnt == 5:
            break
    chkave = chkave / avesumcnt
    if chkave == 0.0:
        chkave = profits_def
    if abs(profits_def) > abs(chkave * protchksp_def):
        chknum = "ERROR"
    return chknum

この関数の引数にはそれぞれ下記を入れて使用しています

profits_def」 → 価格差の値
aveary_def」 → 価格差を集めた配列
protchksp_def」 → 11行目で作成した変数「profits_check_spread」

まずは価格差を集めた配列から最新の5つを足して平均をもとめます
現在の価格差 > 平均値 × 「profits_check_spread」の場合異常値として”ERROR”という文字列を返しています

reversed(配列)

「reversed」を使うことで配列の後ろから値を取得することができます

break

「break」はforの途中でもループから抜けることができます
今回は「avesumcnt」という変数が5になった場合forから抜けています

 

実際に価格差を調べてみる

58~63行目 : 変数の作成

csvlistab = []
csvlistac = []
csvlistbc = []
averageSary = {'ab':[],'ac':[],'bc':[]}
averageEary = {'ab':[],'ac':[],'bc':[]}
loopcount = 0

csvlistab,ac,bc」 → 3つの取引所の価格差をCSV出力する為に値を配列で収集
averageSary」 → 価格差を比較する為に配列で収集
averageEary」 → 収束した価格差を比較する為に配列で収集
loopcount」 → データを収集した回数をカウントする為

 

65~89行目 : 板情報の取得

while True:
    now = datetime.now()
  timeval = now.replace(microsecond=0)

    ngchkAB = 'true'
    ngchkAC = 'true'
    ngchkBC = 'true'
    
    itaA = exchangeA.fetch_order_book(symbol='BTC/JPY')
    itaB = exchangeB.fetch_order_book(symbol='BTC/JPY')
    itaC = exchangeC.fetch_order_book(symbol='BTC/JPY')
    
    asksA = average_cost(btcamount,itaA['asks'])
    asksB = average_cost(btcamount,itaB['asks'])
    asksC = average_cost(btcamount,itaC['asks'])
    bidsA = average_cost(btcamount,itaA['bids'])
    bidsB = average_cost(btcamount,itaB['bids'])
    bidsC = average_cost(btcamount,itaC['bids'])
    
    if(asksA == "NG" or asksB == "NG" or bidsA == "NG" or bidsB == "NG"):
        ngchkAB = 'false'
    if(asksA == "NG" or asksC == "NG" or bidsA == "NG" or bidsC == "NG"):
        ngchkAC = 'false'  
    if(asksB == "NG" or asksC == "NG" or bidsB == "NG" or bidsC == "NG"):
        ngchkBC = 'false'

まずは「while」でループを始めます

次にループ1回毎に値を更新したい変数を作っていきます

    now = datetime.now()
  timeval = now.replace(microsecond=0)

「now」は現在の時間を入れます

「timeval」は現在の時間のマイクロ秒を無くした値をいれます
※マイクロ秒有り → 2018-11-21 22:56:06.696282
マイクロ秒無し → 2018-11-21 22:56:06

    ngchkAB = 'true'
    ngchkAC = 'true'
    ngchkBC = 'true'

「ngchkAB,BC,CB」はそれぞれの取引所間で指定枚数BTCを取得できたかのチェックをする為の変数です

    itaA = exchangeA.fetch_order_book(symbol='BTC/JPY')
    itaB = exchangeB.fetch_order_book(symbol='BTC/JPY')
    itaC = exchangeC.fetch_order_book(symbol='BTC/JPY')

13~15行目で作った変数を使ってそれぞれの取引所の板情報を取得します

    asksA = average_cost(btcamount,itaA['asks'])
    asksB = average_cost(btcamount,itaB['asks'])
    asksC = average_cost(btcamount,itaC['asks'])
    bidsA = average_cost(btcamount,itaA['bids'])
    bidsB = average_cost(btcamount,itaB['bids'])
    bidsC = average_cost(btcamount,itaC['bids'])

ここで19~35行目で作った関数「average_cost」がでてきます

この関数は指定枚数分BTCを取引した場合の平均取得価格を求めるものでした

各変数に関数「average_cost」で計算した売り、買いそれぞれの平均取得価格を入れていきます

    if(asksA == "NG" or asksB == "NG" or bidsA == "NG" or bidsB == "NG"):
        ngchkAB = 'false'
    if(asksA == "NG" or asksC == "NG" or bidsA == "NG" or bidsC == "NG"):
        ngchkAC = 'false'  
    if(asksB == "NG" or asksC == "NG" or bidsB == "NG" or bidsC == "NG"):
        ngchkBC = 'false'

関数「average_cost」は一定の板の間で指定枚数分のBTCを取得できなかった場合は”「NG”」を戻り値で返しています

各取引所の売り、買い、1つでも「”NG”」が返ってきた場合は変数「ngchk○○」の値を「’true’」から「’false’」に置き換えて以降の処理で分岐させます

 

91~128行目 : 価格差をCSV出力用変数へ代入

    if(ngchkAB == 'true'):
        profitsab_start = bidsB - asksA
        profitsab_end = asksB - bidsA
        averageSary['ab'].append(profitsab_start)
        averageEary['ab'].append(profitsab_end)
        
        profitsab_start = outliers_check(profitsab_start,averageSary['ab'],profits_check_spread)
        profitsab_end = outliers_check(profitsab_end,averageEary['ab'],profits_check_spread)
        
        csvlistab.append([timeval,profitsab_start,profitsab_end])
    else:
        csvlistab.append([timeval,'ERROR','ERROR'])
        
    if(ngchkAC == 'true'):
        profitsac_start = bidsC - asksA
        profitsac_end = asksC - bidsA
        averageSary['ac'].append(profitsac_start)
        averageEary['ac'].append(profitsac_end)
        
        profitsac_start = outliers_check(profitsac_start,averageSary['ac'],profits_check_spread)
        profitsac_end = outliers_check(profitsac_end,averageEary['ac'],profits_check_spread)
            
        csvlistac.append([timeval,profitsac_start,profitsac_end])
    else:
        csvlistac.append([timeval,'ERROR','ERROR'])
        
    if(ngchkBC == 'true'):
        profitsbc_start = bidsC - asksB
        profitsbc_end = asksC - bidsB
        averageSary['bc'].append(profitsbc_start)
        averageEary['bc'].append(profitsbc_end)
        
        profitsbc_start = outliers_check(profitsbc_start,averageSary['bc'],profits_check_spread)
        profitsbc_end = outliers_check(profitsbc_end,averageEary['bc'],profits_check_spread)
            
        csvlistbc.append([timeval,profitsbc_start,profitsbc_end])
    else:
        csvlistbc.append([timeval,'ERROR','ERROR'])

ここのプログラムは長いですが、各取引所間で値を変えて計算は同じことをしているだけです

変数「ngchk○○」が「’true’」の場合

まずは関数「average_cost」で「”NG”」にならず「ngchk○○」が「’true’」の場合の処理です

        profitsab_start = bidsB - asksA
        profitsab_end = asksB - bidsA

「profitsab_start」 → 現在時間で取得した場合の価格差
「profitsab_end」 → 現在時間で決済した場合の価格差

        averageSary['ab'].append(profitsab_start)
        averageEary['ab'].append(profitsab_end)

それぞれの対応した配列に現時点の取得、決済の価格差を入れます

        profitsab_start = outliers_check(profitsab_start,averageSary['ab'],profits_check_spread)
        profitsab_end = outliers_check(profitsab_end,averageEary['ab'],profits_check_spread)

ここで39~54行目で作った2つ目の関数「outliers_check」を使います

この関数は価格差に異常な変化が生まれていないかを調べるものでした

異常が無ければそのままの価格差を、異常があれば「”ERROR”」を返します

この戻り値を各変数に入れます

        csvlistab.append([timeval,profitsab_start,profitsab_end])

csv書出し用の配列に「現在時間」「現在時間の取得価格差」「現在時間の決済価格差」の3つを入れます

以降~128行目までは、プログラムは同じで各取引所の変数を入れ替えているだけです

変数「ngchk○○」が「’false’」の場合
    else:
        csvlistab.append([timeval,'ERROR','ERROR'])

指定枚数分BTCを取得できなかったので、取得、決済それぞれの価格差は「’ERROR’」とします

csv書出し用の配列に「現在時間」「‘ERROR’」「‘ERROR’」の3つを入れます

 

130~134行目 : ループの継続判断

    loopcount += 1
    print(loopcount)
    if loopcount == 8641:
        break
    sleep(1)

変数「loopcount」に1を足し、もし値が8641になったらループを終了します

「sleep(1)」で処理を一定時間止めています

各取引所、一定時間でデータを取れる回数が決まっています
あまり短い間隔でデータ取得していると一定時間アクセス拒否されてしまいます

 

136~144行目 : CSV書出し

#配列にCSVの見出し用の値を追加
dfab = pd.DataFrame(csvlistab,columns=['time','start','end'])
dfac = pd.DataFrame(csvlistac,columns=['time','start','end'])
dfbc = pd.DataFrame(csvlistbc,columns=['time','start','end'])

ここでライブラリ「pandas」の「DataFrame」を使用します

配列をこんな感じの見出しがついた表にしてくれます

time start end
9:36:54 -64.59641205 1818.882164
9:36:56 -130.0008058 1819.721706
9:36:57 -142.9108984 1820.903079
9:36:58 -157.1988272 1809.811394
#CSVファイルを出力
dfab.to_csv("profitsAB.csv", index=False)
dfac.to_csv("profitsAC.csv", index=False)
dfbc.to_csv("profitsBC.csv", index=False)

こちらはライブラリ「pandas」の「to_csv」を使ってcsvに書き出しています
書き出し先はこのプログラムが保存されている場所と同じ階層です

 


思いのほか長くなってしまったので収集したデータに関しては次回にします

このプログラムが出来て記事を書いている間にも売買用のプログラミングをしていましたが、こちらの価格差調査プログラムも結構直したいところが出てきましたね

異常値の計算方法や価格差の収集方法等、時間が出来たら記事も含めて更新したいと思います

コメント