固定のポジションサイズは相場環境を無視します。ボラティリティターゲティングは、現在の市場がどれだけ荒れているかに応じて、投入する量を調整します。
同じ戦略なのに、穏やかな数か月は退屈すぎて止めたくなり、市場が激しく動き始めたその一週間には、バックテストの平均からは想像もつかないほど深いドローダウンになる。変わったのはシグナルではなく市場です——そして、あなたが毎回張る額はずっと同じままでした。
ボラティリティターゲティングとは、ポジションサイズを市場の実現ボラティリティと逆方向に連動させることです。ボラティリティが目標より高ければ少なく、低ければ多く投入します——ただし上限付きです。
基本的な Type A 戦略は 1.0(ロング)または 0.0(ノーポジション)のシグナルを出力します。シグナルが 1.0 のとき、Runner は配分された資金を全額投入します。これでも動きますが、隠れた問題があります。相場が穏やかであろうと荒れていようと、戦略は同じ大きさの賭けをしてしまうのです。
低ボラティリティ局面では、1 日 1% の値動きは大きな動きです。高ボラティリティ局面では、1% の値動きはノイズにすぎません。どちらの環境でも同じポジションサイズでエントリーすると、実際の金額ベースのリスクは大きく振れます——そして高ボラティリティ局面のドローダウンは、バックテストの平均値が示唆する水準よりはるかに大きくなります。
Blave Agent はボラティリティターゲティングを二段階で実装しています。
log_return = log(Close / Close.shift(1)) realized_vol = log_return.rolling(lookback).std() * sqrt(periods_per_year)
対数リターンのローリング標準偏差を計算し、sqrt(periods_per_year) を掛けて年率換算します。結果は年率換算した実現ボラティリティで、シャープレシオで使うボラティリティと同じ単位です。
デフォルト値は lookback=720 本、periods_per_year=8760 です。これは 1h 足の戦略に合わせて調整した値です。720 本 = 30 日 × 24 時間、8760 = 24 × 365 です。
scale = (target_vol / realized_vol).clip(upper=vol_cap) scaled_signal = signal * scale
シグナル(1.0 = フルロング)にボラティリティ比率を掛け、vol_cap を上限とします。結果は小数のポジションサイズになります。
デフォルト値は target_vol=0.30(年率 30%)、vol_cap=2.0(最大 2× レバレッジ)です。
| 実現ボラティリティ(年率) | 目標ボラティリティ | 素のスケール | 上限適用後 | ポジションサイズ |
|---|---|---|---|---|
| 10% | 30% | 3.0× | 2.0×(上限) | 配分資金の 200% |
| 20% | 30% | 1.5× | 1.5× | 配分資金の 150% |
| 30% | 30% | 1.0× | 1.0× | 100% —— ベースライン |
| 60% | 30% | 0.5× | 0.5× | 配分資金の 50% |
| 90% | 30% | 0.33× | 0.33× | 配分資金の 33% |
実現ボラティリティが 30% のとき、戦略はフルサイズで稼働します。危機時に実現ボラティリティが 90% まで跳ね上がると、戦略は自動的にポジションを三分の一まで絞ります。ボラティリティが 10% の穏やかな局面では、上限の 2× までスケールアップします。
デフォルト値(lookback=720、periods_per_year=8760)は 1h 戦略向けです。他の時間足では、両方の値を再計算する必要があります。
| 時間足 | periods_per_year | lookback(30 日ウィンドウ) |
|---|---|---|
5min | 105,120(12 × 24 × 365) | 8,640 本 |
1h(デフォルト) | 8,760 | 720 本 |
4h | 2,190 | 180 本 |
8h | 1,095 | 90 本 |
1d | 365 | 30 本 |
VOL_TARGETING = True
TARGET_VOL = 0.30 # 年率の目標ボラティリティ 30%
VOL_CAP = 2.0 # 最大 2× レバレッジ
VOL_LOOKBACK = 720 # lookback ウィンドウの長さ(本)
PERIODS_PER_YEAR = 8760 # 1h 足用
def fetch_data(hdrs):
from lib.data import fetch_kline
from lib.strategy import add_realized_vol
df = fetch_kline(SYMBOL, INTERVAL, START, END, hdrs)
if VOL_TARGETING:
add_realized_vol(df, VOL_LOOKBACK, PERIODS_PER_YEAR)
return df
def compute_signals(df):
from lib.strategy import apply_vol_scaling
signal = pd.Series(np.nan, index=df.index)
# ... ここにシグナルのロジック ...
if VOL_TARGETING:
return apply_vol_scaling(signal, df, TARGET_VOL, VOL_CAP)
return signal
| パラメータ | デフォルト | 意味 | 変えるべきとき |
|---|---|---|---|
TARGET_VOL | 0.30 | 年率の目標ボラティリティ 30% | 許容できる MDD ÷ 2 に合わせる |
VOL_CAP | 2.0 | 最大 2 倍のレバレッジ | レバレッジを一切使いたくなければ 1.0 |
VOL_LOOKBACK | 720 | ボラティリティ推定のウィンドウ長(本) | 時間足を変えたら計算し直す。上の表を参照 |
PERIODS_PER_YEAR | 8760 | 年率換算に使う年間の期数(1h 足) | 時間足を変えたら計算し直す。上の表を参照 |
add_realized_vol は fetch_data の中で(シグナルを計算する前に)呼び出す必要があります。apply_vol_scaling が読み取る df['realized_vol'] は、最初の関数がインプレースで書き込む列だからです。
上限があるのは、低ボラティリティが続く環境ではボラティリティターゲティングが極端なレバレッジを生み出しうるからです。実現ボラティリティが 5% まで下がると、上限がなければスケールは 6×、つまり配分資金の三倍になります。この状態で 1 日の不利な値動きが来ると、シグナル自体は本質的に健全でも壊滅的なドローダウンを招きかねません。
デフォルトの 2× という上限は、戦略が投入できる資金を配分資金の二倍までに抑えます。レバレッジを一切避けたい戦略では vol_cap=1.0 を設定してください。こうするとボラティリティターゲティングは、レバレッジなしの純粋なボラティリティ調整型ポジションサイジングになります——ポジションサイズは高ボラティリティ時に縮むだけで、フルサイズを超えて膨らむことはありません。
役に立つ経験則があります。target_vol ≈ 許容できる MDD ÷ 2 です。これはトレンドフォロー戦略における年率ボラティリティと最大ドローダウンの経験的な関係から来ています。
| target_vol | 想定される典型的な MDD | 想定用途 |
|---|---|---|
| 10% | 約 −20% | 保守的、資金保全を優先 |
| 20% | 約 −40% | 中程度 |
| 30%(デフォルト) | 約 −60% | 積極的——リスク許容枠を使い切る |
periods_per_year は時間足と合っていますか?デフォルトの 8760 は 1h 用です。5min なら 105,120、4h なら 2,190、8h なら 1,095、1d なら 365 です。ここを間違えると年率換算のステップが狂い、その後のセルがすべて狂います。
lookback はまだ 30 日ですか?720 本=1h × 30 日です。時間足に合わせて変えてください。5min なら 8,640 本、4h なら 180 本、8h なら 90 本、1d なら 30 本。日足で 30 本だけは短いので、より安定した推定値を得るには 252 本(一年)も検討してください。
target_vol は許容できる MDD と合っていますか?経験則は target_vol ≈ 許容できる MDD ÷ 2 です。10% なら約 −20%、20% なら約 −40%、デフォルトの 30% なら約 −60%。(MDD の読み方は〈バックテストの読み方〉をご覧ください。)
vol_cap が与えるレバレッジは望んだものですか?デフォルトの 2.0 は、配分資金の二倍までという意味です。1.0 にすれば、高ボラティリティ時にポジションを絞るだけで、低ボラティリティ時にフルサイズを超えて膨らむことはありません。
個別戦略の TARGET_VOL は、ポートフォリオ Manager の target_vol_pct と整合していますか?Manager が全体のレバレッジを決めるのも同じロジックです(レバレッジ = target_vol ÷ ポートフォリオの実現ボラティリティ)。両者が食い違っていると、複数の戦略を組み合わせたときにリスクを二重に数えてしまいます。
lookback 本から計算されます。ウィンドウが長いほど推定は安定しますが反応は遅くなり、短いほど一度の短期的な急騰に過剰反応します。どちらも、ボラティリティがこれから跳ねることを前もって教えてはくれません。