שלחת לי בשבוע שעבר את המחברת: אותו אות, אותו יקום נכסים, אותם 14 חודשים של נתוני BTCUSDT perp. השינוי היחיד היה בביצוע. הפסקת לחצות את המרווח והתחלת להציב פקודות קנייה במנוחה, טיק אחד פנימה, וה־Sharpe עלה מ־0.42 ל־2.14. שאלת אם זה אמיתי או שמשהו השתבש.
משהו השתבש. אני רוצה להראות לך בדיוק איפה, כי הבאג הוא שורה אחת, והלקח שמתחתיה גדול בהרבה מהשורה.
הנה כלל המילוי שלך, מועתק מהמנוע:
if bar.low <= limit_price: fill(limit_price)
המשמעות היא: אם בשוק הודפסה עסקה במחיר שלי או מתחתיו במהלך הדקה הזאת, הפקודה שלי התמלאה במחיר שלי. בפועל, מה שהכלל מקודד הוא שהמחיר נגע ברמה שלך. נגיעה אינה מילוי. בין שני הדברים האלה עומד תור, ואת התור מידלת כאילו הוא ריק.
מה עומד לפניך בתור
כשאתה מציב פקודת קנייה ב־84,120.0 בחוזה BTCUSDT perp, אתה מצטרף לסוף התור של כל מה שכבר ממתין שם. ברמה העליונה בספר הפקודות של החוזה הזה, מדובר בדרך כלל בכמות שבין 4 ל־30 BTC, בהתאם לשעה ביום; החציון בחלון המדגם שלך הוא בערך 12 BTC. הפקודה שלך היא 0.4 BTC. כדי שתתבצע עסקה מולך, המוכרים צריכים לפגוע ברמה הזאת בהיקף מצטבר שיספיק כדי לעבור את כל מי שהגיעו לפניך, והם צריכים לעשות זאת לפני שהרמה מתבטלת מתחתיך או שהשוק עולה ומתרחק.
לכן השאלה שבדיקת העבר שלך צריכה לשאול אינה "האם המחיר הגיע ל־84,120.0", אלא "האם בוצעו לפחות 12.4 BTC של מכירות בשוק במחיר 84,120.0 בזמן שהפקודה שלי המתינה שם". אלה אירועים שונים בתכלית. בנתונים שלך, מספר האירועים מהסוג הראשון גדול בערך פי 3.
שלושה כללי מילוי, שלוש אסטרטגיות שונות
הרצתי מחדש את האות שלך עם אותן כניסות ושלושה מודלים לביצוע. אותו אלפא, אותן עמלות, אותו מימון. רק לוגיקת המילוי השתנתה.
| כלל מילוי | מילויים | יתרון ממוצע ל־60 שניות אחרי המילוי | Sharpe |
|---|---|---|---|
נגיעה: low <= limit | 4,180 | +2.6 bp | 2.14 |
חדירה מלאה: low < limit - 1 tick | 1,712 | +0.4 bp | 0.61 |
| סימולציית תור לפי נפח ברמה | 1,306 | +0.9 bp | 0.77 |
השורה האמצעית היא התיקון הגס שכולם מנסים קודם: סופרים מילוי רק אם השוק נסחר ממש מעבר למחיר שלך, מתוך הנחה שאם המחיר עבר אותו, הוא בהכרח מילא את הפקודה שלך. זה בכיוון הנכון ומחסל את רוב האשליה. אבל יש בו גם הטיה רצינית, שאגיע אליה עוד מעט.
השורה התחתונה היא המודל שכדאי לך לבנות. הוא לא דורש פיד L3 ולא שחזור של כל פקודה בנפרד. את רוב מה שצריך כבר יש לך.
סימולציית תור שאפשר לבנות מ־aggTrades
משוך את זרם העסקאות המצטברות במקום נתוני klines. בכל הדפסה יש מחיר, כמות, חותמת זמן ודגל שמציין את צד ה־maker, וכך אפשר לדעת אם התוקפן קנה או מכר. זה מספיק כדי לדמות באופן שימושי את הפקודה הפסיבית שלך:
- כשהפקודה שלך מתפרסמת, תעד תמונת מצב של הכמות הממתינה במחיר שלך. אם יש לך רק פיד ספר פקודות של 100ms, השתמש בתמונת המצב האחרונה; השגיאה קטנה לעומת השגיאה שאתה מתקן.
- הגדר
queue_ahead = resting_size. ננקוט הנחה פסימית ונניח שאתה אחרון בתור. זה המצב, אלא אם אתה זה שיצר את הרמה. - עבור על זרם העסקאות. כל הדפסה שבה התוקפן מוכר, במחיר שלך או מתחתיו, מפחיתה את
queue_aheadלפי הכמות שלה. כשהערך יורד מתחת לאפס, הפקודה שלך מתמלאת במחיר שלך, באותה חותמת זמן. - אם המחיר מתרחק בטיק אחד, אל תאפס את התור. הפחת ממנו חלק בהדרגה. חלק מהפקודות שלפניך מתבטלות כשהרמה מתיישנת, וחלק נשארות. הפחתה של 30-40% לכל שנייה מלאה שבה המחיר אינו ברמה התאימה לשחזורים שלי יותר משתי האפשרויות הקיצוניות.
- אם האסטרטגיה שלך הייתה מבטלת ומפרסמת מחדש, מידל את הפרסום מחדש כפקודה חדשה בסוף התור ברמה החדשה. על השלב הזה אנשים מדלגים, ושם מסתתרת יתרת האשליה.
על שלב 2 תרצה להתווכח איתי. כן, לפעמים אתה קרוב לראש התור, כי פרסמת את הפקודה ברגע שהרמה נוצרה. בסדר — מדוד את זה, אל תניח. תעד את הכמות הממתינה ברגע הפרסום ותן לנתונים להראות לך איזה חלק מהפקודות שלך באמת מגיע מוקדם. באסטרטגיה שלך זה היה 11%, כי האות שלך מופעל אחרי תנועה, כלומר הרמה שאליה אתה מצטרף כבר קיימת וכבר יש עליה קהל.
המילויים שאתה מקבל הם אלה שהיית מעדיף שלא לקבל
ועכשיו החלק שבאמת חשוב, והסיבה שכלל החדירה המלאה מוטה.
חשוב מתי פקודת הקנייה שלך נצרכת במלואה. זה קורה כשלחץ המכירות גדול מספיק כדי לאכול את כל הרמה. מעצם ההגדרה, זה הרגע שבו השוק יורד דרך המחיר שלך. המילויים שאתה יכול להיות בטוח ביותר שתקבל הם אלה שמיד משאירים אותך בצד הלא נכון של השוק.
חלק את המילויים לפי אופן התרחשותם ומדוד את התשואה לאחר 60 שניות:
| סוג מילוי | חלק מכלל המילויים | תשואה לאחר 60 שניות |
|---|---|---|
| המחיר נסחר ברמה ואז קפץ מעלה | 38% | +3.1 bp |
| המחיר נסחר ברמה ונשאר יציב | 21% | +0.2 bp |
| המחיר נסחר מעבר לרמה ב־2 טיקים או יותר | 41% | −2.4 bp |
בדיקת העבר הנאיבית שלך כללה את שלוש הקבוצות ותמחרה את כולן כאילו התקבלו בחינם. כלל החדירה המלאה נותן לך כמעט רק את הקבוצה השלישית, ולכן היתרון שלו קרס יותר מזה של סימולציית התור. אף אחד מהמודלים לא נכון. סימולציית התור נותנת לך תמהיל מציאותי, וכל המשחק הוא התמהיל הזה: ביצוע פסיבי מרוויח לך את המרווח וגובה ממך מחיר בדמות בחירה שלילית, והיחס בין השניים הוא האסטרטגיה האמיתית שלך.
הניסוח הוותיק של דסק המניות עדיין תקף: פקודת maker היא אופציה בחינם שכתבת לשוק. מישהו מממש אותה כשהמימוש משתלם לו. בדיקת העבר שלך גבתה את הפרמיה ושכחה שיש לאופציה גם צד תשלום.
העסקאות שלא קיבלת משנות את האסטרטגיה, לא רק את העלות
זה החלק שהכי חשוב לי שתתעכב עליו. כשאתה ממדל ביצוע taker בצורה גרועה, אתה מקבל את העסקאות הנכונות במחיר הלא נכון, ותיקון העמלות פותר את רוב הבעיה. כשאתה ממדל ביצוע maker בצורה גרועה, אתה מקבל סט עסקאות שגוי לחלוטין. בערך 2,900 מתוך 4,180 הכניסות שלך מעולם לא היו מתבצעות. חלקן היו האותות הטובים ביותר שלך, בנרות שזינקו מעלה ואז התהפכו — בדיוק התבנית שבה השוק ברח לך.
לכן הענף שבו הפקודה לא מתמלאת צריך לוגיקה אמיתית. מה האסטרטגיה עושה כשהכניסה לא מתמלאת עד שהאות מתיישן? רודפת אחרי המחיר עם פקודת taker ומשלמת את המרווח ואת השפעת השוק? מפרסמת שוב במחיר נמוך יותר ומקבלת בסיס כניסה אחר? מדלגת על העסקה ונשארת ללא פוזיציה? כל בחירה יוצרת עקומת הון שונה מהותית, ואף אחת מהן אינה "נניח שהפקודה התמלאה". בהרצות שלנו, הוספת כלל רדיפה מציאותי (חציית המרווח אחרי 20 שניות ללא מילוי, עם תקרת החלקה של 3 bp) החזירה בערך שליש מהעסקאות החסרות וכמחצית מהפער בין ה־Sharpe הנאיבי לזה של סימולציית התור. זו תוצאה מעניינת מאוד, והיא מתגלה רק כשהמודל מציאותי מספיק כדי שהשאלה תהיה בעלת משמעות.
בדיקת היגיון פשוטה, עשר דקות: קח את יומן המסחר המדומה שלך בזמן אמת ואת בדיקת העבר, והשווה ביניהם על פני אותו חלון זמן. השווה את שיעור המילוי, לא את הרווח וההפסד. אם בדיקת העבר ממלאת 100% מהפקודות הממתינות והמסחר המדומה ממלא 34%, אין לך השוואה בין אסטרטגיות — יש לך באג במודל המילוי. תקן אותו לפני שאתה בוחן אפילו מספר תשואה אחד.
עוד שתי נקודות קטנות לפני שאסיים
דחיות של post-only. אם אתה משתמש ב־post-only כדי להבטיח את מדרגת העמלות של maker וספר הפקודות משתנה בין ההחלטה שלך לאישור הבורסה, הפקודה נדחית במקום להמתין בספר. ביומני המסחר המדומה שלנו מדובר ב־3-6% מהניסיונות ב־BTCUSDT בשעות רגילות, ומעל 15% בדקה שאחרי פרסום מדד המחירים לצרכן בארה״ב. פקודה שנדחתה אינה פקודה שהתמלאה וגם אינה פקודה ממתינה שלא התמלאה; זו עסקה שמעולם לא התקיימה. אם למצב הזה אין ייצוג בבדיקת העבר שלך, מספר העסקאות מנופח דווקא במשטרי השוק שהכי מעניינים אותך.
מניעת עסקאות עצמיות וטביעת הרגל שלך. בהיקף של 0.4 BTC אתה לא מזיז את BTCUSDT, אז אפשר להתעלם מזה. אבל אמרת שאתה רוצה להריץ את זה על חוזה perp של אלטקוין עם שווי שוק בינוני, שבו השווי הנומינלי בספר הפקודות העליון נמוך לעיתים קרובות מ־$15k. שם, הפקודה שלך היא חלק משמעותי מהתור, והכמות שממתינה לפניך בתמונת המצב כוללת את ההשפעה של הפקודה הקודמת שלך. ברגע שהגודל שלך עולה על בערך 10% מהרמה הממתינה, הסימולציה צריכה להביא בחשבון שמשתתפים אחרים מגיבים אליך. למען האמת, בשלב הזה הייתי סומך על מסחר מדומה יותר מאשר על כל סימולציה שאתה או אני יכולים לכתוב.
הרץ את זה מחדש עם סימולציית התור ושלח לי את טבלת התשואה לאחר מילוי, בחלוקה לפי סוג המילוי. אם קבוצת הקפיצה מעלה עדיין מחזיקה ברוב היתרון וקבוצת החדירה לא אוכלת את כולו, אולי יש כאן משהו ששווה לבדוק במסחר מדומה. אם כל העניין נשען על 2,900 המילויים שמעולם לא היית מקבל, עדיף לגלות את זה עכשיו ולא אחרי ארבעה שבועות שבהם אתה רואה חשבון מסחר מדומה שלא עושה את מה שהמחברת הבטיחה.
← כל הפוסטים


