週単位、月単位で勝つためのシャープレシオ (2017/05/14)

あるトレード戦略が週単位、月単位で勝つためにはどの程度のシャープレシオが必要かということを考えてみる。

勝てる戦略とは

例えば、年単位の利益が10%、年単位のリスクが10%だとする。すると、シャープレシオは

\[ sharpe = 0.1 / 0.1 = 1.0 \]

となる。したがって、1年後の想定利益は10±10%、つまり0~20%となる。

ところで、リスクは1σで計算している。したがって、想定利益は68.27%の確率でその範囲内に収まる。つまり1年後の利益がプラスである確率は68.27%以上となる。なぜ68.27%「以上」になるかというと、年単位20%を越える利益となる可能性もあるからである。

もう少し正確には

\[ prob = 68.27 + (100 - 68.27) / 2 = 84.135 \]

で、84.135%となる(手計算なので誤差あり)。もちろん、これは机上の空論だ。だが、一応の目安にはなる。

ここではプラスとなる確率が84.135%以上であれば「勝てる戦略」と見なすことにする。すると、シャープレシオが1.0以上なら年単位では「勝てる戦略」と言える。同様に週単位、月単位のシャープレシオが1.0以上であれば、週単位、月単位で「勝てる戦略」と言える。

シャープレシオの計算式

ここで、週単位、月単位、年単位のシャープレシオの計算式を定義しておく。

ここでは1週間を5営業日、1ヶ月を20営業日、1年を260営業日としておく。また、1日単位の利益をr、1日単位のリスクをsとする。また、リスクフリーレートは考慮しないことにする。

すると、

\[ sharpe_{weekly} = \frac {5r} {\sqrt{5s}} \] \[ sharpe_{manthly} = \frac {20r} {\sqrt{20s}} \] \[ sharpe_{yearly} = \frac {260r} {\sqrt{260s}} \]

となる。

週単位で勝つためのシャープレシオ

では、先ず、週単位のシャープレシオが1.0以上であるには、年単位のシャープレシオがどのくらい以上でなければならないかを考える。

\[ \frac {sharpe_{yearly}} {sharpe_{weekly}} = \frac {\frac {260r} {\sqrt{260s}}} {\frac {5r} {\sqrt{5s}}} \] \[ {sharpe_{weekly}} = 1, \ \frac {sharpe_{yearly}} {1} = \frac {\frac {260r} {\sqrt{260s}}} {\frac {5r} {\sqrt{5s}}} \] \[ sharpe_{yearly} = \frac {\frac {260r} {\sqrt{260s}}} {\frac {5r} {\sqrt{5s}}} \] \[ = \frac {260r} {\sqrt{260s}} \times \frac {\sqrt{5s}} {5r} \] \[ = \frac {52} {\sqrt{52}} \times \frac {1} {1} \] \[ = \frac {52} {\sqrt{52}} \] \[ = \frac {52 \times \sqrt{52}} {\sqrt{52} \times \sqrt{52}} \] \[ = \frac {52 \times \sqrt{52}} {52} \] \[ = \sqrt{52} \] \[ \approx 7.21 \]

となる。つまり、週単位のシャープレシオが1.0以上であるには、年単位のシャープレシオが約7.21以上である必要がある。

月単位で勝つためのシャープレシオ

次に、月単位のシャープレシオが1.0以上であるには、年単位のシャープレシオがどのくらい以上でなければならないかを考える。

\[ \frac {sharpe_{yearly}} {sharpe_{monthly}} = \frac {\frac {260r} {\sqrt{260s}}} {\frac {20r} {\sqrt{20s}}} \] \[ {sharpe_{monthly}} = 1, \ \frac {sharpe_{yearly}} {1} = \frac {\frac {260r} {\sqrt{260s}}} {\frac {20r} {\sqrt{20s}}} \] \[ sharpe_{yearly} = \frac {\frac {260r} {\sqrt{260s}}} {\frac {20r} {\sqrt{20s}}} \] \[ = \frac {260r} {\sqrt{260s}} \times \frac {\sqrt{20s}} {20r} \] \[ = \frac {13} {\sqrt{13}} \times \frac {1} {1} \] \[ = \frac {13} {\sqrt{13}} \] \[ = \frac {13 \times \sqrt{13}} {\sqrt{13} \times \sqrt{13}} \] \[ = \frac {13 \times \sqrt{13}} {13} \] \[ = \sqrt{13} \] \[ \approx 3.61 \]

となる。つまり、月単位のシャープレシオが1.0以上であるには、年単位のシャープレシオが約3.61以上である必要がある。

まとめ

年単位のシャープレシオが3.61以上というのはなかなか難しい。まして、7.21以上というのは更に難しい。したがって、少なくとも単体の戦略で週単位、月単位で勝つということはなかなか容易ではないと言える。

逆に言えば、週単位、月単位で勝つためには複数の戦略が必要ということになるだろう。しかし、複数の戦略を用いるにしても、お互いの戦略のパフォーマンスが正の相関であれば、あまり意味がない。しかし、無相関、負の相関の戦略同士を組み合わせるというのは「言うは易く行うは難し」なのである。

だとすれば、週単位、月単位で勝つというのは理想として求めるべきものではある。だが、実際には難しいこととして、週単位、月単位で負けることはよくあること、とゆったりした気持ちで取り組むべきだろう。

スリッページの損得

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

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

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

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

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

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

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

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

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

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

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

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

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

でも、ただの言い訳かも

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

(2017/02/15更新)

勝つためのボラティリティと的中率の関係

勝つためのボラティリティと的中率は逆相関

勝つためのボラティリティと的中率の関係を調べてみる。初心者の中にはスキャルのほうがトレード機会も多く、儲けやすいと考える人を時折見かける。しかし、必ずしもそうではないということをこれから示そうと思う。

ボラティリティは時間軸の平方根に比例することが経験的に知られている。つまり、時間軸が短いほど、ボラティリティは小さくなる。スキャルは時間軸が非常に短いので、ボラティリティも相当に小さくなる。

さて、単純化のために始値でエントリーし、次の始値でエグジットするようなトレードをしており、ボラティリティが一定であると仮定する。その場合、上がるか下がるかの予測的中率が50%を超えれば利益が得られるように見える。だが、それはコストを考慮しなかったらの話である。

予測が的中した場合はボラティリティ - コストを利益とし、予測が的中しなかった場合はボラティリティ + コストを損失としてシミュレーションを行ってみる。コストは1.0で固定とする。

グラフを見ると、ボラティリティが小さいほど、高い的中率が要求されていることが分かる。ボラティリティが十分に大きい場合、的中率が50%をいくらか超えていれば勝てそうである。一方、ボラティリティが非常に小さい場合、100%近くならないと勝てそうにもない。

サンプルプログラム

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

import matplotlib.pyplot as plt
import numpy as np

cost = 1.0
volatility = np.empty(10)
probability = np.empty(10)

for i in range(10):
    upper_limit = 50.0
    lower_limit = 0.0
    volatility[i] = (upper_limit + lower_limit) / 2
    profit = volatility[i] - cost
    loss = volatility[i] + cost
    probability[i] = (i + 11) * 0.05
    for j in range(100):
        if (profit * probability[i] >= loss * (1 - probability[i])):
            upper_limit = volatility[i]
            volatility[i] = (upper_limit + lower_limit) / 2
            profit = volatility[i] - cost
            loss = volatility[i] + cost
        else:
            lower_limit = volatility[i]
            volatility[i] = (upper_limit + lower_limit) / 2
            profit = volatility[i] - cost
            loss = volatility[i] + cost

plt.plot(volatility, probability, label='Cost=1.0')
plt.title('Volatility and Probability for Win')
plt.xlabel('Volatility')
plt.ylabel('Probability')
plt.legend()
plt.savefig('volatility_and_probability_for_win.png', format='png', dpi=150)
plt.show()
(2017/02/02更新)

週単位、月単位で勝つための1日当たりのトレード数

週単位、月単位で勝つ(例えば週単位、月単位で損益がプラスとなる確率が80%以上)には勝率どのくらいのトレードを1日に何回行えばよいかということを考えてみる。ここでは単純化して1トレード当たりの利益/損失は常に一定で「1.0」と見なしてプログラムを組んでいる。

損益プラス確率で勝ちと見なす閾値を80%、1トレード当たりの勝率を55%とした場合を例とする。

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

threshold = 0.8
wp = 0.55

def func(n, r):
    if n == 0 or r == 0:
        return 1
    else:
        return func(n, r - 1) * (n - r + 1) / r

prob = 0.0
n_trade = -1
while (prob < threshold):
    n_trade = n_trade + 2 # 必ず奇数にする
    prob = 0.0
    for i in range(int((n_trade + 1) / 2), n_trade + 1):
        r = i
        temp = func(n_trade, r) * wp**r * (1 - wp)**(n_trade - r)
        prob = prob + temp
print("週単位で勝つための1日当たりのトレード数 = ", n_trade / 5.0)
print("月単位で勝つための1日当たりのトレード数 = ", n_trade / 20.0)

出力

週単位で勝つための1日当たりのトレード数 =  14.2
月単位で勝つための1日当たりのトレード数 =  3.55
(2017/01/05更新)

的中率とシャープレシオの関係

「ディープラーニングで株価予測的中率が70%!」みたいな情報をあちこちで見かける。その真偽は置いておいて、的中率が何%というのはシャープレシオに置き換えるとどのくらいなのかということを考えてみる。

単純化のために、ここでいくつかの仮定を置く。

先ず、ボラティリティは1.0で固定とする。固定でさえあれば、実際にはどの数値でも結果は変わらない。

次に1年当たりのトレード数を260回とする。これは数値を変えると結果も変わるので動かせない。この数値は1年当たりの営業日が260日であることに基づく。また、ディープラーニングや機械学習で株価予測を試みる人はデータとして日足を使っていることが多いように思われる。したがって、260回という数値を使うのは妥当だと考える。イメージとしては毎日、上がるか下がるかを予測して売買し、1日の損益の絶対値は常に一定、と考えればいい。

最後にコストは考慮していない。

さて、検証結果によると

シャープレシオ = 32.2490309932 * 的中率 + (-16.1245154966)

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

上の計算式を見れば分かるが、的中率が5%上昇するだけでシャープレシオは約1.61上昇する。的中率で見ると、5%くらい大した違いではないように思うかもしれないが、これは大変な改善なのである。もし的中率が70%であったら、

シャープレシオ = 32.2490309932 * 0.7 + (-16.1245154966) = 6.4498061986399975

となる。

シャープレシオが3.0に達するなら、それは素晴らしい戦略だろう。6.0を超えるとなると、もはや尋常ではない。言い換えれば、的中率が70%に達するというのは尋常ではないのである。

他人が主張する的中率を信じるか否かはお任せする。だが、それが本当なのだろうかということはじっくりと考えて欲しいものである。

サンプルプログラム

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

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

def create_model(accuracy, slope, intercept):
    return slope * accuracy + intercept

start = 50
end = 105
step = 5
n = int((end - start) / step)
accuracy = np.array(range(start, end, step)) / 100
sharpe_ratio = np.empty(n)
volatility = 1.0
trades = 260
for i in range(n):
    ret = (((volatility * accuracy[i]) - (volatility * (1.0 - accuracy[i])))
        * trades)
    risk = volatility * np.sqrt(trades)
    sharpe_ratio[i] = ret / risk
popt, pcov = curve_fit(create_model, accuracy, sharpe_ratio)
slope = popt[0]
intercept = popt[1]
ax=plt.subplot()
plt.plot(accuracy, sharpe_ratio, label='Actual Value')
plt.title('Sharpe ratio and Accuracy')
plt.xlabel('Accuracy')
plt.ylabel('Sharpe ratio')
plt.xlim(0.5, 1.0)
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.legend(loc="lower right")
plt.savefig('sharpe_ratio_accuracy.png', dpi=150)
plt.show()
plt.close()
print('Slope = ', slope)
print('Intercept = ',intercept)
(2016/12/04更新)