BitMEX用オーダー発注ソフトをPython+Tkinterで作った

追記

2018/8/20 アップデート

・コマンドラインに表示されるログを強化。エラーで止まった時にどこで止まったかすぐに分かります。

・ナンピンボタンを追加。コード内にもコメント入れていますが、kaisu(回数)分、haba(ドル幅)で一括ナンピン注文を指します。デフォルトは5回、5ドル幅で、上記のようにナンピンショート発射すると画像のような指値になります。

・コードが更にスパゲッティになり、よりわかりにくくなりました(白目)。そのうち関数まとめたい…まとめたい

2018/8/11 全注文キャンセルボタンがXBT/USDペアのみ発動するよう修正

2018/8/7 bitFlyer版もやっつけですが作りました。

2018/8/4 apiからtickerが取得できない場合、403エラーで起動できなかったため、データの取得元をCryptowatchに変更。変更箇所はコメントアウトして残しています。こういった不具合修正の他、アップデートも気が向いた時に行う予定です。また、bitFlyer版も予定しています。

どうも。@sj_cryptoでTwitterやってる者です。

ブログ(http://cryptojapan.ml/)の方では何度か進捗状況を記事にしていたのですが、TkinterでわりとカンタンにGUIが作れるというのを知ってテストがてら触ってみたものです。

動機としては、MEXのUIがちょっとごちゃごちゃしていて損益もどれくらい上下したのかがBTC建てでしか分からないのをなんとかしたいなってところからです。

無料で最後まで読めますが、動作テストで成り行き連打して結構スってしまったのでもし気に入ったら購入orサポートしてくださるとモチベが上がります。バグフィクスやアプデはやりますが僕の技量の問題で限界あります。コードは勝手に弄ってもらって大丈夫ですし、なんなら弄ったコードを無料公開してもらってもOKです。

BitMEXの登録がまだの方は是非アフィリンクから登録お願いします。

順に説明していきます。まずBalanceは現在のBitcoin残高です。

Use rateは残高のうちどれくらいの割合を注文で使っているかの表示です。

JPYは、(Bitcoin残高)*(BTC/USD価格)*(取得したドル円レート)から算出した日本円換算の残高概算です。

BTC/USDはいわゆるLTP、最終取引価格です。

Priceの欄は数値が入力されていた場合指値、空欄の場合成り行きで注文します。

Sizeの欄はポジションサイズを指定します。空欄の場合エラーが帰ってきます。基本的にエラーはコンソールに出力され、ログには表示されません。

LONG、SHORTに関しては特に説明はいりませんね。

Reload balanceボタンは、Bitcoin残高、使用率、日本円残高、BTC/USDが更新されます。

Clear all textボタンは、PriceとSizeの欄をクリアします。ログは消えません。

Cancel all ordersボタンは、未約定の注文を全てキャンセルします。

Clear logsボタンは下部にあるログをクリアします。

Liquidationボタンはオープンなポジション(XBTUSDペアのみ)を成り行きでクローズします。

ログには、注文が約定した際にIDが表示されます。また、ポジションを持たない状態でLiquidationボタンを押すとno positionと表示されます。


以下、本体です。コードめちゃくちゃ汚いですが許して。

TkinterはPythonに標準で入っているので特に用意する必要はありません。ccxtはpip install ccxtで導入してください。APIキーとAPIシークレットを自分のものに置き換えれば使えます。

import tkinter
from tkinter import *
from tkinter import font
from tkinter import ttk
import ccxt
import datetime
import time
import urllib
import json
import requests

try:
    print('API calling')
    bitmex=ccxt.bitmex({
        'apiKey':'YOUR_API_KEY',
        'secret':'YOUR_API_SECRET'
    })    
except Exception as e:
    print(e)    

def main():
    
    def button1_clicked():
        lot = 0
        price = 0
        price_entry = entry1.get()
        size_entry = entry2.get()
        if not price_entry:
            order_type='market'
        else:
            price = price_entry
            order_type = 'limit'
            price = float(entry1.get()) 
        if not size_entry:
            pass
        else:
            size = size_entry
            lot = entry2.get()          
        try:
            order = bitmex.create_order('BTC/USD', type=order_type, side='buy', amount=lot, price=price)
            order_id = order.get('id')
            text1.configure(state='normal')
            text1.insert('end', 'success '+order_id+'\n')
            text1.yview(END)
            text1.configure(state='disabled')
        except Exception as e:
            print(e)    
    def button2_clicked():
        lot = 0
        price = 0
        price_entry = entry1.get()
        size_entry = entry2.get()
        if not price_entry:
            order_type='market'
        else:
            price = price_entry
            order_type = 'limit'
            price = float(entry1.get()) 
        if not size_entry:
            pass
        else:
            size = size_entry
            lot = entry2.get()          
        try:
            order = bitmex.create_order('BTC/USD', type=order_type, side='sell', amount=lot, price=price)
            order_id = order.get('id')
            text1.configure(state='normal')
            text1.insert('end', 'success '+order_id+'\n')
            text1.yview(END)
            text1.configure(state='disabled')
        except Exception as e:
            print(e)
    def button3_clicked():
        print('reloading')
        print('fetching balance')
        fetch_balance = bitmex.fetch_balance()
        balance = fetch_balance.get('total').get('BTC')
        balance_free = fetch_balance.get('free').get('BTC')
        balance_used = fetch_balance.get('used').get('BTC')
        balance_use_rate = balance_used/balance*100
        #ticker = bitmex.fetch_ticker('BTC/USD')
        #ltpp = ticker.get('last')
        print('fetching ltp')
        r = requests.get("https://api.cryptowat.ch/markets/bitmex/btcusd-perpetual-futures/price")
        ltpp = r.json()["result"]["price"]
        label7.configure(text='BTC/USD:'+str(ltpp)) 
        print('calculating your jpy balance')       
        jpy_balance = ltpp*fetch_usdjpyrate()*balance
        label1.configure(text='Balance:'+str(f'{balance:.6f}')+'BTC')
        label2.configure(text='Use rate:'+str(f'{balance_use_rate:.1f}')+'%')  
        label3.configure(text='JPY:'+str(f'{jpy_balance:.1f}'), fg='blue')
        print('done!')       
    def button4_clicked():
        entry1.delete(0,tkinter.END)
        entry2.delete(0,tkinter.END) 
        print('deleted entrybox')
    def button5_clicked():
        orders = bitmex.fetch_open_orders('BTC/USD')
        for o in orders:
            cancel = bitmex.cancel_order(o['id'])
            text1.configure(state='normal')
            text1.insert('end', cancel['status'] + ' ' + cancel['id']+'\n')
            text1.yview(END)
            text1.configure(state='disabled') 
    def button6_clicked():
        text1.configure(state='normal')
        text1.delete(1.0,tkinter.END)   
        text1.configure(state='disabled') 
        print('deleted logs')
    def button7_clicked():
        pos = bitmex.privateGetPosition()
        for n in pos:   #XBTUSDのポジションサイズをopenposに格納
            if n['symbol'] == 'XBTUSD':
                openpos = n['currentQty']
            else:
                pass    
        if openpos > 0:
            order = bitmex.create_order('BTC/USD', type='market', side='sell', amount=openpos)
            order_id = order.get('id')
            text1.configure(state='normal')
            text1.insert('end', 'liquidated long '+order_id+'\n')
            text1.yview(END)
            text1.configure(state='disabled')
        elif openpos < 0:
            order = bitmex.create_order('BTC/USD', type='market', side='buy', amount=abs(openpos))    
            order_id = order.get('id')
            text1.configure(state='normal')
            text1.insert('end', 'liquidated short '+order_id+'\n')
            text1.yview(END)
            text1.configure(state='disabled')
        else:
            text1.configure(state='normal')
            text1.insert('end', 'no position.\n')
            text1.yview(END)
            text1.configure(state='disabled')  
    def button8_clicked():
        kaisu = 5   #ナンピンの回数、設定した金額からkaisu回ナンピン
        haba = 5    #ナンピンの幅。整数で1~
        lot = 0
        price = 0
        price_entry = entry1.get()
        size_entry = entry2.get()
        if not price_entry:
            pass
        else:
            price = price_entry
            price = float(entry1.get()) 
        if not size_entry:
            pass
        else:
            size = size_entry
            lot = entry2.get() 
        try:
            for i in range(kaisu):
                order = bitmex.create_order('BTC/USD', type='limit', side='buy', amount=lot, price=price-(i*haba))
                order_id = order.get('id')
                text1.configure(state='normal')
                text1.insert('end', 'success '+order_id+'\n')
                text1.yview(END)
                text1.configure(state='disabled')
        except Exception as e:
            print(e)    
    def button9_clicked():
        kaisu = 5   #ナンピンの回数、設定した金額からkaisu回ナンピン
        haba = 5    #ナンピンの幅。整数で1~
        lot = 0    
        price = 0
        price_entry = entry1.get()
        size_entry = entry2.get()
        if not price_entry:
            pass
        else:
            price = price_entry
            price = float(entry1.get()) 
        if not size_entry:
            pass
        else:
            size = size_entry
            lot = entry2.get() 
        try:
            for i in range(kaisu):
                order = bitmex.create_order('BTC/USD', type='limit', side='sell', amount=lot, price=price+(i*haba))
                order_id = order.get('id')
                text1.configure(state='normal')
                text1.insert('end', 'success '+order_id+'\n')
                text1.yview(END)
                text1.configure(state='disabled')
        except Exception as e:
            print(e)            
    def fetch_usdjpyrate():
        URL = "http://api.aoikujira.com/kawase/json/usd"
        response = urllib.request.urlopen(URL)
        data = json.loads(response.read())
        try:
            usdjpyrate = float(data.get('JPY'))
        except Exception as e:
            print(e)    
        return usdjpyrate
            
    root = tkinter.Tk()
    root.title('BitMEX Oeder Client v1.2')
    root.geometry('320x360')
    print('fetching balance')
    fetch_balance = bitmex.fetch_balance()
    balance = fetch_balance.get('total').get('BTC')
    balance_free = fetch_balance.get('free').get('BTC')
    balance_used = fetch_balance.get('used').get('BTC')
    balance_use_rate = balance_used/balance*100
    #ticker = bitmex.fetch_ticker('BTC/USD')
    #ltp = ticker.get('last')
    print('fetching ltp')
    r = requests.get("https://api.cryptowat.ch/markets/bitmex/btcusd-perpetual-futures/price")
    ltp = r.json()["result"]["price"]
    print('calculating your jpy balance')
    jpy_balance = ltp*fetch_usdjpyrate()*balance
    print('fetching your position')
    pos = bitmex.privateGetPosition()
    print('done!')
    for n in pos:   #XBTUSDのポジションサイズをopenposに格納
        if n['symbol'] == 'XBTUSD':
            openpos = n['currentQty']
        else:
            pass             
    label1 = tkinter.Label(root, text='Balance:'+str(f'{balance:.6f}')+'BTC') 
    label1.grid(row=1, sticky='w')
    label2 = tkinter.Label(root, text='Use rate:'+str(f'{balance_use_rate:.1f}')+'%') 
    label2.grid(row=1, column=1, sticky='w')
    label3 = tkinter.Label(root, text='JPY:'+str(f'{jpy_balance:.1f}'), fg='blue') 
    label3.grid(row=2, sticky='w')
    label4 = tkinter.Label(root, text='Price') 
    label4.grid(row=4, column=0)
    label5 = tkinter.Label(root, text='Size') 
    label5.grid(row=4, column=1)    
    label7 = tkinter.Label(root, text='BTC/USD:'+str(ltp))   
    label7.grid(row=2, column=1, sticky='w') 
    entry1 = tkinter.Entry(root,font=('',12),justify='center',width=15)
    entry1.grid(row=5, column=0)
    entry2 = tkinter.Entry(root,font=('',12),justify='center',width=15)
    entry2.grid(row=5, column=1)   
    button1 = tkinter.Button(text='LONG',command=button1_clicked,width=20,height=5,fg='green')   
    button1.grid(row=6,column=0,sticky='ns') 
    button2 = tkinter.Button(text='SHORT',command=button2_clicked,width=20,height=5,fg='red')   
    button2.grid(row=6,column=1,sticky='ns') 
    button3 = tkinter.Button(text='Reload balance',command=button3_clicked)
    button3.grid(row=8,column=0,sticky='ns')  
    button4 = tkinter.Button(text='Clear all text',command=button4_clicked) 
    button4.grid(row=8,column=1,sticky='ns') 
    button5 = tkinter.Button(text='Cancel all orders',command=button5_clicked) 
    button5.grid(row=9,column=0,sticky='ns')
    button6 = tkinter.Button(text='Clear logs',command=button6_clicked) 
    button6.grid(row=9,column=1,sticky='ns')
    button7 = tkinter.Button(text='Liquidation', command=button7_clicked, fg='violet')
    button7.grid(row=11, column=0)
    button8 = tkinter.Button(text='nang-ping LONG', command=button8_clicked, fg='green')
    button8.grid(row=7, column=0)
    button9 = tkinter.Button(text='nang-ping SHORT', command=button9_clicked, fg='red')
    button9.grid(row=7, column=1)
    text1 = tkinter.Text(height=5,width=5) 
    text1.grid(row=10,column=0,ipadx=100,sticky='nsew',columnspan=2) 
    text1.configure(state='disabled')
    scrollbar = ttk.Scrollbar(
        root, 
        orient=VERTICAL, 
        command=text1.yview)
    text1['yscrollcommand'] = scrollbar.set
    scrollbar.grid(row=10,column=2,sticky='ns')
    root.mainloop() 

if __name__ == '__main__':
    main()    

万全を期したはずですが、万が一プログラムの不具合で損失を被った場合の補償はできかねますのでご了承ください。

もし不具合や要望があればTwitterかブログまでお願いします。


この続きをみるには

この続き:29文字

BitMEX用オーダー発注ソフトをPython+Tkinterで作った

sj_crypto

200円

この記事が気に入ったら、サポートをしてみませんか?気軽にクリエイターを支援できます。

25

sj_crypto

bot

コメントを投稿するには、 ログイン または 会員登録 をする必要があります。