ProCat Solutions
Kripto kereskedő és automatizáló botok: amit a Web3 után építettünk
Tőzsdei REST és WebSocket API-k, megbízás-életciklus, rate limitek, kockázatkezelés és monitorozás: mérnöki tapasztalatok kripto botok fejlesztéséből.
Ez a cikk nem befektetési tanácsadás. Mi szoftvert építünk: kereskedési és automatizáló botok infrastruktúráját, nem pedig kereskedési stratégiát az ügyfelek pénzére. A stratégia mindig a megbízóé, a mi feladatunk az, hogy a rendszer pontosan, kiszámíthatóan és biztonságosan azt csinálja, amit a stratégia előír. Az alábbiakban azt foglaljuk össze, milyen mérnöki kérdések kerültek elő, amikor a smart contract és token presale projektek után a tőzsdei automatizálás felé fordultunk.
Tőzsdei API-k: REST és WebSocket együtt
Szinte minden centralizált tőzsde két csatornát ad. A REST API-n keresztül adunk le és vonunk vissza megbízást, kérdezzük le az egyenleget és az előzményeket. A WebSocket csatornán érkeznek az árfolyamok, az order book frissítései és a saját megbízásaink állapotváltozásai. A két csatorna nincs szinkronban: előfordul, hogy a WebSocketen már látjuk a teljesülést, a REST még “nyitott” állapotot mutat, vagy fordítva.
A gyakorlati megoldásunk: a WebSocket az elsődleges forrás az állapothoz, de periodikus REST-egyeztetés (reconciliation) fut mögötte, és eltérés esetén a tőzsde REST-válaszát tekintjük igazságnak. A WebSocket kapcsolatot heartbeat figyeli, és újracsatlakozáskor teljes snapshotot kérünk, nem csak a folytatást, mert a kimaradt üzenetek pótlása tőzsdénként másképp működik, és ritkán megbízható.
Megbízás-életciklus, idempotencia és rate limitek
Egy megbízás állapotgépe egyszerűnek látszik (létrehozva, nyitott, részben teljesült, teljesült, visszavont, elutasított), a nehézséget az átmenetek jelentik. Mi történik, ha a REST hívás timeoutol? Nem tudjuk, hogy a megbízás bekerült-e. Ezért minden megbízáshoz saját kliensoldali azonosítót rendelünk, amit a tőzsdék többsége elfogad, így egy ismétlés nem hoz létre duplikátumot, hanem visszaadja a már létező megbízást.
Az összes állapotváltozást append-only eseménynaplóba írjuk PostgreSQL-ben, és az aktuális állapot ebből származtatott. Ha bármi vitás, az eseménysorból rekonstruálható, mi történt és mikor.
A másik korlát, amibe minden bot beleszalad, a rate limit. A tőzsdék súlyozott rate limiteket alkalmaznak: egy order book lekérés többe kerül, mint egy ping. A limit túllépése rövid tiltást hoz, ami épp akkor fáj, amikor gyorsan kellene visszavonni egy megbízást. Ezért:
- kliensoldali token bucket számolja a fogyasztást, a tőzsde saját fejléceiből visszaolvasva;
- a kritikus műveletek (visszavonás, kill switch) számára tartalék keretet hagyunk, amit a lekérdezések nem használhatnak el;
- hibánál exponenciális backoff jitterrel, és a 429-es válasz után a tőzsde által kért várakozást tartjuk be, nem a sajátunkat.
Kockázatkezelés a szoftver szintjén
A kockázatkezelés nálunk nem a stratégia része, hanem attól független, alatta futó réteg, amit a stratégia nem tud megkerülni:
- pozíciólimitek eszközönként és összesítve, a limit fölött a rendszer nem ad le nyitó megbízást;
- maximális napi veszteség, amelynek elérésekor a bot csak záró megbízásokat enged;
- kill switch: egyetlen parancs, ami minden nyitott megbízást visszavon, és a botot passzív módba teszi, amit külön csatornán (nem a bot saját webes felületén) is el lehet érni;
- paper trading mód, ahol minden ugyanúgy fut, csak a megbízások egy szimulált végrehajtóhoz mennek.
A paper trading mód nem opcionális kényelmi funkció, hanem a tesztelés alapja: minden kódváltozás először ott fut napokig, valós adatfolyamon.
Monitorozás és titkok
Egy bot, ami csendben leáll, rosszabb, mint egy, ami hangosan hibázik. Ezért a bot állapotát heartbeat metrikák jelzik (utolsó árfolyam-frissítés kora, nyitott megbízások száma, kapcsolat állapota), és riasztás megy, ha bármelyik a várt tartományon kívül esik, vagy ha az adatfolyam elakad, még ha nem is jött hibaüzenet. A riasztás több csatornán megy ki, mert a bot maga nem tudhatja, melyik csatorna él éppen.
Az API-kulcsok soha nem kerülnek a repóba és a Docker image-be. Titkos tárolóból, indításkor, környezeti változóként érkeznek, a kulcsok jogosultsága minimális (kereskedés igen, kivétel nem), és IP-korlátozottak. A visszavonhatóság fontosabb, mint a titkosítás: egy kompromittált kulcsot percek alatt cserélni kell tudni.
Determinizmus és a backtesting csapdái
A backtesting csábító, mert szép görbéket mutat, de az eredmény csak annyira jó, mint a feltételezések. A tipikus hibák, amiket a szoftver oldaláról kezelni tudunk:
- look-ahead: a stratégia olyan adatot használ, ami az adott pillanatban még nem volt elérhető, ezért a backtesting motor csak időbélyeg szerint “már megérkezett” adatot szolgáltat;
- csúszás és díjak: a szimulált végrehajtó nem a középárfolyamon teljesít, hanem az order book alapján, díjakkal;
- adathiány: a hiányzó gyertyákat nem interpoláljuk, hanem jelöljük, és a stratégia számára is látható;
- nem determinisztikus futás: ugyanaz a bemenet mindig ugyanazt az eredményt adja, mert a véletlenszerűséget seedeljük és az időt injektáljuk, nem a rendszerórából olvassuk.
Ez a réteg azt garantálja, hogy a backtest reprodukálható és őszinte. Azt nem garantálja, hogy a stratégia jó. A kettőt fontos szétválasztani, és a fejlesztés során mi az elsőért vállalunk felelősséget.