ランダムウォークデータ同士のペアとボラティリティ

各データとペアのボラティリティの関係

2つのランダムウォークデータのボラティリティとペアのボラティリティの関係を調べてみる。第1のランダムウォークデータをベースとし、平均は0、標準偏差は変動させる。第2のランダムウォークデータをクウォートとし、平均は0、標準偏差は1.0で固定とする。ベースの標準偏差(ボラティリティ)を変動させることによってペアのボラティリティがどうなるかを見る。

データは累積和で作成しているので対数として扱う。したがって、ペアは「ベース / クウォート」ではなくて、「ベース - クウォート」となる。

試しにシミュレーションしてみたところ、どうやら以下のような関係にあるように思われた。

ペアのボラティリティ = sqrt(ベースのボラティリティ^2 + クウォートのボラティリティ^2)

さて、この想定に基づき、実測値と予測値のグラフを作成した。

実測値の上にほぼ完全に予測値が重なってしまい、よく分からなくなってしまった。それくらい一致しているわけで、上の想定で合っていると考えていいだろう。つまり、ランダムウォークデータ同士のペアのボラティリティは(ベースのボラティリティの2乗 + クウォートのボラティリティの2乗)の平方根である。

サンプルプログラム

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

import matplotlib.pyplot as plt
import numpy as np

def create_randomwalk_data(mean, std, size):
    temp = np.random.normal(loc=mean, scale=std, size=size)
    data = np.cumsum(temp)
    return data

n = 1000000

base_volatility = np.empty(10)
base_quote_volatility = np.empty(10)
base_quote_volatility_pred = np.empty(10)
quote = create_randomwalk_data(0.0, 1.0, n)
for i in range(10):
    base_volatility[i] = 1 + i
    base = create_randomwalk_data(0.0, base_volatility[i], n)
    base_quote = base - quote
    diff = base_quote[1:] - base_quote[:-1]
    base_quote_volatility[i] = np.std(diff)
    base_quote_volatility_pred[i] = np.sqrt(base_volatility[i]**2 + 1**2)

plt.plot(base_volatility, base_quote_volatility, label='base_quote_volatility')
plt.plot(base_volatility, base_quote_volatility_pred,
         label='base_quote_volatility_pred')
plt.title('Base/Quote Volatility')
plt.xlabel('Base Volatility (Quote Volatility = 1.0)')
plt.ylabel('Base/Quote Volatility')
plt.xlim(1, 10)
plt.legend()
plt.tight_layout()
plt.savefig('base_quote_volatility.png', dpi=150)
plt.show()
(2017/02/07更新)

ランダムウォークの歪度と計算期間の関係

ランダムウォークの歪度は0のはずだが…

ランダムウォークの歪度と計算期間の関係を調べてみる。ところで、ランダムウォークは正規分布に従っており、歪度は0のはずである。しかし、乱数を使っていることにより、歪度がちょうど0になることはほとんどない。

歪度は計算期間の平方根に反比例

平均は計算期間に比例し、標準偏差は計算期間の平方根に比例することが知られている。私は経験的に歪度は計算期間の平方根に反比例しているように予想していた。そこで

歪度の予測値 = 基準となる計算期間の歪度 / sqrt(計算期間 / 基準となる計算期間)

として歪度の予測値を求め、実際の歪度と比較してみた。

手順としては先ず平均=0、標準偏差=0.0001、歪度=100に従う乱数で5分足データを1年分作成した。歪度が0だと小さすぎて比較しにくいかもしれないと考えた。そこで、あえて100という極端に大きい数値を指定した。

次に計算期間ごとの歪度とその予測値を求めてグラフにした。

実測値と予測値はほぼ一致しているように思われる。本来なら数学的に証明すべきだろうが、とりあえずランダムウォークの歪度は計算期間の平方根に反比例すると考えてよさそうだ。

サンプルプログラム

①以下のコマンドを端末にコピー&ペーストして「Enter」キーを押す。

%run -t ~/py/make_randomwalk_data.py --mean 0.0 --std 0.0001 --skew 100.0

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

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

fs.remove_temp_folder()

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')
period = np.array(range(5, 55, 5))
skew = np.empty(10)

for i in range(10):
    ret = fs.i_log_return('RANDOM', 5, period[i], 0)[start:end]
    skew[i] = stats.skew(ret)

pred = skew[0] / np.sqrt(period / period[0])

plt.plot(period, skew, label='skew')
plt.plot(period, pred, label='pred')
plt.title('Skew and Period')
plt.xlabel('Period')
plt.ylabel('Skew')
plt.legend()
plt.savefig('skew_and_period.png', format='png', dpi=150)
plt.show()

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

ランダムウォークの勝率とペイオフレシオの関係

コツコツドカンはよくないと言われるが、それは勝率が高くても、それ以上にペイオフレシオが悪ければ資産は減っていくからである。逆に勝率が低くても、それ以上にペイオフレシオがよければ資産は増えていく。

損小利大はいいとよく言われるが、ペイオフレシオがよくても、それ以上に勝率が低ければ資産は減っていく。逆にペイオフレシオが悪くても、それ以上に勝率が高ければ資産は増えていくのである。大事なのは勝率とペイオフレシオの兼ね合いである。

一般的には勝率が高ければペイオフレシオは悪く、勝率が低ければペイオフレシオはよいと信じられている。そこで、ランダムウォークのデータを作成して、勝率とペイオフレシオの関係を調べてみる。

その前に、どのような関係にあるかを予想してみる。

一般に信じられている考えに基づくと、

平均利益 / 平均損失 = 負ける率 / 勝つ率

となるだろう。

これを整理すると、

ペイオフレシオ = 負ける率 / 勝つ率
ペイオフレシオ = (1 - 勝率) / 勝率
ペイオフレシオ * 勝率 = 1 - 勝率
ペイオフレシオ * 勝率 + 勝率 = 1
(ペイオフレシオ + 1) * 勝率 = 1
勝率 = 1 / (ペイオフレシオ + 1)

となる。

そこで

勝率 = a * (1 / (ペイオフレシオ + 1)) + b

というモデルを作成し、ペイオフレシオでうまく勝率を予測できるか見てみる。特に理由がなければ、a(傾き)は1、b(切片)は0になるはずである。

先ず、ペイオフレシオを1:9、2:8、3:7、4:6、5:5、6:4、7:3、8:2、9:1で場合分けする。次にランダムウォークのデータを作り、それぞれのペイオフレシオでの勝敗をカウントするシミュレーションを1万回行って勝率を求める。これは買いの場合と売りの場合の2つのパターンで行った。同じ値幅での上昇確率は下落確率よりわずかに高いという不均衡があり、買いがわずかに売りより有利と考えられるからである。

これについては以下を参照。

https://storage.googleapis.com/imoz-jp/slides/160321_trading.pdf(p19-22)

そしてペイオフレシオとそれに対応する勝率の関係を調べる。すると、以下のような結果になった。

ちょっと分かりにくいが、実際の勝率とペイオフレシオを元に予測した勝率はほぼ一致しており、2本の線に見えるのは上が買いの場合の勝率、下が売りの場合の勝率である。わずかに買いの勝率が売りの勝率より高いことが分かる。

さて、傾きが1、切片が0であることを期待していたのだが、わずかに違う。誤差とするには少し大きすぎるように思うが、なぜこうなるかは分からない。

とりあえず目安としては買いと売りを分けずに

勝率 = 0.9 * (1 / (ペイオフレシオ + 1)) + 0.05

くらいに考えておけばいいだろう。

これがランダムウォークで予測される勝率であるから、これより大きな勝率でなければランダムウォークに勝っていないことになるわけである。

サンプルプログラム

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

import matplotlib.pyplot as plt
import numpy as np
from scipy.optimize import curve_fit

def func(payoff_ratio, a, b):
    return a * (1 / (1 + payoff_ratio)) + b

n = 1000
max_iter = 10000
wp_long = np.empty(9)
wp_short = np.empty(9)
payoff_ratio_long = np.empty(9)
payoff_ratio_short = np.empty(9)

for i in range(9):
    profit = i + 1
    loss = 9 - i
    win_long = 0
    lose_long = 0
    win_short = 0
    lose_short = 0
    for j in range(max_iter):
        rnd = np.random.randn(n) * 0.01
        close = np.cumsum(rnd) + np.log(100)
        close = np.exp(close)
        count = 0
        for k in range(n):
            if count < k:
                count = k
            if close[k] >= 100 + profit:
                win_long += 1
                lose_short += 1
                break
            if close[k] <= 100 - loss:
                lose_long += 1
                win_short += 1
                break
    wp_long[i] = win_long / (win_long + lose_long)
    wp_short[i] = win_short / (win_short + lose_short)
    payoff_ratio_long[i] = profit / loss
    payoff_ratio_short[i] = loss / profit

popt, pcov = curve_fit(func, payoff_ratio_long, wp_long)
a_long = popt[0]
b_long = popt[1]
wp_long_pred = a_long * (1 / (1 + payoff_ratio_long))  + b_long
popt, pcov = curve_fit(func, payoff_ratio_short, wp_short)
a_short = popt[0]
b_short = popt[1]
wp_short_pred = a_short * (1 / (1 + payoff_ratio_short))  + b_short
ax=plt.subplot()
plt.plot(payoff_ratio_long, wp_long, label='wp_long')
plt.plot(payoff_ratio_long, wp_long_pred, label='wp_long_pred')
plt.plot(payoff_ratio_short, wp_short, label='wp_short')
plt.plot(payoff_ratio_short, wp_short_pred, label='wp_short_pred')
plt.title('Winning Percentage and Payoff Ratio')
plt.xlabel('Payoff Ratio')
plt.ylabel('Winning Percentage')
plt.xlim(0, 9)
plt.ylim(0, 1)
plt.text(0.1, 0.9, 'Long', transform=ax.transAxes)
plt.text(0.1, 0.85, 'Slope = ' + str(a_long), transform=ax.transAxes)
plt.text(0.1, 0.8, 'Intercept = ' + str(b_long), transform=ax.transAxes)
plt.text(0.1, 0.7, 'Short', transform=ax.transAxes)
plt.text(0.1, 0.65, 'Slope = ' + str(a_short), transform=ax.transAxes)
plt.text(0.1, 0.6, 'Intercept = ' + str(b_short), transform=ax.transAxes)
plt.legend(loc="upper right")
plt.savefig('wp_por.png', format='png', dpi=150)
plt.show()
plt.close()
(2016/12/13更新)

ランダムウォークのバンドウォークと計算期間の関係

バンドウォークとは

ここにおけるバンドウォークとは終値の移動平均を安値が連続して上回る、または高値が連続して下回る期間とする。符号は上回る場合はプラス、下回る場合はマイナスとしている。今回、ランダムウォークのデータにおいて、ある計算期間のバンドウォークがどのくらいかになるかを調べてみる。これを第1のパターンとする。

また、終値の移動平均を終値が連続して上回る、または終値が連続して下回る期間というパターンでも調べてみた。これを第2のパターンとする。

第一感としてはバンドウォークとその計算期間は比例するのではないかと思われる。そこで、計算期間を説明変数、バンドウォークの標準偏差を目的変数とする線形モデルを想定した。モデルは具体的には以下のようである。

バンドウォークの標準偏差 = 傾き * 計算期間 + 切片

バンドウォークと計算期間は比例関係

検証の結果は以下のグラフの通りである。

先ず、第1のパターンでは、

バンドウォークの標準偏差(高値・安値ベース) = 0.608286152451 * 計算期間 + (-1.1255204743)

という関係にあるようである。もちろん、乱数を用いているので、結果は毎回わずかな違いがある。

次に、第2のパターンでは、

バンドウォークの標準偏差(終値ベース) = 0.641485021915 * 計算期間 + 2.75461596213

という関係にあるようである。

第1のパターン、第2のパターンともにバンドウォークと計算期間は比例関係にあるようである。また、第1のパターンのほうが第2のパターンよりいくらか傾きがゆるやかである。これは高値や安値のほうが終値より先に移動平均線にタッチするため、バンドウォークが短くなるからで、容易に理解できるだろう。

ただ、なぜ傾き、切片がこのような数値となるのか、その数学的根拠は分からない。今回はシミュレーションによって経験的には大体以上のような関係にあるということを確認したことで満足することとする。

サンプルプログラム

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

import forex_system as fs
import matplotlib.pyplot as plt
import numpy as np
from scipy.optimize import curve_fit

def func(period, slope, intercept):
    return slope * period + intercept

fs.remove_temp_folder()

timeframe = 5

actual_value = np.empty(50)
plt.figure(figsize=(6, 8))
period = np.array(range(5, 255, 5))
for i in range(50):
    actual_value[i] = fs.i_bandwalk('RANDOM', timeframe, period[i],
    'MODE_SMA', 0).std()
popt, pcov = curve_fit(func, period, actual_value)
slope = popt[0]
intercept = popt[1]
predicted_value = slope * period + intercept
rmse = np.std(actual_value - predicted_value)
ax=plt.subplot(2, 1, 1)
plt.plot(period, actual_value, label='Actual Value')
plt.plot(period, predicted_value, label='Predicted Value')
plt.title('Bandwalk(High&Low) and Period')
plt.xlabel('Period')
plt.ylabel('Bandwalk')
plt.xlim(50, 250)
plt.text(0.05, 0.9, 'Slope = ' + str(slope), transform=ax.transAxes)
plt.text(0.05, 0.85, 'Intercept = ' + str(intercept), transform=ax.transAxes)
plt.text(0.05, 0.8, 'RMSE = ' + str(rmse), transform=ax.transAxes)
plt.legend(loc="lower right")
print('Slope = ', slope)
print('Intercept = ', intercept)
print('RMSE = ', rmse)

actual_value = np.empty(50)
period = np.array(range(5, 255, 5))
for i in range(50):
    actual_value[i] = fs.i_bandwalk_cl('RANDOM', timeframe, period[i],
    'MODE_SMA', 0).std()
popt, pcov = curve_fit(func, period, actual_value)
slope = popt[0]
intercept = popt[1]
predicted_value = slope * period + intercept
rmse = np.std(actual_value - predicted_value)
ax=plt.subplot(2, 1, 2)
plt.plot(period, actual_value, label='Actual Value')
plt.plot(period, predicted_value, label='Predicted Value')
plt.title('Bandwalk(Close) and Period')
plt.xlabel('Period')
plt.ylabel('Bandwalk')
plt.xlim(50, 250)
plt.text(0.05, 0.9, 'Slope = ' + str(slope), transform=ax.transAxes)
plt.text(0.05, 0.85, 'Intercept = ' + str(intercept), transform=ax.transAxes)
plt.text(0.05, 0.8, 'RMSE = ' + str(rmse), transform=ax.transAxes)
plt.legend(loc="lower right")
print('Slope = ', slope)
print('Intercept = ', intercept)
print('RMSE = ', rmse)
plt.tight_layout()
plt.subplots_adjust(hspace=0.3)
plt.savefig('bandwalk_period.png', dpi=150)
plt.show()

fs.remove_temp_folder()
(2017/01/30更新)