イベントとボラティリティ (2017/08/04)

イベントとボラティリティとがどのような関係にあるかを調べてみる。

ただ、因果関係が逆にはなるものの、ボラティリティの拡大をイベントの発生と考えることにする。イベントの情報を集め、それを入力するのは面倒なので、ここは手を抜く。

イベントとしては経済指標を想定している。特定の時間に発表されることが多く、再現性が見込まれるからである。

そこで、0時0分から23時55分まで、5分ごとのボラティリティを調べた。調べた期間は2014年1月1日から2016年12月31日まで、通貨ペアはEURJPY、EURUSD、USDJPYである。

ボラティリイティはTrue Rangeで測り、正規化した。

ボラティリティが拡大する時間

それでは実際に検証してみる。

①この記事で使うライブラリをインポートする。

import forex_system as fs
import matplotlib.pyplot as plt
import numpy as np
from datetime import datetime

②データの位置から時間を求める関数を定義する。

def find_time(position, timeframe):
    position = position * timeframe
    minute = position % 60
    hour = int(np.floor((position % 1440) / 60))
    return hour, minute

③検証して結果をグラフに表示する。

fs.remove_folder('temp')

timeframe = 5
n = 288
x = np.arange(n)
y = np.empty(n)
event = np.zeros([n, 3])
plt.figure(figsize=(6, 12))

start = datetime.strptime('2014.01.01 00:00', '%Y.%m.%d %H:%M')
end = datetime.strptime('2016.12.31 23:59', '%Y.%m.%d %H:%M')
close = fs.i_close('USDJPY', timeframe, 0)[start:end]
index = close.index
hour = fs.time_hour(index)
minute = fs.time_minute(index)

cnt = 0
for symbol in ['EURJPY', 'EURUSD', 'USDJPY']:
    plt.subplot(3, 1, cnt+1)
    atr = fs.i_atr(symbol, timeframe, 1, 0)[start:end]
    mean = atr.mean()
    std = atr.std()
    for i in range(n):
        temp = atr[(hour*60+minute)==i*timeframe].mean()
        y[i] = (temp-mean) / std
    if cnt == 0:
        event = y
    else:
        event = np.c_[event, y]
    plt.plot(x, y)
    plt.title('Event and Volatility (' + symbol + ')')
    plt.xlabel('Hour')
    plt.ylabel('Normalized True Range')
    plt.xlim(0, 288)
    plt.ylim(-1, 2)
    plt.xticks([0, 48, 96, 144, 192, 240, 288], [0, 4, 8, 12, 16, 20, 24])
    plt.axvline(x=48, color='black', linestyle=':')
    plt.axvline(x=96, color='black', linestyle=':')
    plt.axvline(x=144, color='black', linestyle=':')
    plt.axvline(x=192, color='black', linestyle=':')
    plt.axvline(x=240, color='black', linestyle=':')
    plt.axhline(y=0.0, color='black', linestyle=':')
    plt.tight_layout()
    cnt += 1
plt.savefig('event_and_volatility.png', dpi=150)
plt.show()

所々でボラティリティが拡大している時間のあることが分かる。グラフでは分かりにくいが14時30分が最も高くなる。これは米雇用統計の発表時間である。

ボラティリティ拡大の時間リスト

グラフでは分かりにくいので、ボラティリティが0.5以上となる時間をピックアップしてみる。

④各通貨ペアごとにボラティリティが拡大する時間リストを作る。

cnt = 0
for symbol in ['EURJPY', 'EURUSD', 'USDJPY']:
    print(symbol)
    position = np.where(event[:, cnt]>=0.5)[0]
    size = len(position)
    for i in range(size):
        time = find_time(position[i], timeframe)
        print(time)
    cnt += 1

fs.remove_folder('temp')

EURJPY
(9, 0)
(10, 0)
(11, 0)
(14, 30)
(14, 35)
(14, 40)
(14, 45)
(14, 50)
(14, 55)
(15, 0)
(15, 5)
(15, 10)
(15, 15)
(15, 20)
(15, 25)
(15, 30)
(15, 35)
(15, 40)
(15, 45)
(15, 50)
(15, 55)
(16, 0)
(16, 5)
(16, 10)
(16, 15)
(16, 20)
(16, 25)
(16, 30)
(16, 35)
(16, 40)
(16, 45)
(16, 50)
(16, 55)
(17, 0)
(17, 5)
(17, 10)
(17, 15)
(17, 55)
EURUSD
(9, 0)
(10, 0)
(11, 0)
(14, 30)
(14, 35)
(14, 40)
(14, 45)
(14, 50)
(14, 55)
(15, 0)
(15, 5)
(15, 10)
(15, 15)
(15, 20)
(15, 25)
(15, 30)
(15, 35)
(15, 40)
(15, 45)
(15, 50)
(15, 55)
(16, 0)
(16, 5)
(16, 10)
(16, 15)
(16, 20)
(16, 25)
(16, 30)
(16, 35)
(16, 40)
(16, 45)
(16, 50)
(16, 55)
(17, 0)
(17, 5)
(17, 10)
(17, 15)
(17, 55)
USDJPY
(2, 0)
(14, 30)
(15, 30)
(15, 35)
(16, 0)
(16, 55)
(17, 0)

いずれの通貨ペアでも14時30分が含まれていることが分かる。

(メモ)

経済指標の発表時間は様々である。時間が決まっていないもの、時間が決まっているもの、曜日と時間が決まっているもの、月の第何週と曜日と時間が決まってるもの、その他いろいろである。

となると、何曜日か、さらには第何週かで細分すべきかもしれない。例えば、米雇用統計のある第1金曜日以外ではボラティリティは14時30分に必ずしも拡大しないだろう。

ただ、トレードに資するかどうかという実用的な観点で見るならば、細分することでパフォーマンスが向上するということはなさそうである。もちろん、トレード戦略の性質にもよる。

特に第何週まで細分化すると、第5金曜日という3ヶ月に1回くらいしか出現しないデータが出てきて、データが少ない分、不安定化してボラティリティが大きく見えるという場合もある。

ただ、細分化しないとイベントが発生しない時間が含まれて、イベントが発生した時間のボラティリティが実際より小さく見えるという問題もある。

思うに、実用的な観点で見ると、細分化はせず、その代わりにイベントが発生したと判断する閾値を低目にするのがよさそうである。例えばボラティリティが0.5以上でイベント発生と判断する、というようにである。とはいえ、トレード戦略の性質にもよるので、一律に適用するのは問題があるだろう。

平均回帰戦略とボラティリティ

ボラティリティが平均回帰戦略にどのような影響を与えるかを調べてみる。

検証に当たっては「トレード戦略」カテゴリの「基本的な平均回帰戦略」を用いた。シグナルはそのままとして、指定したボラティリティを超えた部分をカットしてパフォーマンスがどうなるかを見てみる。

ボラティリティは平均回帰戦略に悪影響か

先ずリターンの標準偏差を求め、標準偏差の0.5、1.0、1.5、2.0の4つのパターンで調べる。

In [1]:
import forex_system as fs
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
import numpy as np
from datetime import datetime

fs.remove_temp_folder()

symbol = 'USDJPY'
timeframe = 5
period = 10
spread = 0.0
position = 2
start = datetime.strptime('2016.01.01 00:00', '%Y.%m.%d %H:%M')
end = datetime.strptime('2016.12.31 23:59', '%Y.%m.%d %H:%M')

entry_threshold = 1.5
filter_threshold = 10

zscore1 = fs.i_zscore(symbol, timeframe, period, 'MODE_SMA', 1)
bandwalk1 = fs.i_bandwalk(symbol, timeframe, period, 'MODE_SMA', 1)
buy_entry = (zscore1 <= -entry_threshold) & (bandwalk1 <= -filter_threshold)
buy_exit = zscore1 >= 0.0
sell_entry = (zscore1 >= entry_threshold) & (bandwalk1 >= filter_threshold)
sell_exit = zscore1 <= 0.0
data = symbol, timeframe, buy_entry, buy_exit, sell_entry, sell_exit, 0.1, 0, 0
signal, lots = fs.calc_signal(data)
ret = fs.calc_ret(symbol, timeframe, signal, spread, position, start, end)
cumret = (ret + 1.0).cumprod() - 1.0
sharpe = fs.calc_sharpe(ret, start, end)
print('std\t', 'sharpe ratio')
print('org\t', sharpe)
plt.plot(cumret, label='org')

op = fs.i_open(symbol, timeframe, 0)
std = np.std(op.shift(-1)/op-1.0)
for i in range (4):
    rnd = (i + 1) * 0.5
    ret2 = ret.copy()
    ret2[ret2/std>=rnd] = std * rnd
    ret2[ret2/std<=-rnd] = std * (-rnd)
    cumret = (ret2 + 1.0).cumprod() - 1.0
    sharpe = fs.calc_sharpe(ret2, start, end)
    print(str(rnd), '\t', sharpe)
    plt.plot(cumret, label=str(rnd))

plt.title('Mean Reversion and Volatility')
plt.xlabel('Date')
plt.ylabel('Cumulative Return')
plt.legend(loc='upper left')
ax=plt.subplot()
ax.set_xticklabels(cumret.index, rotation=45)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
plt.tight_layout()
plt.savefig('mean_reversion_and_volatility.png', dpi=150)

fs.remove_temp_folder()


std      sharpe ratio
org      2.22657550743
0.5      5.68911369135
1.0      4.93028818462
1.5      4.02796478075
2.0      3.48310576555


指定したボラティリティで超えた部分をカットした場合、いずれも元の戦略のシャープレシオを超えている。また、カットする部分が多いほどシャープレシオは高くなっている。

グラフを見ると、標準偏差1.0以上の場合では累積リターンでも元の戦略を超えている。標準偏差0.5の場合ではさすがにカットされる部分が大きすぎるようだが、それでも累積リターンは元の戦略と大差ない。

指定したボラティリティを超えた部分をカットした場合、カットされるのは損失だけではなく、利益もである。にもかかわらず、カットしたことによって累積リターンが増えたのはなぜか。これは損失のときのほうが利益のときよりボラティリティが大きく、カットされる部分がより大きいからだろう。

この結果はボラティリティが平均回帰戦略に悪影響を与えていることを示唆しているようにも思える。だが、そうだと断定するのは早計だろう。別の角度からも検証するべきかと思う

ボラティリティは避けるべきか

私は数時間程度の時間軸では為替レートは平均回帰的であるが、ファンダメンタルによる変化は回帰しないというイメージを持っている。ファンダメンタルの変化は往々にして大きなボラティリティをもたらす。こうして起きた変動は多少は戻すこともあるだろうが、基本的に戻らないと考えるのである。

すると、ボラティリティが拡大する局面でトレードを避けたならば、平均回帰戦略のパフォーマンスを向上させることができるのではないかということは容易に思いつく。この想定に基づいて、いろいろ検証してはいるのだが、今のところ、結果は出ていない。

現時点ではボラティリティはやはり避けたほうが無難だと考えている。だが、単純に避けるだけではパフォーマンスを向上させることはできず、さらに工夫を加える必要がある。

(2017/03/09更新)

ラウンドナンバーとボラティリティ

価格がラウンドナンバーを上抜き、または下抜きしたとき、ボラティリティがどうなるかを検証してみる。

100pipsの単位が更新されたとき、ラウンドナンバーを上抜き、または下抜きしたと見なすこととする。例えばUSDJPYなら100円未満から100円以上になったときに上抜きと見なすといった具合である。

ボラティリティを計る指標としてはTrue Rangeを用いた。先ず10pipsの単位で10pipsごとにラインを10本設け、それを上抜き、または下抜きした場合のTrue Rangeを求める。次にその平均を取ってベンチマークとする。そして、それぞれのTrue Rangeを平均で除した。つまりベンチマーク=1.0である。

10pipsの単位が0のライン、すなわちラウンドナンバーが他のラインと差があるかどうかを見てみる。使用した足の種類は5分足である。

ラウンドナンバーでボラティリティはやや拡大

検証結果によると、価格がラウンドナンバーを上抜き、または下抜きしたとき、ボラティリティはやや拡大する傾向があるように見える(Decimalが0.0のところ)。

ボラティリティはやや拡大しているとはいうものの、それほど明瞭ではない。ラウンドナンバーはしばらく更新されていないといった条件がないと、大した意味を持たないのかもしれない。USDJPYはいくらか明瞭な感じがする。

サンプルプログラム

○以下のコマンドを「IPython console」にコピー&ペーストして「Enter」キーを2回押す。

import forex_system as fs
import matplotlib.pyplot as plt
import numpy as np
from datetime import datetime

def get_one(data):
    temp = data.copy()
    temp = np.floor(temp)
    ret = temp % 10
    ret = ret.astype(int)
    return ret

def get_hundredth(data):
    temp = data.copy()
    temp *= 100
    temp = np.floor(temp)
    ret = temp % 10
    ret = ret.astype(int)
    return ret

fs.remove_temp_folder()

timeframe = 5
n = 10
x = np.empty(n)
y = np.empty(n)
plt.figure(figsize=(6, 20))
cnt = 0
for symbol in ['AUDUSD', 'EURUSD', 'GBPUSD', 'USDJPY', 'RANDOM']:
    cnt += 1
    plt.subplot(5, 1, cnt)
    for year in [2012, 2013, 2014, 2015, 2016]:
        start = datetime.strptime(str(year) + '.01.01 00:00',
                                  '%Y.%m.%d %H:%M')
        end = datetime.strptime(str(year) + '.12.31 23:59',
                                '%Y.%m.%d %H:%M')
        ret = fs.i_trange(symbol, timeframe, 0)[start:end]
        high0 = fs.i_high(symbol, timeframe, 0)[start:end]
        high1 = fs.i_high(symbol, timeframe, 1)[start:end]
        low0 = fs.i_low(symbol, timeframe, 0)[start:end]
        low1 = fs.i_low(symbol, timeframe, 1)[start:end]
        for i in range(n):
            if symbol == 'USDJPY' or symbol == 'RANDOM':
                n_h0 = get_one(high0+0.1*i)
                n_h1 = get_one(high1+0.1*i)
                n_l0 = get_one(low0+0.1*i)
                n_l1 = get_one(low1+0.1*i)
            else:
                n_h0 = get_hundredth(high0+0.001*i)
                n_h1 = get_hundredth(high1+0.001*i)
                n_l0 = get_hundredth(low0+0.001*i)
                n_l1 = get_hundredth(low1+0.001*i)
            x[i] = 0.1 * i
            y[i] = np.mean(ret[(n_h0!=n_h1) | (n_l0!=n_l1)])
        benchmark = np.mean(y)
        y /= benchmark
        plt.plot(x, y, label=str(year))
    plt.title('Round Number and Volatility (' + symbol + ')')
    plt.xlabel('Decimal (100pips=1.0)')
    plt.ylabel('True Range (Benchmark=1.0)')
    plt.xlim(0.0, 0.9)
    plt.ylim(0.8, 1.2)
    plt.legend()
    plt.axhline(y=1.0, color='black', linestyle=':')
    plt.tight_layout()
plt.savefig('round_number_and_volatility.png', dpi=150)
plt.show()

fs.remove_temp_folder()
(2017/02/17更新)

直前のTR拡縮と現在のTR拡縮の関係

直前のTR拡縮と現在のTR拡縮は負の比例関係

直前のTR(トゥルー・レンジ)の拡縮と現在のTRの拡縮がどのような関係にあるか調べてみた。足の種類は5分足である。TRの拡縮は標準化している。

グラフを見ると、直前のTRの拡縮と現在のTRの拡縮とは負の比例関係にあることが分かる。

サンプルプログラム

TRは正の値しか取らないためか、拡縮を標準化すると分布に偏りがある。そこで、先ずTRを対数変換し、それから拡縮を標準化している。

○以下のコマンドを「IPython console」にコピー&ペーストして「Enter」キーを2回押す。

import forex_system as fs
import matplotlib.pyplot as plt
import numpy as np
from datetime import datetime

fs.remove_temp_folder()

timeframe = 5
n = 6
x = np.empty(n)
y = np.empty(n)
plt.figure(figsize=(6, 20))
cnt = 0
for symbol in ['AUDUSD', 'EURUSD', 'GBPUSD', 'USDJPY', 'RANDOM']:
    cnt += 1
    plt.subplot(5, 1, cnt)
    for year in [2012, 2013, 2014, 2015, 2016]:
        start = datetime.strptime(str(year) + '.01.01 00:00',
                                  '%Y.%m.%d %H:%M')
        end = datetime.strptime(str(year) + '.12.31 23:59',
                                '%Y.%m.%d %H:%M')
        atr = fs.i_atr(symbol, timeframe, 1, 0)
        atr[atr==0] = np.nan
        atr = atr.dropna()
        atr = np.log(atr)
        change = atr - atr.shift(1)
        mean = np.mean(change[start:end])
        std = np.std(change[start:end])
        change = (change - mean) / std
        change0 = change[start:end]
        change1 = change.shift(1)[start:end]
        for i in range(n):
            x[i] = i - 2.5
            y[i] = np.mean(change0[(change1.shift(1) >= x[i] - 0.5) &
             (change1.shift(1) < x[i] + 0.5)])
        plt.plot(x, y, label=str(year))
    plt.title('Previous TR Change and Current TR Change (' + symbol + ')')
    plt.xlabel('Previous TR Change')
    plt.ylabel('Current TR Change')
    plt.xlim(-2.5, 2.5)
    plt.ylim(-0.2, 0.2)
    plt.legend(loc='upper right')
    plt.axvline(x=0, color='black', linestyle=':')
    plt.axhline(y=0, color='black', linestyle=':')
    plt.tight_layout()
plt.savefig('previous_tr_change_and_current_tr_change.png', dpi=150)
plt.show()

fs.remove_temp_folder()
(2017/02/12更新)

直前のTRと現在のTRの関係

直前のTRと現在のTRは比例関係

直前のTR(トゥルー・レンジ)と現在のTRがどのような関係にあるか調べてみた。足の種類は5分足である。TRは標準化している。

グラフを見ると、直前のTRと現在のTRとは比例関係にあることが分かる。

サンプルプログラム

TRは正の値しか取らないためか、そのまま標準化すると正のほうに偏る。そこで、先ず対数変換し、それから標準化している。

○以下のコマンドを「IPython console」にコピー&ペーストして「Enter」キーを2回押す。

import forex_system as fs
import matplotlib.pyplot as plt
import numpy as np
from datetime import datetime

fs.remove_temp_folder()

timeframe = 5
n = 6
x = np.empty(n)
y = np.empty(n)
plt.figure(figsize=(6, 20))
cnt = 0
for symbol in ['AUDUSD', 'EURUSD', 'GBPUSD', 'USDJPY', 'RANDOM']:
    cnt += 1
    plt.subplot(5, 1, cnt)
    for year in [2012, 2013, 2014, 2015, 2016]:
        start = datetime.strptime(str(year) + '.01.01 00:00',
                                  '%Y.%m.%d %H:%M')
        end = datetime.strptime(str(year) + '.12.31 23:59',
                                '%Y.%m.%d %H:%M')
        ret = fs.i_atr(symbol, timeframe, 1, 0)[start:end]
        ret[ret==0] = np.nan
        ret = ret.dropna()
        ret = np.log(ret)
        mean = np.mean(ret)
        std = np.std(ret)
        ret = (ret - mean) / std
        for i in range(n):
            x[i] = i - 2.5
            y[i] = np.mean(ret[(ret.shift(1) >= x[i] - 0.5) &
             (ret.shift(1) < x[i] + 0.5)])
        plt.plot(x, y, label=str(year))
    plt.title('Previous True Range and Current True Range (' + symbol + ')')
    plt.xlabel('Previous True Range')
    plt.ylabel('Current True Range')
    plt.xlim(-2.5, 2.5)
    plt.ylim(-1.75, 1.75)
    plt.legend(loc='upper left')
    plt.axvline(x=0, color='black', linestyle=':')
    plt.axhline(y=0, color='black', linestyle=':')
    plt.tight_layout()
plt.savefig('previous_true_range_and_current_true_range.png', dpi=150)
plt.show()

fs.remove_temp_folder()
(2017/02/12更新)