La șase săptămâni după ce am rulat un backtest, l-am rulat din nou cu același cod, aceeași configurație și același interval de date. Capitalul propriu final era diferit. Nu cu mult, dar suficient cât să coboare rata Sharpe de la 1.31 la 1.08 și să schimbe setul de parametri care părea cel mai bun.
Strategia nu se schimbase. Datele istorice se schimbaseră. Un furnizor revizuise datele pentru mai multe zile de tranzacționare și bare, iar la prima rulare nu păstraserăm instantaneul exact al datelor de intrare. În următoarele câteva zile, am încercat să aflăm dacă noul rezultat se datora unei corecții a datelor, unei erori în pipeline sau unui proces de cercetare pe care nu îl puteam reproduce.
Luni: rerularea a dat un rezultat diferit
Primul indiciu a fost o mică diferență într-o pereche spot lichidă. Bara originală avea un maxim de 62,418. În rerulare, maximul era 62,431. Diferența părea inofensivă până când am urmărit intrarea strategiei: pragul ei era între aceste prețuri. Câteva bare modificate au însemnat semnale diferite, iar acestea au dus la poziții diferite câteva zile mai târziu.
Am comparat numărul zilnic de tranzacții și am descoperit că nepotrivirea începea la o singură dată. Apoi am comparat rândurile brute care alimentau acea dată. Răspunsul curent al furnizorului conținea câteva tranzacții corectate și o bară agregată revizuită. Răspunsul pe care îl folosisem nu era însoțit de un jurnal al modificărilor.
Acesta este un comportament obișnuit al datelor. Bursele repară înregistrări; furnizorii completează golurile, corectează tranzacțiile înregistrate eronat și ajustează logica de agregare. O interogare pentru „datele din ianuarie” poate da un răspuns diferit în martie.
Marți: am găsit dovada lipsă
Înregistrasem numele furnizorului, simbolul și intervalul de date în jurnalul rulării. Nu stocaserăm nici răspunsul propriu-zis, nici o amprentă de conținut pentru fiecare partiție de intrare. Așadar, puteam dovedi ce solicitaserăm, dar nu și ce octeți ne trimisese furnizorul.
Am recuperat cele mai vechi fișiere din cache pe care le-am găsit și le-am comparat cu răspunsul curent. Diferența nu ținea de ajustarea pentru split sau de conversia fusului orar. Era vorba despre un număr mic de tranzacții corectate care treceau prin generatorul nostru de bare. Asta a exclus o ipoteză, dar cache-ul era incomplet, așa că tot nu puteam reconstrui exact fiecare bară afectată.
| Ce am înregistrat | La ce ne-a permis să răspundem | Ce lipsea |
|---|---|---|
| Furnizorul, simbolul, intervalul de date | Ce solicitare a făcut rularea | Conținutul răspunsului |
| Versiunea pipeline-ului | Ce cod a generat barele | Rândurile exacte de intrare |
| Momentul rulării | Când am rulat jobul | Ce revizie a furnizorului era disponibilă atunci |
Consideraserăm că „aceeași interogare” înseamnă „aceleași date”. Această presupunere făcuse experimentul să pară mai reproductibil decât era.
Miercuri: corecția a schimbat povestea
Am reluat datele afectate folosind ambele versiuni de date disponibile. Barele revizuite au eliminat două intrări și au mutat o ieșire cu o bară. Aceste trei tranzacții explicau cea mai mare parte a modificării ratei Sharpe. Strategia era sensibilă la câteva observații din apropierea pragului de declanșare, deși seria generală a prețurilor părea aproape identică pe grafic.
Asta nu însemna că rezultatul inițial era fraudulos sau că rezultatul revizuit era automat mai bun. Însemna că trebuia să precizăm întinderea afirmației: rularea inițială descria instantaneul de date disponibil la momentul respectiv, iar rerularea descria istoricul actual al furnizorului. Nu puteam susține cu onestitate că una o reproducea pe cealaltă.
Joi: am modificat evidența cercetării
Am adăugat instantanee imuabile ale datelor de intrare pentru noile rulări de cercetare. Fiecare partiție primește o amprentă de conținut, iar manifestul rulării înregistrează sursa, momentul preluării, versiunea schemei, politica de ajustare și versiunea codului de transformare. Pentru seturile mari de date, păstrăm fișierele efective într-un spațiu de stocare adresabil după conținut, astfel încât partițiile identice să poată fi partajate fără a suprascrie versiunile anterioare.
Când regulile de stocare sau de licențiere ne împiedică să păstrăm datele brute, păstrăm cea mai solidă dovadă permisă de condițiile licenței: amprente, parametri de solicitare, identificatori ai reviziei sursei și un eșantion mic de rânduri parsate pentru diagnosticare. O amprentă nu poate recupera datele, dar ne poate spune când două rulări nu au folosit octeți identici.
Un backtest reproductibil are nevoie de o identitate a datelor de intrare, nu doar de un URL către date. Înregistrează ce a sosit, când a sosit și cum a devenit rândurile consumate de strategia ta.
Vineri: ce am sări data viitoare
Am sări peste compararea exclusivă a indicatorilor principali. O rată Sharpe modificată îți spune că ceva s-a schimbat, dar nu identifică motivul. Începe cu numărul de rânduri și amprentele partițiilor, apoi găsește prima dată de intrare diferită. De acolo, compară în această ordine rândurile transformate, semnalele, ordinele și execuțiile.
Am sări și peste înlocuirea silențioasă a datelor vechi cu istoricul corectat. Corecțiile sunt utile, dar rulările vechi trebuie să rămână legate de datele lor originale. Un set de date nou trebuie să genereze o rulare nouă, cu o legătură vizibilă către cea anterioară.
Regula noastră practică este acum simplă: dacă rerularea trebuie să reproducă un experiment, fixează instantaneul de date. Dacă trebuie să beneficieze de istoricul corectat, eticheteaz-o ca experiment nou. Ne-au trebuit câteva zile să învățăm distincția, iar acum o consemnăm într-un singur rând din manifestul rulării.
← Toate articolele


