Volume-Weighted Savings Plan (VWSP): 小口VWAP積立の実践ガイド

執行最適化

要点(Executive Summary):本記事は、ドルコスト平均法(DCA)を一歩進め、1日の出来高プロファイルに沿って小口で分割発注する「小口VWAP積立(Volume-Weighted Savings Plan; VWSP)」を解説します。目標は、同じ予算であっても平均取得価格(バイアスの小さい指標としてのVWAP比)をわずかに改善し、長期の積立効率を高めることです。オプションやレバレッジを使わず、現物・スポット取引と基礎的な指値・成行・逆指値のみで実装できます。

VWSPは難解な高速取引の話ではありません。市場に存在する「時間帯ごとの流動性の濃淡(U字型の出来高曲線)」に合わせて買付タイミングを調整するだけのシンプルな運用です。東証や米国株式、主要FX通貨ペア、流動性の高い暗号資産(BTC・ETHなど)で有効性が期待できます。

1. なぜDCAに「執行アルファ」を足すのか

DCAは価格予測を放棄し、時間分散で価格変動リスクを平準化する王道の手法です。とはいえ、同じ金額を「いつどのように」発注するかで、実現される平均取得価格には小さくない差が生まれます。実務上の主因は、スプレッド・約定スリッページ・ブックの薄さです。これらは一日の中でも変動し、一般に出来高が厚い時間帯ほど取引コストが低下しやすく、指値の充足率も高くなります。

VWSPは、時間帯別の出来高ウェイトで日次の買付予算を配分し、発注サイズを小さく刻んで板にやさしく当てていく設計です。価格予測は不要で、ルールどおりに流動性のある時間に多めに、薄い時間に少なめに買うだけです。

2. VWSPの設計図:5ステップ

Step 1:対象アセットの選定

流動性が高く、スプレッドが安定し、最小約定単位が小さい銘柄が適します。例:
・日本株:指数連動ETF(例:TOPIX連動、S&P500連動等)
・米国株:超大型ETF(VOO、IVV、QQQ等)
・FX:USD/JPY、EUR/USD 等のメジャー
・暗号資産:BTC/USDT、ETH/USDT 等の板厚ペア

Step 2:日次/週次の予算と頻度

たとえば「毎営業日 10,000 円」や「毎週 月水金 各 5,000 円」といった運用リズムを先に固定します。頻度は高いほどVWAPに近づきやすいですが、手数料・最小約定・時間の制約も加味してください。

Step 3:時間帯別の出来高プロファイル(U字カーブ)

市場はしばしば「寄り」と「引け」に出来高が集中します。東証(現物)は 9:00–10:00 と 14:30–15:00、米国株は 9:30–10:30 と 15:30–16:00 に厚くなる傾向があります。FXは指標・重なるセッション(ロンドン/NY)で厚みが出ます。暗号資産は24/7ですが、取引所や曜日で偏りが見られます。本稿ではデフォルトの重みを提示し、必要に応じて自分の銘柄に合わせて微調整する方法を後述します。

Step 4:発注の置き方(指値・成行・逆指値)

VWSPの核は「小口の連続発注」です。基本は以下のいずれかで運用します。
成行/IOC:厚い時間帯のみ使用。約定率重視。
指値:ミッドからスプレッドの半分~1倍だけ内側に置く。流動性が薄い時間は距離を広げる。
逆指値:ブレイク期待の銘柄で最小限。過度な追随は避ける。

Step 5:残弾ロールとエラー処理

各時間帯の割当で未約定が出たら、次の厚い時間帯へロールします。引けまで残った弾は、引け前の厚い時間でまとめて消化する「引けプール」を用意すると未約定が溜まりにくくなります。

3. デフォルト時間配分テンプレート

以下は、実務で使いやすい初期値です。ご自身の銘柄の出来高比率に合わせて見直してください。

東証(現物株・ETF)

時間帯 重み 備考
09:00–09:30 25% 寄りの厚い板に成行/IOCまたは浅い指値
10:30–11:00 10% ランチ前の薄め時間は控えめ
12:30–13:00 10% 後場寄り、浅い指値
14:00–14:30 15% 出来高回復
14:30–15:00 40% 引けに向け厚い板で消化(引けプール)

米国株(NYSE/Nasdaq)

時間帯(ET) 重み 備考
09:30–10:30 35% オープンの厚い流動性
11:30–13:00 10% ミドルは控えめ
14:30–16:00 55% 引けに集中

FX(例:USD/JPY)

時間帯(JST) 重み 備考
16:00–18:00 25% ロンドン参入
21:30–23:00 45% 米指標・NY序盤
01:00–02:00 30% NY後半のまとまった板

暗号資産(BTC/USDT)

時間帯(JST) 重み 備考
08:00–10:00 20% アジア時間の流動性
17:00–20:00 30% 欧州~米国前
22:00–01:00 50% 米国時間の厚み

4. 発注ロジック:サイズ決定と価格の置き方

日次予算 B を時間帯の重み w_i に配賦し、各スロットの予定発注額を B_i = B × w_i とします。最小発注金額や1株単位・ロットサイズを満たすように丸め、板状況を見て次のルールで執行します。

  • スプレッドが通常より広がっている(例:平時の1.5倍以上)場合はスキップし、引けプールにロール。
  • 急な指標・ニュース時はスロットを停止し、次のスロットへロール。
  • 指値は「気配の中ほど(ミッド)からスプレッドの0.5~1倍内側」を初期値に、板の食われ具合で微調整。
  • 未約定は引け前の最後の厚い時間で IOC/成行に切替えて消化。

このルールにより、約定コストのドローダウンを抑えつつ、VWAPに近い(あるいはやや有利な)取得価格を目指します。

5. 具体例:東証ETFでの一日運用(予算 10,000 円)

例として、S&P500 連動の東証ETFを毎営業日 10,000 円ずつ積み立てるケースを考えます。前掲の重みに従い、各スロットの割当は下表の通りです。

時間帯 割当額 執行方法
09:00–09:30 4,000 円 IOC/成行 or 浅い指値
10:30–11:00 1,000 円 指値
12:30–13:00 1,000 円 指値
14:00–14:30 1,500 円 指値
14:30–15:00 2,500 円 IOC/成行(引けプール)

1株単位の制約で端数が出る場合は、最後のスロットに寄せて調整します。指値が刺さらなかった分も同様に引けプールへロールします。

6. 効果測定:VWAP比・スリッページ分解

運用の良し悪しは、同日の取引量加重平均価格(VWAP)との差(VWAPスプレッド)で確認します。
VWAPスプレッド =(自分の平均取得価格 − 当日の市場VWAP)/ 市場VWAP

さらに以下の要素に分解します。

  • 手数料:売買手数料、為替・入出金手数料。
  • スプレッド:成行・IOC使用時の気配差。
  • スリッページ:板の薄さによる追加コスト。
  • タイミング誤差:時間配分が実際の出来高カーブとズレたことによる影響。

VWSPの改善幅は数bp~数十bpと小さいですが、長期の積立で複利的に効きます。効果を可視化するため、月次でVWAPスプレッドの中央値・平均値・標準偏差を記録すると良いでしょう。

7. リスク管理と運用ルール

  • 経済指標・決算・重要イベントの直前はスロットを停止または縮小。
  • 最低ロットの複数回執行で板へのインパクトを抑制。
  • 異常スプレッド(平時の1.5倍以上)は自動スキップ。
  • 未約定は引けプールで強制消化し、残弾持ち越しを禁止。
  • 月次で重みをリバランスし、銘柄固有の出来高カーブに合わせる。

8. ブローカー/取引所チェックリスト

  • 売買手数料と最小約定単位(少額を刻めるか)
  • 板情報の提供有無、API/自動売買機能(IFD/IFD-OCO/IOC)
  • スプレッドの平常時レンジと異常拡大量の頻度
  • 引け成行・VWAP連動注文の可用性
  • 入出金コスト・為替コスト(海外ETF/暗号資産)

9. よくある失敗と対策

  • 未約定の累積:引けプールを設定する。最後はIOC/成行で消化。
  • 板薄で指値が刺さらない:重みを厚い時間帯に再配分。指値の距離を詰める。
  • イベントでスプレッド拡大:自動スキップの閾値を導入。
  • 手数料でメリットが相殺:まとめ買いと刻みのバランスを月次で最適化。

10. 自動化:MQL4(MT4)EAの完全コード

以下はFX(例:USDJPY)でVWSPを自動執行するサンプルEAです。指定した時間帯ウェイトに従い、ロットを分割してAsk/Bidのスプレッド状態を監視しながら指値/成行を切り替えます。教育目的の簡易実装であり、実運用前にデモ検証を行ってください。

/*
 * VWSP_Executor.mq4
 * 概要:時間帯ウェイトに基づく小口VWAP積立(VWSP)を自動執行するEA(教育用)
 */
#property strict

input string   Sym         = "USDJPY";
input double   DailyBudget = 10000;    // 1日の想定円換算予算(口座通貨に合わせて調整)
input double   BaseLot     = 0.01;     // 最小ロット
input double   LotPerSlot  = 0.01;     // スロットあたりのロット上限
input double   MaxSpread   = 0.015;    // 許容スプレッド(例:USDJPYで0.015=1.5pips相当)
input bool     UseLimit    = true;     // 指値を優先(falseで成行)
input int      Magic       = 20250907;

struct Slot { int startH; int startM; int endH; int endM; double weight; };
// JSTベースのサンプルスロット(ロンドン・NY厚い時間)
Slot slots[] = {
  {16,0,18,0,0.25},
  {21,30,23,0,0.45},
  {1,0,2,0,0.30}
};

datetime lastExec = 0;

double GetSpreadPoints() {
  double p = (MarketInfo(Sym, MODE_ASK) - MarketInfo(Sym, MODE_BID)) / MarketInfo(Sym, MODE_POINT);
  return p;
}

bool InSlot(datetime t, Slot s) {
  int h = TimeHour(t);
  int m = TimeMinute(t);
  int cur = h*60 + m;
  int a = s.startH*60 + s.startM;
  int b = s.endH*60   + s.endM;
  if(a <= b) return (cur >= a && cur < b);
  // 跨日スロット
  return (cur >= a || cur < b);
}

double CalcTodayWeightSum() {
  double sum = 0;
  for(int i=0;i<ArraySize(slots);i++) sum += slots[i].weight;
  return sum;
}

int OnInit() { return(INIT_SUCCEEDED); }
int OnDeinit() { return(0); }

void OnTick() {
  if(Symbol() != Sym) return;
  datetime now = TimeCurrent();
  // 1分に1回だけ意思決定(無駄な発注を抑制)
  if(lastExec != 0 && (now - lastExec) < 60) return;
  lastExec = now;

  // スロット判定
  int idx = -1;
  for(int i=0;i<ArraySize(slots);i++) if(InSlot(now, slots[i])) { idx = i; break; }
  if(idx < 0) return;

  // 本日の消化ロットを計算(簡易:口座残高や予算に応じてLotPerSlotを採用)
  double lot = MathMin(LotPerSlot, BaseLot);
  if(lot <= 0) return;

  // スプレッドチェック
  double spr = GetSpreadPoints() * MarketInfo(Sym, MODE_POINT);
  if(spr > MaxSpread) return; // 異常スプレッドはスキップ

  // 既存ポジを確認(教育用にBuyのみ)
  int buys = 0;
  for(int i=0;i<OrdersTotal();i++) {
    if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
      if(OrderSymbol()==Sym && OrderMagicNumber()==Magic && OrderType()==OP_BUY) buys++;
    }
  }
  if(buys >= 100) return; // 安全制限

  // 指値/成行 執行
  double ask = MarketInfo(Sym, MODE_ASK);
  int    slip = 3;
  if(UseLimit) {
    double price = ask - (spr*0.5); // ミッド寄りに浅く置く
    int ticket = OrderSend(Sym, OP_BUYLIMIT, lot, NormalizeDouble(price, Digits), slip, 0, 0, "VWSP", Magic, 0, clrNONE);
  } else {
    int ticket = OrderSend(Sym, OP_BUY, lot, ask, slip, 0, 0, "VWSP", Magic, 0, clrNONE);
  }
}

※ 実ブローカー仕様に合わせてスプレッド・小数桁・ロット最小値を調整してください。教育用の簡易コードであり、損切り・資金管理・例外処理を各自で補強する必要があります。

11. 補足:TradingView(Pine Script)で時間帯ヒートマップ

時間帯ごとの出来高ウェイトを可視化するための簡易スクリプトです。銘柄固有のカーブを抽出し、月次で重みを見直す際の参考にします。

//@version=5
indicator("VWSP Time-Of-Day Volume Heatmap", overlay=true)
tf = input.timeframe("5", "Base TF")
s = request.security(syminfo.tickerid, tf, volume)
hour = hour(time(tf))
bins = array.new_float(24, 0.0)
count = array.new_float(24, 0.0)
for i = 0 to 23
    v = s[0] * (hour == i ? 1 : 0)
    array.set(bins, i, array.get(bins, i) + v)
    array.set(count, i, array.get(count, i) + (hour == i ? 1 : 0))
w = array.new_float(24, 0.0)
for i = 0 to 23
    c = array.get(count, i)
    ww = c > 0 ? array.get(bins, i) / c : 0
    array.set(w, i, ww)
// ヒートマップのプロット(簡易)
for i = 0 to 23
    plot(hour == i ? array.get(w, i) : na, style=plot.style_columns, linewidth=2)

12. 実行テンプレート(コピペ可)

  1. 対象銘柄:出来高が安定したETF/メジャー通貨/主要暗号資産。
  2. 日次予算:◯◯円。週次予算:◯◯円。
  3. 時間配分:寄り35%・引け55%(市場に応じて調整)。
  4. 発注方法:厚い時間はIOC/成行、薄い時間は浅い指値。
  5. 残弾処理:未約定は引けプールで消化。
  6. モニタリング:月次でVWAPスプレッドを記録し、重みを更新。

13. まとめ

VWSPは、価格予想に依存せずに「執行」を起点に期待値を微調整する積立手法です。U字の出来高カーブに沿って小口で刻むだけの運用でも、長期では無視できない差になります。プロの世界では当たり前の考え方ですが、個人投資家でも少額・低頻度から実行できます。まずはテンプレートをそのまま試し、1か月ごとにデータで見直すことから始めてください。

コメント

タイトルとURLをコピーしました