Bot

仮想通貨botの開発記録#68(2024/4/3)「ヒゲ取りBotの開発①」

前回の記事に引き続き、今回も仮想通貨botの開発状況をまとめていきます。

今回は、ヒゲ取りBotのプロトタイプを作ってみました。

ヒゲ取りBotとは、何らかの理由で価格が急激に変化したトークンを売買することで利益を上げるタイプの Botです。

Yodaka

参考にしたのは、以下の記事です。

ヒゲ取りBotのコード

Yodaka

今回は、ArbitrumCurveにおけるUSDTのデペグをキャッチするBotです。

関連
Arbitrum(アービトラム)完全ガイド!【イーサリアムのL2】

続きを見る

関連
Curve Finance(カーブファイナンス)を使ってみよう!【DeFiの基本】

続きを見る

関連
ステーブルコイン解説【DeFiで稼ぐヒント】

続きを見る

注意

以下のコードはプロトタイプであり、実際の運用には調整が必要です。

from web3 import Web3
from pathlib import Path
import logging
import json
import schedule
import time

# Arbitrum RPCのURL
RPC_URL = 'https://arbitrum-rpc.com/'

# アドレスとプライベートキー(環境変数から取得する)
ACCOUNT_ADDRESS = 'YOUR_ADDRESS'
ACCOUNT_PK = 'YOUR_PRIVATE_KEY'

# 通貨のDECIMALS
DECIMALS = 10 ** 6

# 1回のTrade量 $0.1
TRADE_AMOUNT = int(0.1 * DECIMALS)

# 購入・売却価格
BUY_PRICE = 0.90
SELL_PRICE = 0.98

# スケジュール間隔
SCHEDULE_SEC = 10

# スリッページ
MIN_RECEIVE = 0.99

# トークンのインデックス
USDT_INDEX = 2
USDC_INDEX = 1

# Arbitrum上のコントラクトアドレス(適切なアドレスに置き換える)
TUSD_POOL_CONTRACT = '0xExampleAddressForTUSDOnArbitrum'
USDC_TOKEN_CONTRACT = '0xExampleAddressForUSDCOnArbitrum'
USDT_TOKEN_CONTRACT = '0xExampleAddressForUSDTOnArbitrum'

# トレードの最小量
SELL_MIN_AMOUNT = 0.01 * DECIMALS

# Arbitrum Chain ID
CHAIN_ID = 42161

# ログ設定
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)

# web3インスタンスの生成
web3 = Web3(Web3.HTTPProvider(RPC_URL))

# ABIのロード
base_path = str(Path(__file__).resolve().parent)
curve_abi = json.load(open(f"{base_path}/abi/curve.json", 'r'))
erc20_abi = json.load(open(f"{base_path}/abi/erc20.json", 'r'))

# コントラクトインスタンスの生成
curve_contract = web3.eth.contract(address=TUSD_POOL_CONTRACT, abi=curve_abi)
usdc_contract = web3.eth.contract(address=USDC_TOKEN_CONTRACT, abi=erc20_abi)
usdt_contract = web3.eth.contract(address=USDT_TOKEN_CONTRACT, abi=erc20_abi)

def run_schedule():
    """
    scheduleの実行
    """
    logger.info('Bot started..')
    schedule.every(SCHEDULE_SEC).seconds.do(bot_main)
    while True:
        schedule.run_pending()
        time.sleep(1)

def bot_main():
    """
    Botのロジック
    """
    # PoolからPriceを取得
    usdc_usdt_price = curve_contract.functions.get_dy(USDT_INDEX, USDC_INDEX, TRADE_AMOUNT).call()
    logger.info(f'USDC/USDT Price: {usdc_usdt_price / DECIMALS}')

    # 保有USDCを取得
    usdc_amount = usdc_contract.functions.balanceOf(ACCOUNT_ADDRESS).call()
    logger.info(f'USDC Amount: {usdc_amount / DECIMALS}')

    # 保有USDTを取得
    usdt_amount = usdt_contract.functions.balanceOf(ACCOUNT_ADDRESS).call()
    logger.info(f'USDT Amount: {usdt_amount / DECIMALS}')

    # 保有USDCがTRADE_AMOUNTを下回ったらエラー
    if usdc_amount < TRADE_AMOUNT:
        raise RuntimeError('Not enough USDC')

    # Priceが購入条件を下回り、USDTのポジションがない場合、購入
    if usdc_usdt_price < BUY_PRICE * DECIMALS and usdt_amount <= TRADE_AMOUNT:
        logger.info('Buying USDT...')
        buy_usdt(TRADE_AMOUNT)
    # Priceが売却条件を上回り、USDTのポジションがある場合、売却
    elif usdc_usdt_price > SELL_PRICE * DECIMALS and usdt_amount >= TRADE_AMOUNT:
        logger.info('Selling USDT...')
        sell_usdt(TRADE_AMOUNT)
    else:
        logger.info('Do nothing.')

def buy_usdt(amount):
    """
    USDCからUSDTへの購入
    """
    tx_receipt = send_buysell_tx(from_coin=USDC_INDEX, to_coin=USDT_INDEX, amount=amount)
    logger.info(f'Transaction receipt: {tx_receipt}')
    if tx_receipt['status'] == 1:
        logger.info('Bought USDT successfully!')
    else:
        logger.error('Error buying USDT')

def sell_usdt(amount):
    """
    USDTからUSDCへの売却
    """
    tx_receipt = send_buysell_tx(from_coin=USDT_INDEX, to_coin=USDC_INDEX, amount=amount)
    logger.info(f'Transaction receipt: {tx_receipt}')
    if tx_receipt['status'] == 1:
        logger.info('Sold USDT successfully!')
    else:
        logger.error('Error selling USDT')

def send_buysell_tx(from_coin, to_coin, amount):
    """
    購入・売却トランザクションの送信
    """
    func = curve_contract.functions.exchange_underlying(from_coin, to_coin, amount, int(amount * MIN_RECEIVE))
    nonce = web3.eth.get_transaction_count(ACCOUNT_ADDRESS)
    tx = func.build_transaction({
        "chainId": CHAIN_ID,
        "gas": 1000000,  # Arbitrumではこの値を調整する必要がある
        "gasPrice": web3.eth.gas_price,
        "nonce": nonce,
    })
    signed_tx = web3.eth.account.sign_transaction(tx, ACCOUNT_PK)
    tx_hash = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
    tx_receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
    return tx_receipt

if __name__ == "__main__":
    run_schedule()
Yodaka

それぞれの関数がどのような役割を果たしているのか、解説します。

run_schedule()

この関数は、設定した時間間隔で定期的に他の関数(この場合は bot_main)を実行するためのスケジューラーです。ここでは、schedule.every(SCHEDULE_SEC).seconds.do(bot_main) という行で、SCHEDULE_SEC で指定した秒数ごとに bot_main 関数を呼び出すようにスケジュールしています。while True ループ内で schedule.run_pending() を呼び出し、予定されているタスクがあれば実行します。これにより、自動で繰り返し取引を試みることができます。

bot_main()

この関数はボットのメインロジックを担います。具体的には、現在のUSDC/USDTの価格を取得し、設定された条件に基づいてUSDCをUSDTに交換するか、その逆の操作をするかを決定します。取引条件は、BUY_PRICESELL_PRICE によって設定されています。保有している通貨の量が取引を行うのに十分かどうかを確認し、条件に応じて buy_usdt または sell_usdt 関数を呼び出します。

buy_usdt(amount)

USDCを使用してUSDTを購入するための関数です。指定された amount 分のUSDTを購入しようと試みます。内部で send_buysell_tx 関数を呼び出し、ブロックチェーン上で実際の取引を実行します。取引が成功すれば、購入したことをログに記録します。

sell_usdt(amount)

USDTを使用してUSDCを売却するための関数です。指定された amount 分のUSDCを売却しようと試みます。こちらも buy_usdt 関数と同様に、send_buysell_tx 関数を使って実際の取引を行います。取引が成功すれば、売却したことをログに記録します。

send_buysell_tx(from_coin, to_coin, amount)

実際の取引をブロックチェーン上で行うための関数です。from_coin から to_coin への交換を試み、指定された amount 分の取引を行います。この関数では、取引に必要な情報(どの通貨からどの通貨への交換か、交換量、ガス料金など)を設定し、トランザクションをブロックチェーンに送信します。トランザクションが承認されれば、その結果がレシートとして返され、取引が成功したかどうかをログで確認できます。

Yodaka

Arbitrum用に調整しましたが、具体的なコントラクトアドレスや取引のディテール(例えば、使用するコントラクトの関数、スリッページの値など)は、実際に取引を行いたい条件に基づいて適宜調整する必要があります。また、トランザクションのガス費用は、Arbitrumのネットワーク状況に応じて変わります。そのため適切な値に調整する必要があります。

DECIMALS

クリプト(暗号資産)の文脈における「DECIMALS」は、トークンの最小単位を指し、そのトークンが分割可能な程度を定義しています。

例えば、1トークンをどれだけの小さな単位まで分割できるかを示します。これは伝統的な通貨における「セント」や「ペニー」の概念に似ていますが、クリプトの世界ではより柔軟性があり、トークンによって異なる数の小数点以下の桁を設定することができます。

「DECIMALS」の値は、特定のトークンを表すスマートコントラクトによって定義されます。例えば、イーサリアムベースのトークン(ERC-20トークン)では、この値は通常、スマートコントラクトの一部として静的に設定されています。

例を挙げると、もしトークンAが「DECIMALS」を2に設定している場合、1トークンは100の最小単位に分割することができます(1.00)。一方で、もしトークンBが「DECIMALS」を18に設定している場合、1トークンは1,000,000,000,000,000,000の最小単位(1.000000000000000000)に分割することができます。これにより、非常に小さな取引も可能になり、ユーザーは自分のニーズに応じてトークンを細かく使用することができます。

「DECIMALS」の設定は、ユーザーがトークンの価値を理解しやすくするためにも重要です。トークンの価値が非常に高い場合や、微細な取引を可能にする必要がある場合、多くの小数点以下の桁数が設定されます。逆に、トークンの価値が低い場合や、細かい取引の必要性が低い場合は、小数点以下の桁数を少なく設定することが一般的です。

まとめ

今回はヒゲ取りBotのプロトタイプを作りました。

大まかなプロトタイプを作ることで、コードを読む練習にもなる、と感じました。

実際にArbitrumネットワークで稼働させるためには細かい部分の調整が必要なので、ここから調整をしていきます。

また、プライベートキーの管理などのセキュリティ面への対策も、元の記事を読みながら一つずつ実装していきます。

宿題

  • 元記事を読んで、細部の調整を一つずつ確実に行う
  • 別の戦略にスケールさせられないかを考える

Yodaka

今後もこの調子で研究と開発を進めていきます。

-Bot