相関係数とトレンド

相関係数とトレンドとの関係を考えてみる。

正相関ならトレンド的か?

時系列データにおいて、データの階差とその前のデータの階差とが正相関、つまり相関係数がプラスの場合、トレンド的と言えるだろうか。

また、逆にデータの階差とその前のデータの階差とが逆相関、つまり相関係数がマイナスの場合、平均回帰的と言えるだろうか。

この問いに対して何となく「そうだろう」と考えている人は少なくないのではないかと思われる。というか、私がそうだった(笑)。

ただ、共和分の時系列データは平均回帰的だが、必ずしも逆相関ではない。したがって、完全に対応しているわけではない。しかし、それでも対応しているケースが多いのではないかと私は考えていたのである。

y = sin(x)における相関係数

そういうケースが多いか少ないかということは置いておく。ここでは正相関であり、かつ平均回帰的であって、しかも、それが極端であるデータを作成してみる。これはいたって簡単で、「y = sin(x)」のデータを作成すればいいだけである。

先ず、データを作成して相関係数を見てみる。

In [1]:
import matplotlib.pyplot as plt
import numpy as np

x = np.arange(-360, 360, 1)
y = np.sin(x / 180 * np.pi)

diff = y[1:] - y[:-1]
diff0 = diff[1:]
diff1 = diff[:-1]
cor = np.corrcoef(diff1, diff0)[0, 1]
print('cor = ', cor)


cor =  0.999846842234


相関係数はほぼ1.0で、完全に近い正相関となっている。考えてみれば、これは当然である。sin(x)の向きはずっとプラスか、ずっとマイナスで、プラスからマイナス、またはマイナスからプラスに転じるのは頂点だけだからだ。

y = sin(x)のグラフ

では次に、同じデータを使ってグラフを作成して見てみる。

In [2]:
plt.plot(x, y, label='y = sin(x)')
plt.title('y = sin(x)')
plt.xlabel('x')
plt.ylabel('y')
plt.xticks([-360, -270, -180, -90, 0, 90, 180, 270, 360])
plt.legend(loc='upper right')
plt.axvline(x=0.0, color='black')
plt.axhline(y=0.0, color='black')
plt.tight_layout()
plt.savefig('sinx.png', dpi=150)
plt.show()



高校数学でよく見かけるグラフだが、改めて見ると完全に平均回帰的である。-1で買い、+1で売れば百戦百勝だ。

相関係数とトレンドは別物

ほぼ完全な正相関でありながら完全に平均回帰的であるデータを作成できるということを考えると、相関係数とトレンド(あるいは平均回帰)は全くの別物と見たほうがいいだろう。

(2017/03/20更新)

ランダムウォーク・データの作成 (2017/04/11)

ランダムウォークのデータを作成する。作成にあたってはUSDJPYのデータの日時を利用しているので、あらかじめ「ヒストリカルデータの加工」でデータを作成しておく必要がある。

import forex_system as fs
fs.get_randomwalk_data()

開始日と終了日はUSDJPYと同じになるので指定する必要はない。作成される足の種類もUSDJPYと同じである。

デフォルトの設定では1分足の単位で平均=0.0、標準偏差=0.01 / sqrt(1440)、歪度=0.0の正規分布に従うランダムウォークのデータが作成される。日足での標準偏差が0.01(1%)となるイメージである。

デフォルトとは違う設定で作成したい場合は以下のようにする。

import forex_system as fs
fs.get_randomwalk_data(mean=-0.00001, std=0.0005, skew=1.0)

この場合、平均=-0.00001、標準偏差=0.0005、歪度=1.0の非正規分布に従うランダムウォークのデータが作成される。歪度が0ではないのでランダムウォークと呼ぶのは正しくないかもしれないが、ここではランダムウォークとして扱う。

文字列の一括置換

プログラムを作成しているとき、変数名だけ変えて、他はすべてコピペしたいときがある。数が多いと面倒なので、Linuxコマンドで一括置換する。

あらかじめ古いテキスト用のファイルと新しいテキスト用のファイルを準備しておく。ここでは「~/py」フォルダーに「old_text.txt」と「new_text.txt」の各ファイルを作成したことにする。

例として、「old_text.txt」ファイルに以下の

古い1
古い2
古い3

をコピー・アンド・ペーストして保存しておく。そして、これを以下のコマンドで書き換える。

$ sed -e "s/古い/新しい/g" ~/py/old_text.txt > ~/py/new_text.txt

すると、「new_text.txt」ファイルに

新しい1
新しい2
新しい3

と置換されて書き込まれる。

(2017/03/16更新)

想定スプレッド

バックテストを行うとき、スプレッドをどのくらいに想定するかを考えてみる。

FX会社のスプレッド比較

先ずは参考にFX会社のスプレッドを比較してみた。

私はFX会社のことはよく知らない。漏れている会社もあると思う。また、ここに掲載した会社を勧めているわけでもない。

選択の基準は

  • 取引手数料が無料であること
  • スプレッドが原則固定であること
  • USDJPYのスプレッドが0.4銭以下であること

の3つである。

FX会社は五十音順で並べている。また、通貨ペアごとに最狭スプレッドはオレンジ色にしてある。「×」はFX会社がその通貨ペアを取り扱っていない、取り扱ってはいるがスプレッドが原則固定ではない、情報がない、などの意味である。

(※) FX会社によっては原則固定を明記していない場合があります。もし間違いがありましたら、ご一報いただけると助かります。

FX会社 AUDJPY AUDUSD EURAUD EURGBP EURJPY EURUSD GBPAUD GBPJPY GBPUSD USDJPY 確認日 備考
インヴァスト証券 0.60 1.40 2.00 1.90 0.50 0.30 × 1.00 1.40 0.30 2017-03-15
FXブロードネット 0.60 × 6.80 2.90 0.50 0.30 × 1.00 2.60 0.30 2017-03-15 ブロードコース
FXTF 1.40 1.90 2.80 1.60 0.60 0.80 4.30 2.00 1.10 0.30 2017-03-15
SBI FXトレード 0.77 1.39 1.50 0.90 0.69 0.48 1.60 1.19 1.49 0.27 2017-03-15 1万通貨単位まで
岡三オンライン証券 0.90 1.20 1.60 1.40 0.90 0.70 1.90 1.40 1.30 0.40 2017-03-15
外為ジャパン 0.70 1.10 × 1.40 0.60 0.50 × 1.10 1.10 0.30 2017-03-15
外為どっとコム 0.70 1.10 1.60 × 0.60 0.50 × 1.10 1.10 0.30 2017-03-15
GMOクリック証券 0.70 1.10 1.60 1.40 0.60 0.50 1.90 1.10 1.10 0.30 2017-03-15
JFX株式会社 0.70 1.00 1.60 1.40 0.50 0.40 1.70 1.30 1.00 0.30 2017-03-15 赤字のみ
DMM FX 0.70 1.10 1.60 1.40 0.60 0.50 1.90 1.10 1.10 0.30 2017-03-15
ヒロセ通商 0.70 1.00 1.60 1.40 0.50 0.40 1.70 1.30 1.00 0.30 2017-03-15
みんなのFX 0.70 × × × 0.60 0.50 × 1.00 × 0.30 2017-03-15
楽天証券 1.20 1.90 × 4.00 1.10 0.40 × 1.00 1.00 0.30 2017-03-15
YJFX! 0.80 1.40 2.30 1.80 0.70 0.60 × 1.20 1.50 0.30 2017-03-15


最狭スプレッドを目安に想定

最狭スプレッドを目安としてスプレッドを想定してみる。SBI FXトレードは0.01刻みだが、この位は切り上げることとしよう。すると以下のようになる。

AUDJPY: 0.6
AUDUSD: 1.0
EURAUD: 1.5
EURGBP: 0.9
EURJPY: 0.5
EURUSD: 0.3
GBPAUD: 1.6
GBPJPY: 1.0
GBPUSD: 1.0
USDJPY: 0.3


コストは高めに想定すべきか

各FX会社の中で最も狭いスプレッドを目安にするというのは甘すぎるのではないかと疑問に思う人もいるだろう。だが、あまりコストを高めに想定すると、狭いスプレッドでは非常に有効な戦略が排除されてしまうことになりかねないと私は考えている。

我々は安易にFX会社を信用してはなるまい。スプレッドは固定であっても、エントリーやエグジットのときに不利なレートで執行されているかもしれない。

そうでなくても、スリッページは起こりうる。するとバックテストでのパフォーマンスと実際のそれとの間には乖離が生じる。コストをあまり低めに想定すると、このような乖離に対して脆弱になる。

いずれにせよ、メリットもあれば、デメリットもあって難しい。ただ、ほんの数年前までスプレッドは現在の何倍もあった。そのときのスプレッドでは短期トレードはコスト負けしてしまい、日足中心のトレードを考えるしかなかったのである。

少なくとも私はそうであった。もし、スプレッドがここまで狭くならなかったなら、私は短期トレードをやらなかっただろう。だが、私は短期トレードによって利益を上げられることを日々実感しているのである。

このようなことから、多少甘いとは思うが、最も狭いスプレッドを目安にしようと思う。

一般トレーダーの優位性

FX会社のスプレッドを見ると、すべての通貨ペアで狭いというところはない。ある通貨ペアでは非常に狭いのに、別の通貨ペアでは意外と広かったりする。

想像するに、FX会社は特定の通貨ペアを選好するトレーダーを取り込むために赤字覚悟でその通貨ペアのスプレッドを狭くしているのではないだろうか。そして、他の通貨ペアは広くしてバランスを取っているのではないだろうか。

何百万通貨単位で取引するような大口トレーダーにはこのように狭いスプレッドは提供されていない。とすると、通貨ペアごとにスプレッドが最も狭いFX会社で取引できる、言葉は悪いが、つまみ食いできるというのは一般(零細?)トレーダーの優位性ではないだろうか。

為替レートは一見したところ、ランダムウォークのようである。だが、コスト負けするような領域では明確な傾向を見せる。そこに一般トレーダーの活路がある。

情けない話かもしれないが、資金力のある相手とまともに張り合うのは賢明ではないだろう。そういう相手が手出しできない領域でほそぼそと、だが着実に利益を挙げることが一般トレーダーの生き残る道なのではないかと私は考えている。

(参考) OANDAのスプレッド

私は主にOANDAでトレードしているので、参考にOANDAのスプレッドも掲載しておく。

FX会社 AUDJPY AUDUSD EURAUD EURGBP EURJPY EURUSD GBPAUD GBPJPY GBPUSD USDJPY 確認日 備考
OANDA 1.0※1 1.1 2.0-2.8※2 0.9-2.0 0.7 0.5 3.1-4.5※2 1.4 1.1 0.4 2017-03-16 ベーシックコース

(※1) 原則固定とは明記されていないが、固定されているように思われる。あるいは実績を作ってから原則固定と明記するのかもしれない。AUDUSD、GBPUSDのときがそうで、固定されてしばらくしてから明記された。

(※2) この範囲は私の観察によるもので、公式のものではない。

(2017/03/16更新)

仲値とリターン

仲値がリターンにどのような影響を与えるかを調べてみる。仲値の時間は午前10時ごろとか、午前9時55分とか言われる。ここでは午前9時55分とする。

仲値のドル買いはあるのか

「仲値のドル買い」ということがよく言われるが、実際にそのようなことはあるのか。もし、あるとしたら仲値に向けてUSDJPYは上昇し、仲値通過後は横ばい、もしくは下落する傾向を持つだろう。

また、仲値のドル買いはゴトー日でより顕著だとされる。そこで、ゴトー日だけを対象とした検証も行ってみた。

足の種類は1分足である。リターンは計算期間1本(1分)のROCで計算した。また、標準化してある。

これを元に日本時間で午前9時0分から午前11時0分までの2時間の累積和を求めてレートとした。そして、全体から仲値の午前9時55分のレートを減じて、仲値でのレートを0にし、これを基準値とした。

なお、元のデータには夏時間が含まれている。そこで、大雑把に3月から10月までを夏時間と見なして調整している。

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

fs.remove_temp_folder()

timeframe = 1
size = 120

x = np.empty(size)
y = np.empty(size)
plt.figure(figsize=(6, 32))
cnt = 0
for symbol in ['USDJPY', 'RANDOM']:
    plt.subplot(8, 1, cnt+1)
    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')
        roc0 = fs.i_roc(symbol, timeframe, 1, 0)[start:end]
        temp = (roc0 - np.mean(roc0)) / np.std(roc0)
        index = roc0.index
        for i in range(2):
            for j in range(60):
                x[i*60+j] = i * 60 + j - 55
                y[i*60+j] = np.mean(temp[(((fs.time_month(index)<3) |
                                           (fs.time_month(index)>=11)) &
                                           (fs.time_hour(index)==2+i) &
                                           (fs.time_minute(index)==0+j)) |
                                          ((fs.time_month(index)>=3) &
                                           (fs.time_month(index)<11) &
                                           (fs.time_hour(index)==3+i) &
                                           (fs.time_minute(index)==0+j))])
        y = np.cumsum(y)
        y -= y[55]
        plt.plot(x, y, label=str(year))
    plt.title('Nakane and Return (' + symbol + ')')
    plt.xlabel('Minute (9:55=0)')
    plt.ylabel('Rate (9:55=0.0)')
    plt.xlim(-55, 60)
    plt.ylim(-3, 2)
    plt.legend(loc='upper right')
    plt.axhline(y=0.0, color='black', linestyle=':')
    plt.axvline(x=0, color='black', linestyle=':')
    plt.tight_layout()

    x = np.empty(size)
    y = np.empty(size)
    plt.subplot(8, 1, cnt+2)
    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')
        roc0 = fs.i_roc(symbol, timeframe, 1, 0)[start:end]
        temp = (roc0 - np.mean(roc0)) / np.std(roc0)
        index = roc0.index
        for i in range(2):
            for j in range(60):
                x[i*60+j] = i * 60 + j - 55
                y[i*60+j] = np.mean(temp[(((fs.time_month(index)<3) |
                                           (fs.time_month(index)>=11)) &
                                           (fs.time_day(index)%5==0) &
                                           (fs.time_hour(index)==2+i) &
                                           (fs.time_minute(index)==0+j)) |
                                          ((fs.time_month(index)>=3) &
                                           (fs.time_month(index)<11) &
                                           (fs.time_day(index)%5==0) &
                                           (fs.time_hour(index)==3+i) &
                                           (fs.time_minute(index)==0+j))])
        y = np.cumsum(y)
        y -= y[55]
        plt.plot(x, y, label=str(year))
    plt.title('Nakane and Return (' + symbol + ', Gotobi)')
    plt.xlabel('Minute (9:55=0)')
    plt.ylabel('Rate (9:55=0.0)')
    plt.xlim(-55, 60)
    plt.ylim(-3, 2)
    plt.legend(loc='upper right')
    plt.axhline(y=0.0, color='black', linestyle=':')
    plt.axvline(x=0, color='black', linestyle=':')
    plt.tight_layout()

    start = datetime.strptime('2012.01.01 00:00', '%Y.%m.%d %H:%M')
    end = datetime.strptime('2016.12.31 23:59', '%Y.%m.%d %H:%M')
    roc0 = fs.i_roc(symbol, timeframe, 1, 0)[start:end]
    index = roc0.index

    sell_entry = ((((fs.time_month(index)<3) |
                    (fs.time_month(index)>=11)) &
                    (fs.time_hour(index)==2) &
                    (fs.time_minute(index)==55)) |
                   ((fs.time_month(index)>=3) &
                    (fs.time_month(index)<11) &
                    (fs.time_hour(index)==3) &
                    (fs.time_minute(index)==55)))
    sell_exit = sell_entry.shift(60) == True
    sell_exit = sell_exit.fillna(0)
    buy_entry = sell_entry * 0
    buy_exit = sell_entry * 0

    trading_rules = (symbol, timeframe, buy_entry, buy_exit, sell_entry,
                     sell_exit, 0.1, 0, 0)
    signal, lots = fs.calc_signal(trading_rules)
    ret = fs.calc_ret(symbol, timeframe, signal, 0.4, 2, start, end)
    plt.subplot(8, 1, cnt+3)
    plt.plot(ret.cumsum())
    plt.title('Nakane and Return (' + symbol + ')')
    plt.xlabel('Date')
    plt.ylabel('Cumulative Return')
    plt.ylim(-0.025, 0.2)
    plt.axhline(y=0.0, color='black', linestyle=':')
    plt.tight_layout()

    sell_entry = ((((fs.time_month(index)<3) |
                    (fs.time_month(index)>=11)) &
                    (fs.time_day(index)%5==0) &
                    (fs.time_hour(index)==2) &
                    (fs.time_minute(index)==55)) |
                   ((fs.time_month(index)>=3) &
                    (fs.time_month(index)<11) &
                    (fs.time_day(index)%5==0) &
                    (fs.time_hour(index)==3) &
                    (fs.time_minute(index)==55)))
    sell_exit = sell_entry.shift(60) == True
    buy_entry = sell_entry * 0
    buy_exit = sell_entry * 0
    trading_rules = (symbol, timeframe, buy_entry, buy_exit, sell_entry,
                     sell_exit, 0.1, 0, 0)
    signal, lots = fs.calc_signal(trading_rules)
    ret = fs.calc_ret(symbol, timeframe, signal, 0.4, 2, start, end)
    plt.subplot(8, 1, cnt+4)
    plt.plot(ret.cumsum())
    plt.title('Nakane and Return (' + symbol + ', Gotobi)')
    plt.xlabel('Date')
    plt.ylabel('Cumulative Return')
    plt.ylim(-0.025, 0.2)
    plt.axhline(y=0.0, color='black', linestyle=':')
    plt.tight_layout()
    cnt = 4
plt.savefig('nakane_and_return.png', dpi=150)
plt.show()

fs.remove_temp_folder()


グラフを見る限り、USDJPYが仲値に向けて上昇する傾向があるようには見えない。ゴトー日でさえ、これといった傾向は見られない。

むしろ仲値通過後に下落する傾向があるように見える。ゴトー日に限るとデータが少なくなるのでやや不安定にはなる。だが、仲値通過後の下落ということでは、ゴトー日であるかないかはあまり影響がないように見える。

仲値後のドル売りが有効か

今回、簡単なバックテストも行ってみた。仲値でUSDJPYを売り、1時間後に決済というものである。スプレッドは0.4pipsとした。

グラフを見ると、一応右肩上がりの曲線を描いている。仲値のドル買いということがよく言われるが、むしろ仲値後のドル売りのほうが有効なように思われる。

(2017/03/13更新)