مسیر Quant Developer · بخش ۲: اقتصاد و ریسک Market Making · درس ۷
Spread Capture، کارمزد و PnL
سود و زیان بازارساز را دقیق حساب میکنیم: ناخالص و خالص، تحققیافته و تحققنیافته، اثر کارمزد Maker و Taker، و تجزیه PnL به سهم Spread، سهم موجودی و سهم کارمزد.
۱. یک عدد، چند معنی
«امروز چقدر سود کردیم؟» سادهترین سؤال یک سیستم معاملاتی است و جوابش بسته به اینکه چه چیزی را بشماری، کاملاً فرق میکند. پیش از هر چیز این اصطلاحها را از هم جدا کنیم:
- PnL (Profit and Loss): سود و زیان.
- Gross PnL یا سود ناخالص: سود و زیان معاملات پیش از کارمزد.
- Net PnL یا سود خالص: بعد از کارمزد (و ریبیت).
- Realized PnL یا سود تحققیافته: سود و زیان بخشی از موجودی که بسته شده است؛ یعنی خریدی که فروختهای یا برعکس.
- Unrealized PnL یا سود تحققنیافته: سود و زیان کاغذی موجودیای که هنوز در دست داری.
- Mark-to-Market: ارزشگذاری موجودی باز با قیمت فعلی بازار. قیمتی که با آن ارزشگذاری میکنی را Mark میگویند.
در درس ششم PnL را به شکل «نقد بهعلاوه موجودی ضرب در قیمت پایانی» حساب کردیم. آن عدد درست بود، ولی نمیگفت پول از کجا آمد و کجا رفت. این درس همین را باز میکند.
۲. کارمزد Maker و Taker، با عدد
در درس ۳ دیدیم Maker و Taker کارمزد متفاوتی دارند. در این درس همان نرخهای فرضی درس ۱ را به کار میبریم:
| نقش | نرخ فرضی | برای 0.1 BTC در قیمت حدود 100,000 |
|---|---|---|
| Maker | ریبیت ۰.۰۱٪ (۱ basis point) | حدود ۱ دلار دریافتی |
| Taker | کارمزد ۰.۰۵٪ (۵ basis point) | حدود ۵ دلار پرداختی |
کارمزد روی ارزش معامله (Notional) گرفته میشود، نه روی سود. حالا این را با درآمد Spread مقایسه کن: اگر 0.1 BTC را با نیماسپرد ۱۰ دلاری بخری، سهم Spread در همان یک معامله هم 0.1 × 10 = 1 دلار است. یعنی:
- ریبیت Maker به اندازه کل درآمد Spread هر معامله است.
- اگر بهجای ریبیت، کارمزد Maker ۰.۰۲٪ (۲ دلار) بپردازی، هر معامله پیش از هر حرکت قیمتی زیانده است.
- یک معامله Taker (۵ دلار) درآمد Spread پنج معامله Maker را میبلعد.
این همان نتیجه جدول درس ۱ است، این بار در مقیاس یک معامله. در بازارسازی با Spread باریک، کارمزد یک جزء فرعی نیست؛ اغلب بزرگترین جزء است.
۳. Realized و Unrealized با روش میانگین بها
برای جدا کردن سود تحققیافته از تحققنیافته، باید بدانی موجودیات را «به چه قیمتی» خریدهای. رایجترین روش در سیستمهای معاملاتی میانگین بها (Average Cost) است:
این فرمول فقط وقتی به کار میرود که موجودی را بزرگتر میکنی (خرید روی Long، یا فروش روی Short). وقتی موجودی را کوچک میکنی، میانگین بها ثابت میماند و سود تحقق مییابد. برای موجودی Long:
و برای باقیمانده موجودی:
Mark معمولاً Mid یا Fair Value درس ۵ است. نسخه محافظهکارانهتر، Long را با Best Bid و Short را با Best Ask ارزشگذاری میکند؛ یعنی قیمتی که واقعاً میتوانی فوراً با آن خارج شوی.
۴. مثال: سه معامله و یک ارزشگذاری
بازار کمی پایین میرود و بازارساز سه معامله Maker انجام میدهد، هر کدام 0.1 BTC. ستون Mid، قیمت میانی بازار در لحظه هر معامله است:
| رویداد | معامله | Mid | موجودی | میانگین بها | Realized |
|---|---|---|---|---|---|
| ۱ | خرید در 99,990 | 100,000 | 0.1 | 99,990 | صفر |
| ۲ | خرید در 99,970 | 99,980 | 0.2 | 99,980 | صفر |
| ۳ | فروش در 100,000 | 99,990 | 0.1 | 99,980 | ۲ دلار سود |
| ارزشگذاری | — | 99,960 | 0.1 | 99,980 | ۲ دلار سود |
قدم به قدم:
- بعد از دو خرید، میانگین بها (99,990 + 99,970) / 2 = 99,980 است.
- فروش در 100,000، 0.1 BTC را میبندد: 0.1 × (100,000 − 99,980) = 2 دلار سود تحققیافته.
- 0.1 BTC باقیمانده با Mid پایانی 99,960 ارزشگذاری میشود: 0.1 × (99,960 − 99,980)، یعنی ۲ دلار زیان تحققنیافته.
- Realized PnL
- +2.00 USDT
- Unrealized PnL
- −2.00 USDT
- Gross PnL
- 0.00 USDT
- Net PnL (با ریبیت حدود 3 دلار)
- +3.00 USDT
سود ناخالص دقیقاً صفر است و تمام سود خالص از ریبیت آمده است. اگر همین سه معامله با کارمزد Maker ۰.۰۲٪ انجام میشد (حدود ۲ دلار برای هر معامله)، نتیجه خالص حدود ۶ دلار زیان بود.
یک نکته مهم: گزارش «۲ دلار سود تحققیافته» به تنهایی گمراهکننده است. داشبوردی که فقط Realized را نشان دهد، استراتژیای را سودده نشان میدهد که زیانهایش را در موجودی باز پنهان کرده است. همیشه Realized و Unrealized را با هم ببین.
۵. تجزیه PnL: سهم Spread و سهم موجودی
تقسیم Realized و Unrealized یک تقسیم حسابداری است. برای فهم اقتصادی استراتژی، تجزیه دیگری مفیدتر است:
Spread Capture یا سهم Spread: هر معامله در لحظه انجام، چقدر از Mid بهتر بوده است.
که s برای خرید برابر ۱ و برای فروش برابر منفی ۱ است. خرید زیر Mid و فروش بالای Mid هر دو سهم مثبت دارند.
Inventory PnL یا سهم موجودی: سود و زیان ناشی از نگه داشتن موجودی، وقتی Mid بین دو رویداد حرکت میکند.
برای مثال بالا:
| بازه | Spread Capture | موجودی در بازه | تغییر Mid | Inventory PnL |
|---|---|---|---|---|
| رویداد ۱ | 0.1 × 10 = ۱ دلار | |||
| ۱ تا ۲ | 0.1 | 100,000 به 99,980 | ۲ دلار زیان | |
| رویداد ۲ | 0.1 × 10 = ۱ دلار | |||
| ۲ تا ۳ | 0.2 | 99,980 به 99,990 | ۲ دلار سود | |
| رویداد ۳ | 0.1 × 10 = ۱ دلار | |||
| ۳ تا ارزشگذاری | 0.1 | 99,990 به 99,960 | ۳ دلار زیان | |
| جمع | ۳ دلار سود | ۳ دلار زیان |
سهم Spread سه دلار، سهم موجودی سه دلار زیان، و جمع صفر؛ همان Gross PnL بخش ۴. با اضافه شدن کارمزد، تصویر کامل این است:
(کارمزد اینجا منفی است چون ریبیت گرفتهایم.) حالا داستان روشن است: استراتژی Spread را درست گرفت، ولی همهاش را در موجودی پس داد. این الگو، یعنی خریدن درست قبل از پایین رفتن قیمت، دقیقاً همان چیزی است که درس ۸ اندازهاش را میگیرد.
۶. بستن موجودی با سفارش Taker
فرض کن بعد از ارزشگذاری، سیستم ریسک تصمیم بگیرد 0.1 BTC باقیمانده را فوراً ببندد. Mid برابر 99,960 است و Best Bid برابر 99,950. فروش Market یعنی:
- Realized: 0.1 × (99,950 − 99,980)، یعنی ۳ دلار زیان. جمع Realized از ۲ دلار سود به ۱ دلار زیان میرسد.
- Spread Capture این معامله: 0.1 × (99,950 − 99,960)، یعنی ۱ دلار زیان. این بار ما نیماسپرد را پرداختیم.
- کارمزد Taker: 0.1 × 99,950 × 0.05% ≈ 5 دلار پرداختی.
| پیش از بستن | بعد از بستن | |
|---|---|---|
| موجودی | 0.1 BTC | صفر |
| Gross PnL | صفر | ۱ دلار زیان |
| کارمزد خالص | ۳ دلار ریبیت | ۲ دلار پرداختی |
| Net PnL | ۳ دلار سود | ۳ دلار زیان |
حذف ریسک 0.1 BTC، ۶ دلار هزینه داشت: ۱ دلار نیماسپرد و ۵ دلار کارمزد Taker. این قیمت «خروج فوری» است و توضیح میدهد چرا بازارسازها ترجیح میدهند موجودی را با Skew (درس ۶) و بهعنوان Maker کم کنند و Taker شدن را برای وضعیت اضطراری نگه دارند. گزینههای دیگر پوشش ریسک را در درس ۲۱ میبینیم.
۷. در کد: محاسبه PnL از فهرست معاملات
تابع زیر فهرست معاملات را میگیرد و با روش میانگین بها موجودی، سود تحققیافته، سود تحققنیافته و کارمزد را حساب میکند. حالت عبور از صفر (مثلاً فروش بیشتر از موجودی Long که آن را به Short تبدیل میکند) هم پوشش داده شده است.
# Hypothetical fee rates in basis points (1 bp = 0.01%); negative = rebate
FEE_BPS = {"maker": -1.0, "taker": 5.0}
def pnl_report(fills, mark):
"""fills: (side, price, qty, liquidity). Average-cost accounting."""
pos = avg = realized = fees = 0.0
for side, price, qty, liq in fills:
signed = qty if side == "buy" else -qty
fees += abs(signed) * price * FEE_BPS[liq] / 10_000
if pos * signed >= 0: # opening or adding: update average cost
avg = (avg * abs(pos) + price * abs(signed)) / (abs(pos) + abs(signed))
pos += signed
else: # reducing (or flipping) the position
closed = min(abs(signed), abs(pos))
realized += closed * (price - avg) * (1 if pos > 0 else -1)
pos += signed
if abs(pos) < 1e-12:
pos, avg = 0.0, 0.0
elif pos * signed > 0: # flipped: remainder opens at this price
avg = price
unrealized = pos * (mark - avg)
gross = realized + unrealized
return dict(pos=round(pos, 8), avg=avg, realized=realized,
unrealized=unrealized, gross=gross, fees=fees, net=gross - fees)
fills = [
("buy", 99_990, 0.1, "maker"),
("buy", 99_970, 0.1, "maker"),
("sell", 100_000, 0.1, "maker"),
]
for key, val in pnl_report(fills, mark=99_960).items():
print(f"{key:10s} {val:10.2f}")
print("--- after closing with a taker sell at 99,950")
for key, val in pnl_report(fills + [("sell", 99_950, 0.1, "taker")], mark=99_960).items():
print(f"{key:10s} {val:10.2f}")
خروجی:
pos 0.10
avg 99980.00
realized 2.00
unrealized -2.00
gross 0.00
fees -3.00
net 3.00
--- after closing with a taker sell at 99,950
pos 0.00
avg 0.00
realized -1.00
unrealized 0.00
gross -1.00
fees 2.00
net -3.00
همان اعداد بخش ۴ و ۶. چند نکته درباره این کد:
- علامت کارمزد: اینجا کارمزد مثبت یعنی پرداختی و منفی یعنی ریبیت؛ پس
net = gross - fees. در سیستم واقعی این قرارداد علامت را یک بار تعریف و همهجا رعایت کن؛ اشتباه علامت کارمزد از رایجترین باگهای گزارش PnL است. - اعداد اعشاری: این کد برای آموزش با float کار میکند. همانطور که در درس ۳ گفتیم، سیستم واقعی قیمت و حجم را بهصورت عدد صحیح (Tick و Lot) یا Decimal نگه میدارد. خط
round(pos, 8)و شرط1e-12فقط وصلهای برای خطای گرد کردن float است. - روشهای دیگر: بهجای میانگین بها میشود از FIFO استفاده کرد (اولین خرید، اولین فروش). جمع Gross PnL در هر دو روش یکی است، ولی تقسیمش بین Realized و Unrealized فرق میکند. قواعد حسابداری یا مالیاتی ممکن است یکی را الزامی کنند.
۸. تمرین این درس
گزینه مناسب هر سؤال را انتخاب کن و در پایان نتیجه را ببین. نرخها همان نرخهای فرضی این درساند.
درس بعدی
در مثال این درس، Spread را درست گرفتیم و همهاش را در موجودی پس دادیم. آیا این بدشانسی بود؟ در درس هشتم میبینیم معمولاً نه: Adverse Selection یعنی بخشی از کسانی که با تو معامله میکنند، بیشتر از تو میدانند. و با Markout یاد میگیریم اندازه این زیان را برای هر معامله، در افقهای زمانی مختلف، اندازه بگیریم.
این مطلب صرفاً آموزشی است و توصیه خرید، فروش یا سرمایهگذاری نیست.