بناء خوارزمية صناعة السوق لأزواج العملات المشفرة باستخدام نموذج 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هما سعرا العرض والطلب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. إذا كان لديك مخزون إيجابي (كثير من الأصل)، ينخفض سعر العرض، وينخفض سعر الطلب أكثر لتشجيع البيع وتقييد الشراء. والعكس للمخزون السلبي.
الخطوة 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
البحوث والاستراتيجيات الكمية