این هم یک کندل. پرپچوال USDⓈ-M بایننس، BTCUSDT، دقیقهای که از ساعت 13:46:00 UTC آغاز میشود. این کندل از اندپوینت klines بهصورت یک آرایهٔ ساده از دوازده مقدار بیرون آمده و رایجترین واحد ورودی در پژوهشهای کوانت خرد و نزدیک به آن است. تقریباً هر استراتژیای که تولید میکنیم با چیزی به همین شکل سروکار دارد.
پس بیایید آن را ایندکس به ایندکس تکهتکه کنیم و ببینیم بکتست چقدر از آن را اشتباه میفهمد.
| ایندکس | مقدار | نام |
|---|---|---|
| 0 | 1773495960000 | زمان باز شدن (میلیثانیه) |
| 1 | "84120.50" | بازشدن |
| 2 | "84177.90" | بیشینه |
| 3 | "84098.10" | کمینه |
| 4 | "84163.40" | بستهشدن |
| 5 | "38.417" | حجم پایه (BTC) |
| 6 | 1773496019999 | زمان بسته شدن (میلیثانیه) |
| 7 | "3232108.94" | حجم مظنه (USDT) |
| 8 | 1204 | تعداد معاملات |
| 9 | "21.883" | حجم پایهٔ خرید تیکر |
| 10 | "1841203.55" | حجم مظنهٔ خرید تیکر |
| 11 | "0" | نادیده (ignore) |
[0] و [6]: این کدام دقیقه است، و ساعت چه کسی این را تعیین میکند
زمان باز شدن 1773495960000، زمان بسته شدن 1773496019999. به دومی دقت کنید: با 19999 تمام میشود، یعنی درست یک میلیثانیه کمتر از باز شدن کندل بعدی. این بازه نیمهباز است و صرافی این را صراحتاً به شما میگوید. نیمی از باگهای دادهای که تا امروز دنبال کردهام از جایی شروع شدهاند که کسی هر دو انتهای بازه را شاملِ خودشان فرض کرده و یک معامله در مرز را دوبار شمرده، یا یک ریسمپل را طوری تراز کرده که هر کندل ۵ دقیقهای یک میلیثانیه از همسایهاش قرض گرفته است.
این تایماستمپها زمانِ موتور تطبیق (matching engine) صرافی هستند. نه ساعت شما، نه ساعت اینجستِ فروشندهی داده، و نه همان ساعتی که اسنپشات فاندینگ از آن استفاده میکند. وقتی یک سری kline را با یک سری نرخ فاندینگ یا یک سری بهرهی باز که از اندپوینت دیگری کشیدهاید جوین میزنید، دارید منابعی را به هم متصل میکنید که اغلب اوقات تنها در حدود یک ثانیه با هم توافق دارند. برای یک استراتژی ۱ دقیقهای، این یعنی خطای زمانبندیِ 1.7% در هر ردیف جوینشده. برای هر چیز زیرِ یک دقیقه، مهلک است.
و این کندل با زمانِ باز شدن خود مهر خورده است. اطلاعات داخل آن تا 13:46:59.999 قابل دانستن نیست. هر پرسش دربارهٔ قرارداد ایندکسگذاری — آیا یک واحد شیفت بدهم، آیا از تایماستمپ بستهشدن یا باز شدنِ کندل بهعنوان زمان رویداد استفاده کنم — در واقع همان پرسشِ لوکاِهد است که لباس عوض کرده.
[1] «84120.50»: بازشدنی که نمیتوان معاملهاش کرد
بازشدن، قیمت اولین معاملهای است که در داخل این بازه ثبت شده. این یک تراکنشِ تکمیلشده بین دو نفر دیگر است. تا زمانی که این کندل بهصورت یک ردیف در دیتافریم شما ظاهر شود، آن قیمت شصت ثانیه قدمت دارد.
کندلِ قبلی در 84109.80 بسته شده بود، پس $10.70 حرکتِ قیمتی در شکاف میانِ دو کندلِ ۱ دقیقهایِ مجاور نشسته است. این یعنی 1.3 بیسیسپوینت، روی چارت نامرئی، و تقریباً یکچهارمِ کارمزد تیکر. خب، مشکلی نیست. اما همین سری را روی یک آلتکوین پرپچوال در زمان انتشار CPI آمریکا نگاه کنید؛ آنوقت این شکافهای بینکندلی به 15 تا 40 بیسیسپوینت میرسند. بکتستی که با بستهشدنِ کندل سیگنال میدهد و در باز شدنِ کندلِ بعدی پر میشود، بیسروصدا فرض میکند این شکاف صفر است — و دقیقاً وقتی صفر است که اهمیتی ندارد.
[2] و [3]: «84177.90» بیشینه، «84098.10» کمینه
این دو، جایی است که اکثر موتورهای پر کردنِ سفارش (fill engine) جان میدهند، پس این بخش طولانیترین است.
بیشینه و کمینه، قیمتهای لمسشده هستند. نه اندازهای دارند و نه مدتی. از روی تیپ خامِ معاملات برای همین دقیقه، هرچه در 84100.00 یا پایینتر معامله شده، 0.62 BTC در نُه پرینت و در یک بازهٔ 1.4 ثانیهای بوده است. پس یک استاپِ بکتست در 84100 «پر میشود» — و اگر پوزیشن شما 3 BTC باشد، در مسیر نزول کل بوکِ قابلمشاهده را خوردهاید و بقیهٔ اندازهٔ سفارشتان جایی در بازگشتِ 84105–84130 پر شده است. کندل میگوید کمینه 84098.10 بوده. کندل نمیگوید که تنها $52,000 نوسیونال در آنجا معامله شده است.
جهتِ دیگر بدتر است، چون به نفعِ شما تمام میشود. فرض کنید یک سفارشِ لیمیتِ فروشِ منتظر در 84175 داشتهاید. بیشینهٔ کندل 84177.90 است، پس یک موتورِ سادهلوح شما را در 84175 پر میکند و بهجای هزینهٔ تیکر، ریبیتِ میکر ثبت میکند. اینکه آن پرشدن واقعاً اتفاق افتاده یا نه، به موقعیتِ صف (queue position) در آن سطحِ قیمتی بستگی دارد، چیزی که کندل نمیتواند بداند و شما احتمالاً هرگز ثبتش نکردهاید. لمسشدن بهمعنای پر شدن نیست.
قاعدهای که در موتورِ پر کردنِ سفارشمان روی آن به توافق رسیدیم: یک سفارشِ لیمیتِ منتظر تنها زمانی پر میشود که کندل از آن سطح عبور کند، نه اینکه صرفاً به آن برسد. پر شدن در دقیقاً همان تیکِ اکستریم نیازمندِ شواهدِ حجم-در-قیمت از تیپ معاملات است، وگرنه رد میشود. این قاعده حدود 6% از معاملاتِ یک بوکِ بازگشتبهمیانگینِ معمولی را حذف کرد و شارپِ بکتستشدهٔ یکی از کاندیداها را از 1.9 به 1.1 رساند. آن کاندیدا هرگز واقعی نبود؛ قاعدهٔ پر شدن تنها اولین چیزی بود که آنقدر صادق بود این را بگوید.
تلهای مرتبط: مسیرِ درونکندلی. اگر بازهٔ یک کندل هم استاپ و هم تیکپرافیتِ شما را دربربگیرد، OHLCV نمیتواند بگوید کدامیک زودتر اتفاق افتاده. هر موتوری باید یک قرارداد انتخاب کند. موتورِ ما همیشه استاپ-اول را فرض میکند، که بدبینانه است و گاهی اشتباه، اما هرگز یک بردِ ساختگی تولید نمیکند. اگر موتورِ شما تیکپرافیت-اول را فرض کند، کندلهای با بازهٔ گسترده از دلِ ابهام سود میسازند، و کندلهای با بازهٔ گسترده دقیقاً همانهایی هستند که بر توزیعِ PnL شما مسلط میشوند.
[4] «84163.40»: غیرقابلاتکاترین عددِ کندل
بستهشدن، آخرین پرینتِ داخلِ بازه است. همین. ممکن است یک اُدلاتِ 0.002 BTC باشد که یک بات در 13:46:59.8 برای بستنِ یک پوزیشن ثبت کرده. همین یک تیکِ ساختاراً دلبخواهی، چیزی است که اکثر پایپلاینهای پژوهشی برای محاسبهٔ هر سیگنال، مارککردنِ هر پوزیشن، و ارزیابیِ هر خروج از آن استفاده میکنند.
روی پرپچوالهای BTC تقریباً اهمیتی ندارد؛ روی یک پرپچوالِ کمعمقِ آلتکوین در 04:00 UTC اهمیتِ فراوانی دارد، و تفاوتِ بینِ بستهشدنِ دو صرافی برای همان دقیقه میتواند از کلِ ادجِ هر معاملهٔ شما بیشتر شود. وقتی PnL یک استراتژی بهطور مشخص به بستهشدن وابسته است، ما آن را دوباره با مارککردن روی قیمتِ مارکِ صرافی اجرا میکنیم که برگرفته از ایندکس است و دستکاریاش بسیار سختتر است. اگر نتایج واگرا شوند، یعنی استراتژی داشته آرتیفکت را معامله میکرده، نه بازار را.
[5] و [7]: «38.417» و «3232108.94»، حجم بر حسبِ چه واحدی
حجم پایه BTC است؛ حجم مظنه USDT است. هر دو اینجا هستند، که لطفی است که همهٔ صرافیها آن را نمیکنند. دلیلِ اهمیتدادن به این، تجمیع در میانِ چند صرافی است. قراردادهای کوین-مارجینشده بر حسبِ قراردادهایی با نوسیونالِ $100 مظنه میشوند. برخی فیدهای سهام، لاتهای گرد گزارش میکنند. صرافیهای بازارِ پیشبینی، تعدادِ شیر (share) گزارش میکنند که هر شیر یک ادعای دوجملهایِ دلاری است. جمعکردنِ «حجم» در میانِ یک جهانِ مختلط بدونِ نرمالسازی به یک واحدِ نوسیونالِ واحد، یک رتبهبندیِ نقدشوندگی تولید میکند که خالصاً بیمعناست، و بهشکلی پایدارنما بیمعنا خواهد بود که از بازبینی هم جان سالم بهدر میبرد.
همهچیز را در زمانِ اینجست به نوسیونالِ مظنه نرمال کنید. فیلدِ خام را هم ذخیره کنید، اما هرگز نگذارید یک استراتژی آن را ببیند.
[8] 1204: فیلدی که باید مدلِ ایمپکتِ شما را تعیین کند
تقسیمِ حجم پایه بر تعداد معاملات، میانگینِ پرینتی معادلِ 0.032 BTC، حدودِ $2,700، میدهد. اگر استراتژیِ کاندیدای شما بخواهد $250,000 را یکجا وارد کند، در واقع دارد تقاضا میکند که تقریباً 92 برابرِ اندازهٔ معاملهٔ معمولِ آن دقیقه باشد. این عدد — نه یک ثابتِ اسلیپیجِ عمومیِ 5-بیسیسپوینتی — چیزی است که باید ترمِ ایمپکتِ ریشهدومِ شما را هدایت کند. ما نرخِ مشارکت را بهازای هر کندل بهعنوان یک ستونِ درجهیک محاسبه میکنیم و استراتژیهایی را که میانهٔ ورودشان از چند درصدِ نوسیونالِ کندل فراتر میرود رد میکنیم، چون هر چیزی پاییندستِ آن، داستان است.
تعدادِ معاملات همچنین دقیقههای عجیب را بهسادگی و ارزانی نشان میدهد. حجم عادی، اما تعدادِ معاملات به 11 فروریخته؟ یعنی کسی یک بلاک زده. حجم عادی، اما تعدادِ معاملات 9,000؟ یعنی یک آبشارِ لیکوییدیشن در حالِ جویدهشدن در قطعاتِ ریز است.
[9] و [10]: «21.883»، فیلدی که همه دور میریزند
حجمِ پایهٔ خریدِ تیکر. از 38.417 BTC این کندل، 21.883 توسط خریدار آغاز شده، پس دلتای حجمِ علامتدار +5.349 BTC است و تقسیمِ تهاجم 57/43 به نفعِ خریداران است. صرافی دارد عدمِ توازنِ جریانِ سفارش (order flow imbalance) را مجانی به شما تحویل میدهد، در فیلدی که اکثرِ آدمها هرگز نمیخوانند چون pandas ستون را برایشان نامگذاری نکرده.
ادعا نمیکنم که این بهتنهایی بازده را پیشبینی میکند؛ استراتژیهای سادهٔ مبتنی بر دلتا از قابلاعتمادترین راهها برای اهدا به دفترِ کارمزد هستند. اما این واقعاً یک سنجشِ متفاوت از قیمت است، در همان درخواستی در دسترس است که از قبل انجام میدادید، و به شما اجازه میدهد یک رالیِ خریداریشده را از رالیِ ناشی از کنارکشیدنِ فروشندگان تشخیص دهید. این دو در OHLC یکسان بهنظر میرسند اما ده دقیقه بعد متفاوت رفتار میکنند. ایجنتِ پژوهشیِ ما هر فرضیهای که تقسیمِ تیکر را در صرافیای که آن را منتشر میکند نادیده بگیرد، معادلِ برجاگذاشتنِ شواهد روی میز میداند.
[11] «0»: نادیده — و هر چیزِ دیگری که اینجا نیست
ایندکس 11 یک فیلدِ منسوخشده است، همیشه صفر. جالبتر، فهرستِ چیزهایی است که این کندل شاملِ آنها نیست: نه بید، نه اَسک، نه اسپرد، نه عمقِ بوک، نه نرخِ فاندینگ، نه بهرهٔ باز، نه لیکوییدیشن، نه قیمتِ مارک، نه قیمتِ ایندکس. و از همه مهمتر، هیچ راهی برای دانستنِ اینکه آیا سفارشِ شما میکر بوده یا تیکر، که در این صرافی تفاوتِ بینِ پرداختِ 0.045% و کسبِ 0.01% است.
پس هر مدلِ کارمزدی که تنها بر پایهٔ klines ساخته شده، فرضی است که لباسِ یک عدد را پوشیده. ما این را با اجبارِ هر استراتژی به اعلامِ سبکِ اجرایش از پیش حل میکنیم، و سپس روی هر چیزی که خلافِ آن را ثابت نکند، هزینهٔ تیکر اعمال میکنیم.
کندلی که هرگز ظاهر نشد
آخرین قطعه، و همانی که بیرون از میجرها بیشترین گاز را میگیرد. دقیقهای با صفر معامله، اصلاً هیچ kline تولید نمیکند. فروشندگانِ داده و کتابخانهها معمولاً آن را forward-fill میکنند: open = high = low = close = بستهشدنِ قبلی، حجم 0. اندیکاتورِ شما با خوشحالی محاسبه میکند. استراتژیِ شما یک ردیفِ معتبر میبیند و میتواند در دقیقهای که هیچکس در دنیا آن ابزار را معامله نکرده، سیگنال تولید کند.
روی یک پرپچوالِ mid-cap که اینجست کردیم، 4.1% از کندلهای ۱ دقیقهای در یک بازهٔ دوازدهماهه صفر معامله داشتند. یک کاندیدای بازگشتبهمیانگین روی آن نماد، 38% از ورودیهایش را روی کندلهای synthetic قرار میداد، چون قیمتهای ثابتِ synthetic برای هر چیزی که انحراف از میانگینِ متحرک را میسنجد، طعمهای وسوسهانگیز است. بکتستِ آن فوقالعاده بود. اما داشت شکافهای داده را معامله میکرد.
به همین دلیل، لایهٔ اینجست اکنون یک بولینِ synthetic را بهازای هر کندل حمل میکند، که در هر ریسمپل منتقل میشود، و گانتلتِ راستیآزمایی هر استراتژیای را که معاملاتش روی آن خوشهبندی شوند رد میکند. یک ستونِ ارزان. این ستون بیشتر از هر اندیکاتوری که تا بهحال نوشتهایم، کاندیدا کشته است.
دوازده مقدار. چهارتای آنها بهطور روتین اشتباه خوانده میشوند، دوتای آنها بهطور روتین دور ریخته میشوند، و یک دستهٔ کامل از آنها اصلاً در ردیف حضور ندارند و توسط موتور تصور میشوند. پیش از بکتستِ بعدیِ خود، بروید یک کندلِ خام از استورِ خودتان بیرون بکشید و هر فیلد را با صدای بلند در برابرِ آنچه منطقِ پر کردنِ سفارشِ شما دربارهٔ آن فرض میکند، بخوانید. این یک تمرینِ بیستدقیقهای است و من هرگز کسی را ندیدهام که آن را انجام دهد و چیزی پیدا نکند.
← همهٔ مطالب