26 września 2026 · badania

Próbowaliśmy odtworzyć backtest strategii. Oto, skąd wzięły się rozbieżności.

Próbowaliśmy odtworzyć backtest strategii. Oto, skąd wzięły się rozbieżności.

Ponownie uruchomiliśmy zapisany backtest i otrzymaliśmy inną krzywą kapitału. Ten sam kod strategii, ten sam zakres dat, ten sam symbol. Saldo końcowe różniło się o 1.8%, a trzy transakcje przesunęły się o 1 świecę.

To wystarczy, by trudno było zaufać wynikom badań. Jeśli członek zespołu, przyszła wersja Ciebie albo usługa paper trading nie potrafi odtworzyć przebiegu, nie da się stwierdzić, czy zmiana poprawiła strategię, czy tylko zmieniła eksperyment. Oto kolejne kroki, które pomogły nam znaleźć źródło rozbieżności.

Dzień 1: Zapisaliśmy, co oznacza „ten sam przebieg”

Pierwszy błąd polegał na uznaniu, że plik strategii to cały eksperyment. Tak nie było. Przebieg zależał też od danych wejściowych, wersji silnika, kalendarza, metadanych instrumentu i ustawień realizacji zleceń. Kod opisywał tylko jedną część obliczeń.

Zanim cokolwiek zmieniliśmy, przygotowaliśmy manifest przebiegu. Zapisaliśmy w nim commit strategii, identyfikatory migawek danych, zakres dat, venue, harmonogram opłat, źródło funding, model realizacji zleceń i wersje oprogramowania. Zachowaliśmy też zlecenia i transakcje, bo sama krzywa kapitału nie pokazuje, w którym momencie przebiegi zaczęły się różnić.

ArtefaktCo zapisaćDlaczego to ważne
Dane rynkoweID migawki, wersję schematu, korektyDostawcy poprawiają historię i aktualizują uwzględnienie zdarzeń korporacyjnych
Realizacja zleceńPoziom opłat, serię funding, ustawienia realizacji i wpływu na rynekWartości domyślne i założenia dotyczące rachunku zmieniają wyniki
Środowisko uruchomienioweCommit kodu, wersje silnika i zależnościBiblioteki mogą zmienić kolejność, zaokrąglenia lub wskaźniki
WynikZlecenia, transakcje, pozycje i metrykiPokazuje, w którym momencie przebiegi zaczynają się różnić

Dzień 2: Porównaliśmy transakcje, a nie Sharpe

Zbiorcze metryki odwracały uwagę od problemu. W obu przebiegach Sharpe był niemal taki sam, ale dzienniki transakcji ujawniły pierwszą rozbieżność przy rozliczeniu funding. W jednym przebiegu stawkę naliczono od pozycji otwartej w chwili rozliczenia, a w drugim — od pozycji po rebalansowaniu w tym samym momencie.

Kod strategii się nie zmienił. Zmieniła się kolejność zdarzeń w silniku. Niewielka aktualizacja wersji jawnie ustaliła sekwencję, która wcześniej zależała od tego, jak przypadkiem posortowały się dwa zdarzenia.

Doprecyzowaliśmy kontrakt przebiegu: najpierw naliczyć funding od pozycji utrzymywanej w chwili rozliczenia, a następnie przetworzyć decyzje strategii przypadające na ten moment. Dokładna konwencja może się różnić zależnie od venue i silnika. Błędem jest pozostawienie jej jako niejawnej.

Dzień 3: Plik danych „taki sam” okazał się inny

Po ustaleniu kolejności zdarzeń pozostałe rozbieżności skupiały się wokół kilku transakcji na akcjach. Dostawca skorygował historyczne uwzględnienie splitu. Nasz plik miał tę samą nazwę i liczbę wierszy co wcześniej, więc wyglądał na niezmieniony.

Teraz tworzymy odcisk każdej niezmiennej migawki danych i zapisujemy przy niej politykę korekt. Skrót mówi nam, czy bajty się zmieniły, ale nie wyjaśnia dlaczego. Dlatego manifest zawiera też źródło, czas pobrania i wersję transformacji. W przypadku aktualizowanych danych te informacje współtworzą wynik.

Odtwarzalny backtest musi odpowiadać na pytanie: „którą wersję przeszłości widział?”

Dzień 4: Znaleźliśmy jedno niepozorne ustawienie domyślne

Ostatnia różnica wynikała z opłaty maker ustawionej na zero, bo konfiguracja strategii pomijała to pole. Nowsza wersja silnika zastosowała domyślną opłatę dla rachunku. Ta jedna wartość domyślna zmieniła wyniki granicznych transakcji na tyle, by wyjaśnić większość różnicy w saldzie końcowym.

Jawnie określiliśmy ustawienia istotne ekonomicznie i poleciliśmy silnikowi zapisywać rozwiązaną konfigurację w rekordzie przebiegu. Wartości domyślne ułatwiają eksperymentowanie. Nie są dobrym dowodem, gdy porównujemy wyniki z różnych okresów.

3znalezione źródła rozbieżności
1.8%początkowa różnica salda końcowego
0wartość samej zgodności Sharpe

Czego następnym razem nie będziemy robić

Poświęciliśmy pół dnia na porównywanie zagregowanych metryk, zanim sprawdziliśmy pierwszą rozbieżną transakcję. Nie zaczynaj od tego. Posortuj oba dzienniki zdarzeń według czasu i porównaj pierwszą rozbieżność; późniejsze różnice często wynikają z tej jednej przyczyny.

Pominęlibyśmy też założenie, że sam obraz kontenera zapewnia odtwarzalność przebiegu. Utrwala dużą część środowiska programowego, ale nie zewnętrzny plik danych, harmonogram opłat pobierany w czasie działania ani zaktualizowaną historię dostawcy.

Gdy backtest się zmienia, zachowaj manifesty i dzienniki obu przebiegów, a potem usuwaj źródła rozbieżności pojedynczo. Przydatnym wynikiem nie jest tylko krzywa, którą można ponownie wygenerować. To zapis wyjaśniający, jakie dane i założenia doprowadziły do jej powstania oraz dlaczego kolejny przebieg może dać inny wynik.

odtwarzalnośćbacktestinginżynieria danychpaper trading
← Wszystkie wpisy