حيث تتكون الأسعار
السعر على الشاشة نتيجة طابور. ففي البورصة يضع المشترون والبائعون أوامر محددة: «اشترِ 300 عند 99.50» و«بِع 200 عند 100.00». وما لم يُنفَّذ منها يبقى في دفتر الأوامر المحددة: أوامر الشراء (bids) وأفضلها بأعلى سعر، وأوامر البيع (asks) وأفضلها بأدنى سعر. وتقع الصفقة حين يلاقي أمر وارد أمرًا منتظرًا. وكل ما تسميه المواضيع السابقة سعرًا وفارقًا وانزلاقًا وأثرًا يأتي من هذه البنية.
# A side of the book as a dict {price: total size}
bids = {99.5: 300, 99.4: 500, 99.0: 1200}
asks = {100.0: 200, 100.1: 400, 100.5: 800}
best_bid, best_ask = max(bids), min(asks)
print(best_bid, best_ask) # 99.5 100.0
print(round(best_ask - best_bid, 2), (best_bid + best_ask) / 2) # the spread 0.5 and the mid 99.75الفارق والسعر الوسطي والعمق
أفضل سعر شراء وأفضل سعر بيع هما قمة الدفتر. وفرقهما هو الفارق (spread)، ثمن التنفيذ الفوري: فمشترٍ يأخذ سعر البيع ثم يبيع عند سعر الشراء يخسر الفارق. وفي منتصف المسافة السعر الوسطي، المرجع المعتاد للسعر العادل. والعمق هو الحجم المنتظر عند كل مستوى وما بعده، ويقول كم يمكن تداوله قبل أن يتحرك السعر. والجانب الفارغ يعني انعدام سعر فيه فيكون الفارق والوسطي غير معرَّفين: None لا صفر.
def best_bid_ask(bids, asks):
return (max(bids) if bids else None, min(asks) if asks else None)
def spread_and_mid(bids, asks):
bid, ask = best_bid_ask(bids, asks)
if bid is None or ask is None:
return None, None
return ask - bid, (bid + ask) / 2
print(spread_and_mid({99.5: 300, 99.4: 500}, {100.0: 200, 100.1: 400})) # (0.5, 99.75)
print(spread_and_mid({}, {100.0: 200})) # (None, None): one side is emptyإضافة أوامر دون تغيير الماضي
ينضم أمر محدد جديد إلى مستوى سعري قائم فيزيد حجمه الكلي، أو يفتح مستوى جديدًا. وكتابة ذلك دالةً تعيد قاموسًا جديدًا بدل تغيير القديم لها ميزة عملية: تحتفظ بلقطة من الدفتر قبل الأمر، وهذا يفيد الاختبار وإعادة التشغيل. انسخ بـdict(side) وغيّر النسخة وأعدها. والدالة التي تعدّل وسيطها بصمت مصدر أخطاء كلاسيكي عند استخدام الدفتر نفسه في موضعين.
def add_limit_order(side, price, size):
new = dict(side) # a copy: the original book is not touched
new[price] = new.get(price, 0) + size
return new
bids = {99.5: 300, 99.4: 500}
after = add_limit_order(bids, 99.5, 100) # joins the existing level
after = add_limit_order(after, 99.3, 50) # opens a new level
print(after) # {99.5: 400, 99.4: 500, 99.3: 50}
print(bids) # {99.5: 300, 99.4: 500} unchangedمطابقة أمر سوق
يأخذ شراء السوق أوامر البيع من أدنى سعر صعودًا: المستوى الأول كله ثم الثاني حتى يُملأ الأمر أو يفرغ الدفتر. مرّ على الأسعار المرتبة وخذ min(left, size_at_price) عند كل منها وسجّل التنفيذ ثم احذف المستوى (أُخذ كله) أو قلّصه (أُخذ جزء منه). وقائمة التنفيذات تعطي متوسط السعر الذي رأيته في موضوع التنفيذ، والدفتر المتبقي هو السوق بعد صفقتك: أرق في الأعلى وبأفضل سعر بيع أسوأ.
def match_market_buy(asks, size):
remaining = dict(asks)
fills = []
left = size
for price in sorted(asks): # cheapest first
if left <= 0:
break
take = min(left, asks[price])
fills.append((price, take))
left -= take
if take == asks[price]:
del remaining[price] # the level is used up
else:
remaining[price] = asks[price] - take
return fills, remaining
asks = {100.0: 200, 100.1: 400, 100.5: 800}
fills, remaining = match_market_buy(asks, 500)
print(fills) # [(100.0, 200), (100.1, 300)]
print(remaining) # {100.1: 100, 100.5: 800}- كل جانب قاموس
{price: size}: أفضل شراءmax(bids)وأفضل بيعmin(asks)؛ والفارقask - bidوالوسطي متوسطهما، وكلاهماNoneإذا فرغ جانب. - أضف أمرًا بنسخ القاموس والإضافة عند السعر وإعادة النسخة؛ ويبقى الدفتر الممرَّر دون تغيير.
- يمر شراء السوق على أوامر البيع المرتبة آخذًا
min(left, size)من كل مستوى ويحذف المستنفد ويقلّص المأخوذ جزئيًا؛ وللدفاتر الحقيقية طابور عند كل مستوى.
التالي: اختلال تدفق الأوامر، قراءة الضغط في الدفتر لتوقع حركة السعر التالية.
المختبر: ما الذي يُقيَّم
- اعثر على قمة الدفتر باستخدام
best_bid_ask25 نقطة - قِس الفجوة باستخدام
spread_and_mid25 نقطة - ضع أمرًا في الدفتر باستخدام
add_limit_order25 نقطة - التهم عروض البيع باستخدام
match_market_buy25 نقطة