За шість тижнів після першого бектесту ми запустили його знову з тим самим кодом, конфігурацією та діапазоном дат. Підсумковий капітал відрізнявся. Не кардинально, але достатньо, щоб коефіцієнт Sharpe змінився з 1.31 на 1.08, а найкращою стала здаватися інша комбінація параметрів.
Стратегія не змінилася. Змінилися історичні дані. Постачальник переглянув дані про угоди та бари за кілька днів, а під час першого запуску ми не зберегли точний знімок вхідних даних. Наступні кілька днів ми з’ясовували, чи новий результат спричинило виправлення даних, помилка в конвеєрі чи дослідницький процес, який ми не могли відтворити.
Понеділок: повторний запуск дав інший результат
Першою підказкою стала невелика розбіжність у ліквідній спотовій парі. У початковому барі максимум становив 62,418. Після повторного запуску — 62,431. Спершу різниця здавалася несуттєвою, аж поки ми не простежили, як стратегія відкриває позицію: її поріг лежав між цими значеннями. Змінилося кілька барів — змінилися сигнали, а за кілька днів і позиції.
Ми порівняли кількість угод за днями й виявили, що розбіжність почалася в одну конкретну дату. Потім зіставили вихідні рядки, з яких формувалися дані за цей день. У поточній відповіді постачальника було кілька виправлених угод і переглянутий агрегований бар. До відповіді, якою ми скористалися спочатку, журнал змін не додавався.
Для даних це звичайна річ. Біржі виправляють записи; постачальники заповнюють пропуски, коригують некоректні угоди та змінюють логіку агрегації. Запит на «дані за січень» у березні може повернути інший результат.
Вівторок: знайшли загублений чек
У записі про запуск ми зберігали назву постачальника, символ і діапазон дат. Але саму відповідь не зберігали й не фіксували хеш вмісту кожного розділу вхідних даних. Тож ми могли підтвердити, що саме запитували, але не могли встановити, які саме байти повернув постачальник.
Ми знайшли найстаріші кешовані файли й порівняли їх із поточною відповіддю. Різницю спричинили не коригування на спліт і не конвертація часового поясу. Невеликий набір виправлених записів про угоди пройшов через наш генератор барів. Це спростувало одну з версій, але кеш був неповним, тож ми все ще не могли точно відновити кожен змінений бар.
| Що ми записали | На яке запитання це відповідало | Чого бракувало |
|---|---|---|
| Постачальник, символ, діапазон дат | Який запит зробив запуск | Вміст відповіді |
| Версія конвеєра | Який код формував бари | Точні вхідні рядки |
| Час запуску | Коли ми запустили завдання | Яка версія даних постачальника була тоді доступна |
Ми вважали, що «той самий запит» означає «ті самі дані». Через це експеримент здавався відтворюванішим, ніж був насправді.
Середа: виправлення змінило картину
Ми повторно обробили дати, яких торкнулися зміни, використавши обидві доступні версії даних. Переглянуті бари прибрали два входи й пересунули один вихід на бар. На ці три угоди припала більша частина зміни Sharpe. Стратегія виявилася чутливою до кількох спостережень поблизу порога спрацьовування, хоча на графіку весь ціновий ряд виглядав майже однаково.
Це не означало, що початковий результат був недостовірним або що переглянутий результат автоматично кращий. Це означало, що твердженню потрібні чіткі межі: старий запуск описував знімок даних, доступний нам на той час, а повторний — поточну історію постачальника. Ми не могли чесно назвати один відтворенням іншого.
Четвер: ми змінили дослідницький журнал
Для нових дослідницьких запусків ми почали зберігати незмінні знімки вхідних даних. Для кожного розділу обчислюємо хеш вмісту, а в маніфесті запуску фіксуємо джерело, час отримання, версію схеми, політику коригування та версію коду перетворення. Великі набори даних зберігаємо у сховищі з адресацією за вмістом: однакові розділи можна використовувати спільно, не перезаписуючи попередні версії.
Якщо правила зберігання чи ліцензування не дозволяють залишати вихідні дані, ми зберігаємо найповніший можливий підтверджувальний запис: хеші, параметри запиту, ідентифікатори версій джерела та невелику вибірку розібраних рядків для діагностики. Хеш не дає змоги відновити дані, але показує, чи використовували два запуски однакові байти.
Для відтворюваного бектесту потрібен ідентифікатор вхідних даних, а не лише URL даних. Записуйте, що саме надійшло, коли воно надійшло та як перетворилося на рядки, які використала ваша стратегія.
П’ятниця: що ми не робитимемо наступного разу
Ми не обмежуватимемося порівнянням підсумкових метрик. Зміна Sharpe підказує, що щось зрушилося, але не показує причину. Починайте з кількості рядків і хешів розділів, а потім знайдіть першу дату, де розходяться вхідні дані. Далі порівняйте перетворені рядки, сигнали, ордери й виконання саме в такій послідовності.
Також ми не будемо мовчки замінювати старі дані виправленою історією. Виправлення корисні, але старі запуски мають і надалі відповідати своїм початковим вхідним даним. Новий набір даних має створювати новий запуск із помітним зв’язком із попереднім.
Тепер наше практичне правило просте: якщо повторний запуск має відтворити експеримент, зафіксуйте знімок даних. Якщо він має врахувати виправлену історію, позначте його як новий експеримент. Нам знадобилося кілька днів, щоб засвоїти цю різницю, а тепер вона вміщується в один рядок маніфесту запуску.
← Усі статті


