הנה נר אחד. חוזה תמידי Binance USDⓈ-M, צמד BTCUSDT, הדקה שמתחילה ב-13:46:00 UTC. הוא הגיע מנקודת הקצה של klines כמערך פשוט של 12 ערכים, והוא יחידת הקלט הנפוצה ביותר במחקר כמותי קמעונאי ובתחומים הסמוכים לו. כמעט כל אסטרטגיה שאנחנו מייצרים נוגעת בצורה כלשהי בנתונים מהסוג הזה.
אז בואו נפרק אותו, אינדקס אחר אינדקס, ונראה כמה ממנו בק-טסט מפרש לא נכון.
| אינדקס | ערך | שם |
|---|---|---|
| 0 | 1773495960000 | זמן פתיחה (ms) |
| 1 | "84120.50" | פתיחה |
| 2 | "84177.90" | גבוה |
| 3 | "84098.10" | נמוך |
| 4 | "84163.40" | סגירה |
| 5 | "38.417" | נפח בסיס (BTC) |
| 6 | 1773496019999 | זמן סגירה (ms) |
| 7 | "3232108.94" | נפח ציטוט (USDT) |
| 8 | 1204 | מספר העסקאות |
| 9 | "21.883" | נפח בסיס בקניות taker |
| 10 | "1841203.55" | נפח ציטוט בקניות taker |
| 11 | "0" | להתעלם |
[0] ו-[6]: איזו דקה זו, ולפי השעון של מי
זמן פתיחה 1773495960000, זמן סגירה 1773496019999. שימו לב לערך השני: הוא מסתיים ב-19999, אלפית שנייה אחת לפני פתיחת הנר הבא. החלון חצי-פתוח, והבורסה אומרת זאת במפורש. חצי מבאגי הנתונים שחקרתי התחילו בכך שמישהו התייחס לשני קצוות החלון ככוללים וספר עסקה פעמיים בגבול, או יישר דגימה מחדש כך שכל נר של 5 דקות שאל אלפית שנייה מהשכן שלו.
חותמות הזמן הן לפי שעון מנוע ההתאמות של הבורסה. לא לפי השעון שלכם, לא לפי שעון קליטת הנתונים של ספק הנתונים שלכם, וגם לא לפי אותו שעון שבו נרשם צילום מצב של המימון. כשמצרפים סדרת kline לסדרת שיעורי מימון או לסדרת open interest שנמשכה מנקודת קצה אחרת, מצרפים מקורות שמסכימים זה עם זה בדיוק של בערך שנייה, ברוב הזמן. באסטרטגיה של 1-minute זו שגיאת תזמון של 1.7% בכל שורה שמחברים. לכל דבר שמתחת לדקה, זה קטלני.
וחתימת הזמן של הנר הזה מציינת את הפתיחה. אי אפשר לדעת מה המידע שבו עד 13:46:59.999. כל השאלות על מוסכמות אינדוקס — האם להזיז באחד, האם להשתמש בחותמת זמן של סגירת הנר או פתיחתו כזמן האירוע שלי — הן בעצם אותה שאלת lookahead בתחפושת.
[1] "84120.50": מחיר הפתיחה שאי אפשר לסחור בו
הפתיחה היא מחיר העסקה הראשונה שהתבצעה בתוך החלון. זו עסקה שהושלמה בין שני אנשים אחרים. עד שהנר מופיע כשורה ב-dataframe שלכם, המחיר הזה כבר בן 60 שניות.
הנר הקודם נסגר ב-84109.80, כך שבין שני נרות סמוכים של 1-minute יש תנועת מחיר של $10.70. אלה 1.3 נקודות בסיס, בלתי נראות בגרף, ושוות בערך לרבע מעמלת taker. בסדר. אבל בדקו את אותה סדרה בחוזה perp של מטבע חלופי בזמן פרסום מדד המחירים לצרכן האמריקאי, והפערים בין הנרות מגיעים ל-15 עד 40 נקודות בסיס. בק-טסט שמפיק איתות בסגירת הנר ומבצע בפתיחת הנר הבא מניח בשקט שהפער הוא אפס, והוא אפס בדיוק כשההנחה הזו הכי לא מתאימה.
[2] ו-[3]: גבוה "84177.90", נמוך "84098.10"
כאן רוב מנועי הביצוע נכשלים, ולכן זה החלק הארוך.
הגבוה והנמוך הם מחירי קיצון שנגעו בהם. הם לא כוללים גודל או משך זמן. לפי רשומות העסקאות הגולמיות של אותה דקה, כל מה שבוצע במחיר 84100.00 ומטה הסתכם ב-0.62 BTC, על פני 9 עסקאות בחלון של 1.4 שניות. לכן פקודת stop של בק-טסט ב-84100 "מתבצעת" — ואם הפוזיציה שלכם היא 3 BTC, אכלתם את כל הספר הגלוי בדרך למטה, ואת יתרת הכמות ביצעתם איפשהו במהלך החזרה לטווח 84105–84130. הנר אומר שהנמוך היה 84098.10. הנר לא אומר שנסחר שם ערך נקוב של $52,000 בלבד.
הכיוון ההפוך גרוע יותר, כי הוא מחמיא לכם. נניח שהייתה לכם פקודת מכירה מוגבלת ממתינה ב-84175. הגבוה של הנר הוא 84177.90, ולכן מנוע נאיבי מבצע אתכם ב-84175 ורושם החזר maker במקום עלות taker. השאלה אם הפקודה באמת הייתה מתבצעת תלויה במיקום שלכם בתור באותה רמת מחיר, דבר שהנר לא יכול לדעת ושכנראה מעולם לא תיעדתם. נגיעה במחיר אינה ביצוע.
הכלל שקבענו במנוע הביצוע שלנו: פקודת limit ממתינה מתבצעת רק אם הנר נסחר מעבר לרמה, ולא רק מגיע אליה. ביצועים בדיוק בטיק הקיצון מחייבים ראיות של נפח לפי מחיר מתוך רשומות העסקאות; אחרת אנחנו דוחים אותם. כך הוסרו בערך 6% מהעסקאות מספר מסחר טיפוסי של חזרה לממוצע, וה-Sharpe של מועמד אחד ירד מ-1.9 ל-1.1 בבק-טסט. המועמד הזה מעולם לא היה אמיתי; כלל הביצוע פשוט היה הדבר הראשון שהעז לומר זאת.
מלכודת קשורה: המסלול בתוך הנר. אם טווח הנר כולל גם את ה-stop וגם את יעד הרווח שלכם, OHLCV לא יכול לומר מה קרה קודם. כל מנוע חייב לבחור מוסכמה. אצלנו מניחים תמיד שה-stop הופעל ראשון; זו הנחה פסימית, ולעיתים שגויה, אבל היא אף פעם לא מייצרת ניצחון מדומה. אם המנוע שלכם מניח שיעד הרווח הופעל ראשון, נרות עם טווח רחב ייצרו רווח מתוך עמימות, ונרות עם טווח רחב הם בדיוק אלה ששולטים בהתפלגות ה-PnL שלכם.
[4] "84163.40": המספר הכי פחות יציב בנר
הסגירה היא העסקה האחרונה בחלון. זה הכול. ייתכן שזו עסקה זעירה של 0.002 BTC, שבוט השלים בה את סגירת הפוזיציה בשעה 13:46:59.8. זה הטיק היחיד, שנבחר באופן שרירותי מבחינה מבנית, שרוב צינורות המחקר משתמשים בו כדי לחשב כל איתות, לתמחר כל פוזיציה ולהעריך כל יציאה.
בחוזי perp של BTC זה כמעט לא משנה; בחוזה perp דל-נזילות של מטבע חלופי בשעה 04:00 UTC זה משנה מאוד, וההפרש בין מחירי הסגירה של שתי זירות מסחר באותה דקה עשוי לעלות על כל היתרון שלכם לעסקה. כש-PnL של אסטרטגיה תלוי דווקא בסגירה, אנחנו מריצים אותה שוב עם תמחור לפי מחיר ה-mark של הבורסה, שנגזר מהמדד וקשה הרבה יותר לתמרן. אם התוצאות שונות, האסטרטגיה סחרה בתוצר לוואי של הנתונים.
[5] ו-[7]: "38.417" ו-"3232108.94", באילו יחידות נמדד הנפח
נפח הבסיס הוא BTC; נפח הציטוט הוא USDT. כאן יש את שניהם, מחווה שלא כל זירות המסחר מציעות. הסיבה שזה חשוב היא צבירה בין זירות מסחר. חוזים עם בטחונות במטבע מצוטטים בחוזים של $100 ערך נקוב. חלק מפידים של מניות מדווחים במנות עגולות. זירות מסחר של שוקי חיזוי מדווחות במספר מניות, כשכל מניה היא תביעה בינארית הנקובה בדולר. חיבור של "נפח" בין נכסים ביחידות מעורבות, בלי לנרמל ליחידת ערך נקוב אחת, יוצר דירוג נזילות שהוא שטות מוחלטת; זו תהיה שטות שנראית יציבה ועוברת בדיקה.
נרמלו הכול לערך נקוב במטבע הציטוט בזמן הקליטה. שמרו גם את השדה הגולמי, אבל אל תאפשרו לאסטרטגיה לראות אותו.
[8] 1204: השדה שאמור לקבוע את מודל השפעת השוק שלכם
חלוקת נפח הבסיס במספר העסקאות נותנת גודל עסקה ממוצע של 0.032 BTC, בערך $2,700. אם האסטרטגיה המועמדת שלכם רוצה להיכנס לעסקה של $250,000 בבת אחת, היא מבקשת להיות בערך פי 92 מגודל העסקה הטיפוסית באותה דקה. המספר הזה, ולא קבוע החלקה גנרי של 5 נקודות בסיס, אמור לקבוע את רכיב השפעת השוק לפי שורש ריבועי. אנחנו מחשבים שיעור השתתפות בכל נר כעמודה מרכזית ודוחים אסטרטגיות שבהן הכניסה החציונית עולה על כמה אחוזים מהערך הנקוב של הנר, כי כל מה שבא אחר כך הוא בדיה.
מספר העסקאות גם מסמן בזול דקות חריגות. הנפח רגיל, אבל מספר העסקאות צנח ל-11? מישהו ביצע עסקת בלוק. הנפח רגיל, אבל מספר העסקאות טיפס ל-9,000? זו שרשרת חיסולים שמפורקת לחתיכות קטנות.
[9] ו-[10]: "21.883", השדה שכולם משליכים
נפח בסיס בקניות taker. מתוך 38.417 BTC בנר הזה, 21.883 BTC הגיעו מיוזמת קונים, כך שהפרש הנפח החתום הוא +5.349 BTC והחלוקה בין יוזמי העסקאות היא 57/43 לטובת הקונים. הבורסה נותנת לכם חוסר איזון בזרימת הפקודות בחינם, בשדה שרוב האנשים אף פעם לא קוראים כי pandas לא נתנה לו שם בעמודה.
אני לא טוען שהוא מנבא תשואות בפני עצמו; אסטרטגיות נאיביות לפי הפרש נפח הן מהדרכים האמינות יותר לתרום לקופת העמלות. אבל זו מדידה שונה באמת ממחיר, היא זמינה באותה בקשה שכבר שלחתם, והיא מאפשרת להבחין בין עלייה שנקנתה לבין עלייה שהתרחשה מפני שהמוכרים נסוגו. השתיים נראות זהות ב-OHLC ומתנהגות אחרת כעבור 10 דקות. סוכן המחקר שלנו רואה בהשערה שמתעלמת מחלוקת taker בזירת מסחר שמפרסמת אותה, השארת ראיות על השולחן.
[11] "0": להתעלם — וכל מה שלא מופיע כאן
אינדקס 11 הוא שדה שהוצא משימוש, ותמיד ערכו אפס. מעניינת יותר רשימת הדברים שהנר הזה לא מכיל: אין bid, אין ask, אין מרווח, אין עומק ספר פקודות, אין שיעור מימון, אין open interest, אין חיסולים, אין מחיר mark ואין מחיר מדד. וחשוב מכול, אין דרך לדעת אם הפקודה שלכם הייתה מתבצעת כ-maker או כ-taker, וזה ההבדל בין לשלם 0.045% לבין להרוויח 0.01% בזירת המסחר הזו.
לכן כל מודל עמלות שמבוסס רק על klines הוא הנחה בתחפושת של מספר. אנחנו פותרים את זה בכך שאנחנו מחייבים כל אסטרטגיה להצהיר מראש על סגנון הביצוע שלה, ואז גובים עמלת taker מכל דבר שלא הוכח אחרת.
הנר שמעולם לא הופיע
החלק האחרון, וזה שפוגע הכי חזק מחוץ לנכסים הגדולים. דקה ללא עסקאות אינה מייצרת kline כלל. ספקים וספריות רבים ממלאים אותה בהעתקת הערכים הקודמים: פתיחה = גבוה = נמוך = סגירה = הסגירה הקודמת, נפח 0. האינדיקטור שלכם מחשב בלי בעיה. האסטרטגיה שלכם רואה שורה תקינה ויכולה להפיק איתות בדקה שבה איש בעולם לא סחר בנכס הזה.
בחוזה perp אחד של נכס mid-cap שקלטנו, ב-4.1% מנרות 1-minute לאורך חלון של 12 חודשים לא היו עסקאות. אסטרטגיית חזרה לממוצע על הסימול הזה הציבה 38% מהכניסות שלה על נרות סינתטיים, כי מחירים סינתטיים קבועים מפתים כל דבר שמודד סטייה מממוצע נע. הבק-טסט נראה נפלא. האסטרטגיה סחרה בפערים שבנתונים.
לכן שכבת הקליטה מעבירה עכשיו ערך בוליאני synthetic לכל נר, ומפיצה אותו לאורך כל דגימה מחדש; מערך האימות נכשל בכל אסטרטגיה שהעסקאות שלה מתרכזות בנרות כאלה. עמודה זולה. היא חיסלה יותר מועמדות מכל אינדיקטור שכתבנו אי פעם.
12 ערכים. 4 מהם מפורשים שוב ושוב לא נכון, 2 מהם מושלכים שוב ושוב, וקטגוריה שלמה חסרה מהשורה ומומצאת בידי המנוע. לפני הבק-טסט הבא שלכם, שלפו נר גולמי אחד מהמאגר שלכם וקראו בקול כל שדה, מול מה שלוגיקת הביצוע שלכם מניחה לגביו. זה תרגיל של 20 דקות, ומעולם לא ראיתי מישהו עושה אותו ולא מגלה משהו.
← כל הפוסטים


