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

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

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

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

先ずリターンの標準偏差を求め、標準偏差の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更新)

ブレイクアウトとボラティリティ

ブレイクアウトが発生したとき、ボラティリティがどうなるかを検証してみる。ボラティリティを計る指標としてTrue Rangeを用いた。先ず全体のTrue Rangeの平均を求める。次にブレイクアウトが発生したときのTrue Rangeの平均を求める。最後に後者を前者で除して、その比率を求めた。使用した足の種類は5分足である。

ブレイクアウト時にボラティリティは拡大

検証結果によると、ブレイクアウトが発生したとき、ボラティリティは拡大する傾向があるように見える。

すべての通貨、すべての年において、計算期間が長くなるにつれてブレイクアウトが発生したときのボラティリティが拡大する傾向にあることが分かる。

なお、ランダムウォークのボラティリティがすべての計算期間で1.0よりやや大きいのが腑に落ちない。想像するに、全体のTrue Rangeはほとんど0に近い場合もある。このため、平均は小さくなる。だが、ブレイクアウトが発生したときのTrue Rangeでは必ずある程度の変動があるはずである。このため、平均は全体より大きくなる。こういったことにより、比率が1.0よりやや大きくなったのではないだろうか。

サンプルプログラム

○以下のコマンドを「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 = 24
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]
        for i in range(n):
            minute = (i + 1) * 60
            period = fs.convert_minute2period(minute, timeframe)
            highest = fs.i_highest(symbol, timeframe, period, 0)[start:end]
            lowest = fs.i_lowest(symbol, timeframe, period, 0)[start:end]
            x[i] = i + 1
            y[i] = np.mean(ret[(highest==0) | (lowest==0)]) / np.mean(ret)
        plt.plot(x, y, label=str(year))
    plt.title('Breakout and Volatility (' + symbol + ')')
    plt.xlabel('Hour')
    plt.ylabel('True Range (mean=1.0)')
    plt.xlim(1, 24)
    plt.ylim(0.8, 2.8)
    plt.legend(loc='upper left')
    plt.axhline(y=1.0, color='black', linestyle=':')
    plt.tight_layout()
plt.savefig('breakout_and_volatility.png', dpi=150)
plt.show()
(2017/02/17更新)