最適レバレッジ

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

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

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

勝率とドローダウン期間の関係

勝率とドローダウン期間の関係を調べてみる。トレーダーはドローダウン期間が続くと、自分の戦略に疑いを抱き、やたらと戦略に手を加えたくなるものである。だが、ドローダウンはトレードには付き物であって、ある程度は初めから覚悟しなければならない。

その「ある程度」とはどのくらいなのか、ということをシミュレーションによって調べてみる。単純化のためにいくつかの仮定を置く。

  • トレードのリターンは1%、または-1%のいずれかとする。つまりペイオフレシオ=1.0である。
  • トレードは1日に1回とする。
  • 1年の営業日を260日とする。

以上の仮定に基づいて1年間トレードするという試行を10回行い、平均のドローダウン期間を勝率ごとに求める。勝率は5%刻みで5%-100%まで調べる。結果は以下の通りである。

win_per durations
0.05    259.0
0.1     259.0
0.15    258.9
0.2     258.6
0.25    257.5
0.3     256.4
0.35    256.9
0.4     238.9
0.45    240.0
0.5     160.0
0.55    109.7
0.6     50.6
0.65    35.2
0.7     19.2
0.75    16.6
0.8     10.8
0.85    7.6
0.9     5.8
0.95    3.6
1.0     0.0

ペイオフレシオ1.0で勝率60%はなかなかのパフォーマンスである。それでも50.6日(2ヶ月強)のドローダウン期間がある。勝率70%となると、むしろありえないくらいの高パフォーマンスである。それでも19.2日(1ヶ月弱)のドローダウン期間がある。

トレーダーとしてはドローダウンの回復に1ヶ月も2ヶ月もかかったのでは大変つらいことである。だが、勝率60%、70%でもこの程度のドローダウン期間は避けがたいのである。だとすれば数ヶ月のドローダウン期間は覚悟すべきだろう。

サンプルプログラム

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

import forex_system as fs
import numpy as np
import pandas as pd

durations = np.zeros(20)
print('win_per' + '\t' + 'durations')
for i in range(20):
    win_per = (i + 1) / 20
    for j in range(10):
        ret = np.random.choice([0.01, -0.01], 260, p=[win_per, 1 - win_per])
        ret = pd.Series(ret)
        temp = fs.calc_durations(ret, 1440)
        durations[i] += temp
    durations[i] = durations[i] / 10
    print(str(win_per) + '\t' + str(durations[i]))
(2017/02/08更新)

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

ネットでは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更新)

ポートフォリオの最適化

ポートフォリオと言えば複数銘柄の保有比率を指すことが多いと思う。だが、ここでは複数戦略への資金の投入比率として考えることとする。

また、ポートフォリオの最適化は一般的には目標リターンを設定した上でリスクを最小化することを指すようである。目標リターンを設定しないなら、期待リターンとリスクの比、つまりシャープレシオの最大化と考えてもいいのだろう。ここではシャープレシオの最大化ということを考える。

ポートフォリオにも過剰最適化のリスク

ポートフォリオの最適化にもバックテストにおけると同様、過剰最適化のリスクがある。例えばバックテスト自体はウォークフォワードテストを行ったとしても、投入比率の決定に全期間を用いたなら、これにはトレードの時点で存在しないデータを使う先読みバイアスのリスクがある。

複数戦略の中に期待リターンがマイナスの戦略があったとする。ポートフォリオの最適化を行うと、その戦略のウェイトは0、あるいはほぼ0になるだろう。また、期待リターンがプラスの戦略においても、期待リターンが大きい戦略ほどウェイトが大きくなる傾向が出てくるだろう。

となると、最適化の結果、負ける戦略は排除され、勝てる戦略のみが残ることになる。だが、戦略のどれが勝ち、あるいは負けるのかということは後になって分かることであり、トレードの時点では知りようがないのだから、このような最適化はパフォーマンスを過大評価する。

もっとも、ウォークフォワードテストでパフォーマンスの良い戦略をポートフォリオに入れ、悪い戦略は入れない、ということは最適化の前に行われているだろう。これも一種の先読みバイアスではある。

等ウェイトも馬鹿にできない

それはともかくとして、ポートフォリオの最適化はウォークフォワードテストと同様、インサンプル期間で行い、パフォーマンスの評価はアウトオブサンプル期間で行うのが先読みバイアスのリスクを少しでも減らす方策の1つだろう。とりあえずそうするとしても、まだ問題がある。

一応、単体でも勝てる戦略であったとしても、比較的短いインサンプル期間では期待リターンがマイナスになることもある。すると、続くアウトオブサンプル期間において、その戦略のウェイトは0、あるいはほぼ0になってしまうと考えられる。果たして、それでよいのだろうか。

調子の良い戦略のウェイトを増やし、調子の悪い戦略のウェイトを減らすのは一見、理に適っているようにも見える。現在の値動きが過去の値動きの影響を受けないなら確かにそうかもしれない。

だが、買い有利の相場と売り有利の相場が交互に来るとか、順張り有利の相場と逆張り有利の相場が交互に来るとかいうことも考えられる。すると、買いで負けたので売りに転換してさらに負けるとか、順張りで負けたので逆張りに転換してさらに負けるとかいった具合で往復ビンタを食らうことにもなりかねない。

実際にこのタイプの最適化を行うと、ある戦略のウェイトがほぼ100%を占めたと思うと、次の期間では別の戦略のウェイトがほぼ100%を占める、といったことがよく起こる。これは、ほぼ100%を占めていた戦略が次の期間ではほぼ0%になっていた、つまり、ほぼ100%の資金を投入した戦略が負けたということであり、ほぼ0%の戦略が次の期間ではほぼ100%を占めていた、つまり、ほとんど資金を投入しなかった戦略が勝ったということである。

その結果、等ウェイトでのパフォーマンスを下回るということも往々にしてある。最適化がかえって劣化をもたらすのである。

逆に何も考えずに等ウェイトでポートフォリオを組むというのはいかにも芸がなく、頭の悪いやり方にも見えるが、意外と等ウェイトでのパフォーマンスを超える最適化というのも難しかったりする。等ウェイトもなかなか馬鹿にできないもので、無理に最適化はせず、等ウェイトでよい、とするのも1つの考え方である。

(2016/11/19更新)

破産レバレッジ?

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

破産レバレッジ = 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更新)