LoginSignup
18
22

More than 5 years have passed since last update.

[Python]FXシストレパラメータの最適化をランダムサーチでやってみる

Posted at

PythonでFXシストレパラメータの最適化
で、トレードシステムのパラメータの組み合わせをすべて試して最適解を求めてみました。

組み合わせの数が少なければ、総当たりでも問題ないのですが、組み合わせの数が多くなると、なかなか大変になってきます。そのための最適化アルゴリズムが、メタヒューリスティクスとして色々と知られています。そのなかでも有名な遺伝的アルゴリズムを実装しようかと思ったのですが、その前にもっと簡単なランダムサーチをPythonで書いてみました。

準備

1分足データから別のタイムフレームに変換する関数とテクニカル指標関数はindicators.pyに、前回のバックテストとその評価を行う関数は、backtest.pyに入れておきます。ソースはGitHubにあります。以下のコードでohlcに1時間足のデータができます。

import numpy as np
import pandas as pd
import indicators as ind #indicators.pyのインポート
from backtest import Backtest,BacktestReport

dataM1 = pd.read_csv('DAT_ASCII_EURUSD_M1_2015.csv', sep=';',
                     names=('Time','Open','High','Low','Close', ''),
                     index_col='Time', parse_dates=True)
dataM1.index += pd.offsets.Hour(7) #7時間のオフセット
ohlc = ind.TF_ohlc(dataM1, 'H') #1時間足データの作成

最適化するトレードシステムのパラメータ

前回の最適化では、以下のように二つのパラメータをある範囲で変化させて最適化しました。

SlowMAperiod = np.arange(10, 61) #長期移動平均期間の範囲
FastMAperiod = np.arange(5, 31)  #短期移動平均期間の範囲

このままだと1066通りしかないですが、組み合わせの数は後で変えてみます。

ランダムサーチ

単純なランダムサーチだと、パラメータの値を順次ランダムに発生させて、バックテストの評価の最も高いものを採用すればいいのですが、ここでは、遺伝的アルゴリズムにも応用できるよう、1回の世代で20個ほどのパラメータの組み合わせの試行を行い、それを何世代か繰り返す方法を取ってみました。

def Optimize(ohlc, SlowMAperiod, FastMAperiod):
    def shift(x, n=1): return np.concatenate((np.zeros(n), x[:-n])) #シフト関数

    SlowMA = np.empty([len(SlowMAperiod), len(ohlc)]) #長期移動平均
    for i in range(len(SlowMAperiod)):
        SlowMA[i] = ind.iMA(ohlc, SlowMAperiod[i])

    FastMA = np.empty([len(FastMAperiod), len(ohlc)]) #短期移動平均
    for i in range(len(FastMAperiod)):
        FastMA[i] = ind.iMA(ohlc, FastMAperiod[i])

    M = 20 #個体数
    Eval = np.zeros([M, 6])  #評価項目
    Param = np.zeros([M, 2], dtype=int) #パラメータ
    RandomSearch(Param, Eval[:,0], SlowMAperiod, FastMAperiod) #パラメータ初期化
    gens = 0 #世代数
    while gens < 100:
        for k in range(M):
            i = Param[k,0]
            j = Param[k,1]
            #買いエントリーシグナル
            BuyEntry = (FastMA[j] > SlowMA[i]) & (shift(FastMA[j]) <= shift(SlowMA[i]))
            #売りエントリーシグナル
            SellEntry = (FastMA[j] < SlowMA[i]) & (shift(FastMA[j]) >= shift(SlowMA[i]))
            #買いエグジットシグナル
            BuyExit = SellEntry.copy()
            #売りエグジットシグナル
            SellExit = BuyEntry.copy()
            #バックテスト
            Trade, PL = Backtest(ohlc, BuyEntry, SellEntry, BuyExit, SellExit) 
            Eval[k] = BacktestReport(Trade, PL)
        #世代の交代
        Param = RandomSearch(Param, Eval[:,0], SlowMAperiod, FastMAperiod)
        gens += 1
    Slow = SlowMAperiod[Param[:,0]]
    Fast = FastMAperiod[Param[:,1]]
    return pd.DataFrame({'Slow':Slow, 'Fast':Fast, 'Profit': Eval[:,0], 'Trades':Eval[:,1],
                         'Average':Eval[:,2],'PF':Eval[:,3], 'MDD':Eval[:,4], 'RF':Eval[:,5]},
                         columns=['Slow','Fast','Profit','Trades','Average','PF','MDD','RF'])

前回と異なるのは、各世代でパラメータの組み合わせを保存しておく配列Paramを用意した点です。そして、各世代で評価値(ここでは総損益)が最も大きいパラメータのセットを保存(エリート保存)しておき、残りのパラメータをランダムに生成することを繰り返します。

エリート保存付きでパラメータをランダムに生成する関数は次のように書けます。

#エリート保存付きランダムサーチ
def RandomSearch(Param, Eval, SlowMAperiod, FastMAperiod):
    Param = Param[np.argsort(Eval)[::-1]] #ソート
    NewParam = np.vstack((np.random.randint(len(SlowMAperiod), size=len(Eval)),
                          np.random.randint(len(FastMAperiod), size=len(Eval)))).T
    NewParam[0] = Param[0] #エリート保存
    return NewParam

評価値をソートして0番目のパラメータを再度セットするという方法です。
これを実行すると、以下のようになります。

result = Optimize(ohlc, SlowMAperiod, FastMAperiod)
result.sort_values('Profit', ascending=False)
Slow Fast Profit Trades Average PF MDD RF
0 27 8 2507.1 264.0 9.496591 1.423497 485.1 5.168213
15 18 5 944.1 428.0 2.205841 1.110187 693.1 1.362141
19 48 14 825.4 238.0 3.468067 1.131883 927.7 0.889727
16 42 29 720.4 308.0 2.338961 1.094974 1011.8 0.711998
8 44 25 589.3 246.0 2.395528 1.089205 1141.1 0.516432
6 60 25 588.8 120.0 4.906667 1.126946 1025.0 0.574439
4 22 5 493.5 124.0 3.979839 1.105354 1106.3 0.446082
11 26 7 391.2 206.0 1.899029 1.069326 1099.8 0.355701
17 13 7 343.2 152.0 2.257895 1.069697 990.9 0.346352
18 45 20 174.5 160.0 1.090625 1.032210 1118.4 0.156026
12 38 20 169.8 170.0 0.998824 1.029754 939.8 0.180677
3 21 11 148.6 460.0 0.323043 1.016830 1203.1 0.123514
5 34 13 -342.0 170.0 -2.011765 0.939258 1160.8 -0.294624
7 42 22 -374.4 156.0 -2.400000 0.933744 1189.6 -0.314728
1 51 20 -441.4 160.0 -2.758750 0.922788 1089.9 -0.404991
9 58 27 -1485.5 222.0 -6.691441 0.787506 2394.0 -0.620510
10 19 25 -1620.0 234.0 -6.923077 0.775828 1845.7 -0.877716
13 33 16 -1625.0 400.0 -4.062500 0.827957 2307.7 -0.704164
2 44 24 -1885.7 508.0 -3.712008 0.828287 2652.2 -0.710995
14 55 12 -2048.3 398.0 -5.146482 0.776908 2813.6 -0.728000

このように最適解は求まっていますが、20個体を100世代分、つまり2000回近く試行しているので、当然といえば当然です。

今回はパラメータが2個しかないので、それほど組み合わせの数は増えないのですが、

SlowMAperiod = np.arange(10, 161) #長期移動平均期間の範囲
FastMAperiod = np.arange(5, 131)  #短期移動平均期間の範囲

のように19,026通りに増やしてみます。結果は

Slow Fast Profit Trades Average PF MDD RF
0 52 48 2003.4 190.0 10.544211 1.426773 1106.4 1.810738
5 125 26 1312.6 58.0 22.631034 1.513939 896.4 1.464302
16 49 47 1203.9 62.0 19.417742 1.448046 773.0 1.557439
11 56 55 989.6 116.0 8.531034 1.245266 922.0 1.073319
14 52 34 704.0 62.0 11.354839 1.261632 527.2 1.335357
13 152 36 545.5 75.0 7.273333 1.133198 936.5 0.582488
2 140 58 457.4 196.0 2.333673 1.085960 867.1 0.527505
18 40 109 308.1 88.0 3.501136 1.073977 1435.0 0.214704
6 75 107 255.1 78.0 3.270513 1.066815 1145.1 0.222775
3 158 85 172.7 122.0 1.415574 1.036899 1530.6 0.112832
12 68 32 -622.3 90.0 -6.914444 0.836838 1629.9 -0.381803
10 120 111 -638.0 37.0 -17.243243 0.787057 1233.4 -0.517269
8 153 30 -667.3 35.0 -19.065714 0.776284 1558.8 -0.428086
7 43 74 -749.0 39.0 -19.205128 0.770245 1766.8 -0.423930
15 63 104 -774.3 92.0 -8.416304 0.835339 1692.6 -0.457462
9 154 56 -1296.9 58.0 -22.360345 0.675945 1733.3 -0.748226
1 152 24 -1315.2 74.0 -17.772973 0.664627 2130.3 -0.617378
19 155 27 -1363.8 60.0 -22.730000 0.672006 1474.4 -0.924986
17 109 40 -1478.5 62.0 -23.846774 0.651379 1784.5 -0.828523
4 148 122 -1957.4 100.0 -19.574000 0.626400 2171.4 -0.901446

となり、最適解は求まりませんでした。ただ、これを何度か繰り返せば、そのうち、最適解が求まります。まあ、総当たりでもなんとかできるくらいの組み合わせなら、ランダムサーチでもそれなりの解が求まるということでしょう。

18
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
22