海外取引所から日本へ出金時の現先構築・解消自動化bot

FTXに続いてバイナンスも破綻するという噂が広まっており、たぶんないとは思いますが、念には念を入れて日本に資金を戻したいと思いました
バイナンスから日本への送金の際、価格変動リスクをなくすため、以下の方法で資金を送ることにしました

①バイナンスで現先のポジションを作る
②現物をbitbankに送金
③binanceとbitbank間で、現先を同時に閉じる。特にbinanceの流動性が低いため、時間をかけて少しずつ閉じる

この、①③を自動化するbotを公開します。取引所が違っても、少し変更すれば動くと思います

①バイナンスで現先のポジションを作る

#!/usr/bin/env python
# coding: utf-8
import datetime
from datetime import timezone
import time
import requests
import json
import ccxt
import traceback
import pprint
import asyncio
import os

def print_log(message, mode=0):
    # mode
    # 0 標準出力のみ
    # 1 ファイル出力のみ
    # 2 標準出力+ファイル出力
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    if mode == 0 or mode == 2:
        print(now + " " + message)
    if mode == 1 or mode == 2:
        filename = f"syslog_{os.path.splitext(os.path.basename(__file__))[0]}.txt"
        with open(filename, 'a') as f:
            print(now + " " + message, file=f)


# 概要
# binanceの先物(Perpetual)現先ポジをオープンする
# basis rate(現先乖離)が指定以上のときは待機する

#==============================================================================
# settings
#==============================================================================
interval = 5 #間隔(秒)
size = 1 #1回の買数量(×100USD(binance側の単位))
sizeMax = 1 #最大買数量(×100USD単位(binance側の単位))

#binance
exchange = ccxt.binance({
    'apiKey': '',
    'secret': '',
    'options': {
        #'defaultType': 'future',  # usd-m
        'defaultType': 'delivery', # coin-m
    },
})

symbolFuturePos = "BTCUSD_PERP" #ポジション参照時のシンボル指定
symbolFuture = "BTC/USD" #注文時のシンボル指定
symbolSpot = "BTC/BUSD"
sizeIncrement = 100000 #現物最小注文数量0.00001

#basis rate
basis_thres = 0.0 # basisがこれ以上大きくなるまで待つ
def basisRate():
    #https://binance-docs.github.io/apidocs/delivery/en/#basis-market-data
    url = f"https://dapi.binance.com/futures/data/basis?pair=BTCUSD&contractType=PERPETUAL&period=5m&limit=1"
    res = requests.get(url)
    res.raise_for_status()
    res = res.json()
    #[{"indexPrice":"56845.01009091","contractType":"CURRENT_QUARTER","basisRate":"0.0681","futuresPrice":"60715.8","basis":"3870.78990909","pair":"BTCUSD","timestamp":1617805800000}]
    basis = float(res[0]["basisRate"])
    print_log(f"basis: {basis}", 2)
    return basis

#==============================================================================
# main
#==============================================================================

exchangeSpot = ccxt.binance({
    'apiKey': exchange.apiKey,
    'secret': exchange.secret
})
coin = symbolSpot.split("/")[0]

# binance先物ポジション数量(BTC換算の初期数量、売りはマイナス)
def position(symbol):
    position = 0
    res = exchange.fetch_positions(symbols=[symbol])
    print_log(pprint.pformat(res), 1)
    for pos in res:
        # notionalValue + unrealizedProfit=BTC換算の初期数量
        pos = pos["info"]
        position += float(pos["notionalValue"]) + float(pos["unRealizedProfit"])
    return position


totalSize = 0
totalSpot = 0
startPosition = position(symbolFuturePos)
print_log(f"startPosition: {startPosition}", 2)
# exit(0)

while True:
    while True:
        try:
            # if basisRate() > basis_thres:
            #     time.sleep(5)
            #     continue
            break
        except Exception:
            print_log(traceback.format_exc(), 2)
            time.sleep(5)

    try:
        #売り
        print_log(f"sell {symbolFuture} size:{size}", 2)
        res = exchange.create_market_sell_order(symbolFuture, size)
        print_log(pprint.pformat(res), 1)

        #注文執行完了待ち
        id = res["id"]
        res = exchange.fetch_order(id, symbol=symbolFuture)
        print_log(pprint.pformat(res), 1)
        while res["status"] != "closed":
            time.sleep(1)
            res = exchange.fetch_order(id, symbol=symbolFuture)
            print_log(pprint.pformat(res), 1)

        #先物ポジション(注文後)
        positionFuture = position(symbolFuturePos)
        print_log(f"positionFuture: {positionFuture}", 2)

        #現物注文数量 = 先物ポジションの増分
        sizeSpot = abs(positionFuture - startPosition) - totalSpot
        sizeSpot = sizeSpot * sizeIncrement // 1 / sizeIncrement

        if sizeSpot > 0:
            # 買い
            print_log(f"buy {symbolSpot} size:{sizeSpot}", 2)
            res = exchangeSpot.create_market_buy_order(symbolSpot, sizeSpot)
            print_log(pprint.pformat(res), 1)

            # 注文執行完了待ち
            id = res["id"]
            res = exchangeSpot.fetch_order(id, symbol=symbolSpot)
            print_log(pprint.pformat(res), 1)
            while res["status"] != "closed":
                time.sleep(1)
                res = exchangeSpot.fetch_order(id, symbol=symbolSpot)
                print_log(pprint.pformat(res), 1)

            # 資金移動 spot to coin-m
            print_log(f"transfer {symbolSpot} size:{sizeSpot}", 2)
            res = exchangeSpot.sapiPostAssetTransfer({
                "type": "MAIN_CMFUTURE",
                "asset": coin,
                "amount": sizeSpot,
                "timestamp": int(time.time() * 1000)
                # https://binance-docs.github.io/apidocs/spot/en/#trade-fee-user_data
            })
            print_log(pprint.pformat(res), 1)

            totalSpot += sizeSpot

        totalSize += size
        print_log(f"total size: {totalSize}", 2)
        print_log(f"total notional value future: {positionFuture - startPosition}", 2)
        print_log(f"total notional value spot: {totalSpot}", 2)

        if totalSize >= sizeMax:
            break

    except Exception:
        print_log(traceback.format_exc(), 2)
        break

    time.sleep(interval)

③binanceとbitbank間で、現先を同時に閉じる

#!/usr/bin/env python
# coding: utf-8
import datetime
from datetime import timezone
import time
import requests
import json
import ccxt
import traceback
import pprint
import asyncio
import os

def print_log(message, mode=0):
    # mode
    # 0 標準出力のみ
    # 1 ファイル出力のみ
    # 2 標準出力+ファイル出力
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    if mode == 0 or mode == 2:
        print(now + " " + message)
    if mode == 1 or mode == 2:
        filename = f"syslog_{os.path.splitext(os.path.basename(__file__))[0]}.txt"
        with open(filename, 'a') as f:
            print(now + " " + message, file=f)


# 概要
# binanceの先物(Perpetual)とbitbank現物の現先ポジをクローズする
# basis rate(現先乖離)が指定以上のときは待機する

#==============================================================================
# settings
#==============================================================================
interval = 5 #間隔(秒)
size = 1 #1回の買数量(×100USD(binance側の単位))
sizeMax = 1 #最大買数量(×100USD単位(binance側の単位))

#binance
exchange = ccxt.binance({
    'apiKey': '',
    'secret': '',
    'options': {
        #'defaultType': 'future',  # usd-m
        'defaultType': 'delivery', # coin-m
    },
})

#bitbank
exchangeSpot = ccxt.bitbank({
    'apiKey': '',
    'secret': '',
})

symbolFuturePos = "BTCUSD_PERP" #ポジション参照時のシンボル指定
symbolFuture = "BTC/USD" #注文時のシンボル指定
symbolSpot = "BTC/JPY"
sizeIncrement = 10000 #現物最小注文数量0.0001

#basis rate
basis_thres = 0.0 # basisがこれ以上大きくなるまで待つ
def basisRate():
    #https://binance-docs.github.io/apidocs/delivery/en/#basis-market-data
    url = f"https://dapi.binance.com/futures/data/basis?pair=BTCUSD&contractType=PERPETUAL&period=5m&limit=1"
    res = requests.get(url)
    res.raise_for_status()
    res = res.json()
    #[{"indexPrice":"56845.01009091","contractType":"CURRENT_QUARTER","basisRate":"0.0681","futuresPrice":"60715.8","basis":"3870.78990909","pair":"BTCUSD","timestamp":1617805800000}]
    basis = float(res[0]["basisRate"])
    print_log(f"basis: {basis}", 2)
    return basis

#==============================================================================
# main
#==============================================================================

# binance先物ポジション数量(BTC換算の初期数量、売りはマイナス)
def position(symbol):
    position = 0
    res = exchange.fetch_positions(symbols=[symbol])
    print_log(pprint.pformat(res), 1)
    for pos in res:
        # notionalValue + unrealizedProfit=BTC換算の初期数量
        pos = pos["info"]
        position += float(pos["notionalValue"]) + float(pos["unRealizedProfit"])
    return position


totalSize = 0
totalSpot = 0
startPosition = position(symbolFuturePos)
print_log(f"startPosition: {startPosition}", 2)
# exit(0)

while True:
    while True:
        try:
            # if basisRate() > basis_thres:
            #     time.sleep(5)
            #     continue
            break
        except Exception:
            print_log(traceback.format_exc(), 2)
            time.sleep(5)

    try:
        #買い
        print_log(f"buy {symbolFuture} size:{size}", 2)
        res = exchange.create_market_buy_order(symbolFuture, size)
        print_log(pprint.pformat(res), 1)

        #注文執行完了待ち
        id = res["id"]
        res = exchange.fetch_order(id, symbol=symbolFuture)
        print_log(pprint.pformat(res), 1)
        while res["status"] != "closed":
            time.sleep(1)
            res = exchange.fetch_order(id, symbol=symbolFuture)
            print_log(pprint.pformat(res), 1)

        #先物ポジション(注文後)
        positionFuture = position(symbolFuturePos)
        print_log(f"positionFuture: {positionFuture}", 2)

        #現物注文数量 = 先物ポジションの増分
        sizeSpot = abs(positionFuture - startPosition) - totalSpot
        sizeSpot = sizeSpot * sizeIncrement // 1 / sizeIncrement

        if sizeSpot > 0:
            # 売り
            print_log(f"sell {symbolSpot} size:{sizeSpot}", 2)
            res = exchangeSpot.create_market_sell_order(symbolSpot, sizeSpot)
            print_log(pprint.pformat(res), 1)

            # 注文執行完了待ち
            id = res["id"]
            res = exchangeSpot.fetch_order(id, symbol=symbolSpot)
            print_log(pprint.pformat(res), 1)
            while res["status"] != "closed":
                time.sleep(1)
                res = exchangeSpot.fetch_order(id, symbol=symbolSpot)
                print_log(pprint.pformat(res), 1)

            totalSpot += sizeSpot

        totalSize += size
        print_log(f"total size: {totalSize}", 2)
        print_log(f"total notional value future: {positionFuture - startPosition}", 2)
        print_log(f"total notional value spot: {totalSpot}", 2)

        if totalSize >= sizeMax:
            break

    except Exception:
        print_log(traceback.format_exc(), 2)
        break

    time.sleep(interval)


いいなと思ったら応援しよう!