Funkcja nazywała się enter_pack. To powinien był być sygnał ostrzegawczy. Agent badawczy napisał BTC cash-and-carry: gdy perpetual notuje więcej niż osiem punktów bazowych ponad spotem, kup spot, shortuj perpa, siedź nieruchomo. Funding i basis zrobią resztę. Backtest wypełniał obie nogi w tej samej milisekundzie, po midzie, plus tick uprzejmości. Krzywa kapitału była schodkami. Nikt by tego nie nazwał ekscytującym. Właśnie to było zaletą.
W poniedziałek przenieśliśmy to na paper.
Poniedziałek: pakiet, który zawsze się wypełniał
Pierwszy sygnał wpadł o 11:17 UTC. Basis na ekranie wynosił 9.4 bps, perp drogi. W backteście enter_pack zwróciło dwa fille i residual równy zero. W paper zwróciło fill spota 0.40 BTC po asku i oczekujący short na perpie, który się nie zrealizował.
Spot zapłacił 4 bp prowizji taker i około 1 bp spreadu. Cena perpa, której chcieliśmy, jeszcze była w arkuszu, a potem już nie. Dwadzieścia sekund później kwotowany basis wynosił 5 bps, bo właśnie podnieśliśmy spota. Czterdzieści sekund później perp się wypełnił, po ruchu. Pakiet się domknął. Pakiet się domknął przy basisie, który już nie płacił za wejście i wyjście.
Backtest zaksięgowałby „+9.4 bps carry, residual 0.” Blotter miał 4 bps kosztu taker na spocie, 40-sekundową niezabezpieczoną długą i lukę wejścia rzędu 5. Miałem próbkę z jednej obserwacji i mimo to za dużo wiary w schodki.
Wtorek: otworzyliśmy funkcję
enter_pack brało listę nóg i docelowy mid. W pętli wypełniało każdą nogę po mid * (1 ± slip) i zapisywało jeden wiersz portfela. Nie było drugiego timestampu. Nie było gałęzi, w której druga noga nie istniała. Test jednostkowy asertował residual == 0.
To taki test, przy którym czujesz się jak dorosły. Nigdy nie padnie.
Rozbiliśmy pakiet na dwa zlecenia z własnym stanem: złożone, częściowe, wypełnione, anulowane. Incomplete stało się obiektem pierwszej klasy. Historyczne odtwarzanie wciąż nie miało prawdziwego arkusza, więc oszukaliśmy. Opóźnij drugi fill o losowanie z poniedziałkowego czekania (średnia 26 sekund, ogon, którego nie lubiłem) i przesuń jego cenę o to, co zrobił spot w tym oknie. Grubo ciosane, ale schodki dostały wcięcia jeszcze przed lunchem: pakiety, które weszły po 3 bps, pakiety, które nigdy nie stały się pakietami.
Środa: trzy sekundy, potem zrzut
Jeśli druga noga nie wskakuje w ciągu trzech sekund, spłaszcz pierwszą. Powinniśmy byli od tego zacząć. Brzmi konserwatywnie, dopóki nie wycenisz abortu. Spłaszczenie znaczy, że przeszedłeś spread na spocie, żeby wejść, i przechodzisz go, żeby wyjść. Dwie prowizje taker, dwa spready, żadnego carry, żadnego fundingu. W przepisanym replayu te aborty nie były rzadkie. Skupiały się na barach, na których basis wyglądał najbardziej „prawdziwie”, a to zwykle bary, na których przez spota już coś płynie.
Do środy wieczorem mieliśmy siedemnaście papierowych sygnałów, o które można było się spierać.
| Backtest, poniedziałek rano | Paper, do środy | |
|---|---|---|
| Wejście | Obie nogi po midzie, jeden timestamp | Spot po asku; perp 5–40s później albo nigdy |
| Residual | Zawsze 0 | Niezabezpieczone na 10 z 17 sygnałów |
| Prowizje przy działającej transakcji | Dwie prowizje taker | Dwie prowizje taker albo cztery, jeśli abortujemy |
| Źródło PnL | Basis + funding | To, co zrobił spot, póki czekaliśmy |
Kolega wciąż ma notebook, który traktuje pakiet jak notowany produkt, BTC_CARRY, z płaskim 2 bp „podatkiem od egzekucji”. Lubiłem ten notebook. Produkował te same schodki co agent. Podatek to średnia. Sieroty to rozkład. Średnie nie zostają rozjechane w cienkiej godzinie; 0.40 BTC osieroconego spotu może.
Czwartek: osierocona pozycja, potem kawa
Trzy sekundy abortowały niemal wszystko, gdy dodatkowo czekaliśmy w kolejce. Poluzowaliśmy paper do piętnastu. O 02:11 UTC w czwartek spot się wypełnił, a perp nie. Byłem wciąż w fazie żałoby „postoi, ktoś podniesie”, gdy poszedłem zrobić kawę.
02:11:08 wypełniono BTCUSDT 0.40 kupno
02:11:08 złożono BTCUSDT-PERP 0.40 sprzedaż, post-only
02:11:41 wciąż aktywne
02:26:00 wycena residual −18.1 bps
BTC spadł o 18 bps w spokojnym oknie. Residual był całą transakcją. Funding, gdy się wydrukował, uznał perpa, którego nie mieliśmy. Spot po prostu siedział i był longiem. Oryginalna ścieżka overnight backtestu nigdy nie zawierała pozycji jednonogiej, więc nigdy nie zadała pytania, które zadajemy każdej innej księdze: jaki najgorszy bar może wydrukować ta osierocona pozycja i gdzie jest stop dla pozycji, której nie mieliśmy zamiaru trzymać?
Siedem pełnych pakietów też nie było bohaterami. Zanim wydrukowała się druga noga, nasze własne podniesienie spotu zdążyło już przechylić indeks, a basis wejścia był o 3–5 bps gorszy niż snapshot, który odpalił sygnał. Próg ośmiu bps był liczbą o ekranie.
Czego następnym razem byśmy nie robili
Nie wypuścilibyśmy multi-leg jako jednej funkcji fill. Ta sama zasada dla carry perp-spot, kalendarza albo call vertical, które agent składa jako dwa limity i świętuje, gdy oba „się wypełniają” po midzie.
- Stan zlecenia per noga, łącznie z partialami. Traktuj pakiet jako rodzica dwóch ticketów.
- Timer max-orphan, który naprawdę odpala, i ścieżka flatten, która płaci dwa spready przy wyjściu.
- Mark-to-book na residualu, ze stopem dobranym do osieroconej pozycji. Spakowany net to zły nominał.
- Żadnego uznanego carry, żadnego naliczonego fundingu, dopóki obie nogi nie są na pozycji. Incomplete jest incomplete.
- Odrzuć mid-on-mid jako default dla czegokolwiek, co ma dwa tickety. Jeśli musisz zgadywać, zgaduj gorzej na cienkiej nodze i później na drugim fillu.
I nie wpuścilibyśmy test_pack_is_atomic do repo. Atomic to cecha silnika kojowania, nie funkcji pomocniczej. BTC spot i BTC perpetual to dwa arkusze, które czasem się rymują. Paper to miejsce, w którym przestają się rymować, i to jedyny powód, dla którego schodki kiedykolwiek były ciekawe.



