Skip to main content
هذه الصفحة مُترجَمة آليًا بواسطة الذكاء الاصطناعي. النسخة الإنجليزية هي المرجع المعتمد.عرض النسخة الإنجليزية →
هذه الصفحة عملية: فهي تقدم الصيغ والاتفاقيات ذات النقطة الثابتة والخطوات المستخدمة من قبل برنامج CLMM. للاطلاع على الأساس المنطقي وراء منحنى السيولة المركزة نفسه — لماذا L = sqrt(x · y) مهم — انظر algorithms/clmm-math. تفترض هذه الصفحة أنك قد قرأت ذلك.

تمثيل الجذر التربيعي للسعر

يخزن CLMM السعر كـ sqrt_price_x64 — الجذر التربيعي لسعر token1 لكل token0، كرقم نقطة ثابتة Q64.64: sqrt_price_x64=⌊p⋅264⌋\text{sqrt\_price\_x64} = \lfloor \sqrt{p} \cdot 2^{64} \rfloor حيث p = token1_amount / token0_amount. العمل بـ sqrt بدلاً من p يخطي رياضيات المبادلة (تصبح دلتا مبالغ الرموز خطية في Δsqrt_price)، والنقطة الثابتة x64 تحافظ على الدقة عبر المبادلات متعددة التيك. يتم حساب تحويل التيك ↔ الجذر التربيعي للسعر مسبقًا عبر تقريب لوغاريتمي bit-by-bit: sqrt_price_x64(t)≈264⋅(1.0001)t/2\text{sqrt\_price\_x64}(t) \approx 2^{64} \cdot (1.0001)^{t/2} مُنفذ كأس بحث عن الأس في tick_math::get_sqrt_price_at_tick.

السيولة كوحدة قانونية

داخل نطاق [sqrt_a, sqrt_b] (مع sqrt_a < sqrt_b)، موضع بـ سيولة L يُعيّن إلى مبالغ الرموز كما يلي. دع sqrt_c = sqrt_price_x64 يكون السعر الحالي للمجمع. تأتي جميع الهويات الثلاث من الثابت x = L / sqrt_p، y = L · sqrt_p الذي تحققه السيولة المركزة داخل نطاق. عادة ما يريد المدمجون العكس: بالنظر إلى إيداع amount0 / amount1، احسب الحد الأقصى L الذي يناسب النطاق. يفعل SDK LiquidityMath.getLiquidityFromTokenAmounts هذا. الصيغة لحالة داخل النطاق: L0=amount0⋅sqrt_c⋅sqrt_bsqrt_b−sqrt_c,L1=amount1sqrt_c−sqrt_a,L=min⁡(L0,L1)L_0 = \text{amount0} \cdot \frac{\text{sqrt\_c} \cdot \text{sqrt\_b}}{\text{sqrt\_b} - \text{sqrt\_c}}, \qquad L_1 = \frac{\text{amount1}}{\text{sqrt\_c} - \text{sqrt\_a}}, \qquad L = \min(L_0, L_1) أيهما يقيد يحدد النسبة المستهلكة فعليًا؛ قد يكون للجانب الآخر بقايا.

خطوة المبادلة أحادية التيك

تتقدم المبادلة في خطوات. كل خطوة إما (أ) تستهلك كل المدخلات المتاحة داخل نطاق التيك الحالي دون عبور تيك، أو (ب) تحرك السعر بالضبط إلى التيك المهيأ التالي. بالنظر إلى الحالة الحالية (sqrt_c, L) ومبادلة لأسفل (token0 داخل، token1 خارج، sqrt_price ينخفض — السعر مقتبس كـ token1/token0، لذا فإن إضافة token0 تخفضه)، التيك المهيأ التالي أدناه يجلس عند sqrt_t < sqrt_c. داخل هذا الفاصل الدقيق، العلاقة بين المدخلات والسعر هي: Δamount0=L⋅(1sqrt_t−1sqrt_c)=L⋅(sqrt_c−sqrt_t)sqrt_c⋅sqrt_t\Delta\text{amount0} = L \cdot \left( \frac{1}{\text{sqrt\_t}} - \frac{1}{\text{sqrt\_c}} \right) = \frac{L \cdot (\text{sqrt\_c} - \text{sqrt\_t})}{\text{sqrt\_c} \cdot \text{sqrt\_t}} و Δamount1=L⋅(sqrt_c−sqrt_t)\Delta\text{amount1} = L \cdot (\text{sqrt\_c} - \text{sqrt\_t}) مبادلة token1-in هي صورة معكوسة: sqrt_price يرتفع نحو التيك التالي أعلاه، والصيغتان تبدلان أي نقطة نهاية يتم طرحها. يشفر البرنامج الاتجاه كـ zero_for_one (true عندما يكون mint المدخلات token_mint_0)، ومبادلة zero_for_one تشد نحو MIN_SQRT_PRICE_X64. يفعل البرنامج أحد شيئين:
  • هل يناسب كل المدخلات؟ إذا كان المدخل المتبقي (بعد الرسم) أقل من Δamount0 للوصول إلى sqrt_t، حل للحصول على sqrt_c' الجديد بالضبط: sqrt_c′=L⋅sqrt_cL+Δinput⋅sqrt_c\text{sqrt\_c}' = \frac{L \cdot \text{sqrt\_c}}{L + \Delta\text{input} \cdot \text{sqrt\_c}} (لمبادلة token0 → token1 دقيقة المدخلات). تكتمل المبادلة في هذه الخطوة دون عبور تيك.
  • هل يتجاوز المدخل Δamount0؟ اضبط sqrt_c' = sqrt_t، عبر التيك (طبق liquidity_net)، قلل المدخل المتبقي بـ Δamount0، زد المخرجات بـ Δamount1، وكرر.
للاتجاه المعاكس (token1 → token0، السعر ينخفض)، الصيغ لها sqrt_c و sqrt_t مبدلة والعكس في الفتحة الأخرى. التنفيذ الكامل لـ Rust يعيش في raydium-clmm/programs/amm/src/libraries/swap_math.rs. المنطق هناك يطابق SwapMath.computeSwapStep لـ Uniswap v3 واحد لواحد.

الرسوم على كل خطوة

يتم أخذ رسوم التجارة من مبلغ المدخل في كل خطوة، نفس الاتفاقية مثل CPMM:
يتم تقسيم حصة LP عبر السيولة المهيأة حاليًا بتحديث مراكم نمو الرسوم العام: fee_growth_globalin+=lp_portion⋅264L\text{fee\_growth\_global}_{\text{in}} \mathrel{+}= \text{lp\_portion} \cdot \frac{2^{64}}{L} — أي أنها مقومة بـ رسوم لكل وحدة سيولة، Q64.64، بحيث يقرأ موضع بحجم L_i الذي بقي داخل النطاق عبر هذه المبادلة لاحقًا L_i · Δfee_growth_global / 2^{64} رموز مستحقة. تتراكم حصص البروتوكول والصندوق على PoolState.protocol_fees_token_{0,1} و PoolState.fund_fees_token_{0,1} على التوالي، مطابقة لـ CPMM. يتم مسحها بواسطة CollectProtocolFee / CollectFundFee.

نمو الرسوم خارج وداخل

الجزء الصعب من محاسبة رسوم CLMM: يكسب الموضع رسوم فقط بينما يكون سعر المجمع داخل نطاقه. يتتبع المجمع الرسوم التراكمية عالميًا؛ يحتاج الموضع إلى معرفة الرسوم التراكمية بينما داخل نطاقه المحدد. الحل هو مراكم قائم على التيك. يخزن كل تيك:
في لحظة تهيئة التيك:
  • إذا كان سعر المجمع أعلى من هذا التيك (tick_current >= this_tick)، fee_growth_outside = fee_growth_global. (كل ما تم كسبه حتى الآن هو “خارج” — أي أسفل — هذا التيك، بالنسبة للسعر الحالي.)
  • وإلا fee_growth_outside = 0.
عندما يعبر السعر تيك، يقلب البرنامج fee_growth_outside لذلك التيك: fee_growth_outside←fee_growth_global−fee_growth_outside\text{fee\_growth\_outside} \gets \text{fee\_growth\_global} - \text{fee\_growth\_outside} الثابت الذي يحافظ عليه: لأي تيك t، fee_growth_outside(t) يساوي الرسوم التي تراكمت بينما كان tick_current على الجانب المعاكس من t. نمو الرسوم داخل نطاق [tick_lower, tick_upper] يُشتق بعد ذلك:
هذه صيغة نمو الرسوم لـ Uniswap-v3، دون تغيير.

ما يخزنه الموضع وما يقرأه

يخزن PersonalPositionState fee_growth_inside_0_last_x64 و fee_growth_inside_1_last_x64: قيم fee_growth_inside في آخر مرة تم لمس الموضع فيها. في أي لمسة لاحقة (زيادة، تقليل، جمع)، يقوم البرنامج بـ:
  1. حساب fee_growth_inside_{0,1}_x64 الحالي باستخدام الصيغة أعلاه.
  2. حساب Δ = fee_growth_inside_now − fee_growth_inside_last (طرح معياري على u128).
  3. إضافة Δ × position.liquidity / 2^{64} إلى tokens_fees_owed_{0,1}.
  4. تحديث fee_growth_inside_last إلى القيمة الجديدة.
تتحرك الرموز فعليًا خارج الأقبية فقط على CollectFees / DecreaseLiquidity، مقابل tokens_fees_owed.

المكافآت

يستخدم كل من تدفقات المكافآت الثلاثة للمجمع نفس آلية النمو داخل، في مراكمه reward_growth_global_x64 الخاص. في وقت الانبعاث: reward_growth_global+=emission_per_second⋅Δt⋅264L\text{reward\_growth\_global} \mathrel{+}= \text{emission\_per\_second} \cdot \Delta t \cdot \frac{2^{64}}{L} — الانبعاثات تتناسب عكسيًا مع السيولة النشطة، لذا يدفع المجمع الأكثر كثافة لكل موضع أقل نسبيًا في الثانية، لكن على مواضع أكثر إجمالاً. المكافأة المستحقة لكل موضع هي reward_owed=(reward_growth_insidenow−reward_growth_insidelast)⋅L/264\text{reward\_owed} = (\text{reward\_growth\_inside}_{\text{now}} - \text{reward\_growth\_inside}_{\text{last}}) \cdot L / 2^{64} ويتم دفعها بواسطة DecreaseLiquidity / DecreaseLiquidityV2 — لا توجد تعليمات جمع مكافآت مستقلة. انظر products/clmm/fees.

مثال عملي: مبادلة دقيقة المدخلات

افترض:
  • tick_spacing = 60
  • sqrt_price_x64 = 1 × 2^{64} — السعر = 1.0، لذا tick_current = 0.
  • السيولة النشطة L = 1_000_000 × 2^{64}.
  • التيك المهيأ التالي أدناه: t = −60 (sqrt_price_b ≈ 0.997005 × 2^{64}). مبادلة token0-in تتحرك لأسفل، لذا التيك أعلاه غير ذي صلة هنا.
  • معدل رسم التجارة: 500 (0.05%).
المستخدم: SwapBaseInput دقيق المدخلات 1,000 token0. الخطوة 1 — الرسوم:
الخطوة 2 — هل 999 يناسب داخل نطاق التيك الحالي؟
999 < 3004.4، لذا يناسب كل المدخل دون عبور التيك. الخطوة 3 — السعر الجديد:
أي أن sqrt_c' يهبط قليلاً أقل من sqrt_c، وهو الاتجاه الصحيح: مبادلة token0 → token1 تضيف token0 إلى المجمع وبالتالي تخفض سعر token1/token0. الخطوة 4 — مبلغ المخرجات:
بعد حساب التقريب، يتلقى المستخدم ≈ 998 token1. يتم تقسيم الرسم (1 token0) بين LP والبروتوكول والصندوق بـ trade_fee_rate × protocol_fee_rate / 1e6 (وبالمثل للصندوق)؛ تتدفق حصة LP إلى fee_growth_global_0_x64.

مطابقة أوامر الحد أثناء المبادلة

عندما تعبر خطوة مبادلة تيك يحتوي على أوامر حد مفتوحة، تستهلك تلك الأوامر مدخل المبادلة قبل منحنى LP، بسعر التيك بالضبط. المطابقة هي FIFO داخل التيك بـ order_phase cohort.

حالة لكل cohort على TickState

تكون تخطيط cohort الثنائي موجودة لأن أوامر جديدة قد تُفتح على تيك بينما cohort أقدم لا يزال يتم ملؤه. تنضم الأوامر المفتوحة حديثًا إلى orders_amount وترث order_phase التالي؛ لا يمكنها الملء حتى يتم استهلاك cohort السابق بالكامل.

خطوة المطابقة

pseudo-code للمطابقة التي تحدث عند كل عبور تيك أثناء المبادلة:
رموز المخرجات التي تذهب إلى مالكي أوامر الحد لا يتم نقلها لكل مبادلة. تجلس فعليًا في قبو المخرجات للمجمع حتى يستدعي مالك الأمر SettleLimitOrder (أو DecreaseLimitOrder). يتتبع المجمع ببساطة مقدار cohort الذي تم ملؤه الآن عبر unfilled_ratio_x64. يخزن كل LimitOrderState لقطة (order_phase, unfilled_ratio_x64) الخاصة به في وقت الفتح، لذا يقلل التسوية إلى:
هذا التسوية O(1) هي النقطة كاملة من تصميم cohort — يمكن لتيك ملء أوامر عديدة بشكل تعسفي دون غاز لكل أمر.

التفاعل مع منحنى LP

في خطوة مبادلة، تحدث مطابقة أوامر الحد عند التيك (صفر Δsqrt_price)؛ استهلاك منحنى LP يحدث بين التيكات. الترتيب بالتالي:
  1. عبور التيك t_cross (طبق تغيير LP liquidity_net أولاً، لأن هذا كيف يفعله Uniswap-V3).
  2. ملء أي أوامر حد جالسة عند t_cross.
  3. المتابعة على طول منحنى LP إلى التيك المهيأ التالي أو إلى استنزاف swap_input.
وبالتالي تعطي أوامر الحد للمتداولين سيولة فعالة أكثر بالضبط بسعر أمر التيك (تأثير تحسين السعر)، على حساب LPs عدم كسب رسوم على تلك الحصة من حجم المبادلة — حصة أمر الحد من التجارة خالية من الرسوم للمتداول، لأن مُنشئ أمر الحد يعمل كصانع. لا تزال الزيادة الرسوم الديناميكية (إن كانت مفعلة) تنطبق على حصة LP من نفس المبادلة.

اشتقاق الرسوم الديناميكية

يحمل PoolState.dynamic_fee_info حالة التقلب. تحسب كل خطوة مبادلة معدل الرسم لكل خطوة كـ: fee_ratetotal=trade_fee_rateconfig+dynamic_fee_control⋅(vol_acc⋅tick_spacing)2Dctrl⋅Svol2⏟dynamic surcharge\text{fee\_rate}_{\text{total}} = \text{trade\_fee\_rate}_{\text{config}} + \underbrace{\frac{\text{dynamic\_fee\_control} \cdot (\text{vol\_acc} \cdot \text{tick\_spacing})^2} {D_{\text{ctrl}} \cdot S_{\text{vol}}^2}}_{\text{dynamic surcharge}} حيث:
  • Dctrl=100,000D_{\text{ctrl}} = 100{,}000 — DYNAMIC_FEE_CONTROL_DENOMINATOR
  • Svol=10,000S_{\text{vol}} = 10{,}000 — VOLATILITY_ACCUMULATOR_SCALE
  • vol_acc هو مراكم لكل مبادلة بعد قاعدة التحديث أدناه
  • tick_spacing من PoolState.tick_spacing
يتم تشديد النتيجة عند 100,000/106=10%100{,}000 / 10^6 = 10\%.

تحديث المراكم

يتم تطبيق قاعدتين لكل مبادلة، بالترتيب: الاضمحلال. يتحلل الحد المرجعي بناءً على الوقت منذ آخر تحديث: vol_ref={0if Δt>decay_periodvol_accprev⋅reduction_factor10,000if filter_period<Δt≤decay_periodvol_refprevif Δt≤filter_period\text{vol\_ref} = \begin{cases} 0 & \text{if } \Delta t > \text{decay\_period} \\ \text{vol\_acc}_{\text{prev}} \cdot \dfrac{\text{reduction\_factor}}{10{,}000} & \text{if } \text{filter\_period} < \Delta t \le \text{decay\_period} \\ \text{vol\_ref}_{\text{prev}} & \text{if } \Delta t \le \text{filter\_period} \end{cases} التراكم. المراكم الجديد هو المرجع بالإضافة إلى مسافة التيك المقطوعة منذ فهرس المرجع السابق: vol_acc=min⁡(vol_ref+∣tref−tnow∣⋅Svol,max_vol_acc)\text{vol\_acc} = \min\left( \text{vol\_ref} + \left| t_{\text{ref}} - t_{\text{now}} \right| \cdot S_{\text{vol}}, \text{max\_vol\_acc} \right) tick_spacing_index_reference (treft_{\text{ref}}) بوحدات tick-spacing، وليس التيكات الخام: tref=⌊tick_current/tick_spacing⌋t_{\text{ref}} = \lfloor \text{tick\_current} / \text{tick\_spacing} \rfloor.

لماذا مكافئ في مسافة التيك

تربيع المراكم يعني أن الرسم يرتفع كـ مربع كم بعيد السعر قد مشى بعيدًا عن نقطة مرجعه. تجريبيًا هذا يطابق تحجيم التباين للسعر تحت ضغط المشي العشوائي: إثارة تيك 2× تعني 4× التقلب الضمني، لذا تفرض 4× الزيادة. معامل dynamic_fee_control يعاير المستوى المطلق. نافذة filter_period تمنع تذبذبات صغيرة دون الثانية (مثلاً، بوتات MEV sandwiching) من تضخيم المراكم. نافذة decay_period تمنع ارتفاع واحد في الماضي من فرض رسوم إلى الأبد بعد أن يكون السوق قد هدأ.

المتانة العددية

  • جميع المنتجات الوسيطة تمر عبر حسابات u128 أو u256. يستخدم CLMM مساعدات U128Sqrt وأنماط FullMath::mulDiv المنقولة مباشرة من Uniswap v3.
  • يتم اختيار تقريب القسمة لكل خطوة لفرض الثابت k' ≥ k محليًا. SwapBaseInput يقرب المخرجات لأسفل؛ SwapBaseOutput يقرب المدخلات لأعلى.
  • عبور التيكات التي تسقط PoolState.liquidity إلى صفر مسموحة (السعر يمكن أن يعبر “حفرة سيولة”) لكن المبادلة ببساطة تتقدم إلى التيك المهيأ التالي دون استهلاك مدخل، دون فرض رسم.
  • حماية الفيضان: يتم الاحتفاظ بـ sqrt_price_x64 في النطاق الشامل [MIN_SQRT_PRICE_X64, MAX_SQRT_PRICE_X64] المقابل لـ [MIN_TICK, MAX_TICK]. مبادلة قد تدفع ماضي أي حد ترجع مع SqrtPriceLimitOverflow.

أين تذهب بعد ذلك

المصادر: