高頻度取引における C++: 市場データから確定的なレイテンシーまで

高頻度取引における C++: 市場データから確定的なレイテンシーまで

高頻度取引における C++: 市場データから確定的なレイテンシーまで

導入

高頻度取引には、技術的な議論を単純化する方法があります。ソフトウェアの多くの分野では、規模、ハードウェア予算、または十分な応答時間の期待の背後にある非効率性を隠しながら、システムは立派な状態を保つことができます。 HFT では、遅さは単にエレガントではないというだけではありません。費用がかかります。不安定性は単に迷惑なだけではありません。それは戦略の品質を損ない、診断を曖昧にし、スタック全体の信頼を弱めます。この領域は理論を排除するものではありませんが、理論に時間への答えを強制します。

だからこそ、C++ は取引システムにおいて非常に重要であり続けます。そこで言語が生き残っているのは、業界が変化できないからでも、エンジニアが不必要な苦労を楽しんでいるからでもありません。 HFT が生き残るのは、メモリ レイアウトの制御、正確なパフォーマンス作業、成熟したネイティブ ツール、深い OS とネットワークの統合、プレッシャーの下で構築された数十年にわたる実際のシステムを通じて蓄積された膨大な実践知識など、C++ が依然として異常に優れて提供するプロパティの組み合わせを繰り返し要求しているためです。

これを、「C++ は速い」という 1 つのスローガンにまとめたくなる誘惑にかられます。しかし、そのスローガンは小さすぎます。 HFT は、抽象的な生の速度に報酬を与えるわけではありません。市場データから意思決定、注文送信までの経路全体にわたる決定的な行動に報酬を与えます。平均レイテンシは役立ちますが、予測可能なレイテンシはさらに役立ちます。時々素晴らしく、定期的に不安定なシステムは、わずかに遅く一貫して理解できるシステムよりも悪いことがよくあります。つまり、より深い話は、C++ が単に速いということではありません。それは、C++ が依然として、動作を詳細に形成、測定、修正できる低遅延システムを構築するための最も強力な言語の 1 つであるということです。

HFT が C++ に戻り続ける理由

時間通りに競争する取引スタックは、他のほとんどのドメインが曖昧にする余裕のある詳細を気にします。ホット パス上で割り当てがいくつ発生しますか?どのデータがキャッシュ内に一緒に存在しますか?どのスレッドがどこで実行されますか?パケットの到着と戦略ロジックを分けるキュー ホップの数は何ですか?パーサーは必要以上に多くのメモリを操作していませんか?ゲートウェイはコア間で移行しますか?無害であると思われるロギングまたは正規化のステップにより、レイテンシ分布の裾が広がりますか?これらは装飾的な質問ではありません。それらが作品なのです。

C++ は、エンジニアがこれらの詳細に直接直面できるため、この作業にとって自然な場所であり続けます。この言語は、システム全体に 1 つの割り当てモデル、1 つのキュー ストーリー、1 つの所有権ストーリー、または 1 つのランタイム スケジューラを強制しません。その自由は不注意なチームの手にかかると危険ですが、HFT はその自由を規律正しく使用することで真の優位性を生み出す場所の 1 つです。成熟した取引組織は、機械に適切に要求することを望んでいません。彼らは、マシンが何をするように指示されているのか、そしてコストがどこに隠れているのかを正確に知りたいと考えています。

人々が認める以上に重要なエコシステムに関する議論もあります。 HFT は言語の問題だけではありません。それはツールと経験の問題です。 C++ には、成熟したコンパイラー、プロファイラー、フレーム グラフ、ハードウェア カウンター ワークフロー、サニタイザー サポート、OS レベルの統合パターン、およびパフォーマンスが重要な隣接業界からの長い継承が付属しています。 AI アシスタントは、同じ公的継承からますます恩恵を受けています。エンジニアがパーサーの改善、キューの強化、またはネイティブ ホット パスでのプロファイリング出力の解釈について支援を求める場合、C++ に関する歴史的な密度は依然として重大な利点となります。

マーケットデータイベントが実際に経験すること

これは、1 つの市場データ イベントを抽象的な情報としてではなく、マシン内を移動する物理的な負担としてイメージするのに役立ちます。パケットが到着します。それは、ネットワーク スタックまたはフィード ハンドラーから受信され、解析され、何らかの内部表現にマッピングされ、1 つ以上のブック構造に適用され、戦略ロジックによって観察され、リスク チェックによってフィルタリングされ、場合によってはアウトバウンド注文またはキャンセルに変換される必要があります。すべてが順調であれば、この連鎖は瞬時に感じられます。アーキテクチャが不注意であると、パケットは各ステップで重みを取得します。

ここに 1 つの追加の割り当て、そこに 1 つの共有キュー、必要以上にコピーする正規化パス、教科書的な意味では洗練されているが記憶に残っていない本の構造、安全のみを目的としたロギング パス、間違った瞬間に移行するスレッド。これらのコストはどれも単独では神話のように聞こえません。それらの危険は蓄積と反復にあります。 HFT エンジニアは、システムが楽観主義を罰するため、この累積的な考え方を学びます。イベントごとの非効率はわずかであっても、市場活動、戦略の頻度、予測可能な反応時間のビジネス上の重要性が加わると、非効率は大きくなります。

これは、トレーディングにおけるホット パスが 1 つの機能だけであることはめったにない理由でもあります。それはエコロジーです。市場データ、状態管理、スケジューリング、シリアル化、リスク、送信はすべて相互作用します。調整とレイアウトをずさんなままにして、最も魅力的なループだけを最適化するエンジニアは、断片的には十分にベンチマークを行うシステムを作成することがよくありますが、重要な唯一の場所、つまり完全なパスでは期待外れになります。

決定的なレイテンシはアーキテクチャ上の規律である

低レイテンシーというフレーズは、あたかも関数の特性を説明するかのように使用されることがよくあります。本格的な HFT では、低遅延はアーキテクチャの特性です。それはシステム全体がどのように形作られているかから現れます。ホットデータはホットのままである必要があります。メモリの所有権は明らかである必要があります。スレッドは放っておくのではなく、意図的に配置する必要があります。共有された可変状態は疑いを持って扱う必要があります。キューが存在する必要があるのは、キューによって図がモジュール化されているように見えるためではなく、必要だからです。可観測性は、システムが独自の診断に溺れることなく検査可能な状態を維持できる程度に低コストである必要があります。

マシンは意図ではなくメモリを介して動作するため、データ レイアウトは重要です。連続したレイアウト、コンパクトな本の表現、プログラマーの感情ではなくアクセス パターンを反映した構造は、再利用可能に見えながらホットな状態があちこちに散在する賢い抽象化よりも価値があります。ホット パス上の動的メモリは、ある平均的な意味で単に遅いだけではないため、割り当ての規律が重要になります。また、ジッター、競合、および残りのランタイムとの予期しない相互作用が発生する可能性もあります。 HFT では、多くの場合、ジッターがより屈辱的な問題となります。

スレッド化も同様に真剣に取り組む必要があります。スレッドが増えても、自動的にパフォーマンスが向上するわけではありません。場合によっては、より多くの調整、より多くのキャッシュ移動、より多くのアフィニティミス、およびオペレーティングシステムが非自発的な共同作成者となる場所が増えることを意味します。成熟した取引システムは、スレッドを意図的に固定し、必要に応じて NUMA 境界を尊重し、共有される決定の数をアーキテクチャが許す限り低く保ちます。これではコードがおしゃれに感じられません。これにより動作がより安定し、通常ははるかに価値があります。

ネットワーキング、解析、および書籍のメンテナンス

トレーディングにおけるネットワーク化の道は、抽象化が最もうそをつきたくなる場所であるため、それ自体が尊重されるべきものです。バイナリ フィードは単なる入力ではありません。これは状態変化の流れであり、忠実かつ迅速に解釈する必要があります。パーサーが高速であればあるほど、下流で混乱が生じる余地は少なくなります。実行する割り当てと分岐が少なくなるほど、マシンが何に支払っているのかを理解しやすくなります。フィード処理コードが厳格に見えることが多いのは、まさにこの理由からです。市場はどのようなエレガンス形態が報われないのか、苦しみを経て学んできたのだ。

オーダーブックのメンテナンスも同様の性質を持っています。本は理論的に美しいから価値があるわけではありません。これは、更新、クエリ、再生、および負荷下での推論が可能であるため、価値があります。ここでは、部外者が予想するよりもリプレイ可能性が重要です。 HFT チームは、実際のトラフィックを再生し、リビジョン間で戦略の動作を比較し、システムが遅くなったり安定性が低下した場所を診断したりすることで、膨大な量のことを学びます。再生または検査するのが難しい書籍表現は、狭いテストでは高速に見えても、操作的には弱い可能性があります。トレーディングでは、速くて診断しやすいものは、速くて神秘的なものに勝ちます。

これは C++ が特に適している部分です。これにより、同じコードベースが流暢に対話して、パーサー、メモリを意識したデータ構造、プロファイリング ツール、および低レベルのオペレーティング システムの動作にフィードを与えることができます。他の言語も取引システムに参加でき、多くの言語が参加していますが、問題のサブシステムがホット パス自体である場合でも、C++ は制御とエコシステム サポートの最良の組み合わせの 1 つを提供します。

リスク、リプレイ、運用の成熟度

HFT をガバナンスを取り除いた純粋なスピードとして想像するのは間違いです。世界最速のパスであっても、間違った注文を送信したり、状態を回復できなかったり、不安定な市場イベントの後に説明不能になったりする可能性がある場合は役に立ちません。したがって、優れた取引システムでは、リスクチェックが明確に行われ、障害処理がリハーサルされ、日常のエンジニアリング業務に近いインフラストラクチャが再現されます。これらは官僚的な付属品ではありません。それらは競争力の一部です。

健全な HFT コードベースは通常、この成熟度を反映しています。可観測性がないのではなく、安価な可観測性が含まれています。チームは、リプレイできないものは自信を持って改善できないことを知っているため、リプレイ ツールが含まれています。これには、厳選されたマイクロカーネルだけでなく、パス全体を調べるベンチマークとプロファイラーが含まれています。デプロイメントの一貫性、コンパイラ設定、アフィニティ戦略、およびマシン構成を第一級のエンジニアリング上の懸念事項として扱います。言い換えれば、最高の取引システムは単なる高速コードではありません。これらは規律ある技術環境です。

これが、安定性が生の賢さを上回ることが多い理由の 1 つです。ラボのベンチマークのわずかな改善は、テールが理解され、フィード処理が説明可能で、戦略の動作が事後に再構築できる再現可能なシステムよりも価値がありません。 HFT に参入するエンジニアは、英雄的な行為を期待することがあります。成熟したチームが代わりによく実践するのは、ある種の冷静な厳しさです。彼らは驚きを取り除きます。市場はすでにそれらを十分に提供しています。

一般的な通説は廃止されるに値する

いくつかの神話はエンジニアに媚びるために生き残っています。 HFT のパフォーマンスは、主に手書きのアセンブリや難解なマイクロ最適化に関係している、という人もいます。実際には、最も有意義な成果は、建築、測定、そして通常の無駄の繰り返しの除去から得られます。ロックのない構造は自動的に優れているという人もいます。時にはそれらがまさに正しいこともあります。場合によっては、より単純な設計のほうがより適切に動作するはずの場所に、複雑さとメモリ順序のコストがインポートされることがあります。 3 番目は、より多くのスレッドが常に役立つと言っています。低遅延システムでは、余分な同時実行により、スループットが向上するよりも早く予測可能性が低下する可能性があります。

HFT での C++ の継続的な使用は、ほとんどが歴史的な慣性によるものに違いないという現代の通説もあります。確かに歴史は重要ですが、システムがお金と時間に対して継続的に測定される分野では、惰性だけでは生き残れません。 C++ が残るのは、言語、そのツール、およびその周囲のエンジニアリング文化が、決定論的な低遅延設計の現実と依然としてよく一致していることがチームによって発見され続けているためです。別の言語が最も人気のある取引経路で一貫してより良い結果を生み出していれば、HFT 企業は気づくでしょう。彼らは注意を払うのに十分な強いインセンティブを持っています。

このドメインがまだ研究する価値がある理由

商社で働いたことのないエンジニアにとっても、HFT はシステムの真実を避けるのを困難にするため、貴重な教師であり続けます。コードと結果の間に密接な関係が強制されます。データのレイアウトは装飾ではないこと、キューは自由ではないこと、平均レイテンシは嘘をつき得ること、再生は理解の一形態であること、そしてアーキテクチャがしばしば最も重要な最適化であることを教えます。これらの教訓は取引以外にも応用できます。

C++ は、エンジニアが難しいバランスを保つことができるため、引き続きそのレッスンの中心にあります。それは、実質的なシステムを構築するのに十分な表現力を持ち、コストを正直に明らかにするのに十分な低レベルであり、ツールと実際の実践の膨大な継承を伴うのに十分古いものです。この組み合わせは、当社の最も要求の厳しいパフォーマンス領域の 1 つにおいて依然として重要です。

HFT に動機を与えるものがあるとすれば、それはスピードそのものの神話ではありません。これは、ソフトウェアはプレッシャーの下でも正確で、測定可能で、威厳のあるものにできるということを思い出させてくれます。 C++ は、依然としてその分野が最も流暢に話されている言語の 1 つです。

ハンズオン ラボ: フィードから書籍への小さなリプレイを構築する

ミニチュア HFT スタイルのおもちゃを構築して終了しましょう。それはお金にはなりません。それは素晴らしいですね。お金を稼ぐことを約束するコード例のほとんどは、最悪の形で教育的です。

それが行うことはより便利です。一連の市場更新を小さなメモリ内のブック表現に再生し、最良の買値と売値をレポートします。

__コード_0__

#include <algorithm>
#include <chrono>
#include <cstdint>
#include <iostream>
#include <limits>
#include <string>
#include <vector>

enum class Side { Bid, Ask };

struct Update {
    Side side;
    int price;
    int qty;
};

struct Book {
    std::vector<Update> bids;
    std::vector<Update> asks;

    void apply(const Update& u) {
        auto& side = (u.side == Side::Bid) ? bids : asks;
        auto it = std::find_if(side.begin(), side.end(), [&](const Update& x) {
            return x.price == u.price;
        });

        if (u.qty == 0) {
            if (it != side.end()) side.erase(it);
            return;
        }

        if (it == side.end()) {
            side.push_back(u);
        } else {
            it->qty = u.qty;
        }
    }

    int best_bid() const {
        int best = 0;
        for (const auto& b : bids) best = std::max(best, b.price);
        return best;
    }

    int best_ask() const {
        int best = std::numeric_limits<int>::max();
        for (const auto& a : asks) best = std::min(best, a.price);
        return best;
    }
};

int main() {
    std::vector<Update> replay{
        {Side::Bid, 10010, 5},
        {Side::Bid, 10020, 3},
        {Side::Ask, 10040, 4},
        {Side::Ask, 10035, 8},
        {Side::Bid, 10020, 0},
        {Side::Ask, 10035, 6},
        {Side::Bid, 10025, 7}
    };

    Book book;
    const auto t0 = std::chrono::steady_clock::now();

    for (const auto& u : replay) {
        book.apply(u);
    }

    const auto t1 = std::chrono::steady_clock::now();
    const auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(t1 - t0).count();

    std::cout << "best_bid=" << book.best_bid() << "\n";
    std::cout << "best_ask=" << book.best_ask() << "\n";
    std::cout << "replay_ns=" << ns << "\n";
}

建てる

Linux または macOS の場合:

g++ -O2 -std=c++20 -o tiny_book main.cpp
./tiny_book

Windows の場合:

cl /O2 /std:c++20 main.cpp
.\main.exe

これが教えてくれること

この小さな再生プログラムでも、すぐに本当の HFT の疑問が生じます。

  • 価格レベルはベクトル、マップ、配列、またはカスタム ラダーに存在する必要がありますか?
  • リプレイが 7 回の更新から 700 万回に増加するとどうなるでしょうか?
  • 状態の更新とレポートにはどのくらいの時間がかかりますか?
  • 構造が動的に拡張される場合、割り当てはどこに表示されますか?

例は小さいですが、質問は決して小さいものではありません。

愛好家向けのテストタスク

  1. apply の線形検索を、より適切に拡張し、再生時間を比較する構造に置き換えます。
  2. 100 万件の合成更新を生成し、単純な構造がどのように劣化するかを測定します。
  3. フィードの再生と書籍の更新の間に SPSC キューを備えたプロデューサー スレッドとコンシューマー スレッドを 1 つ追加し、安定性と複雑さを比較します。
  4. 再生スレッドを Linux 上のコアに固定し、実行ごとの差異を比較します。
  5. 意図的にノイズの多いロギング パスを追加し、「無害な」デバッグの決定がレイテンシの測定にどれだけ早く影響を与えるかを観察します。

これらの練習は地味ですが、だからこそ良いのです。本当の低遅延エンジニアリングは、慎重に選択されたか、後で後悔する多くの質素な構造から構築されています。

まとめ

HFT は単に高速関数を記述するだけではないため、C++ は高頻度取引の中心であり続けます。それは、市場データから注文送信までのパス全体にわたって決定論的な低遅延システムを構築し、それらのシステムをプレッシャーの下で診断できるほど理解しやすくしておくということです。その作業は、規律あるデータ レイアウト、抑制された割り当て、慎重なスレッド化、正直なプロファイリング、再生可能な検証、および速度と同じくらい安定性を重視する文化に依存します。

これが、C++ がその地位を維持し続ける理由です。これにより、エンジニアは、この分野で今でも恩恵を受けている制御レベル、ツールの奥深さ、歴史的な実践を得ることができます。他の言語も取引スタックに貢献する可能性があり、実際に貢献していますが、問題がホット パス自体にある場合、パフォーマンスをスローガンから再現可能なエンジニアリング特性に変えるために、C++ が私たちが知っている中で最も強力な方法の 1 つであることに変わりはありません。

参考文献

  1. NASDAQ TotalView-ITCH 仕様: https://nasdaqtrader.com/content/technicalsupport/specifications/dataproducts/NQTVITCHSpecification.pdf
  2. DPDK ドキュメント: https://doc.dpdk.org/guides/
  3. Linux ソケット API マニュアル ページ: https://man7.org/linux/man-pages/man7/socket.7.html
  4. Linux タイムスタンプに関するドキュメント: https://docs.kernel.org/networking/timestamping.html
  5. Linux PTP ハードウェア クロック インフラストラクチャ: https://docs.kernel.org/driver-api/ptp.html
  6. Linux perf マニュアル ページ: https://man7.org/linux/man-pages/man1/perf.1.html
  7. Brendan Gregg による炎グラフ: https://www.brendangregg.com/flamegraphs.html
  8. インテル VTune プロファイラーのドキュメント: https://www.intel.com/content/www/us/en/docs/vtune-profiler/overview.html
Philip P.

Philip P. – 最高技術責任者

ブログに戻る

接触

会話を始める

明確な線が数本あれば十分です。システム、プレッシャー、そして妨げられた決断について説明してください。 または直接書いてください midgard@stofu.io.

01 What the system does
02 What hurts now
03 What decision is blocked
04 Optional: logs, specs, traces, diffs
0 / 10000