背景知識:Execution Simulator実装

「終値でBacktestingすることは、地図上の直線距離を使ってハイキングルートを計画するようなものです - すべての実際の障害物を無視しています。」


I. なぜExecution Simulatorが必要なのか?

1.1 BacktestとLive Tradingのギャップ

Backtest仮定Live現実
シグナル発火 = 即座の実行シグナルから実行までの遅延
Close価格で実行Order bookレベルを歩かなければならない
無制限の流動性Order book深度は限定的
注文は市場に影響しない大きな注文は価格を動かす
100% Fill rateLimit orderはフィルしない可能性

Execution Simulator目標:Backtesting中にこれらの実世界の制約をシミュレートして、「理想的な世界でのみ利益を出す」戦略をフィルタリングします。

1.2 Simulatorレベル

Execution Simulator Levels

II. Level 1:Fixed Slippage Model

2.1 原理

最も単純なシミュレーション:取引ごとに固定コストを差し引く。

実際の実行価格 = 理論価格 x (1 + スリッページ方向 x スリッページ率)

買い:実際価格 = 理論価格 x (1 + slippage)
売り:実際価格 = 理論価格 x (1 - slippage)

2.2 典型的なパラメータ

市場推奨Slippage適用シナリオ
US Large Cap0.02-0.05%AAPL、MSFT、SPY
US Small Cap0.1-0.3%ADV < $10M
A-Shares0.05-0.1%CSI 300構成銘柄
Crypto Major0.03-0.1%BTC、ETH
Crypto Altcoins0.3-1%小さな時価総額コイン

2.3 コード実装

class FixedSlippageSimulator:
    """固定Slippage実行Simulator"""

    def __init__(self, slippage_rate: float = 0.001,
                 commission_rate: float = 0.0003):
        self.slippage_rate = slippage_rate
        self.commission_rate = commission_rate

    def execute(self, order: dict) -> dict:
        """
        注文実行をシミュレート

        order = {
            'symbol': 'AAPL',
            'side': 'buy',  # 'buy'または'sell'
            'quantity': 100,
            'price': 185.0,  # 理論価格(例:Close)
        }
        """
        price = order['price']
        quantity = order['quantity']

        # Slippage調整
        if order['side'] == 'buy':
            exec_price = price * (1 + self.slippage_rate)
        else:
            exec_price = price * (1 - self.slippage_rate)

        # コストを計算
        notional = exec_price * quantity
        commission = notional * self.commission_rate
        slippage_cost = abs(exec_price - price) * quantity

        return {
            'exec_price': exec_price,
            'exec_quantity': quantity,
            'fill_rate': 1.0,  # 固定Modelは100% fillを仮定
            'commission': commission,
            'slippage_cost': slippage_cost,
            'total_cost': commission + slippage_cost
        }

2.4 制限

  • 注文サイズの影響を反映しない
  • 流動性品質を区別しない
  • 部分フィルをモデル化しない
  • 楽観的すぎるか悲観的すぎる(パラメータ設定に依存)

III. Level 2:Square-Root Impact Model

3.1 理論的基礎

Almgren-Chrissや他の研究によると、市場影響は注文サイズの平方根に比例します:

Slippage = k x sigma x sqrt(Q / ADV)

Where:
  k   = 影響係数(経験値0.5-1.5)
  sigma = 日次ボラティリティ
  Q   = 注文金額
  ADV = Average Daily Volume

直感的な説明

  • 大きな注文はより深いOrder bookレベルを「消費」
  • しかし関係は線形ではない - 最初のレベルの影響は後のものより大きい
  • より高いボラティリティは市場を影響に対してより敏感にする

3.2 コード実装

import numpy as np

class SquareRootImpactSimulator:
    """平方根影響Model実行Simulator"""

    def __init__(self,
                 impact_coef: float = 1.0,
                 commission_rate: float = 0.0003,
                 min_slippage: float = 0.0001):
        self.impact_coef = impact_coef
        self.commission_rate = commission_rate
        self.min_slippage = min_slippage

    def execute(self, order: dict, market_data: dict) -> dict:
        """
        order = {
            'symbol': 'AAPL',
            'side': 'buy',
            'quantity': 100,
            'price': 185.0,
        }

        market_data = {
            'volatility': 0.015,   # 日次ボラティリティ
            'adv': 10_000_000_000, # 平均日次ボリューム(ドル)
            'spread': 0.0001,     # Bid-ask spread比率
        }
        """
        price = order['price']
        quantity = order['quantity']
        notional = price * quantity

        vol = market_data['volatility']
        adv = market_data['adv']
        spread = market_data.get('spread', 0.0001)

        # 平方根影響Model
        participation = notional / adv
        impact = self.impact_coef * vol * np.sqrt(participation)

        # 半分のspreadを追加(spreadを横断)
        total_slippage = max(impact + spread / 2, self.min_slippage)

        # 実行価格
        if order['side'] == 'buy':
            exec_price = price * (1 + total_slippage)
        else:
            exec_price = price * (1 - total_slippage)

        # コスト計算
        commission = notional * self.commission_rate
        slippage_cost = abs(exec_price - price) * quantity

        return {
            'exec_price': exec_price,
            'exec_quantity': quantity,
            'fill_rate': 1.0,
            'commission': commission,
            'slippage_cost': slippage_cost,
            'total_cost': commission + slippage_cost,
            'impact_bps': impact * 10000,  # Impactをベーシスポイントで
        }

3.3 紙演習

シナリオ:$1,000,000の株を購入

ボラティリティADVParticipation期待Slippage
AAPL1.5%$10B0.01%1.5% x sqrt(0.0001) = 0.015%
TSLA3%$3B0.033%3% x sqrt(0.00033) = 0.055%
Small Cap X4%$10M10%4% x sqrt(0.1) = 1.26%

発見:同じ$1M注文がSmall CapではAAPLと比較して84倍高いSlippageを持っています!


IV. Level 3:Order Book Replay Model

4.1 原理

実際の履歴Level-2データを使用して、注文がOrder bookを「歩く」ことをシミュレートします。

Order Book Snapshot(t=09:30:01.123):

Ask 5: $100.10 x 500
Ask 4: $100.08 x 300
Ask 3: $100.06 x 200
Ask 2: $100.04 x 100
Ask 1: $100.02 x 50
---------------------
Bid 1: $100.00 x 80
Bid 2: $99.98  x 150
...

Market Buy 400株:
  50株 @ $100.02  (Ask 1をクリア)
  100株 @ $100.04 (Ask 2をクリア)
  200株 @ $100.06 (Ask 3をクリア)
  50株 @ $100.08  (Ask 4の一部)

VWAP = (50x100.02 + 100x100.04 + 200x100.06 + 50x100.08) / 400
     = $100.055

Mid price = ($100.02 + $100.00) / 2 = $100.01
Slippage = ($100.055 - $100.01) / $100.01 = 0.045%

4.2 完全実装

from typing import List, Tuple, Optional
from dataclasses import dataclass

@dataclass
class OrderBookLevel:
    """単一Order bookレベル"""
    price: float
    size: float  # 株数またはドル金額

@dataclass
class OrderBook:
    """Order bookスナップショット"""
    timestamp: float
    bids: List[OrderBookLevel]  # 買い側、価格降順
    asks: List[OrderBookLevel]  # 売り側、価格昇順

    @property
    def mid_price(self) -> float:
        if self.bids and self.asks:
            return (self.bids[0].price + self.asks[0].price) / 2
        return 0.0

    @property
    def spread(self) -> float:
        if self.bids and self.asks:
            return self.asks[0].price - self.bids[0].price
        return float('inf')

class OrderBookReplaySimulator:
    """Order book replay実行Simulator"""

    def __init__(self,
                 commission_rate: float = 0.0003,
                 partial_fill_enabled: bool = True):
        self.commission_rate = commission_rate
        self.partial_fill_enabled = partial_fill_enabled

    def execute_market_order(self,
                             order_book: OrderBook,
                             side: str,
                             quantity: float) -> dict:
        """
        Market orderがOrder bookを歩くことをシミュレート
        """
        if side == 'buy':
            levels = order_book.asks
        else:
            levels = order_book.bids

        if not levels:
            return self._empty_fill(quantity)

        mid_price = order_book.mid_price
        remaining = quantity
        total_cost = 0.0
        filled = 0.0
        fills = []

        for level in levels:
            if remaining <= 0:
                break

            fill_qty = min(remaining, level.size)
            fill_price = level.price

            fills.append({
                'price': fill_price,
                'quantity': fill_qty
            })

            total_cost += fill_qty * fill_price
            filled += fill_qty
            remaining -= fill_qty

        if filled == 0:
            return self._empty_fill(quantity)

        # 結果を計算
        avg_price = total_cost / filled

        if side == 'buy':
            slippage = (avg_price - mid_price) / mid_price
        else:
            slippage = (mid_price - avg_price) / mid_price

        commission = total_cost * self.commission_rate
        slippage_cost = abs(avg_price - mid_price) * filled

        return {
            'exec_price': avg_price,
            'exec_quantity': filled,
            'fill_rate': filled / quantity,
            'unfilled': remaining,
            'commission': commission,
            'slippage_cost': slippage_cost,
            'total_cost': commission + slippage_cost,
            'slippage_bps': slippage * 10000,
            'fills': fills,
            'levels_consumed': len(fills),
        }

    def execute_limit_order(self,
                            order_book: OrderBook,
                            side: str,
                            quantity: float,
                            limit_price: float,
                            queue_position: float = 0.5) -> dict:
        """
        Limit order実行をシミュレート

        queue_position: その価格レベルでのキュー位置(0=前、1=後ろ)
        """
        if side == 'buy':
            # 買い注文:limit >= ask 1なら、即座に一部フィル
            if order_book.asks and limit_price >= order_book.asks[0].price:
                return self.execute_market_order(order_book, side, quantity)
            # そうでなければキューに入る
            return self._simulate_queue(order_book, side, quantity,
                                        limit_price, queue_position)
        else:
            # 売り注文:limit <= bid 1なら、即座に一部フィル
            if order_book.bids and limit_price <= order_book.bids[0].price:
                return self.execute_market_order(order_book, side, quantity)
            return self._simulate_queue(order_book, side, quantity,
                                        limit_price, queue_position)

    def _simulate_queue(self, order_book: OrderBook, side: str,
                        quantity: float, limit_price: float,
                        queue_position: float) -> dict:
        """
        Limit orderキューをシミュレート(簡略版)

        実際には、フィルを決定するために後続のOrder flowデータが必要
        これは推定フィル確率を返す
        """
        mid = order_book.mid_price
        spread = order_book.spread

        if side == 'buy':
            # limit_priceでの買い注文
            distance_from_mid = (mid - limit_price) / mid
        else:
            distance_from_mid = (limit_price - mid) / mid

        # 簡略化されたフィル確率推定
        # より遠い距離 = より低いフィル確率
        fill_prob = max(0, 1 - distance_from_mid * 100)
        fill_prob *= (1 - queue_position * 0.5)  # キュー位置ペナルティ

        return {
            'exec_price': limit_price,
            'exec_quantity': 0,  # 即座にフィルされない
            'fill_rate': 0,
            'fill_probability': fill_prob,
            'status': 'pending',
            'queue_position': queue_position,
        }

    def _empty_fill(self, quantity: float) -> dict:
        return {
            'exec_price': 0,
            'exec_quantity': 0,
            'fill_rate': 0,
            'unfilled': quantity,
            'commission': 0,
            'slippage_cost': 0,
            'total_cost': 0,
            'error': 'no_liquidity'
        }

4.3 使用例

# Order bookを構築
order_book = OrderBook(
    timestamp=1704067200.123,
    bids=[
        OrderBookLevel(100.00, 80),
        OrderBookLevel(99.98, 150),
        OrderBookLevel(99.96, 400),
    ],
    asks=[
        OrderBookLevel(100.02, 50),
        OrderBookLevel(100.04, 100),
        OrderBookLevel(100.06, 200),
        OrderBookLevel(100.08, 300),
        OrderBookLevel(100.10, 500),
    ]
)

simulator = OrderBookReplaySimulator()

# 小さな注文:ask 1のみを消費
result_small = simulator.execute_market_order(order_book, 'buy', 30)
print(f"小さな注文30株:exec price ${result_small['exec_price']:.4f}、"
      f"slippage {result_small['slippage_bps']:.2f} bps")

# 中程度の注文:最初の3レベルを消費
result_medium = simulator.execute_market_order(order_book, 'buy', 300)
print(f"中程度の注文300株:exec price ${result_medium['exec_price']:.4f}、"
      f"slippage {result_medium['slippage_bps']:.2f} bps")

# 大きな注文:すべてのレベルを歩いてもまだ不十分
result_large = simulator.execute_market_order(order_book, 'buy', 2000)
print(f"大きな注文2000株:filled {result_large['exec_quantity']} 株、"
      f"fill rate {result_large['fill_rate']:.1%}")

出力

小さな注文30株:exec price $100.0200、slippage 1.00 bps
中程度の注文300株:exec price $100.0467、slippage 3.67 bps
大きな注文2000株:filled 1150 株、fill rate 57.5%

V. Level 4:Full Simulation環境

5.1 考慮される追加要因

要因説明実装複雑さ
キュー位置その価格レベルでのあなたの注文の位置
時間減衰より長いキュー時間 = より多くの人が先にフィル
キャンセルシミュレーション他の人がキャンセルし、あなたの位置が前進する可能性
Hidden ordersIceberg order、Dark pool orderは見えない非常に高
自己影響あなたの注文が後続の価格に影響

5.2 設計フレームワーク

class FullSimulationEngine:
    """
    Full simulation実行Engine(フレームワーク例示)

    実際の実装には必要:
    - 完全なTickデータストリーム
    - イベント駆動アーキテクチャ
    - 注文状態マシン
    """

    def __init__(self):
        self.order_book = None
        self.pending_orders = {}
        self.fills = []
        self.clock = 0

    def on_market_data(self, event: dict):
        """市場データ更新を処理"""
        if event['type'] == 'order_book_update':
            self._update_order_book(event)
            self._check_pending_fills()
        elif event['type'] == 'trade':
            self._process_trade(event)

    def submit_order(self, order: dict) -> str:
        """注文を送信、注文IDを返す"""
        order_id = self._generate_order_id()
        order['status'] = 'pending'
        order['submit_time'] = self.clock
        order['queue_position'] = self._estimate_queue_position(order)
        self.pending_orders[order_id] = order
        return order_id

    def _check_pending_fills(self):
        """保留注文がフィルできるかチェック"""
        for order_id, order in list(self.pending_orders.items()):
            fill = self._try_fill(order)
            if fill:
                self.fills.append(fill)
                if fill['remaining'] == 0:
                    del self.pending_orders[order_id]

    def _try_fill(self, order: dict) -> Optional[dict]:
        """注文をフィルしようと試みる"""
        # 価格に到達したかチェック
        # キュー位置に到達したかチェック
        # フィル可能な数量を計算
        # フィル結果を返す
        pass

    def _estimate_queue_position(self, order: dict) -> int:
        """Order bookでのキュー位置を推定"""
        # その価格レベルでの既存の注文をカウント
        pass

VI. Simulator較正

6.1 Liveデータでの較正

最も正確なSimulatorパラメータは、あなた自身のLive Trading記録から来ます。

較正プロセス

1. Live実行データを収集
   - 注文送信時刻、価格、数量
   - 実際の実行時刻、価格、数量
   - 時刻のOrder bookスナップショット(利用可能な場合)

2. 実際のSlippage分布を計算
   - Slippage = (実際exec価格 - 送信時のmid価格) / mid価格
   - 注文サイズでグループ統計

3. Modelパラメータをフィット
   - 平方根Modelの場合:k値をフィット
   - Order book Modelの場合:ウォークスルーロジックを検証

4. Model予測vs.実際を検証
   - 予測誤差分布を計算
   - 反復的にパラメータを調整

6.2 保守的原則

パラメータが不確実な場合、コストを過大評価することを好む:

class ConservativeSimulator:
    """保守的Simulator:コストを過大評価することを好む"""

    def __init__(self,
                 base_simulator,
                 safety_margin: float = 1.5):
        self.base = base_simulator
        self.margin = safety_margin

    def execute(self, order: dict, market_data: dict) -> dict:
        result = self.base.execute(order, market_data)

        # Slippageを増幅
        result['slippage_cost'] *= self.margin

        # Fill rateを減らす
        result['fill_rate'] = min(1.0, result['fill_rate'] / self.margin)

        # 総コストを再計算
        result['total_cost'] = (result['commission'] +
                                result['slippage_cost'])

        return result

VII. Multi-Agent視点

Multi-AgentシステムにおけるExecution Simulatorの位置:

Execution Simulation in Multi-Agent Architecture

VIII. 一般的な誤解

誤解1:より複雑なSimulatorは常により良い

必ずしもそうではありません。複雑なSimulatorはより多くのデータ、より多くのパラメータを必要とし、新しい不確実性を導入する可能性があります。あなたの戦略頻度に合ったSimulatorを選択してください:

戦略頻度推奨Simulator
日次/週次Level 2(平方根Model)
分レベルLevel 2-3
秒/TickレベルLevel 3-4

誤解2:Simulatorパラメータは一度設定すれば終わり

市場流動性は変化します:

  • 市場パニック中、Slippageは数倍になる
  • 個別株イベント(決算、ニュース)は短期流動性に影響
  • 市場構造の変化(例:Market maker戦略調整)

誤解3:部分フィルを無視

Limit orderの部分フィルは例外ではなく、規範です。これは意味します:

  • ポジションがアンバランスになる可能性
  • チェイスロジックが必要
  • リスクエクスポージャーが期待と異なる

IX. 実践的推奨事項

9.1 段階的実装

フェーズ1:基本検証
  - 迅速な戦略スクリーニングのためにLevel 1(固定Slippage)を使用
  - 総利益 &lt; 0.5%/取引の戦略を排除

フェーズ2:改良
  - Level 2(平方根Model)にアップグレード
  - 資産流動性ごとに異なるパラメータを設定
  - 純利益 &lt; 0の戦略を排除

フェーズ3:Live較正
  - 小資本Live Tradingでデータを収集
  - LiveデータでSimulatorを較正
  - 閉ループを形成

フェーズ4:継続的最適化
  - 新しいLiveデータで定期的にパラメータを更新
  - シミュレーションvs.実際の偏差を監視
  - 異常時にアラートをトリガー

9.2 主要メトリクス監視

def compare_simulated_vs_actual(simulated: dict, actual: dict) -> dict:
    """シミュレート結果と実際の実行を比較"""

    slippage_error = (actual['slippage_bps'] -
                      simulated['slippage_bps'])

    fill_rate_error = (actual['fill_rate'] -
                       simulated['fill_rate'])

    return {
        'slippage_error_bps': slippage_error,
        'fill_rate_error': fill_rate_error,
        'is_conservative': slippage_error < 0,  # シミュレーションが実際より悲観的
        'needs_recalibration': abs(slippage_error) > 5,  # 5bps以上は再較正が必要
    }

X. まとめ

重要なポイント説明
コア目的Backtesting中に実際の実行制約をシミュレート
レベル選択より高い戦略頻度はより洗練されたSimulatorを必要とする
保守的原則パラメータが不確実な場合、コストを過大評価することを好む
閉ループ較正LiveデータでSimulatorを継続的に較正
最終目標Backtest結果をLiveパフォーマンスに近づける

さらなる読み物

この章を引用する
Zhang, Wayland (2026). 背景知識:Execution Simulator実装. In AIクオンツ取引:ゼロからイチへ. https://waylandz.com/quant-book-ja/Execution-Simulator-Implementation
@incollection{zhang2026quant_Execution_Simulator_Implementation,
  author = {Zhang, Wayland},
  title = {背景知識:Execution Simulator実装},
  booktitle = {AIクオンツ取引:ゼロからイチへ},
  year = {2026},
  url = {https://waylandz.com/quant-book-ja/Execution-Simulator-Implementation}
}