想定スプレッド

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

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更新)

最適レバレッジ

レバレッジをどのくらいにすれば利益を最大化できるかということを考えてみる。

レバレッジは高いほどいいのか

FXはレバレッジを使えば少ない資金で大きな利益を挙げられる、という宣伝文句をよく見かける。だが正確には、大きな損失を蒙ることもある、と付け加えるべきだ。宣伝する側は都合のいい面しか強調しないから困ったものである。

「トータルで勝てる人なら大きな損失を大きな利益でカバーして余りあるから、やはりレバレッジは高いほどいいのではないか」と考える人もいるだろう。果たしてそうなのか、これから検討してみる。

レバレッジ1倍で運用した場合の資産曲線

ここに1年当たり100トレード、1トレード当たりの期待利益が0.1%、リスクが0.5%の戦略があるとする。単純化して考えると、1年当たりの期待利益は0.1% * 100 = 10.0%、リスクは√Tルールに基づけば0.5% * sqrt(100) = 5.0%となる。したがって、シャープレシオは10.0 / 5.0 = 2.0となり、戦略としてはまずまずである。

この戦略に基づいて100回トレードを行うシミュレーションをやってみる。乱数を使っているので、結果は毎回違う。シミュレーション結果の利益、リスクも必ずしも指定した数値にはならないことを予め断っておく。

それはさて、先ずはレバレッジ1倍で運用した場合の資産曲線を見てみる。

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

trades = 100
ret = np.random.normal(0.001, 0.005, trades)
equity_curve = np.zeros(trades)
for i in range(trades):
    if i == 0:
        equity_curve[i] = 1.0 * (1.0 + ret[i])
    else:
        equity_curve[i] = equity_curve[i-1] * (1.0 + ret[i])
    if equity_curve[i] < 0.0:
        equity_curve[i] = 0.0
mean = np.mean(ret)
std = np.std(ret)
kelly = mean / (std * std)
ax=plt.subplot()
plt.plot(equity_curve)
plt.xlabel('Trades')
plt.ylabel('Equity curve')
plt.text(0.05, 0.9, 'Kelly = ' + str(kelly), transform=ax.transAxes)
plt.savefig('optimal_leverage1.png', dpi=150)
plt.show()


初期資産は1.0から開始し、そこから何%増減したかを見る。グラフによると最終資産はおよそ1.1となっているので、10%の利益であったことが分かる。

最適レバレッジの計算

グラフに「Kelly = 41.2146523106」とあるが、これはケリー基準による最適レバレッジである。

ケリー基準の計算には本来の計算式と簡易式とがあるが、ここでは

最適レバレッジ = 期待利益 / (リスク * リスク)

という簡易式を用いる。

期待利益が0.1%、リスクが0.5%である場合は

0.001 / (0.005 * 0.005) = 40.0

でレバレッジ40倍が最適レバレッジとなる。

レバレッジ別の最終資産

最後にレバレッジ別の最終資産を見てみる。

In [2]:
n = 100
result = np.zeros(n)
for j in range(n):
    leverage = j
    for i in range(trades):
        if i == 0:
            equity_curve[i] = 1.0 * (1.0 + ret[i] * leverage)
        else:
            equity_curve[i] = equity_curve[i-1] * (1.0 + ret[i] * leverage)
        if equity_curve[i] < 0.0:
            equity_curve[i] = 0.0
        result[j] = equity_curve[trades-1]
plt.plot(result)
plt.axhline(y=1.0, color='red')
plt.axvline(x=kelly, color='black', linestyle=':')
plt.xlabel('Leverage')
plt.ylabel('Balance')
plt.savefig('optimal_leverage2.png', dpi=150)
plt.show()


縦の点線はケリー基準による最適レバレッジの位置である。その付近で利益が最大化していることが分かる。必ずしも一致しないのはこれが簡易式だからである。

利益を最大化した場合、最終資産は約9.0である。800%もの利益があったことになる。レバレッジ1倍のときは10%程度であったのだから、レバレッジの力は偉大である。

だが、最適レバレッジを超えると利益が減っていくことが分かる。赤の水平線は初期資産である1.0の水準だが、レバレッジ60倍を超えた当たりで最終資産が水平線を下回っている。期待利益がプラスであるにもかかわらず、損失を招いているのである。

そしてレバレッジ80倍手前で最終資産は0になっている。つまり破産である。

最適レバレッジならよいのか

検証結果によると、例え利益の期待値がプラスであってもレバレッジは高いほどよいとは言えないことになる。トレードはリスク管理だけで利益を挙げられるわけではない。だが、それをしっかりやっていないと、トータルで勝てるはずのトレードでも損失を蒙り、さらには破産するという結果になる。

では、最適レバレッジを計算してそれを適用すればよいのだろうか。シミュレーションでは期待利益はプラスに設定してあり、勝ちは約束されている。それでも乱数を使っていることから生じるぶれがある。まして、実際のトレードで勝ちが約束されているなどということはありえない。

したがって、実際のトレードはシミュレーションよりはるかに不確実性が高い。算出した最適レバレッジもはるかに大きなぶれがあることを想定しなければならない。

適用するレバレッジが最適レバレッジより小さい場合、利益の最大化はできないが、期待利益がプラスである限り、資産を増やすことはできる。だが、適用するレバレッジが最適レバレッジより大きい場合、利益の最大化ができないばかりか、期待利益がプラスであったとしても資産を減らしたり、最悪、破産したりするのである。

だとすれば、実際に使用するレバレッジは最適レバレッジより小さくしたほうが安全である。「最適レバレッジ」とは言っても、むしろこれは上限と考えるべきなのだ。慣習的には「ハーフケリー」などと言って、算出された最適レバレッジの2分の1を適用するということも行われている。

複利運用でない場合

ケリー基準というのは実は複利運用が前提となっている。複利運用でない場合、レバレッジが高いからといって必ずしも損失や破産をもたらさない。しかし、複利にしなければ資産は算術的にしか増えないのである。

資産を低レバレッジで幾何学的に増やすのと高レバレッジで算術的に増やすのとではどちらがよいか。トレード期間が長くなれば、いずれ「低レベレッジ + 幾何学的」が「高レバレッジ + 算術的」を上回るだろう。だが、この問題については私の知識が不足しているので、これ以上は述べない。

(2017/03/10更新)

トレード戦略の種類

基本的なトレード戦略の種類について述べる。下のSlideShareによると、5つの基本的なクオンツ戦略があるという。

http://www.slideshare.net/JessStauth/d-30965323(p4)

これによると、戦略は

  1. 平均回帰戦略:上がるものは下がる。

  2. モメンタム戦略:トレンドは友である。

  3. バリュー戦略:安く買って高く売る。

  4. センチメント戦略:噂で買って事実で売る。

  5. 季節性戦略:5月に売る。

の5種類に分類される。

ここでは私の独断と偏見による解釈を用いて、各戦略について簡単に説明する。

平均回帰戦略(上がるものは下がる)

上がるものも永遠に上がり続けることはない。いつかは必ず下がるものである。下がるものも永遠に下がり続けることはない。いつかは必ず上がるものである。

上がったものは下がり、下がったものは上がって、いずれ平均に戻ってくる。そこで、平均より下がれば買い、上がれば売る。これが平均回帰戦略であり、つまりは逆張りである。

ところで、平均回帰戦略について、平均の下で買い、上で売れば必ず勝てるのではないか、と勘違いする初心者が時々いる。だが、実際にはそうはいかない。平均自体も動くからである。

例えば、平均の下で買っても平均が下がり続けることがある。そして、価格が平均に戻ったときには平均は買った価格より下になっていることもあるのである。

また、平均の上で売っても平均が上がり続けることがある。そして、価格が平均に戻ったときには平均は売った価格より上になっていることもあるのである。

平均回帰戦略で勝つためには平均自体の動きが水平に近くなるタイミングを測る必要がある。

モメンタム戦略(トレンドは友である)

トレンドは友、というのはtrendとfriendをかけた洒落である。米国の投資家ジョージ・ソロスの言葉らしい。トレンドとは仲良くしろ、逆らうな、従え、ということのようだ。つまりはトレンドフォロー、順張りである。

平均回帰戦略の逆で、上がれば、その勢い、つまりモメンタムに付いて行って買い、下がれば、そのモメンタムに付いて行って売るのである。では、順張りと逆張り、どちらの戦略が正しいのだろうか。

順張りは正しいが、逆張りは間違っている、というような意見をよく見かける。だが、それは違うと私は考えている。値動きというのは時には平均回帰的であり、時にはモメンタム的であるものだ。したがって状況に合った戦略を採用しなければならない。とはいえ、言うは易く行うは難し、ではある。

バリュー戦略(安く買って高く売る)

「安く買って高く売る」を「下がったら買って上がったら売る」と解してはいけないのだろう。それだと平均回帰戦略と変わらない。「価格が本来の価値より安ければ買い、高ければ売る」と解すべきなのだろう。

本来の価値を知るためには価格以外の指標が必要である。この辺が過去の価格さえ分かればよい平均回帰戦略との違いである。FXの場合、各国の経済指標などを見ることになるのだろうか。

経済指標は少なくとも短期トレードでは使いにくい。取引する通貨ペア以外の通貨ペアの価格を用い、取引する通貨ペアのあるべき価格を算出するというのはどうか。私はそれより高いか安いかで売買するという手法をバリュー戦略としてもいいのではないかと考えている。

センチメント戦略(噂で買って事実で売る)

「噂で買って事実で売る」とは、例えば米国の利上げ観測が持ち上がったらドルが高くなるだろうと予測して買う。そして、実際に利上げが決まったらもはやこれ以上は高くならないだろうと予測して売るのである。

だが、センチメントをどのように測るかというのは簡単ではない。テキストなどを用いた定性分析も必要になるだろう。

季節性戦略(5月に売る)

「5月に売る」というのは株価は5月以降に下がる傾向があるので、5月になったら売るということである。実際に5月以降に下がる傾向があるのかは怪しい。だが、年、四半期、月、週、日などを通した周期性があると考えて、それに基づいた売買を行うのが季節性戦略である。

単に時期によって上がりやすい、下がりやすいということだけが季節性ではない。平均回帰的である、モメンタム的である、というのも季節性である。

商品市場では農作物やエネルギーなど、季節によって需要や供給が大きく変化するものがあるだろう。そのような場合、季節性を期待できる。

FXにおける季節性は商慣習に基づくものが多いのではないだろうか。日本の仲値やロンドンフィキシングなどである。しかし、商慣習も一定不変というわけではないことには注意が必要である。

(2017/03/09更新)

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

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

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

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

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