Yuvarlama, çözülmüş bir problem gibi görünür. Bir sayınız var, bir adım büyüklüğünüz var, en yakın geçerli değere yuvarlarsınız. Tek bir yardımcı fonksiyon, yirmi satır, sonsuza kadar bitti.
Tek bir fonksiyon değildir. Birden çok borsaya emir gönderen bir sistemde hassasiyet başlı başına bir alandır ve ürettiği hataların can sıkıcı bir ortak özelliği vardır: istisna fırlatmazlar. Ya azıcık yanlış bir emir üretirler ya da kullanıcının göremeyeceği bir sebeple reddedilen bir emir.
İşte bu çalışmanın gerçekte neye benzediği, emir yolumuzdan üç gerçek problemle anlatılmış hâli.
Önemli Noktalar
- IEEE-754, adım hizalamasını güvenilmez kılar: 0,29'u 100 ile ölçeklemek 28.999999999999996 verir, bu da tam bir adım küçük bir değere yuvarlanır.
- Üç borsa, birbiriyle uyumsuz üç minimum emir kuralı uygular. Biri USDT değerini, biri kontrat sayısını, biri ikisini birden kontrol eder.
- Bu alandaki en kötü hatamız, bir borsanın sahip olmadığı bir kuralı uydurup geçerli emirleri ona göre reddetmekten doğdu.
- API'ın size vermediği bir sınırı asla sentezlemeyin. Bir platformun notional minimumu yoksa, bunu başka alanlardan hesaplamak temkinlilik değil uydurmadır.
- Eksik borsa meta verisi bir varsayılana düşmemeli, hata fırlatmalıdır. Yanlış bir varsayılan, sessizce yanlış emirler üretir.
1. Aritmetik problemi: yuvarlama sayınızı kaydırır
En basit durumdan başlayalım, çünkü insanların güvenli saydığı durum tam da budur.
Bir borsa, miktarları bir adım büyüklüğünün katları hâlinde kabul eder. Elinizde 0.29 ve 0.01 adımı var. Bu değer zaten geçerli, dolayısıyla onu aşağı yuvarlamak hiçbir şey yapmamalı. Standart uygulama önce ölçekler, aşağı yuvarlar, sonra tekrar küçültür:
0.29 / 0.01 = 28.999999999999996 ← IEEE-754, not 29
floor(...) = 28
28 × 0.01 = 0.28
Zaten hizalı olan bir değer, sessizce ve daha küçük emir yönünde tam bir adım aşağı kaydı. İstisna yok, uyarı yok, üstelik bu yalnızca değer ile adımın belirli kombinasyonlarında oluşur; fark etmesini zorlaştıran şey de tam olarak budur.
Çözüm, ölçekleme sırasında uygulanan bir epsilon değeridir: gösterim gürültüsünü yutacak kadar büyük, ama 0.289999 gibi gerçekten alt sınırın altında olan bir değerin hâlâ doğru yuvarlanacağı kadar küçük. Bunu keşfetme sürecini bu platformu kurarken hafife aldıklarım yazısında anlattım; burada önemli olan, ait olduğu kategori.
Çünkü miktarlar üzerindeki aritmetiğin güvenilmez olduğunu bir kez kabul ettiğinizde, bir miktara dokunan her şeye yeniden bakmaya başlarsınız. Asıl sürprizler oradadır.
2. Üç borsa, birbiriyle uyumsuz üç minimum
Her platform bir minimum emir uygular. Kuralın değerinin farklı olmasını beklersiniz, biçiminin değil. Oysa biçimi farklıdır:
| Borsa | Kural |
|---|---|
| Binance | qty × price >= minNotional (USDT değeri kontrolü) |
| Bybit | qty >= minOrderQty ve notional >= minNotional (iki kontrol) |
| OKX perpetual | qty >= minSz (kontrat sayısı, notional kontrolü hiç yok) |
Bu tabloyu, aynı emir hakkında sorulan üç farklı soru olarak okuyun. Binance emrin ne değerde olduğunu sorar. OKX kaç kontrat olduğunu sorar. Bybit ikisini birden sorar ve bir emir bunlardan yalnızca birini karşılayıp yine de reddedilebilir.
OKX'te ek bir incelik var: miktarlar coin değil kontrat cinsindendir, dolayısıyla bunların bir anlam ifade etmesi için önce bir kontrat büyüklüğünün ikisi arasında dönüşüm yapması gerekir. Bir platformda "0,5 BTC" anlamına gelen bir miktar, başka bir platformda "50 kontrat" anlamına gelir.
Vardığımız mimari sonuç şu: her borsa kendi emirlerini doğrular. Strateji motoru, hangi platformla konuştuğuna dair tek bir koşul bile içermez. Emri bir adaptöre teslim eder ve adaptör kendi kurallarını uygular. Bu kuralları tek bir soyutlama arkasında birleştirmeyi her denediğimizde, dördüncü bir durum ortaya çıktığı anda o soyutlama bir yalana dönüştü.
3. Pahalı olanı: var olmayan bir kuralı uydurmak
Bu, başka bir mühendisin kaçınmasını en çok isteyeceğim hata, çünkü yapılan yanlış tam da titizlik gibi görünüyor.
OKX perpetual'ların notional minimumu yoktur. Bunu bilmiyorduk. Diğer platformlardan analoji kurarak bir tane hesapladık: kontrat sayısı minimumu çarpı kontrat büyüklüğü çarpı fiyat. Makul görünüyordu. Akla yatkın bir USDT rakamı üretiyordu. Ve tamamen kurguydu.
Nerede bozulduğu ise çok belirli ve üzerinden geçmeye değer. Bir DCA stratejisi güvenlik emirlerini giriş fiyatının altına yerleştirir. Bizim sentetik minimumumuz bir fiyattan hesaplanıp sonra daha düşük fiyatlı emirlerle karşılaştırıldı, dolayısıyla merdivenin tepesinde geçen aynı miktar, daha aşağıda kaldı.
Sonuç: tam 0.1 kontratlık emirler, ki bu borsanın minimumunun ta kendisi olup kabul edilecekti, sistemimizden hiç çıkamadan kendi doğrulayıcımız tarafından reddedildi. Borsa onları hiç görmedi. Kullanıcı tarafından bakıldığında strateji, yapılandırıldığı işi yapmıyordu.
Alınacak ders, işlem dünyasının çok ötesine genellenebilir:
API'ın size vermediği bir kısıtı sentezlemeyin. Bir platform minimumu kontrat cinsinden sunuyorsa, minimum kontrat cinsindendir. Buna denk bir değer tabanlı karşılık türetmek dikkatli olmak değildir, bir kural uydurup sonra onu kullanıcıya karşı dayatmaktır.
Geriye dönüp bakınca ipucu şuydu: bizim hesapladığımız değer fiyata bağlıyken borsanın gerçek kuralı değildi. Bir kısıta dair sizin versiyonunuzun, gerçeğinde bulunmayan bir girdisi olduğu her an, modellemek istediğinizden farklı bir şey inşa etmişsiniz demektir.
4. Eksik meta veri hata olmalı, varsayılan değil
Son parça bir hatadan çok bir politikadır ve ilk üçünün sessizce tekrar etmesini engelleyen şey de budur.
Her borsa bize sembol bazında meta veri verir: adım büyüklüğü, tik büyüklüğü, minimum miktar, kontrat büyüklüğü. Bunların herhangi biri eksik olabilir ve akla gelen kolay hamle bir yedek değere düşmektir. Adım büyüklüğü mü eksik? 0.001 varsay. Minimum miktar mı eksik? Sıfır varsay, kararı borsa versin.
Biz tam tersini yapıyoruz: eksik olan zorunlu bir alan, herhangi bir emir oluşturulmadan hemen hata fırlatır. Aşağıdaki hiçbir katman bir tahmin üstüne bir şey inşa etme fırsatı bulmaz.
Gerekçe, sonuçların asimetrisidir. Fırlatılan bir hata, hemen ve düzeltebilecek birinin gözü önünde ortaya çıkan gürültülü bir başarısızlıktır. Bir varsayılan ise yanlış büyüklükte bir emir gönderip kendini daha sonra, parayla açığa vuran sessiz bir yanlış cevaptır. Görmezden gelemeyeceğiniz bir hata ile doğrulayamayacağınız bir sayı arasında, hata her seferinde daha ucuzdur.
Bu aynı zamanda meta veri katmanının bildiği şey konusunda dürüst olması demektir. Zorunlu bir alanı okuyan kod ya gerçek bir değer alır ya da bir istisna. Gerçek verinin biçimine bürünmüş bir yer tutucuyu asla almaz.
Akılda Tutulacaklar
- Adım ve tik hizalamasını, değerinizi kaydırabilen bir aritmetik olarak görün, biçimlendirme olarak değil.
- Gerçekten farklı olan kuralları birleştirmeyin. Platform bazında doğrulama, yeni platformlar eklendikçe doğru kalır; uyumsuz kurallar üstündeki ortak bir soyutlama kalmaz.
- API'ın belirtmediği bir sınırı asla türetmeyin. Bir kısıta dair versiyonunuzun, gerçeğinde olmayan bir girdisi varsa, o farklı bir kısıttır.
- Eksik meta veride gürültülü biçimde hata verin. Varsayılanlar, görünür bir hatayı görünmez ve yanlış fiyatlanmış bir emre çevirir.
Hassasiyet çalışması gösterişsizdir ve hiçbir zaman bir özellik listesinde yer almaz. Ama bu hataların her biri ya yanlış bir emir ya da hiç var olmayan bir emir üretti; birinin adına işlem yapan bir sistemde ise bunlar, o kişinin parasını doğrudan kaybetmekle aynı kategoride birer başarısızlıktır.
