- Di bidang seperti high-frequency trading (HFT), di mana latensi langsung menjadi daya saing, pengetahuan optimasi C++ yang masih minim di ruang publik dirangkum dengan fokus pada eksperimen dan implementasi
- Hasilnya terbagi menjadi tiga: Low-Latency Programming Repository, optimasi strategi market-neutral pair trading, dan library pola Disruptor untuk C++
- Benchmarking menilai kecepatan, pemanfaatan cache, dan signifikansi statistik secara bersamaan, dan Cache Warming serta Constexpr menunjukkan manfaat besar dalam mengurangi latensi
- Strategi pair trading yang dioptimalkan meningkatkan kecepatan eksekusi dan profitabilitas, dan implementasi Disruptor menunjukkan performa yang lebih baik dibanding pendekatan queue tradisional
- Pekerjaan selanjutnya mencakup perluasan repositori, pengujian di lingkungan trading nyata, dan benchmarking seluruh sistem setelah mengintegrasikan Disruptor dengan algoritme trading
Tujuan optimasi latensi rendah untuk HFT
- Tujuannya adalah mengoptimalkan kode yang sensitif terhadap latensi untuk meningkatkan kecepatan eksekusi
- Fokusnya diarahkan pada strategi pemrograman dan struktur data yang digunakan dalam high-frequency trading
- Industri keuangan, khususnya perusahaan buy-side yang menangani pasar publik, tidak banyak membuka pengetahuan terkait karena alasan kerahasiaan dan keunggulan kompetitif
- Untuk mengurangi kesenjangan ini, dibuat Low-Latency Programming Repository khusus yang memuat berbagai teknik dan divalidasi dengan benchmarking statistik
Tiga hasil utama
-
Low-Latency Programming Repository
- Bukan sekadar kumpulan teori, tetapi juga berfungsi sebagai panduan praktis yang mencakup benchmarking statistik
- Mengkurasi teknik pemrograman, design pattern, dan best practice untuk mengurangi latensi pada sistem HFT
-
Optimasi strategi statistical arbitrage pair trading yang market-neutral
- Mengintegrasikan teknik pengurangan latensi dan optimasi tingkat CPU
- Menunjukkan peningkatan pada kecepatan eksekusi dan profitabilitas
-
Library pola Disruptor untuk C++
- Menunjukkan peningkatan performa dibanding pendekatan queue tradisional
- Menunjukkan bahwa struktur data semacam ini dapat diterapkan pada Order Management System (OMS) di sistem HFT
Alasan minimnya pengetahuan publik
- Pengetahuan optimasi sistem HFT terutama berasal dari praktisi industri, tetapi karena kerahasiaan dan keunggulan kompetitif, riset terbaru serta detail implementasi sulit dipublikasikan
- Area seperti peningkatan latensi, efisiensi kode, dan optimasi cache sangat terbatas materi publiknya
- Memang ada riset HFT dari sudut pandang ekonomi dan keuangan serta model matematika untuk algorithmic trading, tetapi jarang yang membahas hingga detail teknis optimasi kode atau pengurangan latensi
- Literatur terkait C++ relatif banyak, tetapi yang terhubung langsung dengan konteks sistem HFT ultra-low-latency tetap terbatas
- Blog dan posting online sering hanya menyajikan data latensi rata-rata secara permukaan, dan kurang menganalisis secara rinci perilaku akses cache atau latensi eksekusi instruksi
Evaluasi dan peningkatan performa
- Metrik evaluasi mencakup kecepatan, pemanfaatan cache, dan signifikansi statistik
- Di antara teknik dalam Low-Latency Programming Repository, Cache Warming dan Constexpr memberikan manfaat terbesar dalam pengurangan latensi
- Implementasi pola Disruptor memanfaatkan ring buffer, sequence number, dan strategi waiting khusus untuk menghasilkan performa yang lebih baik dalam latensi dan kecepatan dibanding pendekatan queue tradisional
- Strategi market-neutral pair trading mengalami peningkatan kecepatan eksekusi dan profitabilitas melalui optimasi tingkat CPU dan teknik pengurangan latensi
Repositori publik dan pekerjaan lanjutan
- Repositori, strategi trading, dan library Disruptor tersedia di
https://github.com/0burak/imperial hft - Pekerjaan selanjutnya mencakup perluasan repositori
- Masih ada tugas untuk menguji algoritme trading yang telah dioptimalkan di lingkungan trading nyata
- Arah berikutnya juga mencakup integrasi pola Disruptor dengan algoritme trading untuk melakukan benchmarking pada tingkat keseluruhan sistem
1 komentar
Komentar Hacker News
Tulisan ini tampaknya pengantar yang cukup mendasar tentang topiknya
Dari pengalaman saya mengajar mahasiswa sarjana, para mahasiswa pun umumnya sudah mengetahui hal-hal seperti ini. Di kelas arsitektur komputer, mereka mempelajari unsur-unsur dasar performa seperti branch prediction, koherensi cache, dan instruction cache
Agak mengejutkan bahwa faktor klasik penurunan performa, false sharing, sama sekali tidak dibahas, dan tampaknya fokusnya terutama pada latensi single-thread. Saya juga terkejut optimisasi “gratis” seperti fat LTO, PGO,
[[likely]],[[unlikely]]tidak disertakanMasalah performa yang lebih dalam perlu masuk sampai ke API input/output tertentu, primitive sinkronisasi, komunikasi antarproses, dan cara memakai compiler intrinsic yang rumit
Hal yang paling kurang dimiliki dan paling sulit diajarkan kepada programmer latensi rendah adalah semacam paranoia. Mereka perlu benar-benar takut dan marah terhadap alokasi, penyalinan, dan faktor penurun performa yang tidak perlu. Ini seperti naluri untuk secara obsesif menjalankan benchmark dengan callgrind demi menemukan panggilan ke allocator akibat object cache miss di tengah hot loop
Secara pribadi, saat membuat server latensi rendah, momen penting bagi saya adalah ketika menyadari bahwa alih-alih menyusun operasi vectored I/O, menyalin objek-objek kecil ke buffer kontigu lalu melakukan satu
writesecara keseluruhan lebih cepat. Tidak ada penyalinan yang gratis, dan fat pointer pun bukan pengecualianMateri terbaik yang tersedia saat ini pun hanya beberapa presentasi konferensi C++, dan banyak yang terasa kurang memuaskan
Jika kita kesampingkan godaan untuk pamer, dokumen ini merupakan kontribusi yang sangat bagus untuk bidang ini dan mungkin bisa menjadi referensi otoritatif pertama. Pernyataan samar bahwa informasi serupa bisa dirangkai dari kuliah lain bukanlah kontribusi, dan tidak membantu siapa pun
Pertama, demi kecepatan mentah, gunakan FPGA front-end untuk membagi beban menjadi stream data sederhana per aset. Namun karena gesekan dalam pengembangan iteratif, SDM, rantai pasok, dan sebagainya terlalu besar, hindari godaan untuk benar-benar melakukan eksekusi di sini. Inputnya semacam stream FIX, dan outputnya dibagi di sepanjang bus latensi rendah menjadi stream event biner per aset, lalu masuk ke segmen per aset dari klaster yang dapat diperluas yang tersusun dari MCU murah
Kedua, pada platform eksekusi berbasis MCU per aset, hilangkan asumsi sistem operasi umum, sehingga memungkinkan transisi yang lebih cepat dengan kode level rendah yang bisa ditulis orang di atas hardware yang benar-benar tersedia. Ketiga, profit? Dalam struktur seperti ini, supervisor berbasis sistem operasi umum harus memantau keadaan keseluruhan dan, bila perlu, memprogram ulang elemen individual untuk menghentikan atau mengubah strategi
Yang menentukan adalah seberapa rendah latensi sebenarnya. Pada titik tertentu, rasanya lebih baik membayar biaya untuk menempatkan hardware lebih dekat ke core daripada terus mengandalkan rekayasa. Ini akan sangat bergantung pada aturan, data center, dan infrastruktur link yang disediakan oleh bursa atau pool tersebut
Tampaknya cukup banyak operasi yang menguntungkan tidak mengungkapkan pool mana yang mereka hubungkan, dan mungkin menjadikan front-running sebagai bisnis sambil mengabaikan regulasi atau ketentuan layanan. Dalam kasus seperti itu, latensi geografis jaringan relatif antara dua titik eksekusi lebih kuat daripada latensi absolut ke satu titik
Seingat saya, pengetahuan umum yang sering dikatakan orang-orang compiler adalah bahwa dalam sebagian besar kasus, hint seperti ini kontraproduktif bahkan tanpa PGO. Compiler modern lebih mempercayai pass analisis mereka sendiri daripada hint seperti ini dan biasanya mengabaikannya
Sebagai catatan, di kode nyata saya hanya pernah melihat hint semacam ini di tempat yang mudah disisipkan compiler. Misalnya pemeriksaan null setelah pemanggilan
mallocBagian yang ingin saya soroti adalah ini
“Output dari pengujian ini adalah statistik uji (t-statistic) dan p-value terkait. t-statistic, yang juga disebut skor, adalah hasil uji unit root terhadap residual. t-statistic yang lebih negatif menunjukkan bahwa residual lebih mungkin stasioner. p-value memberikan ukuran probabilitas bahwa hipotesis nol pengujian, yaitu hipotesis tidak adanya kointegrasi, adalah benar. Hasil pengujian menghasilkan p-value sekitar 0,0149 dan t-statistic -3,7684.”
Bagian ini sepertinya ditulis dengan LLM
Contohnya juga benar-benar aneh. Setelah melihat korelasi harga penutupan sekali sehari selama 5 tahun, mereka menulis kode yang menghitung spread dengan latensi 65 mikrodetik. Itu tidak masuk akal sebagai sesuatu yang benar-benar akan dilakukan. Orang juga tidak akan menghitung statistik spread di inner loop, dan 65 mikrodetik terlalu lambat untuk inner loop
Intinya mungkin untuk melatih teknik optimisasi, tetapi sebagai target optimisasi, ini kurang representatif
Saya membuat implementasi bursa saham yang menggunakan pola LMAX Disruptor di C++
https://github.com/sneilan/stock-exchange
Implementasi dasar LMAX Disruptor juga sudah saya buat dalam beberapa file C++
https://github.com/sneilan/lmax-disruptor-tutorial
Namun saya sedang mempertimbangkan untuk membuat ulang ini dengan Rust. Saya sudah sampai pada titik mengimplementasikan protokol WebSocket sendiri, sistem autentikasi, SSL, dan sebagainya, lalu menyadari bahwa manajemen memori dan dependensi jauh lebih mudah di Rust. Terutama jika ini proyek perangkat lunak satu orang
Akses memori dapat di-reorder baik oleh compiler maupun CPU, jadi untuk mendapatkan barrier yang dijelaskan dalam paper LMAX Disruptor asli, posisi producer dan consumer harus menggunakan
std::atomicDalam metode
get, posisi consumer dinaikkan terlebih dahulu, yaitu slot dilepaskan ke producer, lalu pointer ke elemen internal queue dikembalikan. Akibatnya, elemen itu bisa ditimpa saat pengguna masih mengaksesnyaSelain itu, posisi producer dan posisi consumer kemungkinan besar berada di cache line yang sama, sehingga terjadi false sharing
T *item = &this->shared_mem_region->entities[this->shared_mem_region->consumer_position];this->shared_mem_region->consumer_position++;this->shared_mem_region->consumer_position %= this->slots;Anda bisa melakukannya seperti ini
uint64_t mask = slot_count - 1; // semua 1 dalam bineritem = &slots[ pos & mask ];pos ++;Dengan kata lain, pembagian/modulo bisa diganti dengan bitwise AND untuk sedikit mengurangi perhitungan. Namun, ukuran ring buffer harus berupa pangkat dua
Lebih jauh lagi, Anda bisa memakai nomor sekuens dengan rentang penuh seperti
uint64_t. Wrapping akan ditangani secara otomatis. Mengurangkan dua nomor sekuens juga bekerja tanpa masalah dengan memperhitungkan wrapping. Masalah konyol harus mengosongkan satu slot untuk membedakan apakah buffer penuh atau kosong juga hilangTentu saja, harus berhati-hati agar jendela nomor sekuens yang “aktif” tidak pernah melebihi ukuran jendela ring buffer
Untuk manajemen memori, layak mempertimbangkan menggantinya dengan
std::shared_ptr. Itu menghilangkan kekhawatiran tersebut sepenuhnya tanpa memperlambat kinerjaUntuk socket, ada pustaka bebas dan open source yang performanya lebih baik daripada kode buatan sendiri serta mengurangi berbagai kasus khusus yang merepotkan. Misalnya, cara mengiterasi
FD_ISSETlebih lambat daripadaepollataukqueueManajemen dependensi di C++ jelas lebih kasar dibanding bahasa lain. Bahkan menemukan dependensi lebih sulit daripada mengelolanya. Kode pustaka yang layak pakai tersebar di mana-mana, dan sebagian bersembunyi di sudut internet yang terlupakan. Menemukannya sendiri adalah sebuah keterampilan, dan jika dilakukan dengan baik, imbalannya besar
Untuk sistem yang sedang dipikirkan, sepertinya sulit mengalahkan ring buffer SPSC, dan jika diperlukan, work stealing juga bisa diimplementasikan dengan lock gaya lama
https://martinfowler.com/articles/lmax.html
Saya teringat https://github.com/CppCon/CppCon2017/blob/master/Presentatio...
Slide tentang server palsu yang memutar ulang data order, server kedua yang menghitung waktu eksekusi, serta pengukuran waktu paket dengan server yang diuji dan hardware switch benar-benar hardcore dengan cara yang menyenangkan
Saya tidak punya keinginan bekerja di bidang finansial, tetapi sepertinya menarik menangani sistem yang performanya begitu kritis sampai secara ekonomi masuk akal untuk membeli hardware berskala rak hanya untuk benchmarking
Saya membuat pustaka logging C++ yang punya banyak kemiripan dengan LMAX Disruptor, dan tampaknya juga digunakan sampai batas tertentu di komunitas HFT
Tujuan awalnya adalah memungkinkan pencatatan log yang sangat mendetail di lingkungan produksi untuk debugging pascakejadian tanpa penurunan performa. Ada rekan kerja yang menolak memasukkan informasi penting untuk pemecahan masalah ke log karena khawatir akan berdampak pada performa, dan pustaka ini mengakhiri perdebatan itu
[1] https://github.com/mattiasflodin/reckless
Keuntungan lain dari dispatch pada waktu kompilasi adalah, ketika compiler dapat menentukan secara statis fungsi mana yang dipanggil, kode fungsi yang dipanggil bisa langsung di-inline di titik pemanggilan
Dengan begitu semua overhead pemanggilan fungsi bisa dihilangkan, dan optimisasi tambahan seperti dead code elimination serta constant propagation juga bisa dimungkinkan
JIT yang bagus mendukung inline polimorfik. Pengalaman saya dengan C++ memang agak lama, tetapi solusi untuk masalah ini dulu adalah PGO. Namun itu tidak banyak digunakan. Sebagai gantinya, pada kode yang sensitif terhadap performa, orang cenderung menghindari dynamic dispatch itu sendiri
Pelajaran yang lebih umum adalah, dalam bahasa apa pun, pada bagian kode yang panas, hindari percabangan dinamis yang tidak perlu kecuali Anda sangat yakin compiler atau JIT dapat menembusnya
https://youtu.be/i5MAXAxp_Tw
Apakah ada alasan bagus mengapa perdagangan frekuensi tinggi harus ada? Orang sering mengkritik Bitcoin karena membuang energi, tetapi ini juga tampaknya jelas merupakan kerugian bersih secara sosial, namun anehnya seolah dibiarkan saja
Sulit mengatakan industri ini sangat prososial, tetapi benar bahwa mempersempit spread berarti mengurangi uang yang masuk ke perantara
HFT memang area yang cukup terkonsentrasi, tetapi skalanya sendiri relatif kecil. Dari sisi pemborosan energi, skalanya beberapa orde besaran lebih kecil daripada Bitcoin
Satu-satunya efek positif HFT adalah likuiditas dan spread yang lebih sempit, meski itu juga bergantung pada bagaimana orang mendefinisikan HFT. Misalnya, Robinhood dan perdagangan gratis mungkin tidak akan ada tanpa ini
Mereka mengambil bagian yang sebelumnya masuk ke broker dan bank. HFT bukan bisnis yang memeras “investor ritel”
Dari sudut pandang saya, dampak negatifnya terhadap masyarakat hampir tidak ada atau tidak ada sama sekali. Jika Anda berinvestasi di pasar saham untuk jangka panjang, hampir tidak ada alasan untuk memedulikan HFT
Bagaimanapun, tidak ada peristiwa alamiah yang membutuhkan perdagangan frekuensi tinggi. Nilai fundamental jarang berubah sangat cepat, dan kalaupun berubah, itu lebih mendekati transisi yang pasti daripada volatilitas
HFT membuat pasar keuangan sedikit lebih akurat dengan menyelesaikan ketidaksesuaian, misalnya ketika tiga pasangan mata uang saling tidak selaras, atau mengoreksi harga yang “jelas” salah
Saat Anda mencoba memperdagangkan sesuatu, ada seseorang di sisi lain. Biasanya besar kemungkinan Anda akan bertransaksi dengan pelaku HFT pada harga yang Anda inginkan. Jika Anda mendapat harga yang lebih baik, uang itu adalah uang yang Anda pertahankan
Saya juga sulit setuju dengan ungkapan “dibiarkan saja”. HFT juga cukup sering dikritik di sini
Jika Anda pengembang profesional, keseluruhannya layak ditonton
https://github.com/CppCon/CppCon2017/tree/master/Presentatio...
Dan juga direktori induknya
Ada yang membuat saya penasaran. Di bidang ini, mengapa logikanya menggunakan atau selama ini menggunakan C++, bukan C? Apa keunggulan C++ dibanding C di area ini? Saya cukup mahir C/assembly, tetapi sama sekali tidak tahu praktik HFT, jadi akan bagus kalau ada yang bisa menjelaskannya dengan sederhana
Tentu saja bisa diperdebatkan apakah ekspresivitas semacam ini sepadan dengan kompleksitas bahasa yang luar biasa besar, tetapi dalam praktiknya orang-orang secara empiris memilih C++
Dari struktur dan nada tulisan ini, bau LLM terasa kuat