CosmosOsmosisアビトラ
created at
CosmosアビトラBotの作り方 その4【コード付き】
前回の続きです。前回は一例としてベルマンフォード法を用いれば、アービトラージパスを探索できるよというところまで話しました。 今、手元にはプールの情報があるので実際に探索してみましょう。
AMMで放出されるトークンの量を算出
まずは、AMM上で、あるトークンをスワップしたときに放出されるトークンの量を算出する関数を書いていきます。今回は構造体Poolのメソッドとして定義していきます。
func (p *Pool) CalcTokenOutAmount(TokenInAmount float64, tokenOut uint) float64 {
if tokenOut == p.Token2.CoinID {
return p.Ry * (1 - math.Pow((p.Rx/(p.Rx+p.CostFactor*TokenInAmount)), (p.Wx/p.Wy)))
} else if tokenOut == p.Token1.CoinID {
return p.Rx * (1 - math.Pow((p.Ry/(p.Ry+p.CostFactor*TokenInAmount)), (p.Wy/p.Wx)))
} else { //なんでこういう風に書いたのか思い出せない。
return 1e10
}
}
その2の記事で紹介した下の式をそのまま使っています。
また、と(プール内のトークンの量)を更新するメソッドも用意します。
func (p *Pool) UpdatePool(Rx, Ry float64) {
p.Rx = Rx
p.Ry = Ry
}
最適なパスの探索
あるトークンをX量分スワップした時に、上で計算した放出されるトークンの量をdXとします。グラフに設定すべきトークンの交換レートはdX/X(前回説明した通りを取ります)となります。これをプール上のすべてのトークンに対して計算し、グラフを完成させていきます。
注意しなければならないのは、AMMではXの値によって交換レートdX/Xの値も大きく変わってくるということです。最適なXの量は何なんだという話はひとまず置いておいて、まずは10ドル分のトークンをスワップした時に導出される交換レートからグラフを完成させてみます。(この時に各トークンのおおよその価格を入れたpriceMapが役立ちます)
package main
import (
bf "cosmos-arb/algorithms"
"encoding/json"
"fmt"
"io/ioutil"
"math"
"net/http"
"strconv"
)
```-------------関数/構造体の定義部分は長くなるので省略。前回までの記事を参考にしてください-------------```
func main() {
active_pools := GetActivePools()
whole_pools := GetPoolAll()
decimalMap := GetDecimalMap()
priceMap, instrumentMap, verticles := getInstrumentDictionaries(active_pools)
pool_information := make(map[int]*Pool)
poolMap := make(map[[2]uint]int)
//とりあえプールID800まで使う。それ以降は流動性に欠ける。
for pool_id := range active_pools {
if pool_id > 800 {
delete(active_pools, pool_id)
}
}
for pool_id := range active_pools {
arranged_pool := arrangePoolInformation(whole_pools.Pools[pool_id-1], instrumentMap, decimalMap)
if arranged_pool != nil {
pool_information[pool_id] = arranged_pool
poolMap[[2]uint{pool_information[pool_id].Token1.CoinID, pool_information[pool_id].Token2.CoinID}] = pool_id
poolMap[[2]uint{pool_information[pool_id].Token2.CoinID, pool_information[pool_id].Token1.CoinID}] = pool_id
}
}
fmt.Println("------Initialization Completed-----")
var token1, token2 uint
var Rx, Ry, X, Y, dX, dY float64
var edges []*bf.Edge
const default_amount float64 = 10
//実際のプールの情報はwhole_poolsに格納されている。アクティブなプールのID毎にプール内での交換レートをグラフのEdgeに渡していく。
for pool_id := range active_pools {
new_pool := whole_pools.Pools[pool_id-1]
if new_pool.Type == "/osmosis.gamm.v1beta1.Pool" {
Rx, _ = strconv.ParseFloat(new_pool.Pool_assets[0].Token.Amount, 64)
Ry, _ = strconv.ParseFloat(new_pool.Pool_assets[1].Token.Amount, 64)
//前回説明分:小数点以下の情報を元に実数値に変換
Rx = Rx / pool_information[pool_id].Dx
Ry = Ry / pool_information[pool_id].Dy
pool_information[pool_id].UpdatePool(Rx, Ry)
token1 = pool_information[pool_id].Token1.CoinID
token2 = pool_information[pool_id].Token2.CoinID
//default_amount = 10ドル分に相当するトークンの量
X = default_amount / priceMap[pool_information[pool_id].Token1.Denom]
Y = default_amount / priceMap[pool_information[pool_id].Token2.Denom]
dX = pool_information[pool_id].CalcTokenOutAmount(X, token2)
dY = pool_information[pool_id].CalcTokenOutAmount(Y, token1)
edges = append(edges, bf.NewEdge(token1, token2, -math.Log(dX/X)))
edges = append(edges, bf.NewEdge(token2, token1, -math.Log(dY/Y)))
}
}
g := bf.NewGraph(edges, verticles)
//OSMOトークンを始点にして計算。
path := g.FindArbitrageLoop(instrumentMap["uosmo"])
fmt.Println("Arbitrage Path: ", path)
}
ここで提示したコードを何回か回してくるとたまに空以外の結果が返ってくるかと思います(またはwhole poolsだけを更新して5秒毎くらいにwhileループを回してみてください)。そのなかで儲かるポテンシャルがあるのは下に挙げた良さそうな例です。
-
ダメな例:Arbitrage Path: [86 86], Arbitrage Path: [86 0 86] ←こういうのは取り除く必要がある。
-
良さそうな例:Arbitrage Path: [86 119 24 86]
上記のコードでは始点をOSMOトークンにしましたので、始点と終点はOSMOトークンとなっています。(上の例ではトークンIDは86となっているが、このIDは実行毎に変わってきます)
問題となってくるのは、上に挙げたような良さそうなパスを見つけたとしても、ガス代によっては実際は儲からないかもしれないということです。となると気になってくるのは、見つかったパスをもとに実際に注文を行うとどうなるのかということだと思いますので、次回からはOsmosisネットワークにトランザクションを送る準備に移ります。果たして、ちゃんと儲かるのでしょうか?
とりあえず、次回はトランザクションの生成、署名に必要なウォレットを定義していくところから始めたいと思います。