Pythonでバックテストのモンテカルロシミュレーションを行う

証券フロンティア事業部サーバーエンジニアの伊奈です。

前の記事(Webhookでのティック取得から東証「arrowhead」への流し込み -後編- - テコテック開発者ブログ)を書いた後から、システムトレードのバックテスト(決められたルールに従って機械的に行う取引を過去の価格データを使って検証すること)をしていました。

その際にバックテストで得られた期待値をモンテカルロシミュレーションしたのでそのことを書きたいと思います。

モンテカルロシミュレーションを行うことで、バックテストでは得られなかった統計的特性を見つけ取引戦略の有効性を評価できます。

目次

モンテカルロシミュレーションとは

乱数によるランダムな変更を加えながら同じ試行や計算を繰り返す手法のことで、繰り返し回数が多いほど結果の統計的有意性は大きくなります。

様々な分野で使用されていますが、よく例に挙げられるのが円周率の求め方です。

一辺が2の正方形の中に入る半径1の円があるとして、その正方形の中にランダムに点を打っていったとき、円の中に打たれた点の数 ÷ 点の総数 × 4 で円周率の近似値が求まり、打つ点の数が多いほど精度が高くなるというものです。

詳しい説明は割愛しますが、ランダムな試行を繰り返すことで確率を近似的に求めたり、得られている推定値の信頼性を評価したりできます。

モンテカルロ法 - Wikipedia

モンテカルロシミュレーションを取引戦略の評価に用いる場合に最も簡単な方法は、バックテストで得られた取引のリストをランダムに入れ替え、それ繰り返し実行する方法です。ある母集団からランダムに標本を抽出して統計値を計算する方法をモンテカルロ法の中でもブートストラップ法と呼びます。

ブートストラップ法 - Wikipedia

なぜモンテカルロシミュレーションを行うか

バックテストの結果をさらに分析することで、戦略の堅牢性や破産確率を検証したり、ほかの統計的特性を見つけたりすることで自分の取引戦略に自信を持つためです。

バックテストの結果から得られる純利益や勝率、プロフィットファクターなどの基準だけでもある程度の戦略の有効性に対して評価を行うことができます。

しかし、バックテストでは右肩上がりに資産が増えていくことが分かった戦略であっても、実際にシストレが動き始めると負け取引や資産減少をリアルタイムに感じることになります。

たとえそれが右肩上がりの一部分の負けであったとしても戦略に自信が持てなくなりますし、それがバックテストで想定されていた連続負け回数や最大ドローダウン(最大資産からの落ち込み幅)を超えて負けている場合には、すぐにプログラムを止めてしまいたくなります。

モンテカルロシミュレーションを行うことにより、バックテストではわからなかった統計的特性を見つけ、予想される負け取引に対して精神的に準備し、シストレを動かし続けるべきかどうかを判断するのに役立てます。

Pythonでモンテカルロシミュレーションをやってみる

バックテストの結果にモンテカルロシミュレーションを行う場合の最も簡単な方法は取引のリストをランダムに入れ替える方法です。

今回は最大ドローダウンに着目して分析していきます。

  • 環境: Google Colaboratory
  • Python 3.7.12
  • ライブラリ: Pandas

元の資産曲線とドローダウンを確認

まずベースとなる取引のリストと資産曲線をバックテストで作っておきます。

必要なのは各取引ごとの損益(Profit)とその時点の純利益(NetProfit)です。

f:id:teco_inas:20220221081106p:plain

NetProfitをそのままプロットすると資産曲線になります。

df['NetProfit'].plot(figsize=(15, 10), legend=False)

f:id:teco_inas:20220221081552p:plain

オリジナルの最大ドローダウンを計算しておきます。 その時点の純利益からその時点以前の最大の純利益を引いたものの最小値で求まります。

max_drawdown = (df['NetProfit'] - df['NetProfit'].cummax()).min()

最大ドローダウンは-99,368円でした。

モンテカルロシミュレーションを実行

次にモンテカルロシミュレーションを行います。 取引をランダムに並び替えて純利益を計算しなおすだけです。これを最低1000回繰り返します。

result = []
for i in range(1000):
    resample = df['Profit'].sample(frac=1).reset_index(drop=True)
    resample_profit = resample.cumsum()
    result.append(resample_profit)

result_df = pd.DataFrame(result).T
result_df.columns=range(result_df.shape[1])

元の取引をすべて使っているので最終的な純利益は変わりませんが、途中の純利益が変わっていることがわかります。 f:id:teco_inas:20220221083949p:plain

結果を確認

1000の資産曲線をすべてプロットするとこうなります。(見やすさのため繰り返し回数100回で実行) ラインが赤いほど最大ドローダウンが小さく、青いほど大きくなっています。 f:id:teco_inas:20220221084306p:plain

最悪のケースの最大ドローダウンは-398,650円でした。目安としては、オリジナルの最大ドローダウンの2倍程度に収まっていると取引戦略の有効性が高いとされています。 プロットしたラインのばらつきが小さく、よりオリジナルのラインに収束していると良い戦略というわけですね。

今回は最悪のケースの最大ドローダウンがオリジナルの4倍程度になっているので良い戦略とは言えなさそうです。

次に1000個の資産曲線に対してそれぞれ最大ドローダウンを求めてプロットしてみます。 約15%以下の確率で最大ドローダウンがオリジナルの2倍の-200,000円以下になってしまうことを表しています。 f:id:teco_inas:20220221102940p:plain

まとめ

バックテストの時点ではそれなりに利益が出そうな戦略でしたが、モンテカルロシミュレーションを実行してみると、確率は低いものの想定される最大ドローダウンよりも4倍のドローダウンが起きる可能性があることが分かりました。

モンテカルロシミュレーションを実行することでバックテスト以上に戦略を分析することができました。この結果をもとに取引戦略を改善していくのか、ロット管理をしつつ運用を続けるかは人それぞれになってくるでしょう。

今回はオリジナルの取引のリストをすべて使いましたが、何%かだけを取り出したり、重複を許して取り出したりした場合の資産曲線がどうなるかなどもやってみたいと思いました。

tecotec.co.jp