26 septembrie 2026 · cercetare

Am încercat să reproducem backtestul unei strategii. Iată unde au deviat cifrele.

Am încercat să reproducem backtestul unei strategii. Iată unde au deviat cifrele.

Am rerulat un backtest salvat și am obținut o curbă a capitalului diferită. Același cod al strategiei, aceeași perioadă, același simbol. Soldul final diferea cu 1.8%, iar trei tranzacții se deplasaseră cu o bară.

E suficient cât să ne facă să ne îndoim de un rezultat de cercetare. Dacă un coleg, o versiune viitoare a ta sau un serviciu de tranzacționare pe hârtie nu poate reproduce rularea, nu poți ști dacă o schimbare a îmbunătățit strategia sau doar a modificat experimentul. Iată pașii pe care i-am urmat pentru a identifica abaterea.

Ziua 1: Am consemnat ce înseamnă „aceeași rulare”

Prima noastră greșeală a fost să tratăm fișierul strategiei drept experimentul. Nu era așa. Rularea depindea și de datele de intrare, versiunea motorului, calendar, metadatele instrumentelor și setările de execuție. Codul descria doar o parte a calculului.

Am creat un manifest al rulării înainte să schimbăm ceva. Am înregistrat commitul strategiei, identificatorii instantaneelor de date, perioada, platforma, grila de comisioane, sursa finanțării, modelul de execuție și versiunile software-ului. Am salvat și ordinele și execuțiile rezultate, fiindcă o curbă a capitalului, de una singură, nu arată unde au început să difere două rulări.

ArtefactCe trebuie înregistratDe ce contează
Date de piațăID-ul instantaneului, versiunea schemei, ajustăriFurnizorii corectează istoricul și revizuiesc acțiunile corporative
ExecuțieNivelul de comision, seria de finanțare, setările de execuție și impactValorile implicite și ipotezele despre cont schimbă rezultatele
Mediu de rulareCommitul codului, versiunile motorului și dependențelorBibliotecile pot schimba ordinea, rotunjirea sau indicatorii
RezultatOrdine, execuții, poziții și metriciArată unde încep să nu mai concorde rulările

Ziua 2: Am comparat tranzacțiile, nu Sharpe

Metricile rezumative ne-au distras atenția. Cele două rulări aveau valori Sharpe aproape identice, dar jurnalele execuțiilor arătau că prima neconcordanță apărea la o decontare a finanțării. O rulare aplica rata poziției deschise la momentul decontării; cealaltă folosea poziția de după reechilibrarea de la acel moment.

Codul strategiei nu se schimbase. Se schimbase ordonarea evenimentelor din motor. O mică actualizare de versiune făcuse explicită secvența, care înainte depindea de ordinea accidentală a două evenimente.

Am actualizat contractul de rulare pentru a preciza ordinea: aplicăm finanțarea poziției menținute până la decontare, apoi procesăm deciziile strategiei pentru acel moment. Convenția exactă poate varia în funcție de platformă și motor. Bugul este să o lăsăm nedefinită.

Ziua 3: Un fișier de date „identic” s-a dovedit diferit

După ce am fixat ordinea evenimentelor, neconcordanțele rămase se concentrau în câteva tranzacții cu acțiuni. Furnizorul corectase o ajustare istorică pentru o divizare de acțiuni. Fișierul nostru avea același nume și același număr de rânduri ca înainte, ceea ce îl făcuse să pară neschimbat.

Acum calculăm amprenta fiecărui instantaneu de date imuabil și păstrăm alături politica de ajustare. Un hash ne spune dacă s-au schimbat octeții; nu ne explică de ce. De aceea, manifestul conține și sursa, momentul preluării și versiunea transformării. Pentru datele care se revizuiesc, aceste detalii fac parte din rezultat.

Un backtest reproductibil trebuie să răspundă la întrebarea „ce versiune a trecutului a văzut?”

Ziua 4: Am găsit o setare implicită trecută neobservată

Ultima diferență era un comision maker setat la zero, deoarece configurația strategiei omitea câmpul. Un motor mai nou aplica comisionul implicit al contului. Acea singură valoare implicită a schimbat suficient tranzacțiile marginale cât să explice cea mai mare parte a diferenței dintre soldurile finale.

Am făcut explicite setările relevante din punct de vedere economic și am configurat motorul să imprime configurația efectivă în înregistrarea rulării. Valorile implicite sunt utile în faza de explorare. Sunt dovezi slabe când compari rezultate din momente diferite.

3surse ale abaterii identificate
1.8%diferența inițială dintre soldurile finale
0valoare în concordanța Sharpe, luată singură

Ce am evita data viitoare

Am petrecut jumătate de zi comparând metrici agregate înainte să ne uităm la prima execuție diferită. Nu începe de acolo. Sortează ambele jurnale de evenimente după marcajul temporal și compară prima divergență; diferențele ulterioare sunt adesea urmarea aceleiași cauze.

Am renunța și la ideea că o imagine de container face, de una singură, o rulare reproductibilă. Ea fixează o mare parte din mediul software, dar nu și un fișier de date extern, o grilă de comisioane preluată în timpul rulării sau istoricul revizuit de un furnizor.

Când se schimbă un backtest, păstrează ambele manifeste și jurnalele rulărilor, apoi corectează câte o sursă de abatere pe rând. Rezultatul util nu este doar o curbă pe care o poți rula din nou. Este o înregistrare care explică ce date și ipoteze au produs-o și de ce următoarea rulare ar putea fi diferită.

reproductibilitatebacktestingingineria datelortranzacționare pe hârtie
← Toate articolele