儲かりそうで儲からない”自称”Botterの話_その1 「高校数学から学ぶDEXアビトラにおける取引量の最適化」
はじめに
初めまして、Asahiです。このnoteはDeFiにおけるDEXアビトラBotを作成する上で、自分が気になったこととそれに対する考察を述べていく記事になります。対象としては、DEXアビトラ初心者?から中級者向け?の記事になります。(執筆時点でBotter歴1~2ヶ月目なので、正直どの層に向けた記事なのか自分でもよくわかりません。)
!!注意!!
自分はマイナーチェーンでアビトラBotを稼働させていました(現在停止中)が、ほとんど儲かっていません!大事なことなのでもう一度言います。儲かっていません!!
いや、もう一度言わせてもらいます。儲かっていません!!!
こんなヘボヘボBotterの戯言でも気になる人がいるのならば、この先読み進めていただければ幸いです。
「は?なんで儲けないやつの記事なんか読まなきゃいけないの?」と思う人は速攻ブラウザバック推奨だ!
また、この界隈に出会ってから日が浅いこともあり、使用する用語が実際に意味するものと違うことも多々あるとは思いますが、そこはこれからの成長に期待して暖かい目で見守ってください。その際、間違っている点をご指摘いただけるとありがたいです。
加えて今回は高校レベルの数学を利用しますが、自分が数学素人ということもあり、計算過程が正しいのか正直わかりません。(助けて!数学つよつよマン!)「明らかに計算違うだろ!」「もっと楽な計算方法や近似できるだろ!」と思った方はそれがあなたのエッジだと思います。それを教えろ下さい。
自己紹介
初めてのnote投稿になるので、簡単な自己紹介をさせていただきます。Asahiという名前は自分が買っているペット(コーンスネークっていうヘビ)の名前からそのまま持ってきました。
2022年6月頃に今となっては悪名名高いSTEPN(S国)からクリプトの世界に入り、案の定大損をこいたこと(¥120,000 →¥10,000)、そしてその損を取り返そうと色々調べた結果Botterという人種にたどり着きました。今のところ利益がほとんど出ていないので、"自称"Botterということにしておきます。(いつか"自称"が取れますように!!!)
きっかけ
DEX BotterのみなさんもBotterなりたての頃、Txを調査している時に、このようなトランザクションの違いを見て疑問に思ったことはあるかと思います。
「$52、$40、$33のように取引額がトランザクション毎に違う」
「入力値はどうやって決めているだ???」
インターネットの海で検索してみても、直接出るわけもなく….
そんな中でこのような記事の一文を見つけました。
なるほど、getReserves()を用いるということはつまり、Pairコントラクトの残高から直接最適な値を計算しているのか!と個人的に考えました。
と言ってもその計算方法がわからずのまま。どうしよう…→とりあえず仮想的な環境で再現してループを回して最適な値を求めてみよう!
という単純な思考のもとプログラムを組みました。
(数学がある程度できる人はこの時点で計算方法思いついてるはず….
一応自分理系大学生なんだが…)
想定環境
想定する環境は片方のDEXにおいて(14000, 31000)もう片方のDEXで(15000, 30000)の通貨ペアがあった場合を想定します。ちなみにこの値は適当に決めました。
2つのDEX間の同じ通貨のペアに価格の乖離が発生した場合を想定して、どのくらいの金額をスワップすれば、最大の見返りを得ることができるかを求めるイメージです。つまり、この図において、$${z-x}$$を最大化するような$${x}$$を求めるということになります。
ここで、UniswapのフォークDEXで使われるAMMについておさらいしてみます。皆さんご存知の通り、流動性が$${x}$$のtoken_0と流動性が$${y}$$のtoken_1のペアが存在する時、以下の式を満たすように価格が決定されます。
$${x*y = k (定数)}$$
ここに手数料の計算などが入るため、結果的には以下のような式で取引が実行されます。(yが出力、xが入力、feeがDEXの取引手数料と仮定)
$${y = \frac{x * (10000 - fee) * (コントラクト内のtoken\_1)}{(コントラクト内の token\_0) * 10000 + x * (10000 - fee)}}$$
コード解説
ということで、Javascriptで簡単にswapできる環境を作って動かしてみます。EVM系のマイナーチェーンにおけるDEXは基本的にUniswapV2のフォークである場合がほとんどである(と自分は思っている)ため、今回はUniswapV2のRouterとPairコントラクトのソースコードから必要なところだけをJavascriptで書き直した環境を整えました。
(とりあえず動けば良いコードなので、ゴミコードです。適宜書き直してください。)
まずは、ある二つの通貨のペアの流動性プールのクラスを作成します。
getAmountOutで交換する通貨の料金を計算して、swapで取引を実行するようなイメージです。
const pool = class {
//poolの作成
constructor(_token_0, _token_1, _fee){
this.token_0 = _token_0; //トークン1の準備金
this.token_1 = _token_1; //トークン2の準備金
this.swapFee = _fee; //取引手数料(1なら0.0001%)
}
//計算
getAmountOut(amountIn, reserveIn, reserveOut, swapFee){
const amountInwithFee = amountIn * (10000 - swapFee);
const numerator = amountInwithFee * reserveOut;
const denominator = reserveIn * 10000 + amountInwithFee;
return numerator / denominator;
}
//スワップ実行
swap(amountIn){
const reserveIn = this.token_0;
const reserveOut = this.token_1;
const amount = this.getAmountOut(amountIn, reserveIn, reserveOut, this.swapFee);
return amount;
}
}
次に、メインの関数として以下のものを実行します。
入力であるxを1から500までループさせて、その都度利益を出力するようなものです。
main() {
//poolの作成
const DEX1 = new pool(14000, 31000, 30);
const DEX2 = new pool(30000, 15000, 30);
var x = 0;
//入力値であるxを1~500まで回して、利益であるprofitをコンソールに出力する
while(1){
var result0 = DEX1.swap(x);
var result1 = DEX2.swap(result0);
var profit = result1 - x;
console.log(x);
console.log(profit);
x++;
if(x > 500) break;
}
}
main();
その結果をとりあえずcsvに出力した結果がこれになります。
ふむ、この場合だと、$${x=339}$$付近で、利益が最大になるようです。
入力値の最適化
ここでグラフを見て自分はようやく気づきました。
「高校数学で言うところの極値(極大点?だっけ)を求めれば良いのでは?」
ということは、利益の計算式を微分したときにその値が0になるような$${x}$$を求めることができれば、それがトランザクション発行時に最適な値となるのです!
ということで早速計算して求めてみましょう!
ここでは、手計算を簡略化するために、微分の計算に影響のない定数を以下のような文字に置き換えます。
<DEX1において>
$${a_{1} = (10000 - 手数料) * コントラクト内のtoken\_0 = a_{3} * コントラクト内のtoken\_0\\a_{2} = 10000 * コントラクト内のtoken\_1\\a_{3} = (10000 - 手数料) }$$
<DEX2において>
$${b_{1} = (10000 - 手数料) * コントラクト内のtoken\_1 = b_{3} * コントラクト内のtoken\_1\\b_{2} = 10000 * コントラクト内のtoken\_0\\b_{3} = (10000 - 手数料) }$$
そして、swapの計算部分を$${f(x)}$$、入力値を$${x}$$、利益を$${g(x)}$$とすると、$${g'(x) = 0}$$を満たすxを求めれば良いので、
$${f_{DEX_1}(x) = \frac{a_{1}*x}{a_{2} + a_{3}*x}\\f_{DEX_2}(x) = \frac{b_{1}*x}{b_{2} + b_{3}*x}}$$
$${g(x) = f_{DEX_2}(f_{DEX_1}(x))- x\\ =\frac{b_{1}*f(x)}{b_{2} + b_{3}*f(x)} - x}$$
$${g'(x) = (\frac{b_{1}f(x)}{b_{2} + b_{3}f(x)})' - (x)' = 0 \\ \Leftrightarrow \frac{b_{1}b_{2}f'(x)}{(b_{3}f(x) + b_{2})^2} - 1 = 0 \\ \Leftrightarrow b_{1}b_{2}f'(x) = (b_{3}f(x) + b_{2})^2 \\ \Leftrightarrow b_{1}b_{2} \frac{a_{1}a_{2}}{(a_{2}+a_{3}x)^2} = b_{3}^2(\frac{a_{1}x}{a_{2}+a_{3}x})^2+2b_{2}b_{3}(\frac{a_{1}x}{a_{2}+a_{3}x}) + b_{2}^2 \\\Leftrightarrow b_{1}b_{2}a_{1}a_{2} = b_{3}^2a_{1}^2x^2+2b_{2}b_{3}a_{1}x(a_{2}+a_{3}x) + b_{2}^2(a_{2} + a_{3}x)^2 \\\Leftrightarrow (a_{1}^2b_{3}^2+2a_{1}a_{3}b_{2}b_{3}+a_{3}^2b_{2}^2)x^2 + (2a_{1}a_{2}b_{2}b_{3} + 2a_{2}a_{3}b_{2}^2)x + (a_{2}^2b_{2}^2 - a_{1}a_{2}b_{1}b_{2}) = 0 \\\Leftrightarrow \{\begin{array}{l} A = a_{1}^2b_{3}^2+2a_{1}a_{3}b_{2}b_{3}+a_{3}^2b_{2}^2 \\ B= 2a_{1}a_{2}b_{2}b_{3} + 2a_{2}a_{3}b_{2}^2 \\ C = a_{2}^2b_{2}^2 - a_{1}a_{2}b_{1}b_{2} \\ x = \frac{-B \pm \sqrt{B^2 -4AC}}{2A}\end{array}}$$
このように$${x}$$は計算することができました。手計算でこんなに面倒臭い微分をしたのは大学入試以来かもです。(計算ミスがあるかもしれないので、あったら指摘してくださると大変助かります。)
次に、簡単なプログラムを組んで検証してみましょう!
const a2 = 10000 * DEX1.token0;
const a3 = 10000 - DEX1.swapFee;
const a1 = a3 * DEX1.token1;
const b2 = 10000 * DEX2.token1;
const b3 = 10000 - DEX2.swapFee;
const b1 = b3 * DEX2.token0;
const A = a1*a1*b3*b3 + 2*a1*a3*b2*b3 + a3*a3*b2*b2;
const B = 2*a1*a2*b2*b3 + 2*a2*a3*b2*b2;
const C = a2*a2*b2*b2 - a1*a2*b1*b2;
const result = (-B + Math.sqrt(B * B - 4*A*C)) / (2*A);
console.log("result is :" + result);
その結果がこちらになります↓
おぉ〜!ちゃんと339になっていることが確認できました!
自分の手計算は間違っていなかったようですね!
ちなみに、同じ考え方で経由する通貨ペアを3つや4つにしても計算することができます。理由はわかりませんが、どれだけ経由するDEXの数を増やしても最終的に計算が2次方程式に帰着するからです。
今回は経由する通貨ペアが2つの場合で計算しましたが、経由するDEXの数をnなどにして抽象書化しても、最適化されたxを求める式を定式化することができます。余力がある人は自分で計算してみてください!ただ、nを大きくすればするほど計算量が多くなるため、Botに実装する際は注意してくださいね!
最後に
なぜ一見儲かりそうなこの知識を公開したかというと、取引量を最適化するようなBotを作成したところで、儲けにつながる可能性が低いからです。
競争が激化するDEXアビトラにおいて重要なのは、
・養分Txをどれだけ早く検知できるか
・アビトラTxをどれだけ早くバリデーターのノードに届けることができるか
であるかなと、短いながらもこの界隈で勉強させていただき、学びました。
つまり、このnoteに書かれているような最適化の計算をしたとしても、以上の2点を強化しない限りは、他のつよつよBotのTxに先を越されてしまう可能性が大きいのです。なので、noteのタイトルに通り「(儲かりそうで)儲からないのです!!」(伏線回収終了)
このnoteの記事の儲けられる点を強いて言うならば、どれだけ小さい鞘でも検知できる点でしょうか。つよつよBotがスルーするかもしれない$0.1以下の鞘を掠め取るぐらいならチェーンによってはできるかもです。
後書き
このnoteは有料にしてありますが、購入したところでBotに関して何も有用な情報は載っていません。つまり、投げ銭用です。金銭的に余裕がある、かつ、このnoteを見て参考になったと思う人は投げ銭していただけると励みになります。そもそも誰の目にもつかないかもしれないですが…(Botで儲けてるんだから500円ぐらい僕に分けてください、お願いしますなんでもしますから)
自分はQASH先生のnoteを見て、DEXBot開発を始めた新参者ですが、いわゆるQASH世代です。もうすでに儲けられる未来が見えないので、趣味でBot開発をやっているようなもんです。大したものは書くことはできませんが、これからはQASH先生がご提案されたnotterとして生きていきます。
ここから先は
¥ 500
この記事が気に入ったらチップで応援してみませんか?