W zeszłym tygodniu podesłałeś mi notebook: ten sam sygnał, ten sam zbiór instrumentów, te same 14 miesięcy danych BTCUSDT perp. Zmienił się tylko sposób realizacji zleceń. Przestałeś przekraczać spread i zacząłeś wystawiać bid o 1 tick wyżej, a Sharpe wzrósł z 0.42 do 2.14. Zapytałeś, czy to prawdziwy wynik, czy może coś zepsułeś.
Coś zepsułeś. Chcę dokładnie pokazać ci, w którym miejscu, bo błąd zajmuje jedną linijkę, a lekcja, która się za nim kryje, jest znacznie większa.
Oto twoja reguła realizacji zlecenia, skopiowana z silnika:
if bar.low <= limit_price: fill(limit_price)
To znaczy: jeśli w ciągu tej minuty na rynku zawarto transakcję po mojej cenie lub niższej, moje zlecenie zostało zrealizowane po cenie limitu. W praktyce zakodowałeś tu tylko to, że cena dotknęła twojego poziomu. Dotknięcie to nie realizacja zlecenia. Pomiędzy nimi jest kolejka, którą w twoim modelu uznałeś za pustą.
Co stoi przed tobą
Gdy wystawiasz bid po 84,120.0 na BTCUSDT perp, trafiasz na koniec całej kolejki zleceń, które już tam czekają. Na najlepszym poziomie arkusza tego kontraktu zwykle jest to od 4 do 30 BTC, zależnie od pory dnia; mediana w twoim oknie próby wynosi około 12 BTC. Twoje zlecenie opiewa na 0.4 BTC. Żeby doszło do transakcji, sprzedający muszą uderzyć w ten poziom łącznym wolumenem wystarczającym do obsłużenia wszystkich, którzy ustawili się przed tobą, zanim poziom zostanie anulowany albo rynek ruszy w górę.
Twoje pytanie w backteście nie powinno więc brzmieć „czy cena doszła do 84,120.0”, lecz „czy zlecenia sprzedaży po cenie rynkowej o wolumenie co najmniej 12.4 BTC zostały zrealizowane po 84,120.0, gdy moje zlecenie czekało w kolejce”. To zupełnie różne zdarzenia. W twoich danych występują z częstością różniącą się mniej więcej 3-krotnie.
3 reguły realizacji zleceń, 3 różne strategie
Uruchomiłem ponownie twój sygnał z tymi samymi wejściami i 3 modelami realizacji. Ta sama alfa, te same opłaty, to samo finansowanie. Zmieniła się tylko logika realizacji zleceń.
| Reguła realizacji | Realizacje | Średni edge w ciągu 60 s od realizacji | Sharpe |
|---|---|---|---|
Dotknięcie: low <= limit | 4,180 | +2.6 bp | 2.14 |
Ścisłe przekroczenie: low < limit - 1 tick | 1,712 | +0.4 bp | 0.61 |
| Symulacja kolejki według wolumenu na poziomie | 1,306 | +0.9 bp | 0.77 |
Środkowy wiersz przedstawia prostą poprawkę, po którą wszyscy sięgają w pierwszej kolejności: realizację zlecenia zaliczamy tylko wtedy, gdy rynek przeszedł poniżej twojej ceny. Założenie jest takie, że skoro cena zeszła niżej, musiała zrealizować twoje zlecenie. Kierunkowo to słuszne i eliminuje większość fikcji. Wprowadza też poważne obciążenie, do którego zaraz przejdę.
Dolny wiersz to model, który warto zbudować. Nie wymaga strumienia L3 ani odtwarzania każdej pojedynczej transakcji. Masz już większość potrzebnych danych.
Symulacja kolejki, którą zbudujesz z aggTrades
Pobierz strumień zagregowanych transakcji zamiast klines. Każdy wydruk zawiera cenę, ilość, znacznik czasu i flagę strony maker, która informuje, czy agresorem był kupujący, czy sprzedający. To wystarczy do użytecznej symulacji twojego własnego zlecenia pasywnego:
- Gdy twoje zlecenie trafia do arkusza, zapisz wolumen oczekujących zleceń po twojej cenie. Jeśli masz tylko dane o arkuszu aktualizowane co 100 ms, użyj ostatniego snapshotu; błąd będzie mały w porównaniu z tym, który właśnie naprawiasz.
- Ustaw
queue_ahead = resting_size. Przyjmij pesymistyczne założenie, że jesteś ostatni. Tak właśnie jest, chyba że to ty tworzysz nowy poziom. - Przejdź przez strumień transakcji. Każda transakcja z agresorem po stronie sprzedaży po twojej cenie lub niższej zmniejsza
queue_aheado swój wolumen. Gdy wartość spadnie poniżej zera, twoje zlecenie zostaje zrealizowane po twojej cenie, w tym momencie. - Jeśli cena oddali się o 1 tick, nie zeruj kolejki. Stopniowo ją zmniejszaj. Część zleceń przed tobą zostanie anulowana, gdy poziom straci aktualność, a część pozostanie. Spadek o 30-40% za każdą pełną sekundę poza najlepszym poziomem lepiej pasował do moich rekonstrukcji niż skrajne warianty.
- Jeśli twoja strategia anuluje zlecenie i wystawia je ponownie, modeluj ponowne wystawienie jako nowe zlecenie na końcu nowej kolejki. Ten krok często jest pomijany — i właśnie tam kryje się reszta fikcji.
Przy kroku 2 pewnie będziesz chciał się ze mną spierać. Tak, czasami jesteś blisko początku kolejki, bo złożyłeś zlecenie w chwili utworzenia poziomu. W porządku — mierz to, nie zakładaj. Zapisuj wolumen oczekujących zleceń w chwili złożenia i pozwól danym pokazać, jaki odsetek twoich zleceń faktycznie pojawia się odpowiednio wcześnie. W twojej strategii było to 11%, bo sygnał pojawia się po ruchu, czyli dołączasz do już istniejącego poziomu z czekającym na nim tłumem.
Realizują się te zlecenia, których realizacji wolałbyś uniknąć
Teraz przejdźmy do tego, co naprawdę ma znaczenie i wyjaśnia, dlaczego reguła ścisłego przekroczenia jest obciążona.
Zastanów się, kiedy twój bid zostaje w pełni zrealizowany. Dzieje się tak, gdy presja sprzedaży jest wystarczająco duża, by wyczyścić cały poziom. Z definicji jest to moment, w którym rynek spada przez twoją cenę. Najpewniej realizujesz zlecenie właśnie wtedy, gdy natychmiast znajdujesz się po niewłaściwej stronie rynku.
Podziel realizacje według sposobu, w jaki do nich doszło, i zmierz mark-out po 60 sekundach:
| Rodzaj realizacji | Udział realizacji | Mark-out po 60 s |
|---|---|---|
| Obrót na poziomie, cena odbiła w górę | 38% | +3.1 bp |
| Obrót na poziomie, cena bez zmian | 21% | +0.2 bp |
| Cena przeszła przez poziom o co najmniej 2 ticki | 41% | −2.4 bp |
Twój naiwny backtest uwzględnił wszystkie 3 grupy i potraktował ich realizacje jak darmowe. Reguła ścisłego przekroczenia uwzględnia niemal wyłącznie trzecią grupę, dlatego edge spadł w niej jeszcze bardziej niż w symulacji kolejki. Żadna z tych reguł nie jest poprawna. Symulacja kolejki daje realistyczną mieszankę, a cała gra rozgrywa się właśnie o jej proporcje: pasywna realizacja pozwala zarobić na spreadzie, ale kosztuje cię selekcję negatywną. Stosunek jednego do drugiego wyznacza rzeczywistą jakość twojej strategii.
Stare podejście z giełd akcji nadal się sprawdza: zleceniem maker wystawiasz rynkowi darmową opcję. Ktoś ją wykonuje, gdy mu się to opłaca. Twój backtest inkasował premię i zapominał, że opcja ma też drugą stronę wypłaty.
Niezrealizowane zlecenia zmieniają strategię, a nie tylko jej koszt
To część, nad którą najbardziej chcę, żebyś się zastanowił. Gdy źle modelujesz realizację taker, dostajesz właściwe transakcje po złej cenie i większość problemu rozwiązuje korekta opłat. Gdy źle modelujesz realizację maker, otrzymujesz zupełnie inny zestaw transakcji. Mniej więcej 2,900 z twoich 4,180 wejść w ogóle nie doszło do skutku. Część z nich opierała się na twoich najlepszych sygnałach, na świecach, które wystrzeliły przez poziom i zawróciły — czyli dokładnie w sytuacji, gdy rynek odjechał bez ciebie.
Gałąź strategii, w której zlecenie nie zostało zrealizowane, musi więc mieć prawdziwą logikę. Co robi strategia, gdy zlecenie wejścia nie zostanie zrealizowane, zanim sygnał straci aktualność? Goni cenę zleceniem taker, płacąc spread i wpływ rynkowy? Wystawia zlecenie ponownie niżej i godzi się na inną cenę wejścia? Pomija transakcję i pozostaje bez pozycji? Każda z tych decyzji daje wyraźnie inną krzywą kapitału, a żadna nie brzmi „załóż, że zlecenie zostało zrealizowane”. W naszych testach uczciwa reguła doganiania rynku (przejście przez spread po 20 sekundach bez realizacji, z limitem poślizgu 3 bp) pozwoliła odzyskać około jedną trzecią brakujących transakcji i mniej więcej połowę różnicy między naiwnym Sharpe a Sharpe z symulacji kolejki. To naprawdę interesujący wynik, który pojawia się dopiero wtedy, gdy model realizacji jest na tyle realistyczny, by takie pytanie miało sens.
Prosty test kontrolny, 10 minut: weź log z bieżącego paper tradingu i backtest z tego samego okresu. Porównaj wskaźnik realizacji, nie PnL. Jeśli backtest realizuje 100% oczekujących zleceń, a paper trading 34%, to nie porównujesz strategii — masz błąd w modelu realizacji. Napraw go, zanim spojrzysz na choćby 1 wynik stopy zwrotu.
Jeszcze 2 drobniejsze kwestie
Odrzucenia post-only. Jeśli używasz post-only, żeby zagwarantować sobie stawkę maker, a arkusz zmieni się między podjęciem decyzji a potwierdzeniem z giełdy, zlecenie zostanie odrzucone zamiast trafić do arkusza. W naszych logach paper tradingu dotyczy to 3-6% prób na BTCUSDT w zwykłych godzinach i ponad 15% w minucie po publikacji CPI w USA. Odrzucone zlecenie nie jest zrealizowane ani nie czeka w arkuszu na realizację — to transakcja, która nigdy nie zaistniała. Jeśli twój backtest nie uwzględnia takiego stanu, zawyża liczbę transakcji dokładnie w tych warunkach rynkowych, na których najbardziej ci zależy.
Zapobieganie samotransakcjom i twój własny wpływ na rynek. Przy 0.4 BTC nie poruszasz rynkiem BTCUSDT, więc możesz ten aspekt pominąć. Wspomniałeś jednak, że chcesz uruchomić strategię na kontrakcie perp na altcoina o średniej kapitalizacji, gdzie nominał w najlepszym arkuszu często nie przekracza $15k. Tam twoje zlecenie stanowi istotną część kolejki, a zapisany przez ciebie wolumen zleceń przed tobą uwzględnia wpływ obecności twojego ostatniego zlecenia. Gdy twoje zlecenie przekracza około 10% wolumenu oczekującego na poziomie, symulacja musi brać pod uwagę reakcje innych uczestników na twoją obecność. Szczerze mówiąc, na tym etapie bardziej ufałbym paper tradingowi niż dowolnej symulacji, którą ty lub ja możemy napisać.
Uruchom test ponownie z symulacją kolejki i podeślij mi tabelę mark-outów z podziałem na rodzaj realizacji. Jeśli grupa odbić nadal odpowiada za większość edge, a grupa przekroczeń nie pochłania całego zysku, być może masz coś, co warto sprawdzić na paper tradingu. Jeśli całość wynikała z 2,900 realizacji, których w rzeczywistości byś nie uzyskał, lepiej dowiedzieć się o tym teraz niż po 4 tygodniach obserwowania, jak rachunek paper tradingowy nie robi tego, co obiecywał notebook.
← Wszystkie wpisy


