Funcția se numea enter_pack. Asta ar fi trebuit să fie avertismentul. Un agent de research scrisese un cash-and-carry pe BTC: când perpetualul se tranzacționa cu mai mult de opt puncte de bază peste spot, cumpără spot, shortează perp-ul, stai nemișcat. Funding-ul și basis-ul făceau restul. Backtestul executa ambele picioare în aceeași milisecundă, la mid, plus un tick de politețe. Curba de equity era o scară. Nimeni n-ar fi numit-o palpitantă. Asta era punctul de vânzare.
L-am promovat pe paper într-o luni.
Luni: pachetul care se executa mereu
Primul semnal a picat la 11:17 UTC. Basis-ul de pe ecran era 9.4 bps, perp-ul scump. În backtest, enter_pack a returnat două fill-uri și un rezidual de zero. Pe paper a returnat un fill pe spot de 0.40 BTC la ask și un short în așteptare pe perp care nu s-a tranzacționat.
Spot-ul plătise un comision taker de 4 bp și cam 1 bp de spread. Prețul de perp pe care îl voiam era încă în carte, apoi nu mai era. Douăzeci de secunde mai târziu, basis-ul cotat era 5 bps, pentru că tocmai ridicasem spot-ul. Patruzeci de secunde mai târziu, perp-ul s-a executat, după mișcare. Aveam pachetul. Aveam pachetul la un basis care nu mai plătea intrarea și ieșirea.
Backtestul ar fi înregistrat „+9.4 bps carry, rezidual 0.” Blotter-ul avea 4 bps de cost taker pe spot, un long neacoperit de 40 de secunde și un gap de intrare de aproximativ 5. Aveam un eșantion de unu și, totuși, prea multă încredere în scară.
Marți: am deschis funcția
enter_pack lua o listă de picioare și un mid țintă. Itera, executa fiecare picior la mid * (1 ± slip) și scria un rând de portofoliu. Nu exista un al doilea timestamp. Nu exista o ramură în care piciorul doi să nu existe. Testul unitar aserta residual == 0.
Ăsta e un test care te face să te simți om mare. Nu o să pice niciodată.
Am spart pachetul în două ordine, fiecare cu starea lui: submitted, partial, filled, canceled. Incompletul a devenit un obiect de primă clasă. Replay-ul istoric tot n-avea o carte reală, așa că am trișat. Întârziem al doilea fill cu o extragere din așteptarea de luni (medie 26 de secunde, o coadă care nu-mi plăcea) și îi deplasăm prețul cu ce făcea spot-ul în fereastra aia. Rudimentar, dar scara a căpătat crestături înainte de prânz: pachete care intrau la 3 bps, pachete care nu deveneau niciodată pachete.
Miercuri: trei secunde, apoi îl arunci
Dacă al doilea picior nu e pe poziție în trei secunde, închizi primul. Ar fi trebuit să începem de acolo. Sună conservator până îi pui preț abort-ului. Închiderea înseamnă că ai traversat spot-ul ca să intri și îl traversezi ca să ieși. Două comisioane taker, două spread-uri, fără carry, fără funding. În replay-ul rescris, abort-urile astea nu erau rare. Se aglomerau pe barele unde basis-ul părea cel mai „real”, care sunt de obicei barele unde deja trece ceva prin spot.
Până miercuri seara aveam șaptesprezece semnale pe paper cu care să ne certăm.
| Backtest, luni dimineața | Paper, până miercuri | |
|---|---|---|
| Intrare | Ambele picioare la mid, un timestamp | Spot la ask; perp 5–40s mai târziu, sau niciodată |
| Rezidual | Întotdeauna 0 | Neacoperit pe 10 din 17 semnale |
| Comisioane pe un trade activ | Două taker | Două taker, sau patru dacă abortăm |
| Driver de PnL | Basis + funding | Ce făcea spot-ul cât așteptam |
Un coleg încă are un notebook care tratează pachetul ca pe un produs listat, BTC_CARRY, cu o „taxă de execuție” fixă de 2 bp. Îmi plăcea notebook-ul ăla. Producea aceeași scară ca agentul. O taxă e o medie. Orfanii sunt o distribuție. Mediile nu sunt călcate în picioare într-o oră subțire; 0.40 BTC de spot rămas, da.
Joi: restul, apoi cafeaua
Trei secunde abortau aproape tot odată ce așteptam și la coadă. Am slăbit paper-ul la cincisprezece. La 02:11 UTC joi, spot-ul s-a executat și perp-ul nu. Eram încă în faza de doliu „o să stea, o să se ridice” când m-am dus să fac cafea.
02:11:08 filled BTCUSDT 0.40 buy
02:11:08 submitted BTCUSDT-PERP 0.40 sell, post-only
02:11:41 încă în lucru
02:26:00 mark rezidual −18.1 bps
BTC a scăzut 18 bps într-o fereastră liniștită. Rezidualul era tot trade-ul. Funding-ul, când s-a printat, a creditat un perp pe care nu-l aveam. Spot-ul stătea acolo, long. Traiectoria overnight a backtestului original nu conținuse niciodată o poziție pe un singur picior, deci nu pusese niciodată întrebarea pe care o punem oricărei alte cărți: care e cea mai proastă bară pe care o poate printa restul ăsta și unde e stop-ul pentru o poziție pe care n-am vrut s-o ținem?
Cele șapte pachete complete nu erau nici ele eroi. Până s-a printat al doilea picior, propriul nostru lift pe spot apăsase deja pe index, iar basis-ul de intrare era cu 3–5 bps mai prost decât snapshot-ul care declanșase semnalul. Pragul de opt bps era un număr despre un ecran.
Ce-am sări data viitoare
N-am mai livra un multi-leg ca o singură funcție de fill. Aceeași regulă pentru un carry perp-spot, un calendar sau verticala de call pe care un agent o trimite ca două limite și apoi sărbătorește când ambele „se execută” la mid.
- Stare de ordin per picior, inclusiv partials. Tratează pachetul ca pe un părinte a două tichete.
- Un timer max-orphan care chiar se declanșează și un traseu de închidere care plătește două spread-uri la ieșire.
- Mark-to-book pe rezidual, cu un stop dimensionat pentru rest. Netul pachetat e noționalul greșit.
- Fără carry creditat, fără funding acumulat, până sunt ambele picioare pe poziție. Incomplet e incomplet.
- Refuză mid-on-mid ca default pentru orice are două tichete. Dacă trebuie să ghicești, ghicește mai prost pe piciorul subțire și mai târziu pe al doilea fill.
Și n-am lăsa test_pack_is_atomic în repo. Atomic e o proprietate a unui matching engine, nu a unei funcții helper. BTC spot și BTC perpetual sunt două cărți care rimează uneori. Paper e locul unde încetează să rimeze, și ăsta e singurul motiv pentru care scara a fost vreodată interesantă.



