破産レバレッジ?

割とどうでもいいが、利益を最大化させる最適レバレッジではなくて、破産させるレバレッジを考えてみる。とりあえず破産レバレッジとでも名付けておこう。計算式は

破産レバレッジ = 1 / 最小リターン * (-1)

とする。

もし最小リターンが-1%だったとする。すると破産レバレッジは1 / (-0.01) * (-1) = 100.0となる。これは簡単に理解できるだろう。最悪で1%の負けでもレバレッジ100倍なら100%の負け、つまり破産である。それまでいくら稼いでいても、である。

最悪の負けはすべてのトレードの中で少なくとも1つはある。それがトレードの最初であろうと、最後であろうと、それ以外であろうと、破産レバレッジでそのトレードが行われた瞬間、たちまち破産するのである。

もちろん、最小リターンがマイナスでない場合、つまりすべてのトレードで1度も負けがない場合、破産レバレッジは意味をなさない。あくまでも勝つこともあれば負けることもあるトレードというのが前提である。

さて、破産レバレッジをシミュレーションで確認してみる。ここに1トレード当たりの期待利益が0.1%、リスクが0.5%の戦略があるとする。だが、最小リターンは実際にシミュレーションしてみないと出てこない数値なので、トレードを100回行うとして先ずは実行してみる。

シミュレーション結果によると、破産レバレッジは63.7157243783で、実測値は63.72となっている。実測値は0.01のインターバルで測っているので、もっと細かくやれば理論値と一致するだろう。最適レバレッジの計算に比べれば非常に単純明快であり、近似式も必要ない。

ただ、最小リターンがシミュレーションしないと出てこないというのが曲者で、トレードを1万回、100万回と増やしていけば最小リターンはより小さく、そして破産レバレッジもより小さくなるだろう。言い換えるとトレードを永遠に続ければいつかは必ず破産することになる。

だが、心配することはない。一生かけてもできない数のトレードでも破産レバレッジが0近くになってトレードが無理、ということにはならない。

下のサンプルプログラムの「trades」の数を変更してシミューレションしてみれば分かるが、トレードを50万回やっても破産レバレッジは45倍前後で、日本国内の規制である25倍よりまだ大きいのである。もちろん、1トレード当たりの期待利益が0.1%、リスクが0.5%の戦略であればの話である。期待利益がマイナスの戦略ならレバレッジなど関係なく、遅かれ早かれ破産するので、トレードはやめたほうがいい。

サンプルプログラム

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

# coding: utf-8
import matplotlib.pyplot as plt
import numpy as np

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

n = 10000
equity_curve = np.empty(trades)
x = np.array(range(n)) / 100
y = np.empty(n)
for j in range(n):
    leverage = j / 100
    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
    y[j] = equity_curve[trades-1]
argmin = np.argmin(y)
ax=plt.subplot()
plt.plot(x, y)
plt.axvline(x=worst_kelly, color='green')
plt.axvline(x=x[argmin], color='green')
plt.axhline(y=1.0, color='red')
plt.xlabel('Leverage')
plt.ylabel('Balance')
plt.text(0.05, 0.9, 'Worst Kelly(actual value) = ' + str(x[argmin]),
         transform=ax.transAxes)
plt.text(0.05, 0.85, 'Worst Kelly = ' + str(worst_kelly),
         transform=ax.transAxes)
plt.savefig('bankruptcy_leverage2.png', dpi=150)
plt.show()
plt.close()
(2016/10/28更新)

最適レバレッジの近似式の誤差

ケリー基準を用いた最適レバレッジでは近似式による計算がよく使われていると思う。

近似式は

最適レバレッジ = リターンの平均 / (リターンの標準偏差 * リターンの標準偏差)

となっている。

本来の式はレバレッジについて級数展開して求めることができるが、レバレッジが小さい場合、第3項以降は無視できるほど小さくなるので、近似式では初めの2項を用いて求めている。つまり、レバレッジが小さくない場合、近似式はかなり大きな誤差を生じることになる。細かい点については以下のページがとても参考になる。

http://www.geocities.jp/y_infty/management/criterion_3.html

私はこれまで、どのくらいの誤差があるのかということを確かめることはせずに近似式を用いてきたが、レバレッジをx、累積リターンをyとした場合のグラフを作成すると、yが最大値となるxと近似式による最適レバレッジとの間になかなか無視できない誤差が時として生じることに気が付いた。そこで、どのくらいの誤差があるのかということを確かめてみた。

ここに1トレード当たりの期待利益が0.1%、リスクが0.5%の戦略があるとする。近似式を用いると最適レバレッジは0.001 / (0.005 * 0.005) = 40.0となる。この戦略に基づいて100回トレードを行うシミュレーションを繰り返し、その中から誤差の小さいものと大きいものとを選んでみた。

誤差の小さい場合

先ずは誤差が小さい場合の例を見てみよう。

誤差は0.01以下で、この程度の誤差であれば確かに無視してもよさそうだ。

誤差の大きい場合

次に誤差が大きい場合を見てみる。

誤差は15以上で大きいといえば大きいが、より問題であるのは近似式での最適レバレッジが本来の最適レバレッジでは破産レベルになっていることだ。これはさすがに無視できない。

近似式は使わないべきか

このように書くと、近似式は恐くて使えないという誤解を与えてしまうかもしれない。だが、このような誤差は何十回もシミュレーションをして、ようやく1回見かけるくらいレアなものである。また、レバレッジが小さくても誤差が大きい場合や逆にレバレッジが大きくても誤差が小さい場合もあるが、やはり近似式の性質上、レバレッジが大きいほど誤差も大きい傾向がある。

FXは日本国内では規制によってレバレッジ25倍までとなっているし、この程度のレバレッジなら、誤差も大したことはない。したがって、日本国内の規制にしたがったトレードをしている限り、近似式で問題ないだろう。

サンプルプログラム

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

# coding: utf-8
import matplotlib.pyplot as plt
import numpy as np

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

n = 10000
equity_curve = np.empty(trades)
x = np.array(range(n)) / 100
y = np.empty(n)
for j in range(n):
    leverage = j / 100
    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
    y[j] = equity_curve[trades-1]
argmax = np.argmax(y)
ax=plt.subplot()
plt.plot(x, y)
plt.axvline(x=kelly, color='green')
plt.axvline(x=x[argmax], color='green')
plt.axhline(y=1.0, color='red')
plt.xlabel('Leverage')
plt.ylabel('Balance')
plt.text(0.05, 0.9, 'Kelly = ' + str(x[argmax]), transform=ax.transAxes)
plt.text(0.05, 0.85, 'Kelly Approximation = ' + str(kelly),
         transform=ax.transAxes)
plt.savefig('optimal_leverage2.png', dpi=150)
plt.show()
plt.close()
(2016/10/28更新)

電子書籍の作成メモ

ブログ記事の原稿ファイルを使って電子書籍を作ってみる。

原稿ファイルの作成

ブログ記事の原稿を書くときはMarkdownで記述し、mdファイルとして保存する。私はmdファイルを「~/ドキュメント/blog」フォルダーに保存している。

cssファイルの作成

以下のプログラムを「layout.css」ファイルとして「~/ドキュメント/blog」フォルダーに保存する。

@charset "utf-8";

blockquote {
    font-style: italic;
    font-weight: bold;
}

font {
    font-weight: bold;
}

h3 {
    border-bottom: 0.125em solid darkcyan;
    color: #111;
    margin-bottom: 1.0em;
    padding: 0.25em 0.5em;
    page-break-before: always;
}

h4 {
    border-bottom: 0.125em solid darkcyan;
    border-left: 0.5em solid darkcyan;
    margin-bottom: 1.0em;
    padding: 0.25em 0.5em;
}

p {
    margin-bottom: 2.0em;
    text-indent: 1.0em;
}

pre {
    background-color: #300A24;
    color: white;
    overflow: auto;
}

原稿ファイルの結合

$ cd ~/ドキュメント/blog/00;\
echo -e '<link href="layout.css" rel="stylesheet"></link>\n## CHAPTER 0 はじめに\n' > ../chapter0.md;cat 01.md 02.md 03.md 04.md >> ../chapter0.md;\
cd ~/ドキュメント/blog/01;\
echo -e '<link href="layout.css" rel="stylesheet"></link>\n## CHAPTER 1 環境の構築\n' > ../chapter1.md;cat 01.md 02.md 03.md 04.md 05.md 06.md 07.md 08.md 09.md 10.md 11.md 12.md 13.md >> ../chapter1.md;\
cd ~/ドキュメント/blog/02;\
echo -e '<link href="layout.css" rel="stylesheet"></link>\n## CHAPTER 2 バックテストの陥穽\n' > ../chapter2.md;cat 01.md 02.md 03.md >> ../chapter2.md;\
cd ~/ドキュメント/blog/03;\
echo -e '<link href="layout.css" rel="stylesheet"></link>\n## CHAPTER 3 パフォーマンス評価\n' > ../chapter3.md;cat 01.md 02.md 03.md 04.md 05.md >> ../chapter3.md
cd ~/ドキュメント/blog/04;\
echo -e '<link href="layout.css" rel="stylesheet"></link>\n## CHAPTER 4 リスク管理\n' > ../chapter4.md;cat 01.md 02.md 03.md 04.md 05.md 06.md >> ../chapter4.md;\
cd ~/ドキュメント/blog/05;\
echo -e '<link href="layout.css" rel="stylesheet"></link>\n## CHAPTER 5 トレード戦略\n' > ../chapter5.md;cat 01.md 02.md 03.md 04.md 05.md >> ../chapter5.md;\
cd ~/ドキュメント/blog/06;\
echo -e '<link href="layout.css" rel="stylesheet"></link>\n## CHAPTER 6 MT4\n' > ../chapter6.md;cat 01.md 02.md 03.md >> ../chapter6.md;\
cd ~/ドキュメント/blog/07;\
echo -e '<link href="layout.css" rel="stylesheet"></link>\n## CHAPTER 7 リターン\n' > ../chapter7.md;cat 01.md 05.md 10.md >> ../chapter7.md;\
cd ~/ドキュメント/blog/08;\
echo -e '<link href="layout.css" rel="stylesheet"></link>\n## CHAPTER 8 ボラティリティ\n' > ../chapter8.md;cat 01.md 02.md 03.md 04.md 05.md >> ../chapter8.md;\
cd ~/ドキュメント/blog/09;\
echo -e '<link href="layout.css" rel="stylesheet"></link>\n## CHAPTER 9 ランダムウォーク\n' > ../chapter9.md;cat 01.md 02.md 03.md 04.md >> ../chapter9.md
cd ~/ドキュメント/blog/10;\
echo -e '<link href="layout.css" rel="stylesheet"></link>\n## CHAPTER 10 その他\n' > ../chapter10.md;cat 01.md 02.md 03.md 04.md 05.md 06.md 07.md 08.md 09.md >> ../chapter10.md

「CHAPTER 0」〜「CHAPTER 10」の各章が作成される。

電子書籍の作成

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

http://conv.denshochan.com/

②「アップロードしてね」で「参照...」ボタンをクリックする。

③「ファイルのアップロード」で「~/ドキュメント/blog」フォルダーに移動する。

④「layout.css」ファイルを選択する。

⑤[Ctrl]キーを押しながら「chapter0.md」、「chapter1.md」、「chapter2.md」、「chapter3.md」、「chapter4.md」、「chapter5.md」、「chapter6.md」、「chapter7.md」、「chapter8.md」、「chapter9.md」、「chapter10.md」の各ファイルを選択する。

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

⑦「情報を入れてね」の「タイトル(必須)」に「FXと定量分析 (2017/03/06)」と入力する。

⑧「作成者」に「西 明」と入力する。

⑨「ページ送り方向」で「左から右 横書き」が選択されているのを確認する。

⑩「お好みでどうぞ」の「ページ自動生成」で「扉ページ」、「目次ページ」にチェックを入れる。

⑪「変換」ボタンをクリックする。

⑫「***.epub を開く」で「ファイルを保存する」を選択する。

ファイル名は作成するたびに違う。

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

***.epub」ファイルが「~/ダウンロード」フォルダーに保存される。

⑭「***.epub」ファイルを「forex_and_quantitative_analysis.epub」ファイルにリネームする。

電子書籍のサンプル

上の手順で作成した電子書籍がこれである。

https://github.com/fxst24/fxst24/blob/master/forex_and_quantitative_analysis.epub

(2017/03/06更新)

FXシステムのダウンロード

ここで言う「FXシステム」とは私が自作した「forex_system」ライブラリのことである。これと関連するファイルをダウンロードする。

○端末で以下のコマンドをコピー&ペーストして「Enter」キーを押し、FXシステムの各ファイルを「~/py」フォルダーに保存する。

wget -P ~/py https://raw.githubusercontent.com/fxst24/fxst24/master/forex_system/convert_hst2csv.py;\
wget -P ~/py https://raw.githubusercontent.com/fxst24/fxst24/master/forex_system/make_historical_data.py;\
wget -P ~/py https://raw.githubusercontent.com/fxst24/fxst24/master/forex_system/make_randomwalk_data.py;\
wget -P ~/py https://raw.githubusercontent.com/fxst24/fxst24/master/forex_system/forex_system.py;\
wget -P ~/py https://raw.githubusercontent.com/fxst24/fxst24/master/forex_system/backtest.py;\
wget -P ~/py https://raw.githubusercontent.com/fxst24/fxst24/master/forex_system/trade.py
(2017/01/26更新)

SciPyのlognorm()関数の引数指定について

SciPyのlognorm()関数を使おうと思ったのだが、引数をどうするのかが分からなくて苦労した。あれこれ試行錯誤してようやく分かったので、メモとして残す。

LibreOffice Calcで適当なセルに「=LOGNORMDIST(4,3.5,1.2,1)」と入力すると「0.0390835557」と返される。

「4」は調べたい数値(対数にする必要はなく、そのままでよい)、「3.5」はデータを対数に変換した後の平均、「1.2」はデータを対数に変換した後の標準偏差、「1」は戻り値として累積分布を指定、という意味である。

書式はExcelと同じである(Excelの場合は「LOGNORM.DIST」だが)。これと同じことをSciPyでやりたい。

この場合、

import numpy as np
from scipy.stats import lognorm
lognorm.cdf(x=4, s=1.2, loc=0, scale=np.exp(3.5))

とすればよい。

出力

0.039083555706800471

つまり、「x」には調べたい数値、「s」にはデータを対数に変換した後の標準偏差、「loc」には0、「scale」にはデータを対数に変換した後の平均を指数に変換(ややこしい)した数値を指定するのである。

非常に紛らわしいのだが、SciPyのnorm()関数で

norm.cdf(x=1.0, loc=0, scale=1)

とした場合、xは同じだが、「loc」は平均、「scale」は標準偏差を指定する。

これが頭にあったので、大混乱した。もう少し統一性を持たせてほしいものだが...。

(2016/10/07更新)