見出し画像

基本編:Lighter MMBotをPythonで作る【基本動作:WebSocket接続・発注・約定処理】

本シリーズはLighterのPerpetual市場でMMBotを実際に本番稼働させた経験をもとに、設計の考え方と実装パターンを解説するものです。収益に直結する処理は非公開ですが、それ以外のインフラ・設計部分をすべて解説します。基本的に、このシリーズだけで本番稼働するMMBotを作れるようになるようガイドしたいと思います。

※この記事の最後にこの記事で解説した実装を1ファイルにまとめたものを載せているのでとにかく動くものが見たい人はそちらを見てください。

本番稼働に必要なAWS環境構築・InfluxDB可視化・急変保護などについては、本記事と同じ設計パターンで書かれた以下の記事を参照してください。Lighterでも同様に活用できます(Lighter固有の差分は本記事末尾の付録に記載)。

👉 応用編:Hyperliquid MMBotを本番稼働させる【環境構築・運用整備・可視化・強化】

Lighterは同じPerp DEXでもHyperliquidやGRVTとは設計思想がかなり違います。注文を「APIリクエスト」ではなく「署名済みトランザクション」として送るという点が最大の違いで、ここからnonce管理という他の取引所には存在しない問題が生まれます。

もうひとつ、手数料が無料なスタンダードアカウントはREST APIが毎分60リクエストに制限されています。この枠は同じIP・同じL1アドレスの全Botで共有されるため、「1銘柄あたり何秒に1回まで板に追随できるか」「何銘柄まで並行稼働できるか」が最初から決まってしまいます。コードの書き方ではなく運用設計そのものを縛ってくる制約で、実装より先に理解しておく必要があります。

この記事の実質的な中心はこの2つです。


利用するライブラリ

このBotはLighterの公式Pythonライブラリ lighter-python を使います。

pip install lighter-sdk

GitHubから直接入れる場合はこちらです。SDKの更新が速いので、動作が怪しいときはバージョンを固定するか最新を追ってください。

pip install git+https://github.com/elliottech/lighter-python.git

主に使うのは以下のクラスです。

import lighter

configuration = lighter.Configuration(BASE_URL)
api_client = lighter.ApiClient(configuration)

signer = lighter.SignerClient(
    url=BASE_URL,
    api_private_keys={API_KEY_INDEX: PRIVATE_KEY},
    account_index=ACCOUNT_INDEX,
)

order_api       = lighter.OrderApi(api_client)
account_api     = lighter.AccountApi(api_client)
transaction_api = lighter.TransactionApi(api_client)

WebSocketはSDKのラッパーを使わず websockets を直接使います。理由は再接続の挙動を自分で握りたいからです(後述)。

pip install websockets

Lighterの認証情報

HyperliquidはウォレットのEVM秘密鍵、GRVTはAPIキー+trading_account_id+秘密鍵でしたが、Lighterは以下の4つを使います。

APIキーの生成は「APIキー一覧」の画面で行えます。
account_index、api_key_indexは同画面上でいつでも確認が可能で、private_keyはAPIキー作成時に表示されたものを使います。

ウォレットの秘密鍵はキー発行時にしか使わず、Botのサーバーに置かなくて済むのがLighterの利点です。

market_indexの調べ方

Lighterは銘柄をシンボルではなく整数で指定します。iniファイルに書くのもこの値なので、稼働前に必ず確認が必要です。署名不要の公開エンドポイントから一覧を取れるので、確認用のスクリプトを用意しました(全文は付録2に載せています)。

$ python lt_market_index.py
symbol         market_index type  status
ETH            0            perp  active
BTC            1            perp  active
SOL            2            perp  active
DOGE           3            perp  active
1000PEPE       4            perp  active
・・・
LDO/USDC       2054         spot  active
AZTEC/USDC     2055         spot  inactive
XAUT/USDC      2056         spot  active
rhSPY/USDC     2057         spot  active
rhQQQ/USDC     2058         spot  active

244 銘柄(perp 233 / spot 11)

銘柄名を渡すと、発注に必要な情報までまとめて出ます。

$ python lt_market_index.py ETH
ETH  market_index=0  type=perp status=active   min_base=0.0050  min_quote=10.000000
 price_dec=2 size_dec=4

この出力に、引っかかりやすい点をまとめて含めてあります。

同じ銘柄名でもperpとspotは別のindexです。 spotは LIT/USDC のようにスラッシュ区切りのシンボルで、market_indexは2000番台に振られています。perpのつもりでspotのindexを書くと、板は取れるのにポジションの挙動が想定と違う、という分かりにくい形で表面化します。(2000番台と書きましたが今後もそうとは限らないので必要な場合は全て表示して確認することをお勧めします)

status が active でない銘柄には発注できません。 上の出力の AZTEC/USDC のように inactive の銘柄が混ざっています。板情報の購読自体は通ってしまうことがあるので、発注だけが延々エラーになる原因として気づきにくい部類です。

min_base / min_quote と小数桁もここで確認できます。 これらはBot起動時に set_qty_info() でAPIから取得するので設定ファイルに書く必要はありませんが、「この銘柄は最低10ドルから」といった前提を先に把握しておくと、発注額の設計が早く決まります。ETHなら最小0.005 ETHかつ10 USDCで、価格は小数2桁、数量は小数4桁です。

見つからない銘柄を指定した場合は終了コード1を返すので、デプロイ前のチェックに組み込むこともできます。

注意:この記事のコードは認証情報・銘柄をハードコードした形で書きます。iniファイルによる設定管理は応用編を参照してください。

アカウント種別・手数料・レイテンシ

Lighterでは口座に3つの種別があり、どれを選ぶかでMMBotの設計そのものが変わります。実装に入る前にここを決めてください。

公式ドキュメントより。数値は変更されることがあるので最新を確認してください)

料金体系の考え方はシンプルで、手数料を払えば速くなるというトレードオフです。スタンダードは無料な代わりに、発注・キャンセルに意図的な遅延が入ります。プレミアムはキャンセルとPost Only発注に追加レイテンシがかからないので、板の変化に対する追随はこちらが圧倒的に速くなります。

スタンダードとプレミアム、どちらを使うか

私は両方を実際に試したうえでスタンダードを使っています

なお、プラスは0.5bps均一でレイテンシはスタンダードとほぼ同じなので、比較は行っていません。

高頻度のマーケットメイキングは、1回あたりの取れ幅が非常に小さい代わりに回数で積み上げる戦略です。プレミアムのメイカー手数料0.0040%は数字として見れば小さいのですが、往復で0.008%、これが1ティックのスプレッドを取りにいく戦略ではそのまま利益率に効いてきます。私の稼働条件では手数料負けしました。レイテンシの優位よりコストの重さが勝った、という結果です。

もうひとつ、プレミアムには手数料とは別にVolume Quotaという発注回数の制限があり、これも高頻度のマーケットメイキングとは相性がよくありませんでした。詳しくは後半のレートリミットの章で扱います。

もちろんこれは戦略次第です。取れ幅が大きい銘柄、レイテンシ勝負で先頭に並ぶことが収益源になっている戦略なら判断は逆になります。両方1週間ずつ動かして実測するのが確実です。

なお、リファラルリンク経由で登録するとプレミアムの手数料が最初の1週間ぶん還元されるので、この比較を実費なしで行えます。
👉 https://app.lighter.xyz/?referral=323512FX&source=none
(還元の条件や注意点は記事末尾にまとめています)

スタンダードには別の制約がある

手数料が無料な代わりに、スタンダードアカウントにはREST APIのレートリミットが極端に厳しいという制約があります。これがMMBotの設計に直接効いてくるので、後の「レートリミットと発注頻度の設計」で詳しく扱います。先に結論だけ書いておくと、私は現在並行稼働を2〜4銘柄程度に絞っています

Bot全体のアーキテクチャ

LighterのSDKはasyncioベースなので、構成はGRVT版に近くなります。ただしnonceを配るデーモンプロセスが別に立つ点が独特です。

【nonce daemon プロセス】(銘柄横断で1つ)
 └─ UNIXソケットで待ち受け、nonceを1つずつ払い出す

【Botプロセス(銘柄ごとに1つ)】
 │
 ├─ WebSocket受信タスク(subscribe)
 │   ├─ order_book/{market_index}        → callback_orderbook() → mm_order()
 │   ├─ trade/{market_index}             → callback_trades()    → protect_check()
 │   └─ account_all_trades/{account}     → callback_account_trades() → fills()
 │
 ├─ 銘柄情報の取得(set_qty_info・起動時に1回)
 └─ メインループ(asyncio.sleep(60)ごと)
     ├─ 未決注文の整合性チェック(all_order_cancel)
     ├─ ポジション同期(position_check)
     ├─ InfluxDBへの書き込み(db_insert)
     └─ JSONファイルへの状態保存(json_dump)

※ protect_check()(急変保護)と db_insert(InfluxDB書き込み)は応用編で追加するもので、基本編の実装には含まれません。

nonceデーモンは基本編の必須要素ではありません。1銘柄だけ動かすならBotプロセス内でnonceを持てば十分です。ただし複数銘柄を並行稼働させた瞬間に必要になるので、この記事では「まずインプロセスで作り、なぜデーモンが要るのか」という順で説明します。

主要なグローバル変数の設計

記事全体を通して登場する変数を先に理解しておくと、後の実装がスムーズに読めます。HyperliquidやGRVTの記事を読んだ方には見慣れた構造のはずです。

注文管理:buy_d / sell_d

buy_d = {
    'id':      None,   # 取引所側の order_index(常にNone・後述)
    'link_id': None,   # 自分がつけた client_order_index
    'qty':     0,      # 発注数量
    'price':   0,      # 発注価格
}

sell_d = {
    'id':      None,
    'link_id': None,
    'qty':     0,
    'price':   0,
}

link_id が None なら「その方向の注文は現在なし」を意味します。

Lighterで厄介なのは、発注時のレスポンスに order_index(取引所側の注文ID)が返ってこないことです。返ってくるのはトランザクションハッシュだけで、板に載った注文のindexは後から account_active_orders で引く必要があります。キャンセルは order_index を指定して行うため、この一手間が必ず入ります。そのため id は常にNoneのままですが、他取引所版と構造を揃えるために残してあります。詳しくはキャンセルの章で扱います。

約定の重複除外:trade_q

from collections import deque
trade_q = deque([], maxlen=1000)

WebSocketの約定通知は再接続直後にスナップショットが再送されることがあるため、trade_id で重複を弾きます。GRVT版と同じ考え方です。

ポジション管理:pos_d

pos_d = {
    'side':    None,   # 'buy' / 'sell' / None(ノーポジ)
    'qty':     0,      # 保有数量
    'amt':     0,      # 保有コスト(qty × 平均取得価格)
    'a_price': 0,      # 平均取得価格(amt / qty)
}

約定データをリアルタイムで積み上げて自前で計算します。ただしLighterでは後述の理由でAPIとの定期同期(position_check)を必ず入れています。

損益管理:profit_d

profit_d = {
    'profit':     0,   # 売買損益の累計
    'fee':        0,   # 手数料の累計
    'all_profit': 0,   # profit - fee(実質損益)
}

先ほどの章で書いたとおり、このBotはスタンダードアカウントで動かしているため手数料は0で、fee は常に0になります。プレミアムアカウントに切り替える場合はここに手数料を積む必要があるので、構造だけ他の取引所と揃えて残しています。

生成されるファイルの一覧

Botは以下のファイルを自動生成・管理します。ファイル名に銘柄名を含めることで複数銘柄の並行稼働でも干渉しません。

※mm_btc.log は本番構成(logging に差し替えた場合)で生成されるものです。記事末尾の完全動作コードは print 出力なので、ログファイルは作られません。

※ここから先は有料公開です。板情報の受信から署名・発注・約定処理・ポジション管理まで、動くMMBotを作るために必要な実装を一通り解説しています。

ここから先は

67,796字 / 3画像

¥ 3,000

Amazon Payで支払うと最大2%還元のチャンス! 9/30まで

この記事が気に入ったらチップで応援してみませんか?