Trade Classification When You Have No Side Flag: Tick, Quote, Lee-Ready, BVC
لنبدأ باستبعاد النسخة من هذه المشكلة التي تفتتح بها معظم كتب البنية المجهرية. في المنصات التي يتداول فيها هذا المدونة فعليا، اتجاه الصفقة ليس شيئا تستنتجه — بل هو حقل جاهز. ترسل aggTrades في Binance الحقل isBuyerMaker، ويرسل تدفق الصفقات في Bybit الحقل side. تأخذ مقالة بناء الأشرطة جانب المبادر مباشرة من تلك الإشارة ولا تشغل أي مصنف:
def on_trade(self, timestamp: int, price: float, qty: float, is_buyer_maker: bool):
side = -1 if is_buyer_maker else 1
هذه هي الحقيقة الأرضية، مجانا، وبدقة نانوثانية. إذا كنت تبني تدفق أوامر موقعا من تغذية CEX حديثة للعملات المشفرة، فلا تحتاج إلى هذه المقالة.
تحتاج إليها في أربع حالات، وهي شائعة بما يكفي لتهمك:
- لا تملك إلا الأشرطة. إن OHLCV التاريخي هو ما يبيعه معظم الموردين وما تعتمد عليه معظم الاختبارات الخلفية؛ فلم تتم أرشفة تدفق الصفقات، أو أن تكلفته أكبر من قيمة البحث. هذه هي مشكلة BVC أدناه.
- المنصة لا تحدد الاتجاه. تمنحك منصات CEX الأصغر، ومجموعات بيانات TAQ للأسهم، وتقريبا جميع الأرشيفات السابقة لعام 2017 السعر والحجم فقط.
- التدفق عبر DEX والسلسلة. يوضح حدث المبادلة أي رمز تحرك وفي أي اتجاه، لكن إسناد المبادرة عبر صفقة متعددة القفزات وموجهة، أو عبر مجمع، لا يترك إشارة يمكن الرجوع إليها.
- تكرر بحثا منشورا. تقديرات لامبدا لكايل، وVPIN، وتحليلات تفكيك السبريد — تكاد تكون جميع أدبيات البنية المجهرية مبنية على بيانات مصنفة بطريقة Lee-Ready، لذا تتطلب قابلية المقارنة تشغيل مصنفهم بدلا من قراءة الاتجاه الحقيقي.
فيما يلي مجموعة القواعد، ونمط الفشل الأهم، والاختصار القائم على الحجم المجمع، وبروتوكول لقياس كل ذلك — لأن العملات المشفرة تمنحك التسميات التي لم تكن أدبيات الأسهم تملكها قط.
اختبار التغير السعري

قارن سعر الصفقة الحالية بالسعر السابق:
الفكرة هي أن الصفقات التي يبدأها المشترون ترفع سعر العرض وتدفع السعر إلى الأعلى؛ أما الصفقات التي يبدأها البائعون فتضرب سعر الطلب وتدفعه إلى الأسفل. تحمل التغيرات الصفرية الإشارة السابقة إلى الأمام، وهنا يكمن معظم الخطأ — ففي أداة سائلة ذات سبريد مقداره تغير سعري واحد، تطبع غالبية الصفقات المتتالية بالسعر نفسه.
sign = np.sign(np.diff(prices, prepend=prices[0]))
sign[sign == 0] = np.nan
directions = pd.Series(sign).ffill().fillna(1).astype(int).values
يوجد بالفعل تطبيق إنتاجي بالمعنى نفسه لحمل الإشارة إلى الأمام باسم _tick_sign() داخل مولدات أشرطة اختلال التغير السعري والحجم في ما وراء الأشرطة الزمنية — أعد استخدامه بدلا من صيانة نسخة ثانية.
قاعدة الأسعار المعروضة

بدلا من مقارنة الصفقات المتتالية، قارن سعر الصفقة بنقطة المنتصف السائدة :
def quote_rule(trade_prices: np.ndarray,
bid_prices: np.ndarray,
ask_prices: np.ndarray) -> np.ndarray:
"""Classify trades using the quote rule.
Returns +1 (buy), -1 (sell), or 0 (unclassified) for midpoint trades.
"""
midpoints = (bid_prices + ask_prices) / 2.0
directions = np.zeros(len(trade_prices), dtype=int)
directions[trade_prices > midpoints] = 1
directions[trade_prices < midpoints] = -1
return directions
تستخدم قاعدة الأسعار المعروضة معلومات أكثر بوضوح من اختبار التغير السعري، لكنها تفشل في موضع مختلف: فهي لا تقول شيئا عن الصفقات التي تطبع عند المنتصف تماما. وفي أداة ذات سبريد ضيق ومقيدة بتغيرات السعر، ليست هذه حالة نادرة — بل هي حصة كبيرة من سجل الصفقات. تحتاج القاعدة إلى آلية رجوع، وهذا هو دور Lee-Ready.
Lee-Ready: النهج الهجين وافتراض تأخر الأسعار المعروضة

جمع Lee وReady (1991) الطريقتين: طبقا قاعدة الأسعار المعروضة، وعند الصفقات الواقعة عند نقطة المنتصف عادا إلى اختبار التغير السعري.
def lee_ready(trade_prices: np.ndarray,
bid_prices: np.ndarray,
ask_prices: np.ndarray,
quote_lag: int = 0) -> np.ndarray:
"""Lee-Ready classification.
quote_lag : number of quote observations to lag (0 = contemporaneous).
Set > 0 only if your quote feed is known to trail the trade
feed; see the discussion of the 5-second rule below.
Depends on tick_test() with carry-forward semantics — the `_tick_sign()`
implementation from /blog/beyond-time-bars-candle-construction.
"""
if quote_lag > 0:
bid = np.concatenate([bid_prices[:quote_lag], bid_prices[:-quote_lag]])
ask = np.concatenate([ask_prices[:quote_lag], ask_prices[:-quote_lag]])
else:
bid, ask = bid_prices, ask_prices
directions = quote_rule(trade_prices, bid, ask)
unclassified = directions == 0
if unclassified.any():
directions[unclassified] = tick_test(trade_prices)[unclassified]
return directions
الجزء الأكثر إثارة للاهتمام في الورقة هو ما يعاد نقله عادة من دون مبرره. لم يقترح Lee وReady مجرد نهج هجين؛ بل اقترحا مقارنة كل صفقة بالسعر المعروض السائد قبل خمس ثوان. هذا الرقم ليس خيارا نمذجا يتعلق بالأسواق. إنه تصحيح لأثر في الإبلاغ: ففي شريط NYSE الموحد خلال ثمانينيات القرن الماضي، كانت الصفقات والأسعار المعروضة تنشر عبر مسارات مختلفة وبحالات تأخر مختلفة، وكانت الأسعار المعروضة تميل إلى الوصول قبل الصفقات التي تخصها. لذلك كانت مقارنة الصفقة بالسعر المعروض المطبوع في اللحظة نفسها تقارنها بسعر يعكسها بالفعل — فتعكس الإشارة تحديدا في الصفقات التي حركت السوق.
ينتج عن ذلك أمران لكل من ينقل هذه الطريقة إلى العملات المشفرة.
قاعدة الخمس ثوان أصبحت قديمة، وتطبيقها ضار فعليا. فهي تصحح تأخرا في النشر لم يعد موجودا. سدّت أسواق الأسهم الفجوة بحلول منتصف التسعينيات؛ أما تدفقات الصفقات وتغيرات دفتر الأوامر في منصة عملات مشفرة فتشترك في فضاء تسلسلي واحد، وتختمها آلة المطابقة نفسها بدقة الميكروثانية. خمس ثوان على BTCUSDT ليست هامش تزامن، بل مئات من تحديثات الدفتر. تأخير الأسعار المعروضة خمس ثوان لا يصلح الأسعار القديمة، بل يصنعها. استخدم quote_lag=0 ما لم تقس إزاحة حقيقية في عملية الالتقاط الخاصة بك.
لكن المشكلة الأساسية باقية في صورة جديدة. أصبح تقادم الأسعار المعروضة الآن خطأ في خط أنابيبك، لا خطأ في المنصة. أعد بناء الدفتر من تدفق تغييرات WebSocket، واربط الصفقات بحسب الطابع الزمني لساعة النظام، وستكون قد أدخلت تأخرا يعتمد اتجاهه وحجمه على التخزين المؤقت لديك، وسياسة إعادة مزامنة اللقطة، وساعتك. قسه قبل الوثوق بأي مصنف يقرأ الدفتر: أعد تشغيل نافذة، واربط الصفقات بحالة الدفتر، وتحقق من نسبة الصفقات التي تقع خارج [bid, ask] المرتبط. إن طباعة صفقة فوق سعر الطلب المرتبط تثبت أن الدفتر الذي ربطتها به كان قديما.
الإصلاح مختلف عن إصلاح Lee وReady؛ أما نمط الفشل فمتطابق.
تصنيف الحجم المجمع

يهاجم Easley وLopez de Prado وO'Hara (2012) مشكلة مختلفة: ماذا لو لم تر الصفقات الفردية قط؟ يصنف BVC الحجم المجمع داخل الشريط، ويقسمه باستخدام دالة التوزيع التراكمية للتوزيع الطبيعي المعياري لتغير السعر:
حيث إن و هو الانحراف المعياري لتغيرات سعر الشريط.
from scipy.stats import norm
def bulk_volume_classification(open_prices: np.ndarray,
close_prices: np.ndarray,
volumes: np.ndarray,
sigma: float | None = None) -> tuple:
"""Split bar volume into buy/sell using the BVC rule.
Returns (buy_volume, sell_volume) as continuous, not integer, splits.
"""
delta_p = close_prices - open_prices
if sigma is None:
sigma = max(float(np.std(delta_p)), 1e-8)
buy_fraction = norm.cdf(delta_p / sigma)
return volumes * buy_fraction, volumes * (1.0 - buy_fraction)
BVC أداة مختلفة، وليست أداة أسوأ، وتوضح ذلك ثلاث خصائص. فهو لا يصنف الصفقات — بل ينتج كسرًا، لذلك يحصل الشريط الذي يغلق دون تغير على تقسيم 50/50 بدلا من قلب عملة لكل صفقة فيه؛ وإذا كان المستهلك يحتاج إلى إشارة لكل صفقة، فلا يستطيع BVC توفيرها. وهو يعمل على الأشرطة لا على سجل الصفقات، وهذه هي الفكرة بأكملها: إنه الطريقة الوحيدة هنا التي تعمل عندما لا توجد بيانات على مستوى الصفقات. كما أن معلمة حرة تحرك النتائج — فتقدير العينة الكاملة تسرب للمستقبل، بينما يجعل التقدير المتحرك القصير تتشبع ويدفع كل شريط نحو 0/100. يستحق هذا الاختيار مسحا للحساسية، لا قيمة افتراضية.
تفيد الورقة الأصلية بأن BVC يتتبع التصنيف على مستوى الصفقات بما يكفي لتقدير VPIN. وتعتمد أرقام الدقة المحددة كثيرا على حجم الشريط والأداة ومقدر ، ونادرا ما تذكر الأرقام المنقولة في المصادر الثانوية العناصر الثلاثة كلها. لا تنقل رقما من دراسة عن الأسهم — بل قسه.
الميزة الوحيدة الخاصة بالتصنيف

إذا دربت نموذجا على تنفيذ ذلك بدلا من تشغيل قاعدة، فإن معظم مجموعة الميزات منشورة بالفعل: السبريد، واختلال العمق، واختلال تدفق الصفقات، والتقلب المحقق المتحرك مجدولة ومطبقة في نمذجة السبريد بالتعلم الآلي، وميزات دفتر الأوامر متعدد المستويات موجودة في DeepLOB. استخدم هذه الميزات.
الميزة الخاصة بهذه المشكلة، والتي لا تظهر في أي منهما، هي موضع الصفقة داخل السبريد:
تتحول الصفقة عند سعر العرض إلى 0، وعند سعر الطلب إلى 1، وعند المنتصف إلى 0.5. وهذا هو التعميم المستمر لقاعدة الأسعار المعروضة: فبدلا من وضع عتبة عند المنتصف، يمنح النموذج المسافة، كي يتعلم أن صفقة عند 0.95 تكاد تكون مؤكدة الشراء، بينما صفقة عند 0.55 بالكاد تحمل معلومات. القيم خارج هي أداة تشخيص تقادم الأسعار المعروضة من القسم السابق، ويستحق تسجيلها بدلا من قصها.
بالنسبة إلى خط أساس معزز فوق هذه الميزات، فإن خط الأنابيب هو المنشور بالفعل في نمذجة السبريد بالتعلم الآلي، مع تبديل الهدف إلى ثنائي — بما في ذلك، وهو أمر مهم، التحقق المتقاطع المتحرك إلى الأمام مع التطهير وفترة الحظر. لا تقيّم مصنف بنية مجهرية باستخدام TimeSeriesSplit عادي؛ تشرح تلك المقالة والتحسين المتحرك إلى الأمام معا سبب تفاؤل الرقم الناتج. وبالنسبة إلى نماذج التسلسل فوق لقطات الدفتر، فإن DeepLOB هو البنية المرجعية، وينطبق قسم اعتبارات التدريب فيه دون تغيير.
القياس السليم: العملات المشفرة تمنحك التسميات

كل رقم دقة في أدبيات تصنيف صفقات الأسهم مقدر مقابل بديل للحقيقة — TORQ، أو عينات فرعية من مسار التدقيق، أو سجلات داخلية لمنصة واحدة — لأن جانب المبادر الحقيقي لم ينشر قط. أما العملات المشفرة فتنشره. إن isBuyerMaker في aggTrades لدى Binance هو التسمية لكل صفقة، بلا تكلفة. ولذلك فالتجربة متاحة لأي شخص، ولم أجدها منشورة للعملات المشفرة. البروتوكول:
البيانات. شهر واحد من aggTrades لـ BTCUSDT، إضافة إلى تدفق مؤشر الدفتر، مع ربط كل صفقة بحيث تحمل سعري العرض والطلب السائدين مباشرة قبلها. كرر ذلك على عملة بديلة قليلة السيولة، لأن السؤال بأكمله هو ما يحدث عندما يكون السبريد واسعا.
التسمية. direction = -1 if is_buyer_maker else +1. ثم تخلص من الإشارة وشغّل كل طريقة على بيانات السعر والأسعار المعروضة وحدها.
الطرق. اختبار التغير السعري، وقاعدة الأسعار المعروضة، وLee-Ready عند quote_lag=0، وLee-Ready عند التأخر التاريخي البالغ خمس ثوان (لقياس الضرر الذي تسببه القاعدة القديمة)، وBVC عند عدة أحجام للأشرطة مقابل كسر حجم الشراء الحقيقي لكل شريط.
التقسيمات. الدقة الإجمالية أقل الأرقام إثارة للاهتمام. قسّم النتائج حسب نظام السبريد (تغير سعري واحد مقابل سبريد واسع — ينبغي أن ينهار حمل اختبار التغير السعري إلى الأمام حيث يكون السبريد مثبتا)، وحسب ما إذا كانت الصفقة قد طبعت عند المنتصف (لعزل موضع امتناع قاعدة الأسعار المعروضة وموضع قيام آلية رجوع Lee-Ready بكل العمل)، وحسب حجم الصفقة (إن عناصر aggTrades هي عمليات مسح؛ الصفقات الكبيرة تقود التأثير وتكلف أكثر عند توقيعها خطأ)، وحسب نظام التقلب (فـ في BVC مقدرة، ولذلك فإن خطأه يعتمد على النظام بحكم البناء).
| الطريقة | الإجمالي | عند المنتصف | سبريد ضيق | سبريد واسع | الصفقات الكبيرة |
|---|---|---|---|---|---|
| اختبار التغير السعري | — | — | — | — | — |
| قاعدة الأسعار المعروضة | — | — | — | — | — |
| Lee-Ready (تأخر 0) | — | — | — | — | — |
| Lee-Ready (تأخر 5 ثوان) | — | — | — | — | — |
| BVC (كسر لكل شريط) | — | لا ينطبق | — | — | لا ينطبق |
الدقة ليست الهدف

استعِن بانضباط العرض من DeepLOB: دقة التصنيف ليست نتيجة، بل مدخل إلى نتيجة. لا يتداول أحد إشارة صفقة — بل يتداول شيئا محسوبا من تدفق كامل للإشارات، والأخطاء لا تنتقل إليه بشكل محايد. فمصنف يخطئ عشوائيا في 10% من الصفقات، وآخر يخطئ منهجيا في 10% التي تكون كبيرة وتحرك السعر، ينتجان تدفق أوامر موقعا مختلفا جدا رغم تساوي الدقة. ويرتبط فشل حمل اختبار التغير السعري إلى الأمام تحديدا بالسعر الثابت — وهو النظام الذي يفترض أن يكون فيه التدفق الموقع مفيدا. لذلك فإن التقسيمات السابقة، لا الرقم الرئيسي، هي الناتج الحقيقي.
مع وجود تسميات الحقيقة الأرضية، يمكن قياس السؤال اللاحق مباشرة: قدّر الكمية التي تهمك من الإشارات الحقيقية، وأعد تقديرها من إشارات كل مصنف، ثم أبلغ عن الفجوة.
- تأثير السعر. ارجع تغيرات سعر المنتصف إلى صافي تدفق الأخذ الموقع عبر نوافذ ثابتة — فالمقدر محدد بالفعل في نماذج تكلفة الانزلاق، وتطبيقه العامل على بيانات Binance العامة موجود في تنفيذ Almgren-Chriss الأمثل. لا تعرّفه من جديد هنا؛ شغّله مرتين وقارن المعامل الملائم.
- اختلال تدفق الأوامر كمؤشر. احسب الارتباط بين OFI والعوائد المستقبلية باستخدام الإشارات الحقيقية وباستخدام الإشارات المصنفة. إن التدهور في ذلك الارتباط هو تكلفة سوء التصنيف، مقومة بالوحدة الوحيدة المهمة.
- مقاييس السمية. إن VPIN وميزات اختلال تدفق الصفقات مستهلكان للحجم المصنف؛ وكلاهما مشروح في نمذجة السبريد بالتعلم الآلي. ويمكن قياس حساسيتهما لخطأ التصنيف بالطريقة نفسها.
ما الذي يترتب على ذلك

إذا كانت منصتك ترسل إشارة المبادر، فاستخدمها. وهذا ينطبق على معظم العملات المشفرة معظم الوقت.
إذا لم تفعل، فإن Lee-Ready مع أسعار معروضة متزامنة هو الخيار الافتراضي الصحيح — ليس لأنه دقيق، بل لأنه المعيار، ولأن قابلية المقارنة مع ثلاثة عقود من التقديرات المنشورة تساوي أكثر من بضع نقاط دقة لا يمكنك التحقق منها. قاعدة الخمس ثوان أحفورة من زمن تأخر الشريط في ثمانينيات القرن الماضي؛ وإعادة إنتاجها على بيانات حديثة تدخل التقادم نفسه الذي صممت لإزالته. وإذا لم تملك إلا الأشرطة، فإن BVC هو الخيار الوحيد الذي يعمل على الإطلاق، وهو يجيب عن سؤال مختلف: تقسيم للحجم، لا إشارة صفقة.
في كل حالة، قس النتائج مقابل التسميات التي تمنحها العملات المشفرة مجانا، وقسّمها حسب نظام السبريد وحجم الصفقة، وأبلغ عن تكلفة الخطأ في المراحل اللاحقة بدلا من نسبة ما كان صحيحا.
Authors
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.