created at

CosmosアビトラBotの作り方 その2【コード付き】

前回の続きです。

前回は以下のコードを用いてネットワーク上の価格情報をAPIから取ってくるというところまで来ました。

func main() {
    active_pools := GetActivePools()
	whole_pools := GetPoolAll()
}

得られたデータを構造体に定義する。

まず、それぞれのコインの型を定義します。

type Coin struct {
	Denom  string
	CoinID uint
}

各コインは定数としてDenom(コインの名称のようなもの)の情報とCoinIDを持っています。そして、そのコインはネットワーク上に存在するプールに組みになって存在しています。

例えば、Dex上でATOMとOSMOを交換したい場合、Osmosis DEXの「プール1」を使います。交換レートは、AMMという仕組みによって「プール1」に存在するATOMとOSMOの量から決定されます。詳しい計算方式は公式ドキュメントをご参照ください。後で計算に使っていきます。

GAMM on Osmosis

では、今度はそのプールを構造体で定義しましょう。とその前に基本的なAMMの仕組みについて簡単に見ていきましょう。

公式ドキュメントから引用します。あるプール内でトークンのスワップが起きた時、プールから放出されるトークンの量=スワップしたユーザーが得られるトークンの量は以下の式で定義されています。

tokenBalanceOut[1tokenBalanceIn/(tokenBalanceIn+(1swapFee)tokenAmountIn)(tokenWeightIn/tokenWeightOut)]tokenBalanceOut * [1 - { tokenBalanceIn / (tokenBalanceIn + (1 - swapFee) * tokenAmountIn)} ^ {(tokenWeightIn / tokenWeightOut)}]

ここでtokenBalanceInとはユーザーがSwapしようとしているトークンのプール内の総量、逆にtokenBalanceInとはユーザーが獲得しようとしているトークンのプール内での総量を指します。tokenAmountIn,tokenAmountOutとはSwapしようとしているトークン/獲得しようとしているトークンの純粋な量です。

tokenWeightInやtokenWeightOutはUniswapなど一般的なDexでは定義されていないことは多いかもしれません。これはトークンの計算時に各トークンのプールの量に対してかかるファクターでトークンのスポット価格を次のように定義する時に使われます。

(tokenBalanceIn / tokenWeightIn) / (tokenBalanceOut / tokenWeightOut)

この重みづけの仕組みによって、あるトークンの流動性が十分にない場合でも重みづけを大きくすればスポット価格がマーケットの適正価格から大きくずれなくなるというメリットが得られます。しかしながら、この仕組みによってアービトラージを行う時の実際の計算が非常に複雑になります。

その複雑な計算はとりあえず置いといて、プールの型定義に進みましょう。

tokenBalanceOutをRy,tokenBalanceInをRx,tokenAmountOut,tokenAmountInをそれぞれDy,Dxとしましょう。重みはそれぞれWx,Wyです。各プールはこの6つのパラメータの他にトークン(Coin)の情報、プールのID、それからプールの使用にかかるコストファクター(ドキュメントの1 - swapFeeの部分)を持っています。

type Pool struct {
	ID         int
	PoolType   string
	Token1     Coin
	Token2     Coin
	Rx         float64
	Ry         float64
	Wx         float64
	Wy         float64
	Dx         float64
	Dy         float64
	CostFactor float64
}

前回の記事で取得した情報をarrangePoolInformation()のような関数を作ってこの構造体に定義していきます。ここでの取得した情報をもう一度見ていきましょう。


type PoolAssets struct {
	Type        string `json:"@type"`
	Id          string
	Pool_params struct {
		Swap_fee string
		Exit_fee string
	}

	Pool_assets [2]struct {
		Token struct {
			Denom  string
			Amount string
		}
		Weight string
	}
}

まず、Pool_params上のSwap_feeとExit_feeはCost Factorに該当します。そして、肝心のPool_assetsという配列には二種類のトークンが存在し、それぞれがDenomとAmountの情報を持っていることがわかります。ここにはWeightの情報も入っているのでPoolの定義は大丈夫そうですね。型定義がしっかりしているところがGo言語の良い点です。

さて、最初に存在しているTypeというパラメータを見ていきましょう。Curlを使って中身を簡単にチェックしますと、よくわからないパラメータがあるのがわかると思います。これはCosmos SDK上で定義されているプールの型であり、ステーブルスワップやマルチカレンシースワップなどプールの種類によって変わってきます。今回は /osmosis.gamm.v1beta1.Poolという基本的なプールに焦点を当てていきます。

では、実際のコードは次のようになります。

import(
	"math"
	"strconv"
)

func arrangePoolInformation(pool PoolAssets, instrumentMap map[string]uint, decimalMap map[string]int) *Pool {
	if pool.Type == "/osmosis.gamm.v1beta1.Pool" {
		ins_x := pool.Pool_assets[0].Token.Denom
		ins_y := pool.Pool_assets[1].Token.Denom
		Rx, _ := strconv.ParseFloat(pool.Pool_assets[0].Token.Amount, 64)
		Ry, _ := strconv.ParseFloat(pool.Pool_assets[1].Token.Amount, 64)
		Rx = Rx / math.Pow10(decimalMap[ins_x])
		Ry = Ry / math.Pow10(decimalMap[ins_y])

		_Wx, _ := strconv.ParseFloat(pool.Pool_assets[0].Weight, 64)
		_Wy, _ := strconv.ParseFloat(pool.Pool_assets[1].Weight, 64)
		Wx := _Wx / (_Wx + _Wy)
		Wy := _Wy / (_Wx + _Wy)

		fee, _ := strconv.ParseFloat(pool.Pool_params.Swap_fee, 64)

		token1 := Coin{Denom: ins_x, CoinID: instrumentMap[ins_x]}
		token2 := Coin{Denom: ins_y, CoinID: instrumentMap[ins_y]}

		return &Pool{
			Token1:     token1,
			Token2:     token2,
			Rx:         Rx,
			Ry:         Ry,
			Wx:         Wx,
			Wy:         Wy,
			Dx:         math.Pow10(decimalMap[ins_x]),
			Dy:         math.Pow10(decimalMap[ins_y]),
			CostFactor: 1 - fee,
		}
	} else {
		return nil
	}
}

はい。実はこのままではコードは動きません。なぜならば、まだここにはinstrumentMapとdecimalMapという重要な情報が欠けているからです。

次回はこの二つについてMap(辞書のようなもの)について説明した後に、今度こそはベルマンフォードアルゴリズムの実装に移っていきたいと思います。

↓おすすめ記事です。Osmosisになれる意味でも読んでおくと良いかもしれません。

CosmosアビトラBotの作り方 その2【コード付き】
コメント
新着記事
Copyright © 2023 Coin News DAO. All rights reserved.

Site Map

Twitter(X)