Pozycja długa od 84,120. Stop na 84,036, target na 84,271. Pojawia się bar kolejnej minuty: otwarcie 84,118, maksimum 84,290, minimum 84,010, zamknięcie 84,240.
Oba poziomy znalazły się w tym barze. Stop został dotknięty i target został dotknięty, a cztery liczby OHLC nie zawierają żadnej informacji o tym, co nastąpiło pierwsze. Backtest i tak zwrócił wynik. Gdzieś w pętli jakaś linijka podjęła decyzję — pewnie taka, o której nie pomyślałeś jako o założeniu modelowym, gdy ją pisałeś.
To największe pojedyncze źródło fałszywych wyników, jakie widzę w strategiach krótkoterminowych — większe niż opłaty i poślizg — bo wcale nie wygląda na założenie. Wygląda na techniczny szczegół implementacji.
Błędne podejście 1: niech decyzję podejmie łańcuch if
Zwykle wygląda to tak:
if bar.high >= target:
exit(target, "tp")
elif bar.low <= stop:
exit(stop, "sl")
Nikt nie wybrał „targety rozstrzygane przed stopami”. Sprawdzenie targetu po prostu wpisano jako pierwsze, bo to korzystny scenariusz i właśnie o nim myślałeś. Zamień kolejność gałęzi, a krzywa kapitału się zmieni. Już samo to powinno ci powiedzieć, że część P&L strategii zależy od tego, jak ułożyłeś kod w edytorze.
Uruchomiłem celowo zwyczajną strategię scalpingową typu mean reversion na kontrakcie perpetual BTCUSDT: 3 miesiące barów 1-minutowych, 4,812 transakcji, stop 0.10% i target 0.18% od ceny wejścia. Te same sygnały, te same opłaty, zmieniła się tylko konwencja rozstrzygania remisu.
Jedna ósma transakcji przesądza o całym wyniku. Taka jest matematyka ciasnej pary stop-target: niejednoznaczne transakcje to te, w których cena poszła w obie strony — a więc większość ciekawych przypadków — i każda z nich jest warta pełną odległość między stopem a targetem, zależnie od rzutu monetą. 12.6% transakcji × 0.28% rozpiętości daje 3.5% obrotu brutto nominałem na jednostkę próby, co znacznie przewyższa faktyczną przewagę strategii.
Częstotliwość zależy od rozmiaru bara w stosunku do odległości między poziomami i szybko rośnie, gdy wydłużasz interwał barów. Te same stopy i targety, te same sygnały, inne interwały:
| Interwał bara | Transakcje, w których oba poziomy znalazły się w jednym barze | Sharpe (najpierw target) |
|---|---|---|
| 1s | 0.3% | 0.91 |
| 1m | 12.6% | 2.31 |
| 5m | 34% | 3.60 |
| 15m | 49% | 4.42 |
| 1h | 71% | 5.88 |
Zastanów się, co właściwie mówi ta tabela. Im grubsze bary, tym lepszy backtest. Intuicja każdego badacza podpowiada, że bary godzinowe są bardziej zachowawczym wyborem: mniej szumu i mniejsze ryzyko dopasowania do mikrostruktury. Jednak przy konwencji intrabarowej, która rozstrzyga na twoją korzyść, grubszy bar to po prostu większe pudełko, w którym możesz założyć, że dopisało ci szczęście. Na barach 1-godzinnych 7 na 10 transakcji rozstrzyga sama konwencja. Taki backtest nie testuje strategii, tylko kolejność 2 instrukcji `if`, powtórzoną 3,400 razy.
Błędne podejście 2: zakładać, że stop został zrealizowany po cenie stopa
Załóżmy, że ustalasz kolejność. Najpierw rozstrzyga się stop, księgujesz stratę dokładnie 0.10% plus opłatę taker i czujesz, że wszystko zostało rzetelnie policzone. Nadal są tu 2 osobne błędy.
Pierwszy polega na tym, że stop to wyzwalacz, a nie cena realizacji. Na Binance USDⓈ-M zlecenie STOP_MARKET staje się zleceniem rynkowym natychmiast po spełnieniu warunku aktywacji, a następnie zjada płynność dostępną w arkuszu. W spokojnej minucie oznacza to poślizg o 1–2 ticki. W minucie, która faktycznie uruchomiła twój stop — tej z korpusem świecy o rozpiętości 40 punktów i kaskadą likwidacji poniżej — arkusz jest płytki dokładnie po tej stronie, po której składasz zlecenie. W mojej próbie dopasowałem aktywacje stopów do taśmy tickowej: mediana realizacji wypadła 1.4 bps za poziomem aktywacji, a 95. percentyl — 11 bps. Przy stopie 10 bps skrajne przypadki kosztują cię dodatkową jedną dziesiątą ryzyka, które, jak sądziłeś, określiłeś.
Drugi błąd jest subtelniejszy i dotyczy kontraktów perpetual: która cena aktywuje zlecenie. Binance domyślnie aktywuje zlecenia stop po cenie mark, wyliczanej na podstawie indeksu i wygładzonej bazy, a nie ostatniej transakcji na danej giełdzie. Twoje dane OHLCV obejmują ostatnią cenę. To 2 różne serie, które najbardziej rozjeżdżają się właśnie podczas zdarzeń aktywujących stopy.
| Ostatnia cena (twoje klines) | Cena mark (domyślny wyzwalacz) | |
|---|---|---|
| Źródło | transakcje na tej giełdzie | indeks z kilku giełd + baza |
| Zachowanie cienia | pełne odchylenie | mocno tłumione |
| Typowa rozbieżność | 1–3 bps w spokojnych warunkach, 20–35 bps w minucie kaskady | |
| Konsekwencja dla backtestu | stopy aktywowane, choć nie powinny, i odwrotnie | |
W backteście cień o rozpiętości 25 bps na taśmie ostatnich cen aktywuje stop, podczas gdy cena mark na żywo nawet nie zbliża się na odległość 10 bps do poziomu aktywacji. Albo odwrotnie — w dniu, gdy porusza się indeks, a twoja giełda reaguje z opóźnieniem. Ustawienie workingType na CONTRACT_PRICE pozwala przynajmniej dopasować zachowanie na żywo do danych i dla badacza zwykle jest to właściwy wybór. Rzetelna symulacja aktywacji po cenie mark wymaga bowiem uwzględnienia drugiej serii w całym silniku realizacji zleceń.
Najlepiej pamiętam taki przypadek: ktoś z naszego zespołu „ulepszył” strategię, przesuwając take-profit z 0.18% na 0.21%. Sharpe wzrósł z 2.3 do 3.1. Żadnej nowej przewagi. Target przesunął się po prostu poza gęstą część rozkładu cieni na barach 1-minutowych, więc mniej transakcji trafiało do niejednoznacznej puli, w której kod po cichu przyznawał im wygraną. Zoptymalizowali regułę rozstrzygania remisu.
Błędne podejście 3: zawsze zakładać najgorszy scenariusz i nazywać to zachowawczością
Odruchowe rozwiązanie to pesymizm. Jeśli dotknięte zostały oba poziomy, przyjmij stop. Gotowe, koniec z optymizmem, można wdrażać.
Kiedyś tak robiłem. To lepsze niż alternatywa, ale nadal błędne — z 2 powodów.
W ten sposób odrzucasz strategie, które są w porządku. Pesymistyczne rozstrzygnięcie 12.6% transakcji kosztowało tę strategię 2.1 punktu Sharpe’a w porównaniu z wynikiem 0.94 ustalonym na podstawie ticków. Jeśli prawdziwy wynik wynosi 0.94, a twoja konwencja pokazuje 0.18, porzucisz pomysł i zajmiesz się czymś gorszym. Zachowawczość myląca się o 2 punkty Sharpe’a to nie zachowawczość, tylko szum przybierający moralną pozę.
Co gorsza, zniekształca to optymalizację. Daj optymalizatorowi siatki parametrów pesymistyczną regułę rozstrzygania remisu, a nauczy się unikać niejednoznaczności, bo ta staje się czystą karą. Zacznie wybierać szerokie stopy i bliskie targety albo wolniejsze interwały, na których oba poziomy rzadko trafiają do tego samego bara, i zaproponuje parametry dobrane pod twoją konwencję realizacji zleceń, a nie pod rynek. To ta sama porażka co przy korzystnej wersji, tylko ze znakiem przeciwnym — równie niewidoczna w raporcie wyników.
Nasza podstawowa zasada: jeśli stop_distance + target_distance jest mniejszy od 75. percentyla zakresu twojego interwału barów, założenie intrabarowe ma większy wpływ na P&L niż sygnał. Oblicz obie wartości. To 4 linijki kodu, a ten jeden test zakończył więcej przeglądów strategii niż jakikolwiek inny.
Co naprawdę działa
Przebieg ceny wewnątrz bara to dane. Pobierz je albo oszacuj granice tego, czego nie możesz uzyskać.
- Rozstrzygaj na najdokładniejszej dostępnej serii. Binance aggTrades dla odpowiednich minut to kilkaset wierszy, które rozstrzygają sprawę: który poziom został dotknięty jako pierwszy i po jakiej cenie zrealizowano zlecenie podczas szybkiego ruchu. Nie potrzebujesz danych tickowych z całego backtestu, tylko z niejednoznacznych barów. W mojej próbie było to 606 minut z 129,600. To niewielki plik do pobrania, a nie projekt infrastrukturalny.
- Jeśli nie masz danych tickowych, do samego rozstrzygania zejdź o 1 lub 2 poziomy interwału. Sygnały na 15m, wyjścia rozstrzygane na barach 1s lub 1m. Niejednoznaczność spada z 49% do ułamka procenta, a pozostałość jest na tyle mała, że można ją uczciwie pominąć.
- Zawsze pokazuj przedział wyników. Uruchom każdy backtest 2 razy, przy optymistycznym i pesymistycznym rozstrzyganiu, i wypisz oba wyniki Sharpe’a obok wyniku ustalonego na podstawie danych. Ta różnica pokazuje niepewność intrabarową i powinna znaleźć się w raporcie wyników obok przedziału ufności dla samego Sharpe’a. Gdy przedział wynosi 0.2–2.3, żaden wniosek mieszczący się w jego granicach nie jest wiarygodny.
- Traktuj poziom niejednoznaczności jako podstawową metrykę. U nas widnieje na górze każdej karty strategii, obok liczby transakcji i obrotu. Wynik powyżej około 5% oznacza, że testujesz logikę wyjścia, a nie logikę wejścia.
- Modeluj aktywację oddzielnie od realizacji. Aktywuj zlecenie na podstawie serii cen faktycznie używanej przez giełdę, a realizację modeluj jako cenę aktywacji plus poślizg wylosowany z rozkładu skalibrowanego na podstawie taśmy, zamiast przyjmować cenę aktywacji.
Na rynku akcji ten sam problem przybiera inną postać. Stop ustawiony na 62.00 dla spółki, której kurs otwiera się po nocnej luce na poziomie 58.40, nie zostanie zrealizowany po 62.00, lecz gdzieś poniżej ceny otwarcia. Backtest na barach dziennych, który przy luce poniżej stopa zakłada poślizg −$0.00, chętnie pokaże, że dodatkowy stop-loss ograniczył obsunięcie. Tak się nie stało. Po prostu strategia nie została przetestowana w dniach, które mają znaczenie. Zawieszenia notowań działają podobnie: zlecenie stop faktycznie zostaje rozliczone podczas aukcji ponownego otwarcia, po cenie, której minimum bara nie pokazuje.
Żadna z tych rzeczy nie jest egzotyczna. Chodzi o to, by pamiętać, że bar jest podsumowaniem, a strategia oparta na stopie i targecie zakłada określoną kolejność zdarzeń pominiętą w tym podsumowaniu. Gdy silnik paper tradingu w końcu uruchomi strategię na danych z rynku na żywo, taśma rozstrzygnie ich kolejność i ani razu nie będzie jej obchodzić, którą gałąź instrukcji if wpisałeś jako pierwszą.
← Wszystkie wpisy

