← 記事一覧に戻る
July 2, 2026
読了時間: 5分

フレームワーク税: バックテストライブラリが素朴な pandas ループより遅くなるとき

フレームワーク税: バックテストライブラリが素朴な pandas ループより遅くなるとき
#アルゴトレード
#バックテスト
#パフォーマンス
#パラメータ探索
#ベクトル化
#ベンチマーク
Part 2 of 10 · Collection
High-Performance Backtest Engines

「幻想のないバックテスト」シリーズの一部。

ほとんどのアルゴトレードプロジェクトの根底には、心地よい思い込みがある。成熟し、スター数の多いバックテストライブラリは 速い という思い込みだ。長年の貢献があり、本物のイベントループがあり、ブローカーモデルがあり、手数料の仕組みがある。自分で書いたであろう場当たり的な pandas ループには、きっと勝てるだろう。だからあなたはそれに手を伸ばし、戦略を配線し、パラメータスイープ — 数千通りの構成、一晩がかりのジョブ — を開始する。朝になって戻ってくると、まだ実行中だ。

私たちは 8 つのバックテストエンジンを、まったく同一のパラメータスイープでベンチマークし、探索のためのツール選びを変えるべき何かを見つけた。最も人気のあるオープンソースのイベント駆動フレームワークのうち 2 つ — backtraderbt — は、スイープを 使い捨てのベースラインとして書いた素朴な pandas ループより遅く 実行した。しかもわずかに遅いというレベルではない。backtrader は pandas ベースラインのおよそ 2.5 倍の時間がかかり、bt は約 4.7 倍だった。一方で、同じ作業を行うベクトル化/コンパイル済みエンジンは、bt より約 13,000 倍高速に実行した。

これがフレームワーク税だ。人気のあるバックテスターは、一度の 誠実な実行 — 一つの戦略、一つのデータセット、慎重な約定、ブローカーらしく振る舞うブローカー — のために作られている。それはまさに、最終検証やライブとの整合性チェックに求めるものだ。だが、アルゴ研究が実際に時間を費やすもの — 同じ戦略を、ノブをわずかに変えながら一万回実行すること — にとっては、まさに間違ったツールだ。この記事はその税を測定し、そのメカニズムを説明し、「本物の」バックテストフレームワークが間違った選択となるのはいつかについての判断ルールを示す。

ここに挙げるすべての数値は、まったく同一でパリティを固定したワークロード上での、一つの再現可能なハーネス(benchmarks/bench_oss_engines.py、コミット 250dbb5)から得たものだ。私たち自身がエンジンを実行しなかった場合は、そう明記し、数値を捏造するのではなく、別の誠実さのセクションに記載する。

一度の実行 対 一万回の実行

同じ戦略計算が 3 つの分岐する経路を下っていく様子: 赤オレンジのイベント駆動トラックはすべてのバー駅で停止を強いられ、琥珀色のウェイト/リバランスはアルゴノードのツリーになり、単一のベクトル化されたエメラルド・シアンのビームは系列全体を一度に解決する — スイープ速度にとってパラダイムは運命である

パラメータ探索を規定する事実は、エンジン は数千回実行されるが 分析 は一度きり行われる、ということだ。バックテストを準備するために支払う固定コスト — イベントループの構築、ブローカーのインスタンス化、バーごとのオブジェクト割り当て — が何であれ、それをあらゆる組み合わせごとに支払うことになる。一度の実行では見えないコスト(「6 秒なんて誰が気にする?」)が、スイープ全体では請求額そのものになる(「6 秒 × 10,000 = 16.6 時間」)。

バックテストエンジンは 3 つのパラダイムに分類され、そのパラダイムがスイープ性能の運命を決める:

  1. イベント駆動 — エンジンはバーごとに歩みを進め、イベントを発行し、あなたの next()/onBars() コールバックを呼び出し、注文をブローカーオブジェクト経由でルーティングする。これは backtrader、backtesting.py、PyAlgoTrade、zipline、nautilus_trader のアーキテクチャだ。ライブトレードが実際にどう動くかを反映しており、まさにそれゆえに現実性の面で信頼される — そしてまさにそれゆえに遅い: バーごとの Python オーバーヘッドが、1 組み合わせあたり 150,000 回支払われる。
  2. ウェイト/リバランスbt はここに属する。目標ウェイト行列を渡すと、指定した日付でリバランスを行う。バーごとのコールバックはないが、それでも Python 内で評価されるイベントごとのオブジェクトグラフ(アルゴのツリー、取引台帳)がある。
  3. ベクトル化 / コンパイル済み — 戦略全体が配列操作(vectorbt)、JIT コンパイルされたカーネル(numba)、あるいはネイティブコード(Rust、MLX GPU カーネル)として表現される。バーごとの Python はまったく存在しない。ループがあるとしても、それはマシン速度で実行される。

この記事の残りは、その分類の実証的な帰結だ。私たちは一つのワークロードを構築し、すべてのエンジンに証明可能な形で同じ作業をさせ、時間を計測した。

ワークロード: 一つの戦略、80 個のノブ、全員に同一

ベンチマークは、すべてのエンジンが同じ仕事をしている場合にのみ誠実だ。私たちのものは意図的に平凡である — エンジン間で異なる 唯一の ものがエンジンそのものであるようなワークロードだ。

  • データ。 単一の合成幾何ブラウン運動の終値系列: 150,000 バー、seed=42、バーごとのボラティリティ sigma=0.0008x0=30000。決定論的なので、誰でもビット単位で再現できる。構造上、終値のみ — 戦略が終値クロスなので、OHLC はすべての足で終値に設定される。
  • 戦略。 ハル移動平均のクロス: 長さ L の HMA を、より速い 1/3 バリアント(HMAHMA3)に対してクロスさせる。常に市場に建玉を持ち、各クロスでロング/ショートを反転する。これは本物の、非自明な指標 — 2 つのネストされた加重移動平均に平方根ウィンドウのスムーザーを加えたもの — であり、おもちゃの SMA ではないので、バーごとの作業は代表的だ。
  • スイープ。 6..200 にわたる 80 個の HMA 長。それは、直接測定できる程度に小さくした「一万回の実行」だ: 80 個の独立した組み合わせで、それぞれが 150k バーにわたる完全なバックテストだ。
  • コスト。 往復手数料 0.09%、片側手数料をモデル化するエンジンでは片側ごとに分割する。同一バー約定は close[i] で行う — バー i のシグナルはそのバーの終値で執行される。これは私たちの本番エンジンが使う慣行だ。

組み合わせごとのタイマーは、正確に 2 つのものを包む: numpy による HMA の事前計算と、エンジンの実行だ。本当に一度きりのセットアップ(データの読み込み、バーオブジェクトの構築)はタイマーの外にある。ウォームアップ実行が 1 回あり、その後 best-of-N の繰り返しがある。そして — イベント駆動エンジンは、80 個の完全な組み合わせだと数分から 1 時間以上かかるほど遅いので — グリッドの均一なサンプルを計測し、線形に外挿する。組み合わせは独立しているので、線形外挿は期待値上は厳密だ。同じ慣行が pandas ベースラインにも適用されるので、それによって有利になるエンジンはない。

パリティ: 全員が同じ作業をしていることの証明

素朴なエンジン比較が陥る罠がここにある: 「速い」エンジンは、単に より少ない 作業をしているだけかもしれない。もし backtrader が 2,700 回の取引を記帳し、あなたのベクトル化エンジンが 40 回を記帳するなら、そのベクトル化エンジンは速いのではない — 間違っているのであり、比較は無意味だ。

そこで私たちは、取引数パリティチェックで比較を固定する。L=104 において、numpy リファレンスは正確に 2,707 回のクローズ済み取引を生成する。すべてのエンジンはこれを ±1 の許容範囲内で再現しなければならず、さもなければ実行は work-parity FAILED アサーションで中止される。この許容範囲が存在するのは、エンジンが記帳の慣行について意見を異にするから — 最後の建玉が強制的にクローズされてカウントされるか、最初のエントリーが「取引」か — であって、取引そのものについてではない:

エンジン L=104 での報告取引数 慣行
numpy リファレンス 2707 クローズ済みの往復取引
backtesting.py 2708 +1: 最後の建玉を終了時に強制クローズ
backtrader 2707 最後の未決済建玉はカウントしない
bt 2708 +1: 最初のエントリーを取引としてカウント
PyAlgoTrade 2708 +1: 最初のエントリーを約定としてカウント

すべてのエンジンが 2707 ± 1 に着地する。速度差が何であれ、それは一つのエンジンがこっそり作業を省略したことの産物ではない。これこそが、イベント駆動フレームワークと GPU カーネルを同じ表に並べ、それを本気で語れるようにする規律だ。

結果

これが表全体で、最速から最遅へと並べてある。combos/s はスループットで、最後の列は完全な 80 組み合わせのスイープにかかる時間だ。ベースライン行は M0 — 素朴な pandas エンジンで、バーを走る for ループにスカラー記帳を組み合わせた、午後のうちに書いて捨ててしまうようなものだ。それより遅いものはすべて太字になっている。

エンジン combos/s パラダイム 完全な 80 組み合わせスイープ
MLX GPU カーネル 779 ベクトル化 (Apple GPU) 0.10 s
ネイティブ Rust ~350 コンパイル済み 0.23 s
mp + numba 246 コンパイル済み JIT + マルチプロセス 0.33 s
vectorbt 56.9 ベクトル化 (numpy/numba) 1.4 s
numba (シングルコア) 39.7 コンパイル済み JIT 2.0 s
backtesting.py 1.42 イベント駆動 56 s
PyAlgoTrade 0.51 イベント駆動 2.6 min
M0 — 素朴な pandas + ループ 0.28 スカラーベースライン 4.8 min
backtrader 0.11 イベント駆動 12.7 min
bt 0.06 ウェイト / リバランス 22.5 min

表を上から下へ読むと、パラダイムが自ずと整列する: 上位 5 つはすべてベクトル化かコンパイル済みで、下位 5 つはすべてイベント駆動かオブジェクトグラフベースだ — そして素朴な pandas ループは、成熟した人気のフレームワーク 2 つの に位置している。上から下への幅は 4 桁だ。まったく同じ 2,707 取引のワークロードで、MLX カーネルはスイープを 10 分の 1 秒で終えるが、bt は 22 分半を要する。それはおよそ 13,000 倍の差だ。

表の真ん中にあるスキャンダル

表の真ん中にあるパラドックス: 地味な灰青色の素朴な for ループのランナーが、赤オレンジに輝く 2 台の華美で機能満載のイベント駆動フレームワークのマシンよりも先にゴールを切っており、それらはブローカーモデルとアナライザーのスタックに重く沈められ、まだ後方であえいでいる — 同一の 2,707 取引のワークロードで、慎ましいベースラインが成熟したフレームワークを打ち負かしている

人目を引く数字は両極端にあるが、示唆に富む 結果は真ん中にある: backtrader (0.11 combos/s) と bt (0.06 combos/s) は、どちらも素朴な pandas ベースライン (0.28 combos/s) より遅い。

これはよく染み込ませておく価値がある。M0 は賢いエンジンではない。DataFrame にインデックスでアクセスし、建玉と現金を平凡なスカラーで追跡し、取引をリストに追加していく Python の for ループだ — 明らかに悪くて打ち負かせる対象を持つために含めた、意図的に最適化していない「対照群」だ。pandas の行ごとのアクセスは悪名高く遅く、私たちはそれを逆手に取った。それなのに、エコシステムで最も推奨されるバックテストライブラリのうち 2 つがそれに負ける: backtrader は 2.5 倍、bt は 4.7 倍だ。

これを誠実に保つ微妙な点: すべてのイベント駆動エンジンが pandas より遅いわけではない。 backtesting.py (1.42 combos/s) はベースラインを 5 倍上回る。なぜなら、それはバーごとのオブジェクト生成を最小限に抑えた、無駄のない numpy ベースのイベントループだからだ。PyAlgoTrade (0.51) もベースラインをわずかに上回る。だから「イベント駆動」は自動的に死刑宣告ではない — しかしバーごとの機構が重ければ重いほど悪化し、ここでは backtrader と bt が最も重い機構を背負っている。パラダイムが天井を設定し、実装がその天井のどこに着地するかを決める。

要点は、これらが悪いライブラリだということではない。backtrader のブローカーモデルと bt のアルゴのツリーの設計は、正確性と表現力 をあなたに買い与えるために存在する — 現実的な注文処理、ポートフォリオのリバランス、アナライザー。それらの機能には実行時コストがあり、そのコストは一度実行するときには見えない。スイープ全体では、それが物語のすべてだ。

なぜイベント駆動エンジンは税を払うのか

バーごとにフレームワーク税が徴収される場所: 単一のバー反復が、その高コストな構成要素へと分解される — 具体化されたバーオブジェクト、コールバックのフレーム、注文をルーティングするブローカー、追記される台帳 — それぞれが硬貨をかすめ取る赤オレンジに輝く料金所であり、1,200 万個の同一のバー反復からなる、後退していく広大な回廊を下って繰り返される

そのメカニズムは謎めいてはいない。イベント駆動バックテストは、バーごとに、おおよそ次のようなことを行う:

  1. クロックを進め、次のバーをデータフィードから切り出し、それをオブジェクト(BarLine、辞書)として具体化する。
  2. ユーザーコード(next()onBars())へのコールバックを発火する。これは独自のフレームを持つ Python 関数呼び出しだ。
  3. コールバックの内部で、ブローカー/建玉の状態を、これもメソッド呼び出しと属性ルックアップを通じて照会する。
  4. 注文が作成された場合、それをブローカー経由でルーティングする: 検証し、証拠金/現金をチェックし、約定をスケジュールし、ポートフォリオオブジェクトを変更し、取引台帳に追記する。
  5. アナライザー、オブザーバー、およびフレームワークが維持するあらゆる記帳を更新する。

さて、これに 150,000 バーを掛け、さらに 80 組み合わせを掛ける: スイープあたり 1,200 万回のバー反復、そのそれぞれが一握りの Python レベルのオブジェクト割り当てと動的ディスパッチだ。Python の 1 操作あたりのオーバーヘッド — 属性ルックアップや小さな割り当てで数十から数百ナノ秒 — は、一度なら些末だが、1,200 万回では破滅的だ。bt のケースは同じ病気の一変種だ: バーごとではなく取引日にのみリバランスするとはいえ、各リバランスはアルゴオブジェクトのツリーを評価し、pandas ベースのポートフォリオ台帳に触れる。そしてそれが 1 組み合わせあたり 2,707 回、それが 80 通りある。

素朴な pandas ループが backtrader と bt に勝つのは、身も蓋もない理由からだ: バーごとにより少ない作業をするからだ。 ブローカー、イベントオブジェクト、アナライザーのスタック、注文ルーティングのステートマシンをスキップする。pandas の醜い行ごとの税は払うが、その一つの醜い税でも、フレームワークの整然として機能完備の、イベントごとに 1 オブジェクトという税よりは安上がりだ。バックテストを「建玉 × 次のリターン − 手数料」まで削ぎ落とすと、フレームワークがバーごとに行うことの大部分は、探索中には使っていないオーバーヘッドだ。

そしてこれこそが罠だ: そのオーバーヘッドは あなたがそのフレームワークを選んだ理由 なのだ。あなたは現実的なブローカーが欲しかった。アナライザーが欲しかった。最終検証の際には、そのすべてが欲しい。だが、片端からスカラーの目的値を一つ読み出すだけの 10,000 組み合わせの探索では、あなたはトラックを周回するためにリムジンの代金を払っている。

表のもう一方の端: ベクトル化とコンパイル済み

表の上部は、バーごとの Python を完全に削除するとどうなるかを示している。

  • vectorbt (56.9 combos/s) は、戦略全体を numpy/numba の配列操作として表現する。Python 内にバーループはない — シグナル、建玉、PnL はすべて配列レベルだ。スイープを 1.4 秒で実行する(bt の 22.5 分に対して): 同一の作業で約 950 倍高速だ。(vectorbt の設計は vectorbt 概説と、より広範な pandas 対 polars 比較でより深く扱っている。)
  • numba (39.7) は、形を変えないまま JIT でネイティブコードにコンパイルされた pandas ループだ。M0 と同じアルゴリズムだが、@njit は 0.28 combos/s を 39.7 に変える — デコレータ一つで約 140 倍の高速化だ。なぜなら、スカラーループを支配していたインタプリタのオーバーヘッドが単純に蒸発するからだ。
  • mp + numba (246) は、コンパイル済みカーネルを CPU コア全体で実行する。組み合わせは並列化がきわめて容易 — それぞれが独立している — ので、マルチプロセスは JIT の上でほぼ線形にスケールする。
  • ネイティブ Rust (~350) は、Python の糊付けの最後の一片を取り除く: スイープ全体がネイティブだ。
  • MLX GPU (779) は、スイープを Apple シリコンの GPU カーネルにマッピングする。80 組み合わせが 80 個の並列な算術レーンになる。エンターキーを離す前にスイープは終わっている。

正確に名指しする価値のあることが 2 つある。第一に、numba は、言語よりもパラダイムのほうが重要であることを証明する。 M0 と numba は 同じアルゴリズム を実行する — 違いは、純粋に、一方がバーごとに解釈される Python で、もう一方がコンパイル済みであることだけだ。それが制御された A/B におけるフレームワーク税のすべてだ: 内側のループからインタプリタを削除することによる約 140 倍。第二に、numba (39.7) から mp+numba (246)、MLX (779) への飛躍は、もはやエンジンについてのものではまったくない — それはオーケストレーションとハードウェアについてのものだ。バーごとの税がなくなれば、速度は、いくつの組み合わせを並列に実行するか、そしてどのシリコン上で実行するかの問題になる。私たちはその完全な進行を バックテストエンジンの速度の階段で歩み、最後の 1 マイルがプロセス/シリアライズのコストに支配される理由を IPC 税の記事で扱う。

私たちが測定しなかったこと(そしてそれを伝える理由)

ベンチマークの信頼性は、それが捏造を拒むものの中に宿る。私たちは 8 つのエンジンをパリティのもとで端から端まで実行した。よく知られたいくつかのフレームワークには数値を 付けなかった。測定していない数字を外挿するよりも、それらを正直に列挙するほうを選ぶ:

  • zipline / zipline-reloaded — イベント駆動、Quantopian の系譜。重いセットアップ(完全な取引カレンダーとデータバンドル)があり、それが厳密な組み合わせごとのタイミング計測を厄介にする。アーキテクチャ上は backtrader とともにイベント駆動陣営に属しており、表のその端の近くに来ると予想するが、それを証明してはいない。
  • nautilus_trader — Rust/Cython のコアを持つイベント駆動で、ライブとの整合性のために明示的に設計されている。コアがコンパイル済みなので、Python 税をフルには払わない可能性が最も高いイベント駆動エンジンだ — 私たちがまだ実行していない、真に興味深い測定だ。
  • QuantConnect Lean — C# ベースで、まったく別のランタイムだ。Python のハーネスでは直接比較できない。
  • Jesse — イベント駆動、暗号資産に特化。その設計は 別のノートでレビューしたが、ここではベンチマークしていない。
  • QSTrader — イベント駆動、ポートフォリオ志向。同じパラダイムの但し書きが当てはまる。
  • fastquant — 試みた。しかし私たちの環境ではインストール/API が壊れていたので、数値はない。私たちはそれを推測するつもりはない。

私たちが 実際に 報告する数値についての、正直な 2 つの但し書き。vectorbt、numba、mp+numba、ネイティブ Rust、MLX の数値は、同一のワークロード上での私たち自身のエンジンの階段から得たものであり、4 つのイベント駆動の行を生み出した OSS ハーネスから得たものではない — 同じワークロードだが異なる測定装置であり、ネイティブ Rust の数値は概算の ~350 であって、厳密な測定ではない。そして絶対的な combos/s はハードウェア固有だ。移ろわないのは 順序と比率 であり、それらは十分に大きい(上から下まで 13,000 倍、pandas 対フレームワークの逆転で 2.5〜4.7 倍)ので、いかなる合理的なハードウェアの違いもそれらを覆さない。

イベント駆動エンジンの擁護

本来の役割におけるイベント駆動エンジン: 生き残った一つの戦略の、単一の綿密で高忠実度な検証実行。現実的な約定をモデル化し、証拠金と注文のライフサイクルを尊重する、精密なエメラルドとシアンの計器として描かれている — 探索中には純粋な無駄だった同じバーごとの機構が、ここでは価値ある職人技として示されている。デプロイ前の、慎重な一度きりのドレスリハーサルだ

これを「イベント駆動バックテスターは悪い」と読むのは簡単だろう。それは間違った教訓であり、不公平でもある。

イベント駆動エンジンは別の仕事のために作られており、そしてそれが 得意 だ。バーごとのブローカー、注文のライフサイクル、約定ロジック、アナライザー — その機構は、バックテストをできるだけライブトレードに近づけるために存在する。目標が、まさにデプロイしようとしている戦略の、単一で信頼できるドレスリハーサルであるとき、あなたはエンジンにすべての約定で汗をかき、部分約定をモデル化し、証拠金を尊重し、得られたはずのない価格で取引させるのを拒んでほしいと 思う だろう。その忠実度こそが製品だ。その実行時コストは現実性の代価であり、一度の実行にとってその代価は無視できる。

失敗はエンジンにあるのではない。それを間違ったフェーズで使うことにある。研究には、正反対の要求を持つ 2 つの異なるフェーズがある:

  • 探索 はスループットを求める。あなたは景観を探索しており、その大半はゴミであり、二度見する価値のある少数を見つけるために数千の点を評価する必要がある。点あたりの忠実度はほとんど問題にならない — あなたはランク付けをしているのであって、デプロイしているのではない。ここではフレームワーク税は純粋な無駄だ。
  • 検証 は忠実度を求める。あなたは一握りの候補を持っており、それらが現実的な執行、手数料、スリッページ、そして紙上のリターンを膨らませる 先読みの罠 を生き延びるかどうかを、できるだけ正確に知る必要がある。ここではイベント駆動エンジンがそのコストに見合う。

フレームワーク税が罰する過ちは、検証 エンジンで 探索 を実行することだ — 99% を捨てることになる景観を探索するために、リムジンの料金を払っている。

判断ルール

実践的な要点は、一つの判断に圧縮される:

探索はベクトル化/コンパイル済みエンジンで行え。生き残ったものはイベント駆動エンジンで検証せよ。

具体的には:

  1. スイープのために高速なカーネルを構築するか借りてくる。 既製品が欲しいなら vectorbt。戦略がきれいにベクトル化できないなら numba でコンパイルしたループ(@njit デコレータだけでここでは約 140 倍を稼いだ)。パラメータ空間全体をそれに通す。
  2. backtrader、bt、zipline、あるいはあらゆる重いイベント駆動フレームワークで、大きなスイープを絶対に実行しない。 それらのどれかでのスイープがあなたのボトルネックなら、解決策はより大きなマシンではない — 間違ったエンジンなのだ。素朴な pandas ループでさえ、そのうち 2 つには勝つだろう。
  3. 短いリストをイベント駆動エンジンへ昇格させて忠実度チェックを行う。 一握りの生存者を取り、それらを現実的なエンジンで再実行する。そこではブローカーモデルと約定ロジックが、高速カーネルが抽象化してしまった問題をあらわにできる。
  4. 2 つのエンジンの間でパリティを強制する。 高速エンジンと忠実度エンジンは、固定された構成における取引数と PnL で一致しなければならない(L=104 での ±1 取引のチェック)。さもなければ、探索と検証は別々の戦略を測定しており、パイプライン全体が嘘になる。

これは、探索と検証が正反対のコストプロファイルを持つときにいつでも現れる、同じ 2 速アーキテクチャだ。そしてこれが、私たち自身のスタックが パラメータ探索のために高速なベクトル化/コンパイル済みの経路を保ち、最終的な 目的関数の評価プラトーチェック のために重い機構を予約している理由だ。

要点

  1. 人気 ≠ スイープで速い。 一つの同一でパリティを固定したワークロード(150k バー、80 通りの HMA クロス、2,707 取引)で、backtrader (0.11 combos/s) と bt (0.06) は、どちらも素朴な pandas ループ (0.28) より 遅く 実行した。成熟しスター数の多いフレームワークが、自動的に速い選択肢になるわけではない。
  2. フレームワーク税はバーごとであり、スイープがそれを掛け算する。 スイープあたり 1,200 万回のバー反復、そのそれぞれがイベントオブジェクト、コールバック、ブローカーの往復を運ぶ。一度の実行では見えないコストが、一万回にわたっては請求額のすべてだ。
  3. パラダイムが天井を設定する。 ベクトル化/コンパイル済みエンジン(vectorbt 56.9、numba 39.7、mp+numba 246、ネイティブ Rust ~350、MLX 779)は、イベント駆動エンジンを 2〜4 桁 — 上から下まで最大約 13,000 倍 — 上回る。同じアルゴリズムが、単に JIT コンパイルされただけで、140 倍速くなった。
  4. すべてのイベント駆動エンジンが等しいわけではない。 backtesting.py (1.42) と PyAlgoTrade (0.51) はそれでも素朴なベースラインに勝つ。税はバーごとの機構がどれだけ重いかに応じてスケールする。天井のどこに着地するかは実装が決める。
  5. 2 つのエンジン、2 つのフェーズ。 探索はベクトル化/コンパイル済みカーネルで、生き残ったものは現実的なイベント駆動エンジンで検証する。両者が同じ戦略を測定するよう、両者の間で取引数/PnL のパリティを強制する。
  6. 何を測定したかについて正直であれ。 私たちは 8 つのエンジンをパリティのもとでベンチマークし、測定しなかったもの(zipline、nautilus_trader、Lean、Jesse、QSTrader、そして実行できなかった fastquant のインストール)を、数値を捏造するのではなく列挙した。

居心地の悪いまとめ: パラメータスイープがあなたのボトルネックなら、問題はおそらくあなたのマシンでもなく、あなたの戦略でもない。問題は、単一の誠実な実行のために作られたエンジンで探索を実行していること — そして、恥ずかしくて残しておけなかったあの pandas ループのほうが、あなたは速かっただろうということだ。

完全なハーネス、パリティのアサーション、そしてエンジンごとの生の JSON 結果は、コミット 250dbb5benchmarks/bench_oss_engines.pybenchmarks/results_oss/ にある。階段のコンパイル済み/GPU 側については、バックテストエンジンの速度の階段IPC 税の分析 を参照。

blog.disclaimer

Authors

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

市場の先を行く

ニュースレターを購読して、独占的なAI取引の洞察、市場分析、プラットフォームの更新情報を受け取りましょう。

プライバシーを尊重します。いつでも配信停止可能です。