السعر الذي لا تحصل عليه
يتداول الاختبار الرجعي بالسعر الموجود في بياناته، أما الأمر الحقيقي فيُنفَّذ في مكان آخر. والانزلاق السعري هو هذا الفرق: تشتري أعلى قليلًا من السعر الذي رأيته وتبيع أدنى قليلًا. وله مصدران. الفارق: أمر السوق يشتري بسعر الطلب ويبيع بسعر العرض، فتدفع نصف الفارق في كل اتجاه قياسًا بالسعر الوسطي. وأثر السوق: الأمر الكبير يستهلك أفضل العروض ويدفع السعر ضد نفسه.
def fill_price(mid, side, slippage_bps):
if side == "buy":
return mid * (1 + slippage_bps / 10000)
if side == "sell":
return mid * (1 - slippage_bps / 10000)
raise ValueError("side must be 'buy' or 'sell'")
print(round(fill_price(50.0, "buy", 4), 4)) # 50.02 pays more
print(round(fill_price(50.0, "sell", 4), 4)) # 49.98 receives less# Crossing the spread: a market buy pays the ask, a market sell receives the bid
bid, ask = 49.98, 50.02
mid = (bid + ask) / 2
half_spread_bps = (ask - bid) / 2 / mid * 10000
print(mid, round(half_spread_bps, 2)) # 50.0 4.0 each way, relative to the mid price
print(round((ask - mid) / mid * 10000, 2), round((mid - bid) / mid * 10000, 2)) # 4.0 4.0قياسه مقابل مرجع
لقياس الانزلاق تحتاج إلى مرجع: سعر الوصول، أي السعر الوسطي حين اتُّخذ القرار بالأمر. ويُعطى الفرق إشارة بحيث يعني الموجب دائمًا أنه كلّفك: للشراء fill - arrival وللبيع arrival - fill، بالنقاط الأساسية من سعر الوصول. والتنفيذ نفسه السيئ للمشتري جيد للبائع، ولذلك تتوقف الإشارة على الاتجاه. وتُنفَّذ الأوامر الكبيرة على أجزاء، فالرقم الذي يُبلَّغ هو متوسط سعر التنفيذ المرجح بالحجم.
import numpy as np
def slippage_bps(fill, arrival, side):
if side == "buy":
return (fill - arrival) / arrival * 10000
if side == "sell":
return (arrival - fill) / arrival * 10000
raise ValueError("side must be 'buy' or 'sell'")
def average_fill(prices, quantities):
prices, quantities = np.asarray(prices, dtype=float), np.asarray(quantities, dtype=float)
return float(np.sum(prices * quantities) / np.sum(quantities))
# Buying 600 shares in three pieces; the price at arrival was 50.00
avg = average_fill([50.0, 50.1, 50.3], [100, 200, 300])
print(round(avg, 4)) # 50.1833
print(round(slippage_bps(avg, 50.0, "buy"), 2)) # 36.67 bps: it cost us
print(round(slippage_bps(avg, 50.0, "sell"), 2)) # -36.67: the same fills would have helped a sellerالأثر يزداد مع الحجم
الفارق رسم ثابت، أما الأثر فيعتمد على مقدار ما تتداوله. وقاعدة عملية مجرَّبة جيدًا هي قانون الجذر التربيعي: الأثر المتوقع كجزء من السعر يساوي نحو k * volatility * sqrt(quantity / ADV) حيث ADV متوسط الحجم اليومي وk ثابت قريب من 1. ومضاعفة الأمر أربع مرات تضاعف الأثر مرتين فقط، فتقسيم الأمر الكبير عبر الزمن يفيد لكن أقل مما تأمل.
import math
def sqrt_impact_bps(volatility, quantity, adv, k=1.0):
return k * volatility * math.sqrt(quantity / adv) * 10000
# A stock with 2% daily volatility and 1,000,000 shares of average daily volume
for shares in (10_000, 50_000, 200_000, 800_000):
print(shares, round(sqrt_impact_bps(0.02, shares, 1_000_000), 1), "bps")
# 10,000: 20.0, 50,000: 44.7, 200,000: 89.4, 800,000: 178.9- الشراء يُنفَّذ فوق السعر الوسطي والبيع تحته:
mid * (1 +/- bps / 10000)؛ والفارق وحده يكلف نصف عرضه في كل اتجاه. - قِس مقابل سعر الوصول بإشارة تجعل الموجب تكلفة؛ وتُدمج التنفيذات الجزئية بمتوسط مرجح بالحجم.
- يتبع الأثر قانون الجذر التربيعي
k * volatility * sqrt(quantity / ADV): أربعة أضعاف الحجم ضعف الأثر.
التالي: تنفيذ أوامر واقعي، محاكاة كيف تُنفَّذ الأوامر فعلًا مقابل دفتر الأوامر وبيانات الشموع.
المختبر: ما الذي يُقيَّم
- سعّر التنفيذ باستخدام
fill_price25 نقطة - قِس الفرق باستخدام
slippage_bps25 نقطة - ادمج التنفيذات الجزئية باستخدام
average_fill25 نقطة - توقّع الأثر باستخدام
sqrt_impact_bps25 نقطة