この度、ナンピン幅および利確幅をATRに連動させて、破綻リスクを確率的に制御するナンピンマーチン型のFX自動売買ツール(EAまたはBOT)の開発を行いました。
この記事は、前回の記事の続きです。
MQL5で開発しMT5で稼働させるEA、MQL4で開発しMT4で稼働させるEA、Pythonで開発しMT5のPython APIを利用して稼働させるBOT、とそれぞれ用意しているのですが、この記事ではそのEAまたはBOTの開発における思考過程を少しずつ具体的に記事にしていきたいと思います。
なお、作成したMT5用EAとMT4用EAについては、以下からダウンロード可能です。
前回まで
以下のようなデータフレーム(df_ATR)を作成しました。
これを用いて、1分足で簡易的に勝率を試算する関数を作成します。
勝率を試算する関数
buyポジションの勝率計算
@numba.njit
def calc_buy_result(op=None, lo=None, hi=None, lc_range=None, tp_range=None, spread=None):
y = op.copy()
y[:] = np.nan
for i in range(op.size):
lc_price = op[i] - lc_range[i] + spread
tp_price = op[i] + tp_range[i] + spread
for j in range(i + 1, op.size):
if lo[j] < lc_price:
y[i] = -1
break
if hi[j] > tp_price:
y[i] = 1
break
return y
以下、各コードの説明をしていきます。
@numba.njit
これは、処理を高速化するおまじないのようなものです。
ChatGPTに説明してもらったので載せておきます。
したがって、ここでは基本的にPandasのdfは、配列に変換してから引数にするという前提で書いています。
引数
以下のような引数を想定した関数になります。
- op:始値(Open)
- lo:安値(Low)
- hi:高値(High)
- lc_range:ロスカット幅
- tp_range:利確幅
- spread:想定スプレッド
return y
この関数は、yを返します。最初の処理でyを初期化しています。
y = op.copy()
y[:] = np.nan
ループ処理
以下の通り、Openのレコード数(op.size)だけループさせます。1分足であれば、毎分処理するイメージです。
for i in range(op.size):
利確価格とロスカット価格
始値から、利確幅だけ上を利確価格、ロスカット幅だけ下をロスカット価格に設定します。その際、スプレッド幅も考慮して、利確がスプレッド分だけ不利に(ロスカットが有利に)なるようにしておきます。
lc_price = op[i] - lc_range[i] + spread
tp_price = op[i] + tp_range[i] + spread
ポジションクローズ
時刻iにおける始値でポジションを取るので、その後のi+1以降の時刻における安値と高値を見にいきます。
for j in range(i + 1, op.size):
if lo[j] < lc_price:
y[i] = -1
break
if hi[j] > tp_price:
y[i] = 1
break
先に安値がロスカット価格を下回ったら負けとしてy=-1、高値が利確価格を上回ったら勝ちとしてy=1、というアウトプットを出力して終了します。
sellポジションの勝率計算
同様に、sellポジションの勝率を計算する関数も用意しておきます。ロジックはほぼ同様です。
@numba.njit
def calc_sell_result(op=None, lo=None, hi=None, lc_range=None, tp_range=None, spread=None):
y = op.copy()
y[:] = np.nan
for i in range(op.size):
lc_price = op[i] + lc_range[i] - spread
tp_price = op[i] - tp_range[i] - spread
for j in range(i + 1, op.size):
if hi[j] > lc_price:
y[i] = -1
break
if lo[j] < tp_price:
y[i] = 1
break
return y
四本値(バー)をインプットに勝率計算
df[‘y’]の追加
用意したdf_ATRとcalc_buy_result関数を使って、以下のようにdf[‘y’]を追加します。
df = df_ATR
df = df.dropna()
df['ATR'] = 12.00
spread = 0.15
tp_factor = 1.0
lc_factor = 1.0
df['y']= calc_buy_result(
op=df['Open'].values,
lo=df['Low'].values,
hi=df['High'].values,
lc_range=lc_factor*df['ATR'].values,
tp_range=tp_factor*df['ATR'].values,
spread = spread,
)
df[‘ATR’] = 12.00とすることによって、df_ATRに入っていたATRを上書きして固定幅にすることが可能です。これを削除すれば、ATRに連動した利確幅とナンピン幅を設定できます。
また、tp_factorとlc_factorはATRの何倍を利確幅・ナンピン幅にするかを設定する係数です。
DMI_flgの反映
こちらは以下のように既に出力されたdf[‘y’]を書き換えるだけで可能です。
df.loc[(df['DMI_flg'] != 1), 'y'] = 0
これは何をしているかと言いますと、フラグを反映させる前は、df[‘y’]には勝ち(+1)または負け(-1)のどちらかの値が入っています。これを、DMI_flgが0の場合は引き分け(0)とします。
つまり、フラグが0であればエントリーしないということを表現しています。
勝率計算
書き方は様々ですが、例えば以下のようにします。
buy_wins = len(df[df['y']==1])
buy_trials = len(df[df['y']!=0])
buy_win_rate = round( buy_wins / buy_trials * 100, 2)
print('buy勝ち:',buy_wins,'buy試行回数:', buy_trials, 'buy勝率:',buy_win_rate,'%')
勝率の変化を確認
以上の流れで勝率を計算し、ATRとDMIを考慮せずに勝率を計算した場合と、考慮して勝率を計算した場合で、どのように変化するかを確認した結果が以下の通りです。
利確幅 | DMI | buy 勝率 | sell 勝率 | buy+sell 勝率 |
12.00固定 | なし | 52.9% | 45.7% | 49.3% |
ATR×3.2 | なし | 50.8% | 47.2% | 49.0% |
12.00固定 | あり | 54.8% | 47.6% | 51.1% |
ATR×3.2 | あり | 54.1% | 50.4% | 52.2% |
検証期間としては、2017年〜2022年の4年間です。勝率50%をやや超えているというのは最低水準クリアですので、なかなか良い感じです。これを応用すれば実装してもそこまで悪くない結果が得られるかもしれません。
まとめ
さて、以上の通り、1分足を使ったEAの簡単なバックテスト方法をご紹介しました。
ベースロジックが思い浮かんだら、このように1分足で勝率を試算してみるというのは有用です。これが良かったら実装しても必ず良い結果が得られるとは限りませんが、少なくともこの結果が悪ければそれ以上ブラッシュアップしてもあまり良いEAにはならなそうということは言えるかと思います。
次回からは、ティックデータを使ってより詳細なバックテストを行う方法をご紹介します。
同じように、Google ColabとPythonを使って進めていく予定です。