「どれくらいの確率か」ではなく「もしそうなったら」を問う
VaRと期待ショートフォールは過去のリターンから推定するため、一度も起きていないことは見えません。ストレステストは確率を捨て、自分で選んだシナリオを当てはめます。株価20%暴落、金利2ポイント上昇、2008年危機の再現、通貨切り下げなどです。そのシナリオでのポートフォリオの損失はリスク委員会が議論できる数字であり、統計的な平均では隠れてしまう集中リスクを明らかにします。
import numpy as np
values = np.array([500000.0, 300000.0, -200000.0]) # two longs and a short
shocks = np.array([-0.20, -0.10, 0.15]) # one scenario: stocks -20%, bonds -10%, the shorted stock +15%
pnl = values * shocks # per position
print(pnl) # [-100000. -30000. -30000.] the short loses when its stock rises
print(float(pnl.sum())) # -160000.0シナリオの表
リスク部門は、歴史的(1987年、2008年、2020年3月)と仮想的なシナリオの常設リストを持っています。シナリオごとに1行、ポジションごとに1列の行列にすると、全シナリオの総損失は行列とベクトルの積1回、scenarios @ values で求まります。このベクトルの最小値が最悪のケースを示し、最も痛いシナリオがどれかも、ポートフォリオの弱点を教える情報です。
import numpy as np
values = np.array([500000.0, 300000.0, -200000.0])
scenarios = np.array([[-0.20, -0.10, 0.15], # equity crash
[-0.05, 0.02, 0.00], # mild dip
[ 0.10, 0.05, -0.20]]) # the shorted stock falls 20%: the short gains
names = ["equity crash", "mild dip", "rally and short gain"]
pnl = scenarios @ values
for name, p in zip(names, pnl):
print(name, round(float(p)))
worst = int(np.argmin(pnl))
print("worst:", names[worst], round(float(pnl[worst])))# A shock does not have to be a price move: a rate jump hits a bond through its duration
duration = 7.5 # years, from the duration topic
bond_value = 1_000_000.0
rate_jump = 0.02 # +2 percentage points
print(round(bond_value * (-duration * rate_jump))) # -150000 the shock expressed as a price return of -15%リバース・ストレステスト
問いを逆にします。資本バッファーなど、ある金額を失うにはショックがどれだけ大きくなければならないか。ショックの方向(たとえばすべてが一緒に下がる)を決め、係数 k で拡大します。損失は -k * (values @ direction) で k に線形なので、ちょうど loss_limit を失う係数は loss_limit / -(values @ direction) です。その方向で損が出ないなら、どんな係数でも限度に届きません。
import numpy as np
def reverse_stress(values, direction, loss_limit):
change = float(values @ direction)
if change >= 0:
return float("inf")
return loss_limit / -change
values = np.array([500000.0, 300000.0, -200000.0])
direction = np.array([-1.0, -1.0, 0.0]) # the two longs fall by the same percentage
k = reverse_stress(values, direction, 100000.0)
print(round(k, 4)) # 0.125 a 12.5% fall in both longs loses 100,000
print(round(float(-(values @ (k * direction))), 2)) # 100000.0シナリオは想像力の分しか役に立たない難しいのは、厳しくてもありうるシナリオを選び、危機時のように相関する資産を一緒に動かすことです。結果も確率のない点推定で、悪い数字はポジションを見直す理由になりますが、良い数字は想定しなかったシナリオについて何も保証しません。ストレステストはVaRを補うものであり、置き換えるものではありません。
- ストレステストは選んだショックを当てはめる。ポジションの損益は
values * shocksで、損は負、儲かるショートは正 - 複数のシナリオは積1回
scenarios @ valuesで求まる。最小値が最悪ケースを示し、ポートフォリオの弱点を教える - リバース・ストレス:ちょうど限度額を失うショックの倍率は
loss_limit / -(values @ direction)。その方向で損が出なければ無限大
次はARIMAモデルです。系列を自身の過去から予測します。
ラボ:採点される内容
shocked_pnlで各ポジションにショックを与える25点scenario_pnlで多数のシナリオを一度に計算する25点worst_scenarioで最悪のケースを特定する25点reverse_stressで損失から逆算する25点