مسیر Quant Developer · بخش ۵: Backtesting و شبیهسازی · درس ۱۹
Latency، Fees و Slippage
تأخیر از کجا میآید و چرا قیمت کهنه یعنی شکار شدن، کارمزد چطور مرز سود و زیان را جابهجا میکند، و اسلیپیج سفارشهای Taker هج را در Backtest چطور مدل کنیم.
۱. سه هزینهای که Backtest ساده صفر میگیرد
در درس ۱۸ دیدیم فرض پر شدن چطور PnL را چند برابر جابهجا میکند. ولی آنجا سه چیز را صفر فرض کردیم:
- Latency (تأخیر): فاصله زمانی بین رخ دادن یک رویداد در صرافی و رسیدن واکنش تو به صرافی.
- Fees (کارمزد): که در درس ۷ با جزئیات حساب کردیم، ولی اینجا اثرش را روی تصمیم «این استراتژی ارزش اجرا دارد یا نه» میبینیم.
- Slippage (اسلیپیج): فاصله قیمت اجرای واقعی یک سفارش Taker با قیمتی که هنگام تصمیم دیده بودی؛ در درس ۴ اندازهگیریاش را دیدیم، اینجا مدل کردنش در Backtest را.
هر سه یک ویژگی مشترک دارند: در Backtest خوشبینانه حذفشان آسان است و در اجرای واقعی هیچوقت صفر نیستند.
۲. Latency از کجا میآید؟
وقتی در صرافی اتفاقی میافتد، مثلاً یک معامله بزرگ قیمت را جابهجا میکند، واکنش تو از چند مرحله عبور میکند. اعداد زیر فرضیاند و فقط مرتبه بزرگی را نشان میدهند؛ مقدار واقعی به فاصله فیزیکی تا صرافی، شبکه و زیرساخت بستگی دارد و میتواند از چند صد میکروثانیه تا دهها میلیثانیه باشد:
| مرحله | از کجا تا کجا | مقدار فرضی |
|---|---|---|
| Market Data Latency | رویداد در Matching Engine تا رسیدن پیامش به برنامه تو | 3 ms |
| Decision Latency | پردازش پیام، بهروزرسانی مدل و تصمیم | 0.5 ms |
| Order Latency | فرستادن سفارش یا لغو تا رسیدن به Matching Engine | 3 ms |
| Ack Latency | تأیید صرافی تا رسیدن به تو | 3 ms |
برای بازارساز مهمترین عدد، زمان واکنش است: از لحظهای که چیزی در صرافی عوض شد تا لحظهای که لغو یا سفارش جدید تو به صرافی رسید.
چند نکته که در یک عدد میانگین دیده نمیشود:
- میانگین کافی نیست؛ دنباله مهم است. اگر میانگین تأخیر ۶.۵ میلیثانیه باشد ولی ۱٪ پیامها ۵۰ میلیثانیه طول بکشند، همان ۱٪ میتواند بیشترین زیان را بسازد. تأخیر را با صدکها (p50، p99) گزارش کن، نه فقط میانگین.
- تأخیر وقتی بیشتر میشود که بیشتر مهم است. در لحظههای پرنوسان، حجم پیامها چند برابر میشود و صف پیامها هم در صرافی و هم در سیستم تو بلندتر میشود.
- برای اندازهگیری، ساعتها باید همزمان باشند. اختلاف زمان صرافی با زمان دریافت فقط وقتی معنا دارد که ساعت سیستم تو دقیق تنظیم شده باشد. این را در درس ۲۸ (Observability) دنبال میکنیم.
- تأخیر جایگاه صف را هم بد میکند. در درس ۱۸ گفتیم جایگاه در صف از لحظه رسیدن سفارش حساب میشود. هر میلیثانیه دیرتر یعنی سفارشهای بیشتری جلوی تو.
۳. قیمت کهنه و شکار شدن
خطرناکترین اثر تأخیر این است که سفارشهایت بعد از تغییر قیمت هنوز در دفتر بمانند. به چنین سفارشی Stale Quote (قیمت کهنه) میگویند.
فرض کن سفارشهای تو 99,990 و 100,010 هستند، هر کدام 0.1 BTC، حول Fair Value برابر 100,000 (درس ۵). ناگهان در بازار دیگری، مثلاً قرارداد Perpetual همین دارایی (درس ۲۲)، قیمت بالا میرود و Fair Value به 100,050 میرسد:
| زمان | چه اتفاقی میافتد |
|---|---|
| 0 ms | Fair Value از 100,000 به 100,050 میرسد |
| 1 ms | یک معاملهگر سریع تغییر را میبیند |
| 3 ms | سفارش خرید او به صرافی میرسد و Ask تو در 100,010 را میخرد |
| 6.5 ms | لغو تو به صرافی میرسد؛ دیگر چیزی برای لغو نمانده |
تو 0.1 BTC را به 100,010 فروختی که حالا 100,050 میارزد:
به این Picked Off شدن (شکار شدن) میگویند. این شکل خالص Adverse Selection درس ۸ است: طرف مقابل چیزی نمیداند که تو ندانی؛ فقط زودتر از تو میداند. نکته مهم این است که این زیان به پهنای Spread تو ربطی ندارد. با هر پرشی بزرگتر از نیماسپرد، قیمت کهنه یعنی زیان؛ تنها سؤال این است که در مسابقه سرعت چند بار میبازی.
۴. تأخیر در Backtest
اسکلت درس ۱۷ تأخیر را از اول در ساختارش داشت: md_latency و order_latency زمان رسیدن هر پیام را جابهجا میکنند. چند قدم برای واقعگرایانهتر کردنش:
- تأخیر را ثابت نگیر. از توزیعی بکش که از اندازهگیری خودت آمده، با دنباله سنگین، و در لحظههای پرحجم بزرگترش کن.
- رقابت را مدل کن. داده تاریخی میگوید معاملهای رخ داد، ولی نمیگوید اگر لغو تو زودتر میرسید، آن معاملهگر چه میکرد. سادهترین مدل این است که زمان واکنش سریعترین رقیب را یک متغیر تصادفی بگیری و هر بار که پرش قیمت رخ میدهد، ببینی او زودتر رسیده یا لغو تو.
- زمان دیدن و زمان رسیدن را جدا نگه دار. تصمیم با دادهای که t + تأخیر داده دیده شده گرفته میشود و در t + تأخیر داده + تأخیر تصمیم + تأخیر سفارش اجرا میشود.
در شبیهسازی بخش ۶ از مدل ساده بند ۲ استفاده میکنیم. اگر زمان واکنش سریعترین رقیب توزیع نمایی با میانگین τ داشته باشد و زمان واکنش تو L باشد، احتمال اینکه او زودتر برسد:
با τ برابر ۱۰ میلیثانیه (فرضی): برای L برابر ۱ میلیثانیه حدود ۱۰٪، برای ۱۰ میلیثانیه حدود ۶۳٪ و برای ۵۰ میلیثانیه بیش از ۹۹٪.
۵. کارمزد: یک سطح، کل استراتژی
در درس ۷ دیدیم برای 0.1 BTC با نیماسپرد ۱۰ دلاری، سهم Spread هر معامله فقط ۱ دلار است و کارمزد هماندازه یا بزرگتر از آن است. بیشتر صرافیها کارمزد را پلکانی (Tiered) تعیین میکنند: هر چه حجم معاملات ماهانه بیشتر، نرخ کمتر. یک جدول فرضی، که سطح آخرش همان نرخهای درس ۱ و ۷ است:
| سطح | حجم ۳۰ روزه (فرضی) | Maker | Taker | کارمزد Maker هر پر شدن 0.1 BTC |
|---|---|---|---|---|
| ۱ | کمتر از ۱ میلیون USDT | ۰.۰۲٪ | ۰.۰۸٪ | ۲ دلار پرداختی |
| ۲ | ۱ تا ۵۰ میلیون | ۰.۰۱٪ | ۰.۰۷٪ | ۱ دلار پرداختی |
| ۳ | ۵۰ تا ۵۰۰ میلیون | صفر | ۰.۰۶٪ | صفر |
| ۴ | بیش از ۵۰۰ میلیون | ریبیت ۰.۰۱٪ | ۰.۰۵٪ | ۱ دلار دریافتی |
حالا یک واحد مشترک لازم داریم. Basis Point (bp) یعنی یکصدم درصد، یعنی ۰.۰۱٪. نیماسپرد ۱۰ دلار روی قیمت 100,000 دقیقاً 1 bp است. پس:
در سطح ۲، کارمزد Maker برابر کل سهم Spread است و استراتژی پیش از هر حرکت قیمتی سربهسر است. در سطح ۱ هر پر شدن زیانده است. در سطح ۴ ریبیت درآمد هر پر شدن را دو برابر میکند. یعنی سطح کارمزد خودش یک پارامتر استراتژی است، و استراتژیای که فقط در سطح ۴ سودده است، تا وقتی به حجم آن سطح نرسی، اصلاً قابل اجرا نیست.
۶. شبیهسازی: حساسیت PnL به تأخیر و کارمزد
حالا دو اثر را کنار هم میگذاریم. مدل عمداً ساده است تا هر عدد قابل ردیابی باشد. فرضها (همه فرضی):
- سفارشها 0.1 BTC در فاصله ۱۰ دلار از Fair Value برابر 100,000 هستند؛ ارزش هر پر شدن حدود 10,000 USDT.
- معاملهگرهای بیاطلاع بهطور تصادفی، بهطور میانگین هر ۲۰ ثانیه یک بار در هر طرف، با تو معامله میکنند. هر پر شدن نیماسپرد را میدهد: ۱ دلار.
- Fair Value بهطور میانگین هر ۲۰ ثانیه یک بار ۵۰ دلار میپرد. اگر سریعترین رقیب زودتر از لغو تو برسد، شکار میشوی: (50 − 10) × 0.1 = 4 دلار زیان.
- زمان واکنش سریعترین رقیب توزیع نمایی با میانگین ۱۰ میلیثانیه دارد.
- ریسک موجودی را کنار میگذاریم؛ هر پر شدن با Fair Value همان لحظه ارزشگذاری میشود.
import numpy as np
rng = np.random.default_rng(19)
HOURS = 200
Q, HALF, JUMP, NOTIONAL = 0.1, 10, 50, 10_000 # BTC, USDT, USDT, USDT per fill
# Uninformed takers hit our bid or ask at random: each such fill earns the half-spread
n_noise = rng.poisson(2 * 0.05 * 3600 * HOURS) # 0.05 fills/s on each side
# Fair value jumps by 50 USDT now and then, and one of our quotes becomes stale
n_jump = rng.poisson(0.05 * 3600 * HOURS) # one jump every 20 s on average
arb_time = rng.exponential(0.010, n_jump) # fastest taker's reaction time (s)
def pnl_per_hour(latency, maker_fee):
picked = np.sum(arb_time < latency) # taker arrives before our cancel
gross = n_noise * HALF * Q - picked * (JUMP - HALF) * Q
fees = (n_noise + picked) * NOTIONAL * maker_fee
return (gross - fees) / HOURS
fees = [-0.0001, 0.0, 0.0001, 0.0002] # hypothetical tiers 4..1 (negative = rebate)
print("latency " + "".join(f"{f * 1e4:+7.0f}bp" for f in fees))
for ms in (1, 5, 10, 20, 50):
print(f"{ms:4d} ms " + "".join(f"{pnl_per_hour(ms / 1000, f):9.0f}" for f in fees))
latency -1bp +0bp +1bp +2bp
1 ms 668 291 -86 -463
5 ms 506 75 -355 -786
10 ms 378 -96 -569 -1043
20 ms 254 -261 -776 -1291
50 ms 185 -353 -891 -1429
همین جدول به زبان سطحهای کارمزد (USDT در ساعت):
| زمان واکنش | سطح ۴: ریبیت 1 bp | سطح ۳: صفر | سطح ۲: 1 bp | سطح ۱: 2 bp |
|---|---|---|---|---|
| 1 ms | ۶۶۸ سود | ۲۹۱ سود | ۸۶ زیان | ۴۶۳ زیان |
| 5 ms | ۵۰۶ سود | ۷۵ سود | ۳۵۵ زیان | ۷۸۶ زیان |
| 10 ms | ۳۷۸ سود | ۹۶ زیان | ۵۶۹ زیان | ۱,۰۴۳ زیان |
| 20 ms | ۲۵۴ سود | ۲۶۱ زیان | ۷۷۶ زیان | ۱,۲۹۱ زیان |
| 50 ms | ۱۸۵ سود | ۳۵۳ زیان | ۸۹۱ زیان | ۱,۴۲۹ زیان |
این مدل آنقدر ساده است که جواب دقیقش را با دست هم میشود نوشت. در هر ساعت بهطور میانگین ۳۶۰ پر شدن بیاطلاع و ۱۸۰ پرش داریم. با f نرخ کارمزد Maker و N ارزش هر پر شدن:
برای کارمزد صفر و تأخیر ۱ میلیثانیه: 360 − 180 × 0.095 × 4 ≈ 291؛ همان عدد شبیهسازی. سه برداشت:
۱. تأخیر میتواند علامت نتیجه را عوض کند. با کارمزد صفر، رفتن از ۱ به ۱۰ میلیثانیه سود ۲۹۱ دلاری را به ۹۶ دلار زیان تبدیل میکند. نقطه سربهسر جایی است که 360 = 180 × P × 4، یعنی P برابر ۰.۵ و L برابر τ × ln 2، حدود ۶.۹ میلیثانیه. در این مدل، استراتژی با زمان واکنش ۶.۵ میلیثانیه بخش ۲ درست روی مرز است.
۲. یک سطح کارمزد هموزن کل محور تأخیر است. فاصله دو ستون کنار هم بین ۳۷۷ و ۵۳۸ دلار در ساعت است، چون ۱ bp یعنی ۱ دلار برای هر پر شدن. این هممرتبه با کل فاصله ۱ تا ۵۰ میلیثانیه در یک ستون است (بین ۴۸۳ و ۹۶۶ دلار).
۳. ریبیت مشکل را پنهان میکند. در ستون سطح ۴، استراتژی حتی با ۵۰ میلیثانیه تأخیر سودده است. ولی تقریباً همه سودش از ریبیت میآید و با هر بار شکار شدن، بخشی از آن را پس میدهد. اگر حجم ماهانه کم شود و به سطح ۳ برگردی، همین استراتژی در هر تأخیری بالای حدود ۷ میلیثانیه زیانده است، و در سطح ۲ در هر تأخیری.
محدودیتهای مدل: پرشها همه هماندازهاند، معاملهگرهای بیاطلاع به تأخیر حساس نیستند، ریسک موجودی و جایگاه صف کنار گذاشته شده، و τ عددی است که در واقعیت باید از داده تخمین زده شود؛ مثلاً از Markout پر شدنهایی که درست بعد از حرکتهای تند قیمت رخ دادهاند (درس ۸ و ۳۱).
۷. اسلیپیج سفارشهای Taker هج
بازارساز معمولاً Maker است، ولی وقتی موجودی از حد مجاز بیرون میزند، ممکن است با سفارش Taker آن را کم کند یا در بازار دیگری هج کند (درس ۲۱). اینجا اسلیپیج وارد میشود. چهار مدل رایج در Backtest، از ساده به دقیق:
- صفر: اجرا با Mid یا با Best Bid و Ask. تقریباً همیشه غلط و خوشبینانه.
- ثابت: هزینهای ثابت بر حسب bp، مثلاً «نیماسپرد بهاضافه ۲ bp». ساده و برای حجم کوچک قابل قبول.
- پیمایش دفتر ثبتشده: سفارش را روی دفتر سفارشی که در داده داری اجرا کن، همان کاری که در درس ۴ کردیم. نکتهاش این است که دفتر را در لحظه رسیدن سفارش بخوانی، نه لحظه تصمیم. در لحظههای پرنوسان، همین چند میلیثانیه بیشترین اسلیپیج را میسازد.
- مدل اثر قیمتی: برای حجمهای بزرگ که چند دقیقه یا چند ساعت اجرا میشوند، دفتر لحظهای کافی نیست، چون بازار به سفارش تو واکنش نشان میدهد. یک رابطه تجربی رایج در پژوهشهای اجرای سفارش، قانون ریشه دوم (Square-Root Law) است:
σ نوسان روزانه، V حجم معاملات روزانه بازار، Q حجم سفارش تو و Y ضریبی از مرتبه یک است که باید برای هر بازار تخمین زده شود. با اعداد فرضی σ برابر ۲٪، V برابر 10,000 BTC، Q برابر 10 BTC و Y برابر ۱، اثر حدود 0.02 × √0.001 ≈ 6.3 bp میشود؛ یعنی حدود ۶۳ دلار برای هر BTC.
برای حجمهای کوچک، مدل ۳ کافی است. دفتر سفارش درس ۱ را در نظر بگیر و فرض کن بعد از پر شدن چند سفارش فروش، موجودی منفی داری و میخواهی با خرید Market آن را ببندی؛ کارمزد Taker همان ۰.۰۵٪ فرضی درس ۷ است:
| حجم هج | میانگین قیمت | اسلیپیج نسبت به Mid | کارمزد Taker | هزینه کل |
|---|---|---|---|---|
| 0.1 BTC | 100,010 | ۱ دلار | ۵.۰ دلار | ۶.۰ دلار |
| 0.5 BTC | 100,014 | ۷ دلار | ۲۵.۰ دلار | ۳۲.۰ دلار |
| 1.0 BTC | 100,019 | ۱۹ دلار | ۵۰.۰ دلار | ۶۹.۰ دلار |
- سهم Spread هر پر شدن Maker، 0.1 BTC
- 1 USDT
- هزینه هج Taker همان 0.1 BTC
- 6 USDT
اگر هر پر شدن را فوراً با Taker هج کنی، هزینه هج شش برابر درآمد Spread آن است. حتی اگر ده پر شدن را جمع کنی و یکجا 1 BTC هج کنی، ۶۹ دلار هزینه در برابر ۱۰ دلار درآمد میماند. با این نرخهای فرضی، کارمزد Taker بخش بزرگتر هزینه است، نه اسلیپیج. به همین دلیل در درس ۶ موجودی را با Skew کم کردیم و در درس ۲۱ میبینیم هج کِی و با چه ابزاری ارزشش را دارد.
۸. تمرین این درس
گزینه مناسب هر سؤال را انتخاب کن و در پایان نتیجه را ببین.
درس بعدی
در درس بیستم، آخرین درس بخش Backtesting، میپرسیم خروجی Backtest را با چه معیارهایی بخوانیم: Net PnL، Drawdown، Sharpe Ratio و دامهای سالانهسازی آن برای PnL پرتکرار، نرخ پر شدن و PnL به ازای حجم معامله، و اینکه چطور بفهمیم نتیجه پایدار است یا فقط حاصل شانس و تنظیم بیش از حد پارامترها.
این مطلب صرفاً آموزشی است و توصیه خرید، فروش یا سرمایهگذاری نیست.