الضغط قبل أن يتحرك السعر
نادرًا ما يتحرك السعر وحده؛ فهو يتحرك لأن أوامر تصل وتسحب سيولة. والنظر إلى الصفقات وحدها يفوّت معظم القصة، فمعظم الحدث أوامر توضع وتُلغى في قمة الدفتر. واختلال تدفق الأوامر (OFI) يلخص كل تحديث لأفضل سعري الشراء والبيع في رقم واحد بإشارة: موجب حين يضيف المشترون أو يتراجع البائعون وسالب في الحالة المعاكسة. وعلى الآفاق القصيرة يتنبأ بتغيرات السعر التالية بدقة لافتة.
def book_imbalance(bid_size, ask_size):
total = bid_size + ask_size
if total == 0:
return 0.0
return (bid_size - ask_size) / total
print(book_imbalance(450, 200)) # 0.3846 more size on the bid: upward pressure
print(book_imbalance(100, 400)) # -0.6 sellers dominate
print(book_imbalance(0, 0)) # 0.0 an empty top does not divide by zeroتحديث واحد، رقم واحد
قارن بين سعرين متتاليين. في جانب الشراء: إذا ارتفع السعر أو بقي يُضاف حجمه الجديد؛ وإذا انخفض أو بقي يُطرح حجمه القديم (وعند بقاء السعر يتحقق الاثنان فيُحتسب تغير الحجم). وفي جانب البيع تنعكس الإشارات: سعر بيع أدنى أو مساوٍ يضيف ضغط بيع (- حجم البيع الجديد) وسعر أعلى أو مساوٍ يزيل القديم (+ حجم البيع القديم). والحدود الأربعة هي (b >= pb) * bs - (b <= pb) * pbs - (a <= pa) * as + (a >= pa) * pas حيث كل مقارنة 1 أو 0.
# The order-flow imbalance of one quote update, tried on four changes of the top of the book
def ofi_event(prev, cur):
pb, pbs, pa, pas_ = prev
b, bs, a, as_ = cur
return (b >= pb) * bs - (b <= pb) * pbs - (a <= pa) * as_ + (a >= pa) * pas_
q0 = (99.5, 300, 100.0, 200)
print(ofi_event(q0, (99.5, 450, 100.0, 200))) # 150 more size on the same bid
print(ofi_event(q0, (99.6, 100, 100.0, 200))) # 100 a higher bid: the whole new size counts
print(ofi_event(q0, (99.4, 300, 100.0, 200))) # -300 the bid price fell: the old size is gone
print(ofi_event(q0, (99.5, 300, 100.1, 200))) # 200 the ask retreated: less selling pressureسلسلة وما تفسره
تطبيق القاعدة على كل زوج متتالٍ من الأسعار يعطي سلسلة OFI. وعلى نافذة قصيرة، كثانية، اجمع الأحداث وقارنها بتغير السعر الوسطي: العلاقة قريبة من الخطية d_mid = slope * OFI. والميل يقول كم يحرك كل وحدة تدفق من السعر، ويصغر حين يكون الدفتر عميقًا. ويُضبط كخط مربعات صغرى يمر بالأصل: sum(ofi * dmid) / sum(ofi ** 2).
# A series of events from consecutive quotes, set beside the change of the mid price
import numpy as np
def ofi_event(prev, cur):
pb, pbs, pa, pas_ = prev
b, bs, a, as_ = cur
return (b >= pb) * bs - (b <= pb) * pbs - (a <= pa) * as_ + (a >= pa) * pas_
quotes = [(99.5, 300, 100.0, 200), (99.5, 450, 100.0, 200), (99.6, 100, 100.0, 150), (99.6, 100, 100.1, 400)]
events = np.array([ofi_event(prev, cur) for prev, cur in zip(quotes, quotes[1:])], dtype=float)
print(events) # [150. 150. 150.]
mid = np.array([(q[0] + q[2]) / 2 for q in quotes])
dmid = np.diff(mid)
print(np.round(dmid, 3)) # the mid moved in the direction of the flow# Recover the price impact of the flow with a slope through the origin
import numpy as np
def impact_slope(ofi, dmid):
return float(np.sum(ofi * dmid) / np.sum(ofi ** 2))
rng = np.random.default_rng(1)
flow = rng.normal(0, 200, size=500) # order-flow imbalance per second
price_change = 0.00005 * flow + rng.normal(0, 0.004, size=500) # the mid moves with the flow, plus noise
print(round(impact_slope(flow, price_change), 6)) # 5e-05, the true impact per unit of flow- اختلال الدفتر
(bid - ask) / (bid + ask)بين -1 و+1؛ واحمِ الحالة الفارغة. - OFI لتحديث:
(b >= pb) * bs - (b <= pb) * pbs - (a <= pa) * as + (a >= pa) * pas؛ والسلسلة تطبقه على كل زوج متتالٍ. - تغيرات السعر الوسطي قريبة من الخطية في OFI:
slope = sum(ofi * dmid) / sum(ofi**2)؛ وهي إشارة قصيرة الأفق وأنفع ما تكون للتنفيذ.
التالي: الشيفرة غير المتزامنة مع asyncio، التعامل مع تدفقات بيانات كثيرة دون انتظار كل واحد.
المختبر: ما الذي يُقيَّم
- قارن الجانبين باستخدام
book_imbalance25 نقطة - قيّم تحديثًا واحدًا باستخدام
ofi_event25 نقطة - تتبّع الضغط باستمرار باستخدام
ofi_series25 نقطة - سعّر التدفق باستخدام
impact_slope25 نقطة