FX専用VPS / OptiMax 活用事例
バックテストの解像度は、データの解像度で決まります。日足・時間足では消えてしまう値動きの構造(スプレッド内の動き、約定の偏り、同時急変)は、tick(約定ごと)データにしか残りません。しかし tick は桁が違います。FX 6通貨ペア・2年分(完全)で4億レコード超。これを「漏れなく集め」「メモリに載せ」「総当たりで最適化する」には、ふつうの小型VPSでは力不足です。本記事では、当社のFX専用VPS OptiMax で、ゼロから完全な2年分tickデータ基盤を構築し、64コアで2,430通りの戦略パラメータを総当たり最適化するまでを、つまずきポイントごと実測値で解説します。
検証環境:OptiMax VPS(64 vCPU / 251GB RAM / Ubuntu 24.04)
計測期間:2023–2024(完全2年)/6通貨ペア。本記事の数値はすべて実測値です。
範囲:データ基盤構築・レート制限回避・並列最適化という汎用エンジニアリング手法のみで、当社の独自シグナル研究の内容は含みません。
なぜ「tickデータ × ハイスペックVPS」なのか #
tick は「約定が起きるたび」の最小単位の記録です。スプレッドの内側で何が起きているか、急変時にどちらに約定が偏るか——こうしたマイクロ構造は、足を丸めた瞬間に消えます。だからこそ短期戦略の検証には tick が要る。ところが tick はデータ量が桁違いで、収集・保持・計算のすべてでマシンの地力が問われます。本記事は「取得したつもりで実は8割欠損していた」失敗とその修正まで、tickデータ収集で誰もが踏む落とし穴を正直に共有します。
何を作ったか(成果物) #
| 指標 | 値 |
|---|---|
| 期間 × 通貨ペア | 完全2年(2023–2024) × 6ペア |
| 総tick数 | 405,616,079(約4.06億) |
| 被覆率 | ≈100%(取引時間の取りこぼしゼロ。各ペア 15,048 時間中 data≈12,473+休場(404)≈2,574、未取得≤6) |
| 1秒バー換算 | 1.41億本 |
| 最適化試行数 | 2,430通り(信号 × 保有時間 × 閾値 × ペア、各々ブートストラップCI付き) |
全工程の所要時間(実測) #
| 工程 | 時間 | 備考 |
|---|---|---|
| ① 完全tick取得(2クリーンIP分割・ギャップ充填) | 約3時間 | 取得役1台あたり3ペアで 10,856 秒、2台並走 |
| ② 計算機(OptiMax)への転送(約1GB) | 51秒 | 約13.6 MB/s |
| ③ 解析用フォーマット変換 | 116秒 | npz → parquet |
| ④ 64コアで2,430通り総当たり最適化 | 581秒 | グリッド構築111s+スイープ470s、約60コア占有 |
ゼロから「分析可能な完全2年tick基盤+最適化結果」まで、実質3時間強です。
STEP 1:データ取得 ― 無料feedと「2つの落とし穴」 #
ヒストリカルtickは Dukascopy の無料データフィードから取得できます(.bi5 = LZMA圧縮 + 20バイト固定長レコード)。1時間=1ファイルで、純Pythonでデコードできます。
# .bi5(LZMA + 20B固定長: ms_offset, ask, bid, ask_vol, bid_vol)を時間単位で並列取得
import lzma, urllib.request, numpy as np, concurrent.futures as cf
REC = np.dtype([("t",">u4"),("ask",">u4"),("bid",">u4"),("av",">f4"),("bv",">f4")])
def fetch_hour(sym, y, m, d, h):
url = f"https://datafeed.dukascopy.com/datafeed/{sym}/{y:04d}/{m-1:02d}/{d:02d}/{h:02d}h_ticks.bi5"
raw = urllib.request.urlopen(url, timeout=30).read()
return np.frombuffer(lzma.decompress(raw), dtype=REC) # 価格 = points/(JPYは1000, それ以外100000)
落とし穴①:HTTP 503(レート制限)は「総量」ではなく「バースト」で出る #
高速化のため複数台に分散させ、停止→即再起動を何度も繰り返したところ、IPアドレスが Dukascopy 側から一時的に 503 → 応答なし でブロックされました。検証の結論:
- 503の引き金は「リクエスト総量」ではなく「短時間の接続バースト」(=連続した停止・再起動、過剰な同時接続数)。
- 一度ブロックされても、リクエストを完全に止めれば 5〜10分で自然回復。
- 鉄則は 「1回だけ・控えめな同時接続で・止めずに流し切る」。今回は 1ペア24スレッド × 2ペア並行(=48接続) が安定帯でした。
落とし穴②(最重要):「サイレント・ドロップ」― 取得したつもりで8割欠損 #
最初の取得は一見「完走」しましたが、後で被覆率を測ると約22%しかありませんでした。原因は、ダウンローダが HTTP 503 を「データ無し(404)」と同じ扱いで黙って捨てていたこと。ブロックされていなくても、混雑時のソフトな503が散発し、その時間足が静かに欠落していたのです。「データが流れている=完全」ではありません。
🛠 どう捕まえ、どう直したか(再現可能なチェックリスト)
- 完全性は『流れているか』でなく『被覆率/欠損率』で検証する。 「nonempty 率」が取引時間の想定(≈80%)を大きく下回ったら欠損を疑う。今回は下流の解析でアラインメントが極端に痩せたこと(5分足パネルが本来15万本のところ200本台)で露呈しました。
- 404 と 503 を区別する。 404=正当な休場(再取得しない)、503/タイムアウト=要再取得。両者を同一視した瞬間に欠損は不可視化します。
- 多パス・ギャップ充填。 各 (日,時) の取得結果を記録し、失敗した時間だけを指数バックオフで再取得 → 失敗0まで反復。
- 被覆レポートで締める。 「total / data / 休場(404) / 未取得」を必ず出力し、未取得≈0 と ticks/年 が想定どおりかを確認してから信頼する。
結果、被覆率は 22% → ≈100%(各ペア未取得 ≤6 時間/15,048)に。完全2年で 4.06億tick を確実に取得できました。
STEP 2:落とし穴③メモリ ― tick処理は「RAMバウンド」 #
tickのデコードは、対象範囲のレコードを一度メモリに展開してから書き出します。つまり必要RAMは期間×ペア数に比例します。
検証では、RAM 1.9GB の小型VPSを補助に使うと、多年×複数ペアで即OOM(メモリ不足で強制終了)。一方 OptiMax(251GB RAM)は完全2年×6ペアを丸ごとメモリに載せても余裕(解析時ピークでも空き多数)。
tickデータ処理は、CPUより先にRAMが壁になります。OptiMaxの大容量メモリは、まさにこの用途のためにあります。
STEP 3:設計の勘所 ―「ダウンロード機」と「計算機」を分ける #
- ダウンロードは回線品質(Dukascopyへの経路)が支配的。
- 解析・最適化はRAMとコア数が支配的。
別の機械の得意分野なので、役割を分けます。今回は クリーンIPの取得役2台でデータを集め(IPあたりのレート制限を2倍に回避=分割の本当の効能)、OptiMax を「計算の母艦」として転送・集約・最適化に専念させました。約1GBの転送はわずか 51秒。どちらのボトルネックにも引きずられません。
STEP 4:64コアで「戦略を総当たり最適化」する #
ここが OptiMax の本領です。MT5のストラテジーテスター最適化と同じ発想で、1試行=1コアに割り当て、全コアを使い切ります。
ポイントは並列の単位。「通貨ペア」だけで並列にすると6コアしか回りません。(ペア × 信号 × 保有時間 × エントリー閾値) の全組み合わせを1試行にすると試行数が一気に増え、64コアを張り切れます。重い前処理(1秒グリッド)は一度だけ作って共有します。
# 重い前処理(1秒グリッド)は1回だけ → fork pool で共有(copy-on-write) → 全組み合わせを総当たり
import multiprocessing as mp
GRIDS = {p: build_grid(p) for p in PAIRS} # 親プロセスで一度だけ構築(COWで子に共有)
tasks = [(p,sig,H,thr) for p in PAIRS for sig in SIGNALS for H in HORIZONS for thr in THRESHOLDS]
with mp.Pool(64) as pool:
results = pool.map(eval_combo, tasks) # 各combo = walk-forward + コスト考慮 + ブートストラップCI
実測:1.41億本の1秒バー上で 2,430通りを約60コア占有・581秒(グリッド構築111s+スイープ470s)で完走。各試行は walk-forward(時系列の学習→検証分割)+往復スプレッドを引いたコスト考慮PnL+ブロック・ブートストラップ信頼区間まで含む本格検証です。
「バックテストだから全コアを使えない」は誤解です。並列の単位を「パラメータの組み合わせ」に取れば、最適化はコア数ぶん素直に速くなります。OptiMaxの64コアはここで文字どおりフル稼働します。
結果(汎用的な学び) #
完全2年・4.06億tick・2,430最適化試行での定量結果:
- シグナル自体は「存在」する:コスト無視(GROSS)のシャープレシオは最大 +429。短期の値動きには確かに構造があります。
- しかしテイカー(成行)では取れない:往復スプレッドを引くと、2,430通りのどれ一つプラスにならない(NET>0 が 0/2,430)。さらに各試行の 95%信頼区間の上限すら 0/2,430 がプラス=統計的にも「プラスになり得る」組み合わせがゼロ(データを完全化したことでノイズ由来の“まぐれ陽性”も消滅しました)。
- これは市場マイクロ構造の教科書的結果(短期エッジは bid-ask スプレッドの内側に収まり、スプレッドを払う側=テイカーには回収できない)を、大規模・完全データで厳密に再確認したものです。
実務的含意:短期エッジの研究は「コストを引いた後」でしか意味を持たない。そしてその大規模検証を現実的な時間で回すには、完全なデータ・RAM・コア数が要る、ということです。
まとめ #
| やったこと | 効いた要素 |
|---|---|
| 完全2年・4.06億tickの取得 | 控えめ並列+404/503区別+多パス・ギャップ充填で被覆22%→100% |
| 4億tickをメモリに展開・処理 | 251GB RAM(小型VPSはOOM) |
| 2,430通りの総当たり最適化 | 64コア+「組み合わせ単位の並列化」 |
| 全工程 | 実質3時間強 |
tickレベルの大規模バックテスト・最適化は、もはや特別な計算機の専有を必要としません。OptiMax VPS なら、必要なときに必要なだけのRAMとコアを確保して、この規模の検証を現実的な時間で回せます。データ収集の落とし穴(503バースト・サイレントドロップ・RAM上限)も、本記事のチェックリストで回避できます。
この検証はすべて OptiMax VPS(64 vCPU / 251GB RAM)で実施しました。
大容量メモリ・多コア・低遅延を、必要なときに必要なだけ。FX自動売買からクオンツ研究まで。