لماذا لا نرسله كله دفعة واحدة
اشترِ 200,000 سهم بأمر سوق واحد وستلتهم دفتر الأوامر: تُنفَّذ الأسهم الأولى بأفضل سعر عرض والتالية بسعر أسوأ وهكذا، ويرفع كل من يراك تشتري أسعاره. وهذا الأثر السوقي كلفة حقيقية، غالبًا أكبر من العمولة. وخوارزمية التنفيذ تقطع الأمر إلى أوامر فرعية أصغر تُرسل عبر الزمن ليُمتص كل منها بأثر أقل. وأبسطها TWAP، خوارزمية متوسط السعر المرجّح بالزمن: قسّم الكمية إلى أجزاء متساوية وأرسل جزءًا في كل لحظة من لحظات متباعدة بانتظام بين بداية ونهاية.
# Splitting 10 shares into 3 child orders: whole shares, earlier pieces take the extra
def twap_schedule(total, slices):
size, extra = divmod(total, slices)
return [size + (1 if index < extra else 0) for index in range(slices)]
print(twap_schedule(10, 3)) # [4, 3, 3]
print(twap_schedule(200_000, 8)) # [25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000]
print(sum(twap_schedule(10_001, 7))) # 10001 nothing is lost to roundingالجدول: كم ومتى
لجدول TWAP جزءان. الأحجام تقسم الإجمالي إلى n قطعة من أسهم صحيحة تختلف بسهم واحد على الأكثر وتجمع الأمر بدقة: تعطي divmod(total, n) الحجم الأساسي وعدد القطع التي تأخذ سهمًا زائدًا. والأوقات هي n وقت بدء متباعدة بانتظام بين البداية والنهاية: start + i * (end - start) / n. وأمر ليوم كامل بـ 200,000 سهم في 40 جزءًا هو 5,000 سهم كل عشر دقائق يرسلها مجدول كالذي في الموضوع السابق. وتُعشوِن التطبيقات الحقيقية الأحجام والأوقات قليلًا لأن النمط المنتظم تمامًا يسهل على الآخرين رصده والتداول ضده.
# When each slice starts: a trading day from 09:30 to 16:00 in 13 half-hour slices (seconds since midnight)
def slice_times(start, end, slices):
step = (end - start) / slices
return [start + index * step for index in range(slices)]
times = slice_times(9.5 * 3600, 16 * 3600, 13)
print(times[0], times[1] - times[0]) # 34200.0 1800.0 09:30 and a slice every 30 minutes
print(times[-1] / 3600) # 15.5 the last slice starts at 15:30, the end (16:00) is not a start timeقياسه: متوسط سعر التنفيذ والانزلاق
بعد تنفيذ الأجزاء يكون متوسط سعر التنفيذ هو متوسط أسعار التنفيذ مرجّحًا بالكميات sum(price * quantity) / sum(quantity): التنفيذ الكبير يُحتسب أكثر من الصغير، فالمتوسط البسيط للأسعار خاطئ. ولتقييم النتيجة قارنها بـ سعر الوصول، سعر السوق لحظة اتخاذ القرار. والفرق هو الانزلاق (slippage) ويُعبَّر عنه بـ نقاط الأساس (1 نقطة = 0.01%): في الشراء (المتوسط - الوصول) / الوصول * 10,000 وفي البيع الإشارة المعاكسة فيكون الرقم الموجب كلفة دائمًا. وهذا جوهر نقص التنفيذ (implementation shortfall)، الطريقة المعيارية لتقييم تنفيذ.
# Average fill price (quantity-weighted) and slippage against the arrival price, in basis points
def average_fill_price(fills):
return sum(price * quantity for price, quantity in fills) / sum(quantity for _, quantity in fills)
def slippage_bps(avg_price, arrival_price, side):
sign = 1 if side == "buy" else -1
return sign * (avg_price - arrival_price) / arrival_price * 10000
fills = [(100.0, 400), (100.2, 300), (100.4, 300)]
average = average_fill_price(fills)
print(round(average, 2), sum(price for price, _ in fills) / 3) # 100.18 100.2 the plain mean of the prices is not the answer
print(round(slippage_bps(average, 100.0, "buy"), 2)) # 18.0 a buy that averaged 100.18 against an arrival of 100.00 cost 18 bp
print(round(slippage_bps(average, 100.0, "sell"), 2)) # -18.0 the same prices would have been a gain for a seller# A toy run: the price drifts up while we buy 10 slices; the slippage is what the drift cost us
prices = [100.00, 100.03, 100.05, 100.04, 100.08, 100.10, 100.09, 100.12, 100.15, 100.14] # price at each slice time
pieces = [100] * 10 # TWAP: equal child orders
arrival = prices[0]
fills = list(zip(prices, pieces))
average = sum(p * q for p, q in fills) / sum(pieces)
print(round(average, 4)) # 100.08
print(round((average - arrival) / arrival * 10000, 1)) # 8.0 bp of slippage for buying into a rising market- الأوامر الكبيرة تحرك السعر؛ وخوارزمية التنفيذ ترسل أوامر فرعية أصغر عبر الزمن. ويرسل TWAP أجزاء متساوية في أوقات متباعدة بانتظام.
- الأحجام:
divmod(total, n)مع الأسهم الزائدة في القطع الأولى؛ الأوقات:start + i * (end - start) / n. - قيّم التنفيذ بمتوسط سعره المرجّح بالكميات مقابل سعر الوصول بنقاط الأساس؛ والرقم الموجب كلفة.
التالي: تنفيذ VWAP، اتباع حجم اليوم بدل الساعة.
المختبر: ما الذي يُقيَّم
- حدّد حجم كل جزء باستخدام
twap_schedule25 نقطة - وزّع الأجزاء زمنيًا باستخدام
slice_times25 نقطة - احسب متوسط تنفيذاتك باستخدام
average_fill_price25 نقطة - سعّر الكلفة باستخدام
slippage_bps25 نقطة