許容損失は資金の何%に抑えるべきか

ネットでは2%が多数派のようだが…

許容損失は資金の何%に抑えるべきかという議論がある。FXサイトなどを見ると、どうやら2%とするのが通説のようである。ところが、2%とする根拠は何かというと、ちゃんとした説明は私の知る限り、見当たらない。どうも有名なトレード本に2%がいいと書いてあるから、ということらしい。

私はいつも思うのだが、有名な本に書かれているから、有名な人が言ったから、という理由で無批判に受け入れてしまうというのはいかがなものだろうか。しかも、そういう意見がコピペを繰り返して、いつの間にかに通説になっていくように思われる。この議論に限ったことではないが、他人の言葉などどうでもよく、自分で検証してみるということが大事だと思う。

2%ではすぐに資金を溶かしてしまうことも

というわけで、簡単な検証をしてみた。

単純化するためにペイオフレシオ = 1.0、つまり平均利益=平均損失と仮定し、勝率がそれぞれ30%、40%、50%、60%、70%であるトレーダーが260回トレードしたとする。1年を260営業日とし、毎営業日1回トレードするとすれば、1年後にどうなったかということを見るわけである。そして、更に許容損失を1%、2%、3%で場合分けした。

このようなシミュレーションを1万回行い、その平均を取った。その結果が以下のグラフである。

乱数を使っているので、必ずしも同じ結果にはならないが、大体このようである。グラフによると、許容損失を2%とした場合、勝率30%のトレーダーはほとんどの資金を、勝率40%のトレーダーでも大半の資金をたったの1年で失っている。

そして許容損失が1%であれば損失が減り、3%であれば損失が増えている。とすると、許容損失は少ないほどいいと言える。

勝てなければ何%にしても勝てない

一方、勝率50%のトレーダーは許容損失の大きさの影響は受けず、勝ちもしなければ負けもしない。まあ、当たり前だが。

勝率60%、70%のトレーダーは許容損失を2%とした場合、たったの1年で資金を倍以上にしている。そして許容損失が1%であれば利益が減り、3%であれば利益が増えている(グラフでは資金の増え方がすごいので上の方をカットした)。とすると、許容損失は多いほどいいと言える。

以上、検証結果を総合すると、「資金の何%」という議論は無意味で、勝てるトレーダーはどんどんリスクを取っていいし(注)、勝てないトレーダーはリスクを減らす、というよりトレードしないのが最善ということになりそうだ。

(注)ところがそうはいかない場合もある。「リスク管理」カテゴリの「最適レバレッジ」の記事を参照。

サンプルプログラム

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

import matplotlib.pyplot as plt
import numpy as np

n_trades = 260
n_tests = 10000
balance = np.empty(4)
risk = np.array(range(4)) / 100
for i in range(5):
    win_per = (i + 3) / 10
    for j in range(4):
        temp = np.empty(n_trades)
        mean = 0
        for k in range(n_tests):
            change = np.random.choice([risk[j], -risk[j]], n_trades,
                                      p=[win_per, 1.0-win_per])
            for l in range(n_trades):
                if l == 0:
                    temp[0] = 1.0 + change[l]
                else:
                    temp[l] = temp[l-1] * (1.0 + change[l])
            mean += temp[n_trades-1]
        mean = mean / n_tests
        balance[j] = mean
    plt.plot(risk, balance, label=str(win_per))
plt.title('Balance and Risk')
plt.xlabel('Risk')
plt.ylabel('Balance')
plt.xlim(0.0, 0.03)
plt.ylim(0.0, 2.0)
plt.xticks([0.00, 0.01, 0.02, 0.03])
plt.legend(title='win_per', fontsize = 'small')
plt.axhline(y=1.0, color='black', linestyle=':')
plt.tight_layout()
plt.savefig('balance_and_risk.png', dpi=150)
plt.show()
(2017/02/09更新)

イベントとボラティリティ

イベントの発生によってボラティリティがどのように変化するかを調べてみる。イベントにも様々な種類がある。ここでは定期的に発表される経済指標発表イベントを考えることにする。

だが、そのためには予めすべての経済指標発表のスケジュールを調べ、入力しておく必要がある。しかし、これは大変な作業だ。そこで逆に定期的にボラティリティの高くなる特定の時間を見つけ、そこでイベントが発生したと考えることとする。

先ず第1月曜日0時0分から第5金曜日23時55分まで、5分ごとのボラティリティを調べた。そして検証したすべての年においてボラティリティが平均の2倍以上となっている時間を抜き出した。この時間にイベントが発生したと考えるわけである。

ボラティリティが拡大する時間

足の種類は5分足である。ボラティリティを計る指標としてTrue Rangeを使っている。各年の平均を取って、それで除している。

グラフが細かすぎてよく分からない。だが、平均を大きく超える時間があるということは分かる。

縦の点線は週の切り替わるところとなっている。よく見ると、ここでボラティリティが高くなる傾向がある。だが、これはイベントではない。月曜早朝の窓開けである。

横の点線はボラティリティの平均である。よく見ると、週に5回、規則正しくボラティリティが平均を下回る時間帯がある。グラフでは分からないが、これは日付の変わる直前である。この時間帯ではイベントがないからだろう。

第5週は月によっては存在しない。データ数が他の週より少なくなるので、分散も大きい。最後のRANDOMを見ると分かる。

ボラティリティ拡大の時間リスト

グラフでは分からないので各通貨ペアごとにボラティリティが拡大する時間リストを作った。それぞれの通貨ペアで2012年初めから2016年終わりまでの5年間すべてでボラティリティが平均の2倍となっている時間である。

数値は(週、曜日, 時, 分)の順に並べている。曜日は1-5で月-金となっている。

(1, 5, 15, 30)、つまり、第1金曜日の15時30分はNYクロージングを0時0分とした場合の米雇用統計発表の時間である。すべての通貨ペアでこの時間のボラティリティは拡大している。その影響か、発表の1時間前から30分後までの1時間半はボラティリティが高くなる傾向がある。

当然ではあるが、RANDOMではボラティリティが拡大する特定の時間は存在しない。

AUDUSD
(1, 1, 0, 0)
(1, 2, 5, 30)
(1, 2, 6, 30)
(1, 2, 6, 35)
(1, 3, 3, 30)
(1, 4, 3, 30)
(1, 5, 14, 30)
(1, 5, 14, 35)
(1, 5, 14, 40)
(1, 5, 15, 30)
(1, 5, 15, 35)
(1, 5, 15, 45)
(1, 5, 16, 0)
(2, 1, 0, 0)
(2, 4, 3, 30)
(3, 2, 3, 30)
(4, 1, 0, 0)
(4, 3, 2, 30)
EURUSD
(1, 2, 16, 0)
(1, 3, 16, 0)
(1, 4, 14, 40)
(1, 4, 16, 0)
(1, 5, 14, 30)
(1, 5, 14, 35)
(1, 5, 14, 40)
(1, 5, 14, 45)
(1, 5, 15, 25)
(1, 5, 15, 30)
(1, 5, 15, 35)
(1, 5, 15, 40)
(1, 5, 15, 45)
(1, 5, 16, 0)
(2, 4, 14, 30)
(2, 4, 15, 30)
(2, 5, 15, 30)
(2, 5, 15, 40)
(2, 5, 16, 0)
(2, 5, 17, 0)
(3, 1, 0, 0)
(3, 4, 14, 30)
(3, 4, 16, 0)
(4, 1, 0, 0)
(4, 4, 14, 30)
(5, 3, 17, 0)
(5, 5, 16, 55)
GBPUSD
(1, 2, 10, 30)
(1, 3, 10, 30)
(1, 3, 16, 0)
(1, 4, 14, 0)
(1, 4, 14, 40)
(1, 4, 16, 0)
(1, 5, 14, 30)
(1, 5, 14, 35)
(1, 5, 14, 40)
(1, 5, 15, 30)
(1, 5, 15, 35)
(1, 5, 16, 0)
(2, 2, 10, 30)
(2, 4, 14, 30)
(3, 2, 10, 30)
(3, 3, 10, 30)
(3, 4, 10, 30)
(3, 4, 14, 30)
(4, 1, 0, 0)
(5, 5, 14, 30)
USDJPY
(1, 1, 0, 0)
(1, 5, 14, 30)
(1, 5, 14, 35)
(1, 5, 14, 40)
(1, 5, 15, 30)
(1, 5, 15, 35)
(1, 5, 15, 40)
(1, 5, 15, 45)
(1, 5, 16, 0)
(2, 1, 0, 0)
(2, 4, 14, 30)
(2, 5, 15, 30)
(3, 1, 0, 0)
(3, 4, 14, 30)
(4, 1, 0, 0)
(5, 1, 0, 0)
RANDOM

サンプルプログラム

以下のプログラムを実行するにはかなり時間がかかる。私の環境では約1時間かかった。

○以下のプログラムをJupyterにコピー・アンド・ペーストして実行ボタンをクリックする。

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

def find_date(position, timeframe):
    position = position * timeframe
    minute = position % 60
    hour = int(np.floor((position % 1440) / 60))
    day_of_week = int(np.floor((position % 7200) / 1440) + 1)
    week_of_month = int(np.floor((position % 36000) / 7200) + 1)
    return week_of_month, day_of_week, hour, minute

fs.remove_temp_folder()

timeframe = 5
n = 7200
x = np.empty(n)
y = np.empty(n)
event = np.zeros([n, 5])
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')
        ret = fs.i_trange(symbol, timeframe, 0)[start:end]
        mean = np.mean(ret)
        for i in range(5):
            for j in range(5):
                for k in range(24):
                    for l in range(12):
                        x[i*1440+j*288+k*12+l] = i*1440+j*288+k*12+l
                        y[i*1440+j*288+k*12+l] = np.mean(
                                ret[(fs.time_week_of_month(ret.index)==i+1) &
                                    (fs.time_day_of_week(ret.index)==j+1) &
                                    (fs.time_hour(ret.index)==k) &
                                    (fs.time_minute(ret.index)==l*5)]) / mean
                        if y[i*1440+j*288+k*12+l] >= 2:
                            event[i*1440+j*288+k*12+l][cnt-1] += 1
        plt.plot(x, y, label=str(year))
    plt.title('Event and Volatility (' + symbol + ')')
    plt.xlabel('Week of Month')
    plt.ylabel('True Range (Mean=1.0)')
    plt.xlim(0, 7200)
    plt.ylim(0, 13)
    plt.xticks([720, 2160, 3600, 5040, 6480], [1, 2, 3, 4, 5])
    plt.legend(loc='upper right')
    plt.axvline(x=1440, color='black', linestyle=':')
    plt.axvline(x=2880, color='black', linestyle=':')
    plt.axvline(x=4320, color='black', linestyle=':')
    plt.axvline(x=5760, color='black', linestyle=':')
    plt.axhline(y=1.0, color='black', linestyle=':')
    plt.tight_layout()
plt.savefig('event_and_volatility.png', dpi=150)
plt.show()

cnt = 0
for symbol in ['AUDUSD', 'EURUSD', 'GBPUSD', 'USDJPY', 'RANDOM']:
    print(symbol)
    position = np.where(event[0:, cnt]==5)[0]
    size = len(position)
    for i in range(size):
        date = find_date(position[i], timeframe)
        print(date)
    cnt += 1

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

VirtualBoxの使い方

VirtualBoxのインストール

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

sudo apt-get install -y virtualbox

②「[sudo] **** のパスワード:」でパスワードを入力して「Enter」キーを押す。

VirtualBoxをLauncherに登録

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

virtualbox

「Oracle VM VirtualBox マネージャー」が起動する。

②Launcherの「Oracle VM VirtualBox マネージャー」を右クリックする。

③「Launcherに登録」をクリックする。

VirtualBoxの起動

○Launcherの「Oracle VM VirtualBox マネージャー」をクリックする。

VirtualBoxの終了

○「Oracle VM VirtualBox マネージャー」の「×」ボタンをクリックする。

仮想マシンの作成

例として仮想マシンを作成し、その仮想マシンにUbuntuをインストールしてみる。

①「Oracle VM VirtualBox マネージャー」のツールバーで「新規」ボタンをクリックする。

②「仮想マシンの作成」の「名前とオペレーティングシステム」で「名前」に「Ubuntu」と入力する。

③「タイプ」が「Linux」に、「バージョン」が「Ubuntu (64-bit)」に変更されたのを確認して「次へ」ボタンをクリックする。

ホストマシンが32bitなら、「Ubuntu (32-bit)」となる。

④「メモリーサイズ」で「4096」と入力する。

ホストマシンの実メモリに基づき、余力を残して設定する。

これは後で設定し直すこともできる。

実マシンでメモリの増設などができるのと同じである。

⑤「次へ」ボタンをクリックする。

⑥「ハードディスク」で「仮想ハードディスクを作成する」を選択する。

⑦「作成」ボタンをクリックする。

⑧「仮想ハードディスクの作成」の「ハードディスクのファイルタイプ」で「VDI(VirtualBox Disk Image)」を選択する。

⑨「次へ」ボタンをクリックする。

⑩「可変サイズ」を選択する。

⑪「次へ」ボタンをクリックする。

⑫「ファイルの場所とサイズ」でサイズに「500.00 GB」と入力する。

ホストマシンのHDDに基づき、余力を残して設定する。

⑬「作成」ボタンをクリックする。

仮想マシンはデフォルトでは「~/VirtualBox VMs」フォルダーに作られる。

仮想マシンの設定

①「Oracle VM VirtualBox マネージャー」で仮想マシン(ここでは「Ubuntu」)を選択する。

②ツールバーの「設定」ボタンをクリックする。

③「Ubuntu - 設定」の「一般」をクリックする。

④「高度」タブをクリックする。

⑤「クリップボードの共有」を「双方向」に変更する。

⑥「ドラッグ&ドロップ」を「双方向」に変更する。

⑦「システム」をクリックする。

なお、メモリのサイズを再設定したい場合は「システム」の「マザーボード」タブの「メインメモリー」に設定したいサイズを入力すればいい。

⑧「プロセッサー」タブをクリックする。

⑨「プロセッサー数」に「2」と入力する。

ホストマシンのプロセッサー数に基づき、余力を残して設定する。

⑩「ディスプレイ」をクリックする。

⑪「スクリーン」タブをクリックする。

⑫「ビデオメモリー」に「32 MB」と入力する。

128MBまで設定できるが、大きければいいというものでもないらしい。

⑬「アクセラレーション」の「3Dアクセラレーションを有効化」にチェックを入れる。

⑭「ストレージ」をクリックする。

⑮「ストレージツリー」で「コントローラー: IDE」の下の「空」をクリックする。

⑯「属性」の「光学ドライブ」右端にあるアイコンをクリックする。

⑰「仮想光学ディスクファイルを選択...」をクリックする。

⑱「仮想光学ディスクファイルを選択してください」で「ubuntu-16.04-desktop-amd64.iso」ファイルのあるフォルダーに移動する。

⑲「ubuntu-16.04-desktop-amd64.iso」を選択する。

⑳「開く」ボタンをクリックする。

㉑「Ubuntu - 設定」で「OK」ボタンをクリックする。

仮想マシンにUbuntuをインストール

①「Oracle VM VirtualBox マネージャー」で仮想マシン(ここでは「Ubuntu」)を選択する。

②ツールバーの「起動」ボタンをクリックする。

「起動」ボタンをクリックすることは仮想マシンからは電源を入れたように見える。また、「仮想マシンの作成」の⑥から⑬の手順により、仮想マシンからは空のHDDがあるように見える。さらに、「仮想マシンの設定」の⑭〜⑳の手順により、仮想マシンからはUbuntuのisoイメージが書き込まれたDVDがDVDドライブに挿入されているように見える。

そのため、仮想マシンを起動すれば、その後は実マシンにUbuntuをインストールする手順と同じである。Ubuntuのインストールについては「簡易マニュアル」カテゴリの「Ubuntu簡易マニュアル」を参照。

(2017/01/09更新)

執行専用EAの作成

執行専用EAはFXシステム(Python)から送られたシグナルに基づいて売買する。

メタエディターの設定

メタエディターを初めて使うときのみ、実行する。

①MT4のメニューバーで「ツール」をクリックする。

②「メタエディター」をクリックする。

メタエディターが起動する。

③メタエディターのメニューバーで「Tools」をクリックする。

④「Options...」をクリックする。

⑤「Options」で「Font」タブをクリックする。

⑥「Font:」で「Takao ゴシック」を選択する。

これは単なる好みである。

⑦「Script」:で「日本語」を選択する。

⑧「OK」ボタンをクリックする。

サンプルプログラムの作成

①メタエディターのツールバーで「New」ボタンをクリックする。

②「MQL Wizard」で「Expert Advisor (template)」が選択されているのを確認する。

③「次へ」ボタンをクリックする。

④「Name:」を「Experts\ExecutionOnly」で上書きする。

⑤「Author:」を消去する。

⑥「Link:」も消去する。

⑦「次へ」ボタンをクリックする。

⑧続けて「次へ」ボタンをクリックする。

⑨「完了」ボタンをクリックする。

⑩メタエディターで以下のプログラムをコピー&ペーストして「ExecutionOnly.mq4」ファイルを上書きする。

// ファイル名は必ず31文字以内にすること。
extern string Filename = "sample_strategy";
extern double Lot = 0.1;
extern int MaxPos = 5;

void OnTick() {
    // 変数を宣言する。
    bool check;
    int file_handle;
    int i;
    int signal;
    int total;
    int pos;
    int ticket;
    string comment = Filename;

    // シグナルをファイルから確認する。
    file_handle = FileOpen(Filename + ".csv", FILE_READ | FILE_CSV);
    if (file_handle != INVALID_HANDLE) {
        // シグナルには2が加えられているので2を減じて元に戻す。
        signal = FileReadNumber(file_handle) - 2;
        FileClose(file_handle);
    }

    // ポジション総数(このEA以外のものも含む)を確認する。
    total = OrdersTotal();

    // このEAのポジションを確認する。
    pos = 0;
    for (i = 0; i < OrdersTotal(); i++) {
        if (OrderSelect(i, SELECT_BY_POS) == TRUE) {
            if (OrderComment() == comment) {
                if (OrderType() == OP_BUY) {
                    pos = 1;
                }
                else if (OrderType() == OP_SELL) {
                    pos = -1;
                }
            }
        }
    }

    // ポジション総数が最大ポジション数以下である前提で、
    // ポジションが「なし」、かつ、
    if (total <= MaxPos && pos == 0) {
        RefreshRates();
        // シグナルが「買い」なら、新規買い注文を送信する。
        if (signal == 1) {
            ticket = OrderSend(Symbol(), OP_BUY, Lot, Ask, 3, 0, 0, comment);
        }
        // シグナルが「売り」なら、新規売り注文を送信する
        else if (signal == -1) {
            ticket = OrderSend(Symbol(), OP_SELL, Lot, Bid, 3, 0, 0, comment);
        }
    }
    // ポジションが「買い」、かつ、シグナルが「なし」、または「売り」なら、
    // 決済売り注文を送信する。
    else if (pos == 1 && (signal == 0 || signal == -1)) {
        for (i = OrdersTotal() - 1; i >= 0; i--) {
            if(OrderSelect(i, SELECT_BY_POS) == TRUE) {
                if (OrderComment() == comment && OrderType() == OP_BUY) {
                    check = OrderClose(OrderTicket(), OrderLots(), Bid, 3);
                }
            }
        }
    }
    // ポジションが「売り」、かつ、シグナルが「なし」、または「買い」なら、
    // 決済買い注文を送信する。
    else if (pos == -1 && (signal == 0 || signal == 1)) {
        for (i = OrdersTotal() - 1; i >= 0; i--) {
            if(OrderSelect(i, SELECT_BY_POS) == TRUE) {
                if (OrderComment() == comment && OrderType() == OP_SELL) {
                    check = OrderClose(OrderTicket(), OrderLots(), Ask, 3);
                }
            }
        }
    }

    // ポジション総数、ポジション、シグナルを出力する。
    Print("total = ", total);
    Print("pos = ", pos);
    Print("signal = ", signal);
}

サンプルプログラムの第1行にcsvファイルの名前を設定する(例として「sample_strategy」としてある)。

警告:csvファイルの名前は半角で必ず31文字以内にする。そうしないとOrderComment()関数に正しく読み込まれない。ファイル名でEAのポジションを識別するようにしているので、正しく読み込まれないと大変なことになる。例えば他のEAのポジションを自分のものと勘違いして決済し、本来の保有者であるEAはポジションが突然決済されてしまったので、再びポジションを持つ。すると、執行専用EAは決済したはずなのにまたポジションが出来てしまったので再び決済する。こうして新規注文、決済注文が繰り返されるが、コンピューターは非常に高速にこれを実行するので、その都度払うスプレッドだけで退場させられるだろう。

⑪ツールバーの「Compile」ボタンをクリックする。

「Toolbox」の「Description」に「0 error(s), 0 warning(s)」と表示されればOK。

空のcsvファイルの作成

シグナルのやり取りはcsvファイルを通じて行う。そこで、あらかじめ空のcsvファイルを作っておく。

上のサンプルプログラムに合わせて、「sample_strategy.csv」ファイルを作成する場合を例とする。また、MT4のインストール先は「~/.wine/drive_c/Program Files (x86)/OANDA - MetaTrader」であるとする。半角スペースのあるフォルダー名は「""」で囲む点に注意。

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

touch ~/.wine/drive_c/"Program Files (x86)"/"OANDA - MetaTrader"/mql4/Files/sample_strategy.csv
(2017/01/08更新)

GitHubの使い方

ここではユーザー名を「fxst24」、リモートリポジトリを「origin」として説明する。

GitHubの登録

注意:手順の一部は実際に登録してみないと分かりませんが、私はすでに登録済で、手順確認のためにまた別に登録するのもどうかと思いますので、以下の手順は不完全なものです。

①以下のアドレスをクリックする。

https://github.com/

②右上の「Sign up」ボタンをクリックする。

③「Create your personal account」で「Username」にユーザー名を入力する。

④「Email Address」でメールアドレスを入力する。

⑤「Password」でパスワードを入力する。

⑥「Create an account」ボタンをクリックする。

この後に「Step 2」、「Step 3」という手順があるようだが未確認のため、省略。

これより下の手順は私が実際に登録したとき(2016年7月ごろ)のメモに基づいています。仕様変更がなければそのまま使えると思います。特に「GitHubの更新」は普段から使っていますので大丈夫です。

初期設定

初期設定は1回やればいいようである。

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

git config --global user.email ******@*****.com;git config --global user.name "fxst24"

メールアドレス、ユーザー名は登録したものを入力する。

ローカルリポジトリの作成

ここでは例として「ホーム」フォルダーに「fxst24」という名前のフォルダーを作成し、これをローカルリポジトリとすることにする。

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

mkdir ~/fxst24

リモートリポジトリの作成

ユーザ名が「fxst24」のリモートリポジトリとして「origin」を作成する。

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

cd ~/fxst24;git remote add origin https://github.com/fxst24/fxst24.git

GitHubの更新

ファイルを更新して、それをGitHubに反映させたい場合、①ローカルリポジトリの初期化、②追加、削除、修正したファイルすべてのインデックスへの追加、③ファイルのコミット、④リモートリポジトリへのプッシュ、という過程を経るようだが、ここでは1つのコマンドにまとめて実行する。

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

cd ~/fxst24;git init;git add --all;git commit -m "update";git push origin master

②ユーザ名を入力して「Enter」キーを押す。

③パスワードを入力して「Enter」キーを押す。

(2017/01/07更新)