Avellaneda-Stoikovモデルを使用した暗号通貨ペアのマーケットメイキングアルゴリズムの構築
こんにちは!今日は、USD+/wETHとUSD+/cbbtc暗号通貨ペアのマーケットメイキングアルゴリズムの構築方法をお見せします。Avellaneda-Stoikov(A-S)モデルを使用し、動的スプレッド最適化のために強化学習(PPO)アルゴリズムで強化します。複雑そうに聞こえますか?心配しないでください、初心者の開発者でも理解できるように、すべてを明確なステップに分解して説明します。
マーケットメイキングの本質を可視化:最適な市場流動性を合成するために、公正価格を中心に売買注文を継続的にバランスさせる。
マーケットメイキングとは何か、なぜ必要なのか
マーケットメイキングとは、トレーダーが資産の売買注文を同時に出し、スプレッド(価格の差)で利益を得る戦略です。DeFi空間では、マーケットメーカーは流動性を提供し、他の市場参加者のスリッページを軽減する重要な役割を果たします。
あなたが市場の売り手で、常に市場価格より少し低く買い、少し高く売る準備ができていると想像してください。あなたの利益は買値と売値の差です。しかし落とし穴があります:価格が突然一方向に動くと、在庫が過剰に蓄積されたり、逆に売るものがなくなったりする可能性があります。
Avellaneda-Stoikovモデル:取引に奉仕する数学
A-Sモデルは、マーケットメイキングの最適価格を決定するための数学的アプローチです。その主な利点は、現在の市場価格だけでなく、ポジションサイズ(在庫)、市場のボラティリティ、リスク許容度も考慮することです。
モデルの主要な公式:
δ_a = S_t + (1/γ) * ln(1 + γ/k) + q_t * σ² * T
δ_b = S_t - (1/γ) * ln(1 + γ/k) - q_t * σ² * T
ここで:
δ_aとδ_bはask価格とbid価格S_tは現在の市場価格γはリスクパラメータ(高いほどスプレッドが広い)kは注文到着率q_tは現在の在庫σはボラティリティTは時間の地平線
オンチェーン取引の特徴
アルゴリズムをオンチェーンに移行すると、追加の課題が発生します:
- レイテンシー – ブロックチェーン上のトランザクションは即時ではなく、注文が執行される前に価格が変わる可能性がある
- ガスコスト – すべてのトランザクションにネットワーク手数料が必要
- AMM/PMMの特性 – 流動性プールのメカニズムは従来の取引所と異なる
これらの要素をアルゴリズムにどう組み込むか見てみましょう。
ステップ1:環境のセットアップとデータ収集
まず、市場データを取得するための環境をセットアップする必要があります。Binance APIを使用して現在の価格と注文板の深度を取得します。
std::tuple MarketMaker::get_binance_data(const std::string& pair) {
// In real code, this would be a request to the Binance API
// Returns: mid_price, bid, ask, bid_volume, ask_volume
double mid_price = 2000.0;
double bid = mid_price - 1.0;
double ask = mid_price + 1.0;
double bid_volume = 10.0;
double ask_volume = 8.0;
return {mid_price, bid, ask, bid_volume, ask_volume};
}
また、ガスコストやネットワークレイテンシーなどのオンチェーンメトリクスも必要です:
std::pair MarketMaker::get_onchain_metrics() {
// In real code, this would be a request to an Ethereum node
// Returns: gas_price (wei), latency (seconds)
return {50e9, 12.0};
}
ステップ2:基本的なA-Sモデルの実装
A-Sモデルを使用したスプレッド計算を実装しましょう:
std::pair MarketMaker::calculate_spreads(double S_t, double sigma, double k, double q_t) {
// Avellaneda-Stoikov formula
double spread_term = (1.0 / gamma_) * log(1.0 + gamma_ / k);
double inventory_term = q_t * sigma * sigma * T_;
double delta_a = S_t + spread_term + inventory_term; // Ask price
double delta_b = S_t - spread_term - inventory_term; // Bid price
return {delta_a, delta_b};
}
inventory_termに注目してください。正の在庫(資産が多い)がある場合、ask価格は下がり、bid価格はさらに下がって売却を促進し、購入を制限します。負の在庫の場合は逆です。
ステップ3:オンチェーン取引向けのモデル適応
ブロックチェーンの特性を考慮する必要があります。まずレイテンシーから:
double MarketMaker::adjust_price_with_latency(double S_t, double sigma, double latency) {
// Simulate random price change due to latency
double latency_adjustment = utils::normal_dist(0.0, sigma * std::sqrt(latency));
return S_t + latency_adjustment;
}
ここではランダムウォークモデルを使用します:ボラティリティが高くレイテンシーが長いほど、注文が執行される前に価格が大きく変動する可能性があります。
次にガスコストを考慮しましょう:
double MarketMaker::calculate_gas_cost(double gas_price, double trade_size) {
const double GAS_LIMIT_PER_ORDER = 100000; // Approximate value per order
return (gas_price * GAS_LIMIT_PER_ORDER * trade_size) / 1e18; // Convert wei to ETH
}
最後に、PMMプールの特性に合わせてスプレッドを適応させましょう:
std::pair MarketMaker::adjust_spreads_for_pmm(double S_t, double delta_a, double delta_b, double pool_depth) {
// Simplified PMM model: adjust spreads based on pool depth
const double MIN_POOL_DEPTH = 10.0;
double depth_factor = std::max(pool_depth, MIN_POOL_DEPTH) / MIN_POOL_DEPTH;
// Reduce spreads with greater pool depth
double spread_reduction = 1.0 / std::sqrt(depth_factor);
double mid_price = (delta_a + delta_b) / 2;
double new_delta_a = mid_price + (delta_a - mid_price) * spread_reduction;
double new_delta_b = mid_price - (mid_price - delta_b) * spread_reduction;
return {new_delta_a, new_delta_b};
}
ステップ4:在庫管理
在庫を追跡・管理するためのシンプルなクラスを作成しましょう:
class InventoryManager {
public:
InventoryManager() : inventory_(0.0) {}
void update_inventory(double size, bool is_buy) {
inventory_ += is_buy ? size : -size;
}
double get_inventory() const {
return inventory_;
}
private:
double inventory_;
};
在庫リスクの可視化:一方向の市場変動に対する過剰なエクスポージャーを避けるためのポジションサイズの監視。
ステップ5:すべてを単一のアルゴリズムに統合
すべてのコンポーネントを単一のマーケットメイキングアルゴリズムに統合しましょう:
void MarketMaker::step(double S_t, double sigma, double k, double latency, double gas_cost, double trade_size) {
// Get current inventory
double current_inventory = inventory_.get_inventory();
// Calculate spreads based on current market conditions and inventory
auto [delta_a, delta_b] = calculate_spreads(S_t, sigma, k, current_inventory);
auto [adjusted_delta_a, adjusted_delta_b] = adjust_spreads_for_onchain(S_t, delta_a, delta_b, latency, sigma, gas_cost, trade_size);
// Generate independent market price
double market_price = S_t + utils::normal_dist(0.0, sigma);
// Determine if trades should occur based on market price and spreads
bool is_buy = (market_price = adjusted_delta_a);
// Execute trades and update inventory
if (is_buy) {
inventory_.update_inventory(trade_size, true);
std::cout reset();
// Take action and get new state, reward, and done flag
std::tuple, double, bool> step(const std::array& action);
private:
// Get current environment state
std::vector get_state() const;
MarketMaker& mm_;
double current_inventory_;
double current_profit_;
int current_step_;
int max_steps_;
// Current market parameters
double mid_price_;
double sigma_;
double latency_;
double pool_depth_;
std::mt19937 rng_;
};
環境の状態は、現在の価格、在庫、ボラティリティ、ネットワークレイテンシー、プール深度のベクトルです。アクションはスプレッドと売買サイズのベクトルです。
次に報酬関数を実装しましょう:
double reward = profit_term - inventory_risk - gas_cost;
ここで:
profit_termは取引からの利益inventory_riskは大きな在庫(リスク)に対するペナルティgas_costは消費されたガス
最後にPPOエージェントを訓練しましょう:
void PPOTrainer::train(int episodes) {
for (int ep = 0; ep states;
std::vector actions;
std::vector rewards;
while (true) {
// Get action from policy
auto action_probs = policy_net_->forward(torch::tensor(state));
auto action = action_probs.multinomial(1);
// Take a step in the environment
auto [next_state, reward, done] = env_.step(action);
// Save transition
states.push_back(torch::tensor(state));
actions.push_back(action);
rewards.push_back(reward);
if (done) break;
state = next_state;
}
// Update PPO policy
update_policy(states, actions, rewards);
}
}
実行中の強化学習:PPOエージェントが複雑な市場状態を処理し、最大期待報酬のために売買スプレッドを動的に最適化。
ステップ7:テストと可視化
アルゴリズムをテストするためのシンプルなシミュレーションを作成しましょう:
int main() {
// Use T = 300 seconds as specified in the task
MarketMaker mm(0.1, 300.0);
// Simulate historical data for volatility
std::vector prices = {2000.0};
double S_t = 2000.0;
double trade_size = 1.0;
double initial_sigma = 0.05; // 5% volatility
for (int i = 0; i < 300; ++i) {
std::cout << "Step " << i + 1 << ": ";
// Get data (stubs)
auto [mid_price, bid_ask] = mm.get_binance_data("USD+/wETH");
auto [gas_cost, latency] = mm.get_onchain_metrics();
// Add random price movement to simulate a real market
S_t = mid_price + utils::normal_dist(0.0, mid_price * 0.01);
// Calculate volatility
double sigma = mm.calculate_volatility(prices, 5);
if (sigma < 0.01) sigma = initial_sigma;
// Order arrival rate (stub)
double k = 5.0;
mm.step(S_t, sigma, k, latency, gas_cost, trade_size);
// Update price for next step
S_t += utils::normal_dist(0.0, S_t * 0.02);
prices.push_back(S_t);
}
return 0;
}
今後の展開
マーケットメイキングアルゴリズムは完成しましたが、改善の方法は多数あります:
- 実際のAPIに接続:スタブを実際のBinance APIとEthereumノードへのリクエストに置き換える
- ボラティリティモデルの改善:GARCHやその他の高度なモデルを使用
- PPOの拡張:状態とアクションにより多くのパラメータを追加
- ガスの最適化:ガスコストを最小化する戦略
- マルチアセット戦略:複数のペアへの同時展開
結論
オンチェーン取引の特徴を考慮し、古典的なA-Sモデルと最新のRL手法の両方を使用するマーケットメイキングアルゴリズムを構築しました。このアプローチにより、変化する市場条件に適応し、リスクを制御しながら利益を最大化できます。
もちろん、実際の取引では考慮すべき追加要素が多数ありますが、このアルゴリズムはさらなる開発のための確固たる基盤を提供します。アルゴリズム取引では、数学だけでなく、徹底したテスト、モニタリング、継続的な最適化も重要であることを忘れないでください。
この記事がマーケットメイキングの原理をより良く理解し、独自のアルゴリズム作成のインスピレーションになったことを願っています。取引の成功を祈ります!
Citation
@software{soloviov2025marketmakingavellanedastoikov,
author = {Soloviov, Eugen},
title = {Building a Market Making Algorithm for Crypto Pairs Using the Avellaneda-Stoikov Model},
year = {2025},
url = {https://marketmaker.cc/en/blog/post/market-making-avellaneda-stoikov},
version = {0.1.0},
description = {A step-by-step guide to building a market making algorithm for USD+/wETH and USD+/cbbtc pairs using the Avellaneda-Stoikov model and PPO. Onchain trading features, inventory management, RL training.}
}
MarketMaker.cc Team
クオンツ・リサーチ&戦略