シグナル生成プログラム作成の小技

シグナルを生成するプログラムを作成するときに使える小技を紹介する。

長いので読む気が失せるかもしれない。だが、単に説明がくどいだけで難しくはない。実際にはほんの数行のプログラムである。

for文を使ったら負け

シグナル生成プログラムを作成する場合、for文を使うと処理が非常に遅くなる。for文の処理が遅いというのはPythonの仕様である。for文を使ったら負けだと考えよう。

日足のように数の少ないデータを使う場合はあまり気にならないかもしれない。だが、日中足のように膨大な数のデータを使う場合、それは耐え難いものになる。

配列関数を使おう

一方、配列関数には高速化されているものがある。今回使うのはPandasのfillna()関数だけである。

C++のような高速な言語であればfor文で処理するところではある。これを基本的にfillna()関数だけを使って処理できるように工夫する。これでfor文の数倍から数十倍は速くなる。

シグナル発生の条件

先ずはシグナル発生の条件を定義する。必要となるのは

  • 買いエントリー
  • 買いエグジット
  • 売りエントリー
  • 売りエグジット

の4つである。

上の4つによってシグナルは「1」(買いポジション)、「-1」(売りポジション)、「0」(ノーポジション)の3つの状態となる。

買いエントリーで「1」となり、買いエグジットで「0」となる。または、売りエントリーで「-1」となり、売りエグジットで「0」となる。エグジットの直後にエントリーが発生した場合は直接「1」から「-1」へ、または「-1」から「1」へとなり、つまりドテンである。

さて、ここでは1本前の足のZスコアを使って上の4つを条件付けることとする。先ずは適当なデータでZスコアを準備する。

In [1]:
import forex_system as fs
import numpy as np
from datetime import datetime

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')
zscore1 = fs.i_zscore('USDJPY', 1440, 5, 'MODE_SMA', 1)[start:end]
print(zscore1.head(20))


2016-01-01   -0.901037
2016-01-04   -1.036465
2016-01-05   -1.704807
2016-01-06   -1.351713
2016-01-07   -1.334252
2016-01-08   -1.344266
2016-01-11   -1.238142
2016-01-12   -0.382672
2016-01-13   -0.249737
2016-01-14    0.375163
2016-01-15    1.295042
2016-01-18   -1.628893
2016-01-19   -0.535417
2016-01-20    0.250968
2016-01-21   -0.942332
2016-01-22    1.080308
2016-01-25    1.608687
2016-01-26    0.614979
2016-01-27    0.545221
2016-01-28    0.697498
Name: close, dtype: float64


買いエントリーの作成

1本前の足のZスコアが-1以下だったら買いエントリーとしよう。

In [2]:
buy_entry = zscore1 <= -1.0
print(buy_entry.head(20))


2016-01-01    False
2016-01-04     True
2016-01-05     True
2016-01-06     True
2016-01-07     True
2016-01-08     True
2016-01-11     True
2016-01-12    False
2016-01-13    False
2016-01-14    False
2016-01-15    False
2016-01-18     True
2016-01-19    False
2016-01-20    False
2016-01-21    False
2016-01-22    False
2016-01-25    False
2016-01-26    False
2016-01-27    False
2016-01-28    False
Name: close, dtype: bool


買いエグジットの作成

1本前の足のZスコアが0以上だったら買いエグジットとしよう。

In [3]:
buy_exit = zscore1 >= 0.0
print(buy_exit.head(20))


2016-01-01    False
2016-01-04    False
2016-01-05    False
2016-01-06    False
2016-01-07    False
2016-01-08    False
2016-01-11    False
2016-01-12    False
2016-01-13    False
2016-01-14     True
2016-01-15     True
2016-01-18    False
2016-01-19    False
2016-01-20     True
2016-01-21    False
2016-01-22     True
2016-01-25     True
2016-01-26     True
2016-01-27     True
2016-01-28     True
Name: close, dtype: bool


売りエントリーの作成

1本前の足のZスコアが1以上だったら売りエントリーとしよう。

In [4]:
sell_entry = zscore1 >= 1.0
print(sell_entry.head(20))


2016-01-01    False
2016-01-04    False
2016-01-05    False
2016-01-06    False
2016-01-07    False
2016-01-08    False
2016-01-11    False
2016-01-12    False
2016-01-13    False
2016-01-14    False
2016-01-15     True
2016-01-18    False
2016-01-19    False
2016-01-20    False
2016-01-21    False
2016-01-22     True
2016-01-25     True
2016-01-26    False
2016-01-27    False
2016-01-28    False
Name: close, dtype: bool


売りエグジットの作成

1本前の足のZスコアが0以下だったら売りエグジットとしよう。

In [5]:
sell_exit = zscore1 <= 0.0
print(sell_exit.head(20))


2016-01-01     True
2016-01-04     True
2016-01-05     True
2016-01-06     True
2016-01-07     True
2016-01-08     True
2016-01-11     True
2016-01-12     True
2016-01-13     True
2016-01-14    False
2016-01-15    False
2016-01-18     True
2016-01-19     True
2016-01-20    False
2016-01-21     True
2016-01-22    False
2016-01-25    False
2016-01-26    False
2016-01-27    False
2016-01-28    False
Name: close, dtype: bool


これで買いエントリー、買いエグジット、売りエントリー、売りエグジットのすべてがそろった。

買いシグナルの作成①

4つの条件がすべてそろったら、先ず買いシグナルを作成することにする。

第1工程として、買いエントリーをコピーして買いシグナルの土台とし、「False」のところを「NaN」で置き換える。この一見したところ無意味な「NaN」を利用することが今回紹介したかった小技である。後でこの「NaN」が役に立つ。

In [6]:
buy = buy_entry.copy()
buy[buy==False] = np.nan
print(buy.head(20))


2016-01-01    NaN
2016-01-04    1.0
2016-01-05    1.0
2016-01-06    1.0
2016-01-07    1.0
2016-01-08    1.0
2016-01-11    1.0
2016-01-12    NaN
2016-01-13    NaN
2016-01-14    NaN
2016-01-15    NaN
2016-01-18    1.0
2016-01-19    NaN
2016-01-20    NaN
2016-01-21    NaN
2016-01-22    NaN
2016-01-25    NaN
2016-01-26    NaN
2016-01-27    NaN
2016-01-28    NaN
Name: close, dtype: float6


「False」が「NaN」に置き換えられる。なお、「True」が「1.0」に変わっているが同じ値である。データの型が元々bool型であったところ、float型である「NaN」を挿入した影響でfloat型に変換されたのである。

買いシグナルの作成②

第2工程として、買いエグジットの「True」と同じ位置にある買いシグナルのデータを「0.0」で置き換える。

In [7]:
buy[buy_exit==True] = 0.0
print(buy.head(20))


2016-01-01    NaN
2016-01-04    1.0
2016-01-05    1.0
2016-01-06    1.0
2016-01-07    1.0
2016-01-08    1.0
2016-01-11    1.0
2016-01-12    NaN
2016-01-13    NaN
2016-01-14    0.0
2016-01-15    0.0
2016-01-18    1.0
2016-01-19    NaN
2016-01-20    0.0
2016-01-21    NaN
2016-01-22    0.0
2016-01-25    0.0
2016-01-26    0.0
2016-01-27    0.0
2016-01-28    0.0
Name: close, dtype: float64


買いシグナルの作成③

第3工程として、買いシグナルの「NaN」を前のデータで置き換える。

In [8]:
buy = buy.fillna(method='ffill')
print(buy.head(20))


2016-01-01    NaN
2016-01-04    1.0
2016-01-05    1.0
2016-01-06    1.0
2016-01-07    1.0
2016-01-08    1.0
2016-01-11    1.0
2016-01-12    1.0
2016-01-13    1.0
2016-01-14    0.0
2016-01-15    0.0
2016-01-18    1.0
2016-01-19    1.0
2016-01-20    0.0
2016-01-21    0.0
2016-01-22    0.0
2016-01-25    0.0
2016-01-26    0.0
2016-01-27    0.0
2016-01-28    0.0
Name: close, dtype: float64


「NaN」となっていたところは買いエントリーも買いエグジットも発生していない。そういうところではどうするか。何もしないことである。

何もしないということは前のデータが「1.0」なら「1.0」で、「0.0」なら「0.0」で埋めるということである。「NaN」がいくつも続いている場合でも、先頭の「NaN」が「1.0」、または「0.0」になり、その後の「NaN」もそれに続くという形で埋められる。これが「NaN」を使った理由である。

Zスコアが-1以下なら買いポジションを持っている。0以上であれば買いポジションを持っていない。これは明確である。だが、-1から0の間はどちらであるか不明確である。

これは前の足で買いポジションを持っていれば持っているし、持っていなければ持っていない。つまり、前の足の条件次第なのである。

for文を使い、その中でif文を使って場合分けすれば同じことができるし、普通はそうするだろう。だが、Pythonでそれをやると時間がかかりすぎる。そこで「NaN」とfillna()関数を利用するのである。

「NaN」は不要な、削除すべきデータのように思われがちだ。しかし、このように自分で「NaN」を挿入して活用する方法もあるのである。

売りシグナルの作成①

次は売りシグナルを作成する。買いシグナルの場合と同じなので説明は省略する。

In [9]:
sell = sell_entry.copy()
sell[sell==False] = np.nan
print(sell.head(20))


2016-01-01    NaN
2016-01-04    NaN
2016-01-05    NaN
2016-01-06    NaN
2016-01-07    NaN
2016-01-08    NaN
2016-01-11    NaN
2016-01-12    NaN
2016-01-13    NaN
2016-01-14    NaN
2016-01-15    1.0
2016-01-18    NaN
2016-01-19    NaN
2016-01-20    NaN
2016-01-21    NaN
2016-01-22    1.0
2016-01-25    1.0
2016-01-26    NaN
2016-01-27    NaN
2016-01-28    NaN
Name: close, dtype: float64


売りシグナルの作成②

In [10]:
sell[sell_exit==True] = 0.0
print(sell.head(20))


2016-01-01    0.0
2016-01-04    0.0
2016-01-05    0.0
2016-01-06    0.0
2016-01-07    0.0
2016-01-08    0.0
2016-01-11    0.0
2016-01-12    0.0
2016-01-13    0.0
2016-01-14    NaN
2016-01-15    1.0
2016-01-18    0.0
2016-01-19    0.0
2016-01-20    NaN
2016-01-21    0.0
2016-01-22    1.0
2016-01-25    1.0
2016-01-26    NaN
2016-01-27    NaN
2016-01-28    NaN
Name: close, dtype: float64


売りシグナルの作成③

In [11]:
sell = sell.fillna(method='ffill')
print(sell.head(20))


2016-01-01    0.0
2016-01-04    0.0
2016-01-05    0.0
2016-01-06    0.0
2016-01-07    0.0
2016-01-08    0.0
2016-01-11    0.0
2016-01-12    0.0
2016-01-13    0.0
2016-01-14    0.0
2016-01-15    1.0
2016-01-18    0.0
2016-01-19    0.0
2016-01-20    0.0
2016-01-21    0.0
2016-01-22    1.0
2016-01-25    1.0
2016-01-26    1.0
2016-01-27    1.0
2016-01-28    1.0
Name: close, dtype: float64


これで買いシグナルと売りシグナルが完成した。

シグナルの作成①

それでは買いシグナルと売りシグナルを合わせて全体のシグナルとする。

In [12]:
signal = buy - sell
print(signal.head(20))


2016-01-01    NaN
2016-01-04    1.0
2016-01-05    1.0
2016-01-06    1.0
2016-01-07    1.0
2016-01-08    1.0
2016-01-11    1.0
2016-01-12    1.0
2016-01-13    1.0
2016-01-14    0.0
2016-01-15   -1.0
2016-01-18    1.0
2016-01-19    1.0
2016-01-20    0.0
2016-01-21    0.0
2016-01-22   -1.0
2016-01-25   -1.0
2016-01-26   -1.0
2016-01-27   -1.0
2016-01-28   -1.0
Name: close, dtype: float64


買いはプラス、売りはマイナスとするので、売りシグナルを減じる。初めから売りシグナルをマイナスにしておいて加えてもいい。

売買ルールが排他的なものとなっているかぎり、買いと売りが相殺して0.0になるというようなことはない。もしそういうことが起きたとしたら、それはプログラムのせいではなくて、売買ルールがおかしいのである。

シグナルの作成②

微調整を行う。残った「NaN」を「0.0」で埋める。

たまたま最初のデータが「NaN」であった場合、それより前のデータがないので「NaN」を埋めることができない。後のデータで埋めることもできることはできる。だが、それをやると、先読みバイアスのミスを犯すリスクがある(1個だけだから大したことはないが)。

とりあえず「0.0」で埋めておくのが無難であろう。

In [13]:
signal = signal.fillna(0.0)
print(signal.head(20))


2016-01-01    0.0
2016-01-04    1.0
2016-01-05    1.0
2016-01-06    1.0
2016-01-07    1.0
2016-01-08    1.0
2016-01-11    1.0
2016-01-12    1.0
2016-01-13    1.0
2016-01-14    0.0
2016-01-15   -1.0
2016-01-18    1.0
2016-01-19    1.0
2016-01-20    0.0
2016-01-21    0.0
2016-01-22   -1.0
2016-01-25   -1.0
2016-01-26   -1.0
2016-01-27   -1.0
2016-01-28   -1.0
Name: close, dtype: float64


シグナルの作成③

最後の仕上げである。float型をint型に変換する。

int型に変換する必要性はない。だが、float型はデータにゴミがあって、「==」などがうまく機能しないことがある。int型にしておいたほうが無難だと思う。

In [14]:
signal = signal.astype(int)
print(signal.head(20))


2016-01-01    0
2016-01-04    1
2016-01-05    1
2016-01-06    1
2016-01-07    1
2016-01-08    1
2016-01-11    1
2016-01-12    1
2016-01-13    1
2016-01-14    0
2016-01-15   -1
2016-01-18    1
2016-01-19    1
2016-01-20    0
2016-01-21    0
2016-01-22   -1
2016-01-25   -1
2016-01-26   -1
2016-01-27   -1
2016-01-28   -1

Name: close, dtype: int64


最後に

お疲れ様でした。

この小技はRでも使える。というか、私はRでこの小技を使っていて、それをPythonでも使っているだけである。

単純ではあるが、シグナル生成だけではなく、けっこういろいろな場面で使えるので、知らなかった人は是非使ってみて下さい。

サンプルプログラム

上のプログラムを1つにまとめ、printなどで余分なものは除いた。「なんだ、こんなに短かったのか」と思われるかもしれない(笑)。

In [15]:
import forex_system as fs
import numpy as np
from datetime import datetime

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')
zscore1 = fs.i_zscore('USDJPY', 1440, 5, 'MODE_SMA', 1)[start:end]

buy_entry = zscore1 <= -1.0
buy_exit = zscore1 >= 0.0
sell_entry = zscore1 >= 1.0
sell_exit = zscore1 <= 0.0
buy = buy_entry.copy()
buy[buy==False] = np.nan
buy[buy_exit==True] = 0.0
buy = buy.fillna(method='ffill')
sell = sell_entry.copy()
sell[sell==False] = np.nan
sell[sell_exit==True] = 0.0
sell = sell.fillna(method='ffill')
signal = buy - sell
signal = signal.fillna(0.0)
signal = signal.astype(int)
print(signal.head(20))


2016-01-01    0
2016-01-04    1
2016-01-05    1
2016-01-06    1
2016-01-07    1
2016-01-08    1
2016-01-11    1
2016-01-12    1
2016-01-13    1
2016-01-14    0
2016-01-15   -1
2016-01-18    1
2016-01-19    1
2016-01-20    0
2016-01-21    0
2016-01-22   -1
2016-01-25   -1
2016-01-26   -1
2016-01-27   -1
2016-01-28   -1
Name: close, dtype: int64


(2017/03/05更新)

確率のイメージ

確率をイメージしたアニメーション画像を作ってみた。

初めはランダムに見えるが…

最初は散布図に点がランダムにプロットされているだけのように見える。だが、しばらくすると、FX業界では割と有名な画像になる。

試行を繰り返して初めて見えてくる

物理学では二重スリット実験という有名な実験がある。電子を飛ばして写真乾板に当てる。だが、途中を2本のスリットのある板で塞ぐ。普通はスリットの後のところだけにしか電子は当たらないと思うだろう。

ところが、板で邪魔されているところにも電子は当たるのである。初めは電子がランダムに当たっているように見える。だが、電子を飛ばし続けると、電子の到達確率に応じた濃淡が生まれるのである。

今回のアニメーション画像はこの二重スリット実験を参考に作ってみた。色はR(赤)、G(緑)、B(青)をそれぞれ0-255の数値にして表すことができる。今回は元画像からR、G、Bがすべて128未満のピクセルに目がけて点を打ち込んだ。

物事の本質というのはわずかな試行や事例でそう簡単に見い出せるものではない。試行を繰り返し、多くの事例を集めて初めて見えてくるものなのである。

サンプルプログラム

①適当なpngファイルを用意し、「image_of_probability.png」ファイルとして「~/py」フォルダーに保存する。

②以下のプログラムをSpyderの「IPython console」にコピー&ペーストして「Enter」キーを2回押す。

import matplotlib.animation as animation
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image

def func(i, x, y, frames):
    size = len(x)
    rng = int(size / frames)
    start = i * rng
    end = start + rng - 1
    plt.scatter(x[start:end], y[start:end], c='dimgray', marker='.')
    plt.title('Image of Probability')
    plt.xlim(0, 1)
    plt.ylim(0, 1)

plt.style.use('dark_background')
fig = plt.figure()
max_size = 100000
frames = 60
interval = 1000

img = Image.open("image_of_probability.png")
width = img.width
height = img.height
adj = np.sqrt(width * height / max_size)
width = int(width / adj)
height = int(height / adj)
img = img.resize((width, height))
size = width * height
x = np.zeros(size)
y = np.zeros(size)
for i in range(height):
    for j in range(width):
        rgb = img.getpixel((j, i))
        if rgb[0] < 128 and rgb[1] < 128 and rgb[2] < 128:
            x[width*i+j] = j / width
            y[width*i+j] = 1 - (i / height)

xy = np.c_[x, y]
np.random.shuffle(xy)
x = xy[:, 0]
y = xy[:, 1]

ani = animation.FuncAnimation(fig, func, fargs=(x, y, frames), frames=frames,
                              interval=interval)
plt.show()
ani.save('image_of_probability.gif', writer='imagemagick')

少し待つ(私の環境では15秒くらい)。設定によっては何も描画されていないウィンドウが表示されるが気にしない。終了すると「~/py」フォルダーに「image_of_probability.gif」ファイルが生成する。

(2017/02/19更新)

電子書籍で数式を使うときの注意

多くのEPUBリーダーはMathJax未対応

よく使われるEPUBリーダーの多くはMathJaxに対応していないようである。私は普段、Google Play ブックスを使っているが、MathJaxに対応していない。iBooksも対応していない。ブログでは使えるし、電子書籍でも使えると思い込んでいたので意外だった。

ネットで調べてみると、対応しているEPUBリーダーは少なく、現状では電子書籍にMathJaxを使うのはあまりよくないようだ。もし電子書籍で数式を使いたいのであれば、画像に変換するべきものらしい。

MathJax対応のEPUBリーダーもあるが…

MathJaxに対応しているとされるEPUBリーダーを調べて、いくつか試してみた。ここではiPhoneのアプリに限って話す。私はiPhoneで電子書籍を読んでいるからである。私が実際に試してみて一応使えそうだと思ったのは「Adobe Digital Editions」と「Gitden Reader」の2つである。

Adobe Digital Editionsを使って気付いた点は

  • iPhoneを縦にして読むときの字が小さい。いくらか大きくすることもできるが、それでも小さい。
  • ページをめくると、字がぼやけ、元に戻るのに少し間がある。
  • 数式があると、少し縦に間延びし、後のページにずれていく。このため、目次で見たいページをクリックしても、ずれて表示される。

である。確かに数式はちゃんと表示されているが、使い勝手がイマイチな感じだ。

一方、Gitden Readerを使って気付いた点は

  • 動きがややもっさりしている。
  • 数式があると、少し縦に間延びし、後のページにずれていく。そして本の最後が尻切れになる。ただし、「スクロール」のモードにすれば最後まで表示される。だが、Adobe Digital Editionsと同様で、目次で見たいページをクリックしても、ずれて表示される。
  • 読み上げ機能がある。

である。使い勝手はそれほど悪くないが、やはり数式があると後のページにずれていくというのが大きなマイナス点ではある。読み上げ機能があるのはなかなかいい。とりあえずはGitden Readerを使ってみようと思う。

MathJaxで記述するときの注意

電子書籍向けにMathJaxで記述するとき、1つだけ気付いたことがある。数式で累乗を表す「^」が消えてしまうのである。

数式で累乗を表すときは例えば

y = x^2

とするのが普通だ。ブログではこれで問題ない。だが、私が電子書籍を作るときに使っているでんでんコンバーターの癖なのか、epubに変換するとなぜか

y = x2

に書き換えられてしまうのである。だが、これを防ぐのは簡単だ。

y = x ^ 2

のように離して記述すればいい。ブログでもこの書き方で問題ない。現時点で気付いている不具合はこれだけである。

(2017/02/16更新)

スリッページの損得

スリッページは順張り(逆張り)に不利(有利)

スリッページというと何となく良くないイメージがあるかと思う。注文を出して自分が想定した価格で売買できなかった場合、売買価格がトレーダーの不利になるように動くことが多いような気がするものだ。ブローカーが本当に公正であるかということに対する不信感も関係あるだろう。だが、それについてはここでは触れない。

私は経験上、スリッページは順張りには不利に、逆張りには有利に動く傾向があるように感じている。あくまでも個人的な印象であって、きちんと検証したわけではない。ただ、バックテストにおいても、実際のトレードにおいても、そのような局面に出くわすことが多い「気がする」のである。

逆張りでは1分1秒を争うようなことはない

例えば、逆張り戦略において、1分足で計算期間50のケースと5分足で計算期間10のケースとでバックテストしたとする。いずれも50分であるから、パフォーマンスにおいて大差はないと思うだろう。実際、大差はないが、5分足のほうがパフォーマンスがよいことが多い「気がする」(これより後は「気がする」は省略)。

私は順張りが苦手で、トレードはもっぱら逆張りである。普段は逆張りシステムにシグナルをメールで知らせるようにしている。メールを確認してからトレードするのであるから、当然、システムより遅れる。5分、10分遅れることはしょっちゅうである。もちろん、トレード機会を失うこともあるが、むしろシステムより有利なレートで売買できることが多い。

トレードというとモニターをいくつも並べてリアルタイムで情報を追い、瞬時の判断が勝敗を決めるというようなイメージを持っている人を見かける。だが、私はスマートフォン1つでトレードしている。チャートもシグナルが来てから確認するが、まったく問題はない。1分1秒を争うようなことはないのである。

ブレイクアウト戦略の劣化がひどい

順張りでは逆のことが起きる。といっても普通の順張りでは使えそうな戦略を作れた試しがないので、ここではブレイクアウト戦略について述べる。ブレイクアウトは5分足より1分足のほうがパフォーマンスのよいことが多い。MT4であれば、モデルを「全ティック」でバックテストしたほうがパフォーマンスはよい。となると、これは1分1秒を争うのである。

私は以前、とても素晴らしい(と思った)ブレイクアウト戦略を作ったことがある。バックテストでもウォークフォワードテストでも素晴らしいパフォーマンスである。私がそれまで、いや、今まで作ったどの戦略よりも優れていた。資産曲線はほぼ一直線の右肩上がりであった。

つい浮かれてしまい、デモ口座で試すこともあまりせずに実弾を投入した。これは手動では無理なのでEAを使った。大儲け間違い無しと思っていたのだが、案に相違してちっとも勝てない。おかしいと思い、取引履歴と同期間のバックテストを比較してみた。すると、バックテストでは勝っているのである。

実際の取引とバックテストとの間で生じた乖離の原因はスリッページである。エントリーにしろ、エグジットにしろ、わずかにバックテストより不利なレートで売買していたのである。

このブレイクアウト戦略はトレード機会は多いが、1トレード当たりの利益は小さかった。つまり薄利多売である。この戦略はスリッページに耐えられなかったのである。ブレイクアウト戦略は机上のパフォーマンスは素晴らしいが、実際のトレードでは劣化がひどい。一方、逆張り戦略では同じ期間でのバックテストと取引履歴とで結果はほぼ一致している。

でも、ただの言い訳かも

とはいうものの、実際には順張りが苦手な私のただの言い訳かもしれない。得意な戦略ではうまくいくが、苦手な戦略ではうまくいかない。これは当たり前のことだ。ただ、スリッページは有利に動くこともあれば、不利に動くこともあり、薄利多売の戦略には致命的な影響を与えるということは最後に強調しておきたい。

(2017/02/15更新)

直前の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更新)