Long de la 84,120. Stop la 84,036, țintă la 84,271. Vine bara următorului minut: deschidere 84,118, maxim 84,290, minim 84,010, închidere 84,240.
Ambele niveluri sunt în interiorul acelei bare. Stopul a fost atins și ținta a fost atinsă, iar cele patru valori OHLC nu conțin absolut nicio informație despre care a fost atinsă prima. Totuși, backtestul tău a returnat un rezultat. Undeva în buclă, o linie a decis — probabil o linie pe care nu ai considerat-o o presupunere de modelare când ai scris-o.
Aceasta este cea mai mare sursă de performanță fictivă pe care o văd în strategiile cu orizont scurt, înaintea comisioanelor și a slippage-ului, fiindcă nu pare o presupunere. Pare doar infrastructură.
Modul greșit unu: lași lanțul if să decidă
Forma obișnuită:
if bar.high >= target:
exit(target, "tp")
elif bar.low <= stop:
exit(stop, "sl")
Nimeni nu a ales „ținta se execută înaintea stopului”. Verificarea țintei a fost doar scrisă prima, fiindcă acesta e scenariul favorabil și la el te gândeai. Inversează cele două ramuri și curba capitalului se schimbă; numai asta ar trebui să-ți arate că P&L-ul strategiei depinde parțial de editorul tău.
Am rulat un scalper de revenire la medie cât se poate de obișnuit pe perpetuul BTCUSDT, pe 3 luni de bare de 1 minut, 4,812 tranzacții, stop la 0.10% și țintă la 0.18% față de intrare. Aceleași semnale, aceleași comisioane, s-a schimbat doar convenția de departajare.
O optime din tranzacții poartă întregul rezultat. Așa funcționează aritmetica unei perechi strânse de stop și țintă: tranzacțiile ambigue sunt cele în care prețul s-a mișcat în ambele direcții — adică majoritatea celor interesante — iar fiecare valorează distanța completă dintre stop și țintă, în funcție de aruncarea monedei. 12.6% din tranzacții × 0.28% din distanță înseamnă 3.5% din rulajul noțional brut per unitate de eșantion, cu mult peste avantajul real al strategiei.
Rata depinde de dimensiunea barei în raport cu distanța dintre niveluri și se înrăutățește rapid când folosești bare mai mari. Același stop și aceeași țintă, aceleași semnale, reeșantionate:
| Intervalul barei | Tranzacții cu ambele niveluri în interiorul unei bare | Sharpe (ținta prima) |
|---|---|---|
| 1s | 0.3% | 0.91 |
| 1m | 12.6% | 2.31 |
| 5m | 34% | 3.60 |
| 15m | 49% | 4.42 |
| 1h | 71% | 5.88 |
Uită-te la ce spune de fapt tabelul. Barele mai grosiere au făcut backtestul să arate mai bine. Intuiția oricărui cercetător e că barele orare sunt alegerea prudentă: mai puțin zgomot, mai puțin overfitting la microstructură. Dar, cu o convenție intrabar care rezolvă în favoarea ta, o bară mai grosieră este doar o cutie mai mare în care poți presupune că ai avut noroc. Pe bare de 1h, 7 din 10 tranzacții depind pur și simplu de convenție. Backtestul nu testează o strategie, ci ordinea a două instrucțiuni `if`, de 3,400 de ori.
Modul greșit doi: presupui că stopul s-a executat la prețul stopului
Să presupunem că ai rezolvat problema ordinii. Stopul se execută primul, înregistrezi o pierdere de exact 0.10% plus comision taker și te simți riguros. Tot mai sunt greșite două lucruri distincte.
Primul: un stop este un declanșator, nu o execuție. Pe Binance USDⓈ-M, un ordin STOP_MARKET devine ordin market în clipa în care este îndeplinită condiția de declanșare, apoi consumă lichiditatea disponibilă în registrul de ordine. Într-un minut liniștit, asta înseamnă un tick sau două de slippage. În minutul care ți-a declanșat efectiv stopul — cel cu o lumânare de 40 de puncte și un val de lichidări dedesubt — registrul e subțire tocmai pe partea pe care o traversezi. În eșantionul meu, comparând declanșările stopurilor cu datele tick, execuția mediană a fost la 1.4 bps peste nivelul de declanșare, iar percentila 95 a fost la 11 bps. La un stop de 10 bps, coada distribuției te costă încă o zecime din riscul pe care credeai că l-ai definit.
Al doilea lucru e mai subtil și specific contractelor perpetue: ce preț declanșează ordinul. Binance setează implicit ordinele stop să folosească prețul mark, iar prețul mark este calculat pornind de la indice plus o bază netezită, nu de la ultima tranzacție de pe platforma respectivă. Seria ta OHLC folosește ultimul preț. Sunt serii diferite, iar divergența dintre ele este cea mai mare tocmai în timpul evenimentelor care declanșează stopurile.
| Ultimul preț (klines-urile tale) | Prețul mark (declanșator implicit) | |
|---|---|---|
| Sursa | tranzacții pe această platformă | indice din mai multe platforme + bază |
| Comportamentul fitilurilor | întreaga mișcare | puternic atenuat |
| Divergență tipică | 1–3 bps în perioade calme, 20–35 bps într-un minut cu lichidări în cascadă | |
| Consecința pentru backtest | stopuri declanșate deși n-ar fi trebuit și viceversa | |
Așadar, un fitil de 25 bps în seria ultimului preț te scoate din poziție în backtest, deși prețul mark live nu s-a apropiat niciodată la mai puțin de 10 bps de declanșator. Sau se întâmplă invers, în ziua în care se mișcă indicele și platforma ta rămâne în urmă. Dacă setezi workingType la CONTRACT_PRICE, aliniez cel puțin comportamentul live cu datele tale, iar pentru un cercetător aceasta este de obicei alegerea potrivită, fiindcă simularea corectă a declanșării la prețul mark presupune să treci o a doua serie prin întregul motor de execuție.
Cel mai bine îmi amintesc un caz de genul acesta: cineva din echipa noastră a „îmbunătățit” o strategie mutând take-profit-ul de la 0.18% la 0.21%. Sharpe a crescut de la 2.3 la 3.1. Niciun avantaj nou. Ținta se mutase pur și simplu în afara zonei dense a distribuției fitilurilor de 1 minut, așa că mai puține tranzacții ajungeau în categoria ambiguă în care codul le acorda pe ascuns victoria. Optimizaseră departajarea.
Modul greșit trei: presupui mereu scenariul cel mai rău și-i spui prudență
Reacția instinctivă este pesimismul. Dacă sunt atinse ambele niveluri, iei stopul. Gata, nu mai există optimism, poți lansa.
Și eu făceam asta. E mai bine decât alternativa, dar tot greșit, din două motive.
Elimină strategii care sunt în regulă. O rezolvare pesimistă pentru 12.6% dintre tranzacții a costat această strategie 2.1 puncte Sharpe față de 0.94 rezolvat pe date tick. Dacă valoarea reală este 0.94, iar convenția ta raportează 0.18, arunci ideea și treci la ceva mai slab. Prudența care greșește cu 2 puncte Sharpe nu e prudență; e zgomot cu o atitudine morală.
Mai rău, corupe optimizarea. Dă-i unui proces de optimizare a parametrilor o regulă pesimistă de departajare, iar optimizatorul învață să evite ambiguitatea, fiindcă ambiguitatea devine o penalizare pură. Va favoriza stopuri largi și ținte apropiate sau bare lente, pe care cele două niveluri se suprapun rar, apoi îți va prezenta parametri aleși în raport cu convenția ta de execuție, nu cu piața. E același eșec ca în varianta generoasă, doar cu semnul opus, la fel de invizibil în raportul de performanță.
Regula orientativă pe care o folosim înainte de orice: dacă stop_distance + target_distance este mai mic decât percentila 75 a intervalului barei tale, presupunerea intrabar are o contribuție mai mare la P&L decât semnalul tău. Calculează ambele valori. Durează 4 linii de cod și a încheiat mai multe evaluări de strategii decât orice altă verificare individuală.
Ce funcționează de fapt
Traseul din interiorul barei este o dată. Obține-o sau delimitează ce nu poți obține.
- Rezolvă folosind cea mai granulară serie disponibilă. aggTrades de la Binance pentru minutele relevante înseamnă câteva sute de rânduri și lămurește fără echivoc întrebarea: care nivel a fost atins primul și la ce preț s-a executat ordinul în mișcarea rapidă. Nu ai nevoie de date tick pentru tot backtestul, ci doar pentru barele ambigue. În eșantionul meu, acestea au fost 606 minute din 129,600. O descărcare mică, nu un proiect de infrastructură.
- Dacă nu ai date tick, coboară 1 sau 2 intervale de timp doar pentru rezolvare. Semnale pe 15m, ieșiri rezolvate pe bare de 1s sau 1m. Ambiguitatea scade de la 49% la o fracțiune de procent, iar partea rămasă e suficient de mică încât să o poți ignora cu bună-credință.
- Raportează întotdeauna intervalul de rezultate. Rulează fiecare backtest de două ori, cu rezolvare optimistă și pesimistă, și afișează ambii Sharpe alături de cel rezolvat. Diferența dintre ei este incertitudinea intrabar și trebuie să apară în raportul de performanță lângă intervalul de încredere pe care l-ai include pentru Sharpe însuși. Când intervalul este 0.2–2.3, nicio concluzie din interiorul lui nu este reală.
- Urmărește rata de ambiguitate ca indicator de bază. La noi apare în partea de sus a fiecărui card de strategie, lângă numărul de tranzacții și rulaj. O rată de peste aproximativ 5% înseamnă că logica de ieșire, nu cea de intrare, este ceea ce testezi.
- Modelează separat declanșatorul și execuția. Declanșează pe seria de preț folosită efectiv de platformă; execută la nivelul de declanșare plus un slippage generat dintr-o distribuție calibrată pe datele tape, nu la prețul de declanșare.
La acțiuni, aceeași problemă apare sub altă formă. Un stop la 62.00 pentru o acțiune care deschide la 58.40 după un gap peste noapte nu se execută la 62.00; se execută undeva sub prețul de deschidere, iar un backtest pe bare zilnice care înregistrează un slippage de −$0.00 la gap-uri peste stop îți va spune cu seninătate că o plasă de siguranță cu stop-loss ți-a îmbunătățit drawdown-ul. N-a făcut-o. Pur și simplu nu a fost testată în zilele care contează. Același lucru se întâmplă la suspendări: licitația de reluare este momentul în care stopul tău se execută efectiv, la un preț pe care minimul barei nu-l arată niciodată.
Nimic din toate acestea nu e exotic. Înseamnă să recunoști că o bară este un rezumat, iar o strategie cu stop și țintă este un pariu pe ordinea evenimentelor eliminate din acel rezumat. Când motorul de paper trading rulează în sfârșit strategia pe un tape live, tape-ul are propria părere despre acea ordine și nu i-a păsat niciodată ce ramură a instrucțiunii if ai scris prima.
← Toate articolele


