27 sierpnia 2026 · inżynieria danych

Twój uniwersum symboli to wehikuł czasu: błąd przeżywalności w backtestach krypto perpetuals

Twój uniwersum symboli to wehikuł czasu: błąd przeżywalności w backtestach krypto perpetuals

Wysłałeś mi notebook w niedzielę wieczorem i od tamtej pory mam go otwartego na drugim monitorze. Przekrojowe momentum, 150 największych perpetuals Binance USDⓈ-M według 30-dniowego wolumenu w dolarach, cotygodniowy rebalancing, long na górnym decylu, short na dolnym, okres od 2021-01 do 2026-06. Sharpe 2.31, maksymalne obsunięcie 14.2% i model kosztów, który wyglądał uczciwie: wejście takerem, wyjście takerem, funding naliczany w każdym interwale, składnik poślizgu zależny od wielkości zlecenia względem arkusza. Trudne rzeczy zrobiłeś dobrze. Potem zapytałeś, dlaczego sześć tygodni paper tradingu przyniosło wynik bliski zeru i czy przewaga zanikła.

Nie zanikła. W backteście nigdy jej nie było. Spójrz na komórkę 4:

info = requests.get(BASE + "/fapi/v1/exchangeInfo").json()
symbols = [s["symbol"] for s in info["symbols"]
           if s["status"] == "TRADING" and s["quoteAsset"] == "USDT"]

Wywołałeś ten endpoint w czerwcu 2026 i użyłeś odpowiedzi, by określić, czym można było handlować w marcu 2021. Z definicji każdy symbol z tej listy przetrwał do czerwca 2026. Oto cały błąd, który ma większy wpływ niż reszta twojego modelu kosztów razem wzięta.

2.31 → 0.74Sharpe: lista ocalałych a dane point-in-time
~1 na 5symboli z twojego uniwersum z 2021 już nie istnieje
380 KBdzienny zrzut exchangeInfo, skompresowany gzipem

Czego endpoint ci nie powie

exchangeInfo nie zawiera historii. Nie ma parametru asOf, archiwum ani dziennika zmian. To fotografia stanu na teraz i Binance nigdy nie obiecywało niczego więcej. Gdy kontrakt zostaje wycofany, jego wpis znika z odpowiedzi, a ticker przestaje istnieć z punktu widzenia API. Od 2019 roku Binance wprowadziło ponad 600 perpetuals USDⓈ-M i wycofało ich grubo ponad 100. BTS, COCOS, TOMO, RAY, FTT, SC oraz długa lista kontraktów z altsezonu 2021, które miały jeden wspaniały kwartał, a potem traciły płynność, aż giełda je usunęła.

Zastanów się, jakie nazwy lubi 30-dniowy filtr momentum. Nie BTC. Lubi aktywo, które właśnie potroiło wartość po pompie związanej z debiutem i cyklu na Twitterze. Te dwie grupy w dużej mierze się pokrywają, a filtr uniwersum usunął tę część wspólną.

Odtworzyłem twój przebieg na naszym archiwum zrzutów, z uniwersum point-in-time, tym samym sygnałem i tymi samymi kosztami. Sharpe wyniósł 0.74, a obsunięcie 31%. Mniej więcej dwie piąte różnicy to wycofane symbole, których nigdy nie powinieneś był trzymać. Kolejna ćwiartka wynika z odwrotnego problemu, subtelniejszego i, jak sądzę, mniej przyjemnego dla ciebie.

Drugi koniec wehikułu czasu: symbole, które jeszcze nie istniały

Twoja cecha to 90-dniowa stopa zwrotu. Twoje okno kroczące ma min_periods=20, bo ustawiłeś je raz, żeby rozgrzewka nie wyzerowała pierwszego kwartału próby, i już do tego nie wracałeś. Kontrakt notowany od 21 dni dostaje więc wynik momentum obliczony na podstawie trzech tygodni zmian ceny po debiucie, trafia do górnego decyla mniej więcej za każdym razem, gdy debiut się udał, i wchodzi do portfela.

Tę część można uznać za uzasadnioną. Oto co uzasadnione nie jest: dostawca danych uzupełnił historię części tych symboli danymi spot lub indeksowymi sprzed powstania perpetuala, więc historia kline kilku kontraktów sięga przed ich własną datę onboardDate. Możesz to sprawdzić w mniej więcej minutę. Połącz tabelę cen z datami debiutu i policz wiersze wcześniejsze niż data notowania. W twoich danych 41 symboli ma świece sprzed debiutu, a jeden z nich, latem 2023 roku, dokłada 6% zysku w jednym tygodniu do krzywej kapitału dzięki pozycji w kontrakcie, który miał pojawić się dopiero za dziewięć dni.

Ciąg znaków tickera to nie identyfikator. To etykieta wynajmowana przez giełdę, czasem dwukrotnie.

To prowadzi do zmian nazw. MATICUSDT zmienił się w POLUSDT. FTMUSDT zmienił się w SUSDT przy konwersji 1:1. Sytuacja z LUNA w maju 2022 pozostawiła LUNCUSDT, a później pojawił się zupełnie nowy LUNAUSDT, którego rdzeń tickera jest taki sam jak aktywa, które straciło niemal całą wartość. Jeśli loader używa ciągu symbolu jako klucza i skleja znalezione pliki, masz tam co najmniej jedną serię z nieciągłością, która nie wynika ze zmiany ceny, a twoja cecha momentum odczyta tę nieciągłość jako najsilniejszy sygnał w przekroju.

Wszystko inne, co zmienia się pod twoim nosem

Gdy już zaakceptujesz, że lista symboli zmienia się w czasie, to samo rozumowanie dotyczy każdego innego pola z tej odpowiedzi. Dla wszystkich używasz dzisiejszych wartości.

PoleJak się zmieniaCo przestaje działać
statusTRADING → SETTLING → usuniętyBłąd przeżywalności; fikcyjne wyjścia po cenie zamknięcia, która nigdy nie wystąpiła
onboardDateCo tydzień nowe notowania; brak daty dla usuniętych symboliHandel kontraktami, zanim jeszcze istniały
tickSize / stepSizeDostosowywane wraz ze zmianą poziomów cenZaokrąglanie zleceń i ceny limit, które zostałyby odrzucone
minNotionalPodnoszone z czasem na płytkich arkuszachMałe pozycje, które twój router w produkcji by odrzucił
fundingIntervalHoursPrzez lata 8h, później 4h lub 1h dla wielu symboliKoszt utrzymania pozycji różni się 2–3× dokładnie dla altów, które wybiera twój filtr
progi dźwigniZmieniają się poziomy i depozyt zabezpieczającyModelowanie likwidacji i dostępny depozyt

W twoim przypadku najbardziej boli funding. Pętla naliczania zakłada trzy płatności dziennie w całej historii. Znacząca część portfela altów przeszła na funding co 4 godziny, a pozycje short w aktywach z wysokim fundingiem odpowiadają za dużą część symulowanego PnL. To nie jest błąd zaokrąglenia. Błąd jest wielokrotny.

Wycofanie z obrotu to zdarzenie, którego nie modelujesz

W twoim przebiegu point-in-time założyłem dla strategii korzystne warunki wyjścia. Rzeczywiste wycofania przebiegają według scenariusza: ogłoszenie, zwykle z wyprzedzeniem od siedmiu do czternastu dni, potem okres, w którym można tylko zmniejszać pozycję, a następnie wymuszone rozliczenie po cenie mark. Ogłoszenie jest informacją publiczną i możesz na nie zareagować, więc uczciwa symulacja zakłada wyjście po cenie zamknięcia w dniu ogłoszenia. Ale przy typowym wycofaniu alta ta cena jest już 10-20% niższa niż tydzień wcześniej, arkusz jest płytki, a model poślizgu musi uwzględniać, że działa w innych warunkach. Jeśli silnik realizacji zleceń podaje ci cenę rozliczenia bez wpływu na rynek, po cichu umożliwiłeś handel umierającymi aktywami po uczciwej wartości.

Jeśli nigdy nie archiwizowałeś exchangeInfo, sytuacja nie jest beznadziejna. Publiczny zrzut pod adresem data.binance.vision/data/futures/um/monthly/klines/ wciąż przechowuje katalogi wycofanych symboli długo po tym, jak API o nich zapomniało. Wylistuj katalogi; pierwszy i ostatni plik miesięczny dla każdego symbolu dadzą ci użyteczne przybliżenie okresu notowania i wycofania, bez żadnego dostawcy danych. To rekonstrukcja, nie zapis, i nie odzyskasz z niej wielkości ticka ani interwałów fundingu. Ale dowiesz się, co i kiedy istniało, czyli 80% tego, czego potrzebujesz na poniedziałek.

Co chciałbym, żebyś zbudował, zanim znów ruszysz sygnał

  1. Zadanie cron, które codziennie pobiera exchangeInfo z każdej analizowanej giełdy i zapisuje je w storage obiektowym pod kluczem daty. Po kompresji gzipem zajmuje mniej niż pół megabajta. Dziesięć lat danych będzie kosztować w S3 grosze, a da ci możliwości badawcze, których później nie da się kupić.
  2. Główny rejestr aktywów zbudowany na podstawie tych zrzutów: jeden wiersz na (venue, symbol, valid_from, valid_to), zawierający pełen zestaw pól. Porównuj kolejne zrzuty i na tej podstawie go twórz, a każdą zmianę pola traktuj jako nowy wiersz.
  3. Funkcję uniwersum z obowiązkowym argumentem czasu. universe(ts), nigdy universe(). Ustaw domyślne zachowanie tak, by nikt, nawet ty w przyszłości o 1am, nie mógł przypadkiem sięgnąć po listę ocalałych.
  4. Asercję w loaderze danych, która wykrywa dane sprzed debiutu: żadna świeca nie może być wcześniejsza niż onboard_ts minus jeden dzień. Przerwij przebieg, nie wyświetlaj ostrzeżenia.
  5. Stabilny wewnętrzny identyfikator instrumentu, który przetrwa zmianę nazwy, a ticker sprowadź do roli atrybutu. Przypisz POL i MATIC do jednego ID i oznacz zmiany nominału, by logika ciągłości mogła odmówić ich łączenia.

Zrób to i uruchom strategię ponownie. Sądzę, że wynik zbliży się do mojego 0.74, a ciekawe pytanie będzie brzmiało, czy wynik 0.74, uwzględniający martwe aktywa, zawiera coś wartego paper tradingu. Może tak. Przekrojowe momentum na perpetuals nie jest bezwartościowe, a część tego, co zostanie po naprawieniu uniwersum, to rzeczywisty carry po stronie short. Zobaczysz też, że wyniki paper tradingu i backtestu zaczną się zgadzać, bo paper trading od początku korzystał z uniwersum point-in-time. Nie miał wyboru.

P.S. To nie specyfika krypto, tylko tutaj problem jest bardziej widoczny. Badacze rynku akcji od uruchomienia CRSP zmagają się ze stopami zwrotu z wycofanych spółek i ponownie wykorzystywanymi tickerami, a rynki predykcyjne doprowadzają to do skrajności: każdy kontrakt wygasa z założenia, więc uniwersum składa się wyłącznie z debiutów i wycofań. Jeśli przeniesiesz ten filtr na Kalshi, najpierw zbuduj główny rejestr aktywów. Tam historia nie ma innej postaci.

błąd przeżywalnościdane point-in-timekontrakty futures na kryptowalutybacktestinginżynieria danych
← Wszystkie wpisy